FrameQuery API
Programmatically upload videos and trigger processing through the FrameQuery cloud pipeline. Integrate FrameQuery into your workflow, automate bulk uploads, or build custom tooling on top of your video library.
Base URL
https://api.framequery.com/v1All requests must be made over HTTPS.
API key via header
120 req/min per key
From $1.50 / hour indexed
Quick start
Process a video via URL in two API calls:
# Submit a video from URL
curl -X POST https://api.framequery.com/v1/api/jobs/from-url \
-H "X-API-Key: fq_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/video.mp4"}'Response envelope
All successful responses are wrapped in a data key. Errors return an error string.
{
"data": { ... }
}Authentication
All API requests require authentication via an API key. Include your key in the request headers using either format:
X-API-Key: fq_live_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6API keys use the fq_live_ prefix followed by 32 hex characters. Create and manage keys from your dashboard. See the section below.
API Keys
Create and manage API keys directly from here. Each key is identified by its keyPrefix, the first 8 hex characters after fq_live_. Keys support scopes: jobs:read, jobs:write, and quota:read.
API reference
You can also manage keys programmatically. These endpoints are authenticated via your session token (not an API key).
/v1/api/keysGenerate a new API key. The full key is only returned once. Store it securely.
Body parameters
| Parameter | Type | Description |
|---|---|---|
| name | string | A human-readable label for this key (e.g. "CI Pipeline") |
| scopes | string[] | Permissions for this key. Defaults to ["jobs:read", "jobs:write", "quota:read"] |
curl -X POST https://api.framequery.com/v1/api/keys \
-H "Authorization: Bearer <your_session_token>" \
-H "Content-Type: application/json" \
-d '{"name": "CI Pipeline"}'Response
200{
"data": {
"key": "fq_live_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
"keyPrefix": "a1b2c3d4",
"name": "CI Pipeline"
}
}/v1/api/keysList all API keys associated with your account. Returns the key prefix, name, scopes, and revocation status, never the full key.
curl https://api.framequery.com/v1/api/keys \
-H "Authorization: Bearer <your_session_token>"Response
200{
"data": [
{
"keyPrefix": "a1b2c3d4",
"name": "CI Pipeline",
"scopes": ["jobs:read", "jobs:write", "quota:read"],
"createdAt": "2026-02-21T14:30:00Z",
"revoked": false
},
{
"keyPrefix": "e5f6a7b8",
"name": "Staging",
"scopes": ["jobs:read", "quota:read"],
"createdAt": "2026-02-19T09:00:00Z",
"revoked": false
}
]
}/v1/api/keys/:keyPrefixPermanently revoke an API key. Any requests using this key will immediately start failing with 401.
Path parameters
| Parameter | Type | Description |
|---|---|---|
| keyPrefix | string | The 8-character key prefix (e.g. a1b2c3d4) |
curl -X DELETE https://api.framequery.com/v1/api/keys/a1b2c3d4 \
-H "Authorization: Bearer <your_session_token>"Response
200{
"data": {
"revoked": true
}
}Jobs
A job represents a video being uploaded and processed through the FrameQuery pipeline. Once complete, the job contains scenes, transcripts, and detected objects. Jobs progress through these statuses:
| Status | Meaning |
|---|---|
| PENDING_UPLOAD | Job created via presigned URL. Waiting for you to PUT the file |
| PENDING_FETCH | Job created from URL. FrameQuery is downloading the file |
| INGEST_PROCESSING | Video is being transcoded and ingested |
| INGEST_TRANSCODING | FFmpeg transcode in progress |
| INGEST_COMPLETED | Ingest finished, moving to scene detection |
| VISION_PROCESSING | AI scene detection and object recognition in progress |
| VISION_COMPLETED | Processing finished with scenes and transcript |
| VIDEO_COMPLETED_NO_SCENES | Processing finished but no scenes were detected |
| FAILED_FETCH | URL download failed (unreachable, private IP, etc.) |
| INGEST_FAILED_TRANSCODE | Video format unsupported or transcode failed |
/v1/api/jobsCreate a job and get a presigned upload URL. You then PUT your file directly to that URL.
Body parameters
| Parameter | Type | Description |
|---|---|---|
| fileName | string | The filename including extension (e.g. "video.mp4") |
| callbackUrl | string | HTTPS webhook URL — receives a POST when the job completes or fails |
| processingMode | string | "all" (default), "transcript" (audio only, 50% off), or "vision" (video only, 50% off) |
| idempotencyKey | string | Client-generated string to prevent duplicate job creation (24h TTL) |
| audioTracks | AudioTrack[] | Array of audio tracks for multi-track transcription (max 16). Each track gets its own presigned upload URL. Mutually exclusive with audioFileName. |
AudioTrack object
| Parameter | Type | Description |
|---|---|---|
| fileName | string | Audio filename including extension (e.g. "boom.wav") |
| syncMode | string | "auto" (default), "timecode" (SMPTE/BWF), or "offset" (manual ms offset) |
| offsetMs | number | Manual offset in milliseconds when syncMode="offset" (range: ±24h) |
| label | string | Human-readable label (e.g. "Host", "Guest"). Defaults to track_N. |
| perChannelTranscription | boolean | If true, each channel/track gets its own independent transcript with separate speaker diarization. If false (default), all audio is mixed to mono for a single unified transcript. Each channel is billed separately when enabled. |
| channels | number[] | Select specific channels from a polyphonic file (0-indexed). Omit to transcribe all channels. |
# Step 1: Create the job and get an upload URL
curl -X POST https://api.framequery.com/v1/api/jobs \
-H "X-API-Key: fq_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{"fileName": "video.mp4"}'
# Step 2: Upload the file to the presigned URL (expires in 15 min)
curl -X PUT "<uploadUrl from step 1>" \
-H "Content-Type: application/octet-stream" \
--data-binary @/path/to/video.mp4Response
200{
"data": {
"jobId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"uploadUrl": "https://storage.googleapis.com/...",
"expiresInSeconds": 900,
"uploadMethod": "PUT"
}
}PUT request with Content-Type: application/octet-stream. Processing starts automatically once the upload completes./v1/api/jobs/from-urlSubmit a publicly accessible video URL for FrameQuery to download and process. No file upload needed.
Body parameters
| Parameter | Type | Description |
|---|---|---|
| url | string | Publicly accessible URL of the video file (http or https) |
| fileName | string | Override the filename (defaults to filename from URL) |
| downloadToken | string | Bearer token for authenticated downloads (e.g. Google Drive OAuth token) |
| provider | string | Cloud provider hint: "gdrive", "dropbox", or omit for plain URLs |
| callbackUrl | string | HTTPS webhook URL — receives a POST when the job completes or fails |
| processingMode | string | "all" (default), "transcript" (audio only, 50% off), or "vision" (video only, 50% off) |
| idempotencyKey | string | Client-generated string to prevent duplicate job creation (24h TTL) |
| audioTracks | AudioTrack[] | Array of audio tracks with URLs for multi-track transcription (max 16). Each track includes url, syncMode, offsetMs, label, and perChannelTranscription. Mutually exclusive with audioUrl. |
curl -X POST https://api.framequery.com/v1/api/jobs/from-url \
-H "X-API-Key: fq_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"url": "https://storage.example.com/rushes/A003_C008.R3D",
"fileName": "Camera A Take 8.R3D"
}'Response
200{
"data": {
"jobId": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
"status": "PENDING_FETCH"
}
}/v1/api/jobs/:jobIdRetrieve the current status and details of a job. Completed jobs include the full processedData with scenes and transcript.
Path parameters
| Parameter | Type | Description |
|---|---|---|
| jobId | string | The job ID |
curl https://api.framequery.com/v1/api/jobs/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
-H "X-API-Key: fq_live_your_key_here"Response (in progress)
Response
200{
"data": {
"jobId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "PENDING_FETCH",
"originalFilename": "A003_C008.R3D",
"source": "api",
"createdAt": "2026-02-21T14:30:00Z",
"updatedAt": "2026-02-21T14:30:00Z"
}
}Response (completed)
Response
200{
"data": {
"jobId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "VISION_COMPLETED",
"originalFilename": "A003_C008.R3D",
"source": "api",
"createdAt": "2026-02-21T14:30:00Z",
"updatedAt": "2026-02-21T14:38:42Z",
"processedData": {
"length": 1847.5,
"scenes": [
{
"sceneId": "scene_a3f7c291-8e4b-4d1a-b562-9c0e3f8a71d4_45.8",
"startTs": 12.467,
"endTs": 45.8,
"description": "A woman in a blue blazer stands at the front of the room presenting a bar chart showing revenue growth across four quarters.",
"mainColor": "#3b82f6",
"detections": [
{
"name": "person",
"x": 0.12,
"y": 0.08,
"width": 0.22,
"height": 0.88,
"confidence": 0.9789
},
{
"name": "bar chart",
"x": 0.42,
"y": 0.05,
"width": 0.52,
"height": 0.68,
"confidence": 0.9156
}
]
}
],
"transcript": [
{
"Speaker": "a4b2c3d4-e5f6-7890-abcd-ef1234567890",
"StartTime": 12.4,
"EndTime": 18.9,
"Text": "Welcome everyone to today's product reveal."
},
{
"Speaker": "a4b2c3d4-e5f6-7890-abcd-ef1234567890",
"StartTime": 19.2,
"EndTime": 27.1,
"Text": "What we're about to show you has been two years in the making."
}
]
}
}
}
processedData field is only present when the job status is VISION_COMPLETED. Transcript fields use capitalized keys: StartTime, EndTime, Text. Times are in seconds.Multi-Track Audio
FrameQuery supports two audio modes when you submit external audio:
Mixed (default)
All audio tracks are mixed down to mono and processed as a single transcript with unified speaker diarization. This is the cheapest option and works well when you just need the words from all sources combined. Billed as 1 transcript channel regardless of how many files you submit.
Per-channel transcription
Each audio track (or each channel in a polyphonic BWF) gets its own independent transcript with separate speaker diarization. Use this when you need to know exactly what each microphone captured — e.g. isolating the host vs. guest in an interview, or getting per-cast-member dialogue from a reality TV field recorder. Set perChannelTranscription: true on the audio track and each channel is billed separately (0.5x base per channel per hour).
iXML track names from professional field recorders (Sound Devices, Zoom F-series) are automatically detected and returned in the response.
/v1/api/jobs/:jobId/audioTracksRetrieve all audio track transcripts for a multi-track job.
curl https://api.framequery.com/v1/api/jobs/a1b2c3d4-.../audioTracks \
-H "X-API-Key: fq_live_your_key_here"Response
200{
"data": {
"jobId": "a1b2c3d4-...",
"tracks": [
{
"trackIndex": 0,
"trackName": "Boom",
"language": "en",
"status": "STT_COMPLETED",
"transcript": [{"startTime": 0.0, "endTime": 2.5, "text": "..."}],
"speakers": ["Speaker 1"]
},
{
"trackIndex": 1,
"trackName": "Lav-Host",
"language": "en",
"status": "STT_COMPLETED",
"transcript": [{"startTime": 0.1, "endTime": 2.4, "text": "..."}],
"speakers": ["Speaker 1"]
}
]
}
}/v1/api/jobs/:jobId/audioTracks/:trackIndexRetrieve a single audio track transcript by index.
audioTrackCount, audioTracksCompleted, and audioTrackNames in the job response. Track transcripts are available progressively as each track completes STT processing.Example: separate audio files (interview with 2 lavs)
curl -X POST https://api.framequery.com/v1/api/jobs/from-url \
-H "X-API-Key: fq_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"url": "https://cdn.example.com/interview.mov",
"audioTracks": [
{"url": "https://cdn.example.com/host-lav.wav", "syncMode": "timecode", "label": "Host"},
{"url": "https://cdn.example.com/guest-lav.wav", "syncMode": "timecode", "label": "Guest"}
]
}'Example: polyphonic BWF with per-channel splitting
# Upload a multi-channel BWF with per-channel splitting
curl -X POST https://api.framequery.com/v1/api/jobs \
-H "X-API-Key: fq_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"fileName": "interview.mov",
"audioTracks": [
{
"fileName": "field-recorder.wav",
"syncMode": "timecode",
"perChannelTranscription": true
}
]
}'
# Response includes audioTrackUploadUrls for each audio file/v1/api/jobsList jobs for your account with optional filtering. Uses cursor-based pagination.
Query parameters
| Parameter | Type | Description |
|---|---|---|
| status | string | Filter by exact status (e.g. VISION_COMPLETED, PENDING_FETCH) |
| limit | integer | Results per page, 1–100 (default: 20) |
| cursor | string | Job ID from a previous response's nextCursor for pagination |
curl "https://api.framequery.com/v1/api/jobs?status=VISION_COMPLETED&limit=10" \
-H "X-API-Key: fq_live_your_key_here"Response
200{
"data": [
{
"jobId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "VISION_COMPLETED",
"originalFilename": "video.mp4",
"createdAt": "2026-02-21T14:30:00Z",
"updatedAt": "2026-02-21T14:38:42Z"
}
],
"nextCursor": "c3d4e5f6-a7b8-9012-cdef-345678901234"
}nextCursor value as the cursor query parameter until nextCursor is null.Batch processing
Submit multiple video URLs in a single request. Choose independent mode (each clip is a separate job) or continuous mode (clips are concatenated and processed as one video).
/v1/api/jobs/batchSubmit a batch of video URLs for processing.
Body parameters
| Parameter | Type | Description |
|---|---|---|
| clips | array | Array of clip objects (max 100) |
| mode | string | "independent" (separate jobs) or "continuous" (concatenated into one) |
| processingMode | string | "all" (default), "transcript", or "vision" |
| callbackUrl | string | HTTPS webhook URL for job completion notifications |
sourceUrl (required), fileName (optional), downloadToken (optional), provider (optional).curl -X POST https://api.framequery.com/v1/api/jobs/batch \
-H "X-API-Key: fq_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"mode": "independent",
"clips": [
{ "sourceUrl": "https://cdn.example.com/clip1.mp4" },
{ "sourceUrl": "https://cdn.example.com/clip2.mp4" }
],
"processingMode": "all"
}'Response
200{
"data": {
"batchId": "f1a2b3c4-d5e6-7890-abcd-ef1234567890",
"mode": "independent",
"jobs": [
{ "jobId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "status": "PENDING_FETCH" },
{ "jobId": "b2c3d4e5-f6a7-8901-bcde-f23456789012", "status": "PENDING_FETCH" }
]
}
}Billing & Credits
Video processing is billed per second based on the duration of the source video, with a 30-second minimum per job. Pricing depends on the codec: camera RAW formats cost more due to heavier decode and analysis. Volume discounts kick in automatically after 500 hours per month.
Pricing
| Tier | Formats | Rate | 500+ hr rate |
|---|---|---|---|
| RAW | ARRIRAW (.ari/.arx), R3D, BRAW | $2.50/hr ($0.042/min) | $2.00/hr ($0.033/min) |
| Normal | All other formats | $1.50/hr ($0.025/min) | $1.00/hr ($0.017/min) |
Billed per second with a 30-second minimum per job.
Transcript-only or vision-only
Need just the transcript or just the visual analysis? Each is available at 50% of the full rate. Pass processingMode: "transcript" or processingMode: "vision" when submitting a job.
Multi-track audio pricing
Vision is billed once at 50% of the base rate. Transcript is billed at 50% per channel. For example, a 1-hour Normal-tier video with 4 audio channels in "all" mode: 0.5 × $1.50 (vision) + 4 × 0.5 × $1.50 (transcript) = $3.75. Without multi-track, the same job costs $1.50 (1 channel default).
Cost examples (Normal tier)
| Video duration | Billed duration | Cost |
|---|---|---|
| 10 seconds | 30 seconds (minimum) | $0.013 |
| 5 minutes | 5 min 0 sec | $0.125 |
| 45 minutes | 45 min 0 sec | $1.13 |
| 1 hour | 1 hr 0 min 0 sec | $1.50 |
| 3 hr 10 min | 3 hr 10 min 0 sec | $4.75 |
Volume discount
After 500 hours of processing in a billing month, all additional usage is billed at the reduced rate: $2.00/hr for RAW, $1.00/hr for Normal. The discount applies automatically.
How FrameQuery compares
FrameQuery delivers scene detection, transcription, object recognition, and semantic search in a single API call, no stitching services together.
| FrameQuery | Twelve Labs | Google Video AI | AWS Rekognition | |
|---|---|---|---|---|
| Price / hr | $1.50 / $2.50 RAW | $2.40 | ~$4.68* | ~$4.32* |
| Volume rate | $1.00 / $2.00 RAW after 500 hr | Custom | None | None |
| Billing granularity | Per second | Per second | Per 1-min chunk | Per 1-min chunk |
| Minimum charge | 30 sec | 10 sec | 1 min | 1 min |
| Scene detection | Included | Included | Add-on | Not available |
| Transcription | Included | Included | Separate API | Separate API |
| Object detection | Included | Limited | Add-on | Included |
| Semantic search | Included | Included | Not available | Not available |
| Speaker diarization | Included | Not available | Separate API | Not available |
| Camera RAW support | Included | Not available | Not available | Not available |
| Single API call | Yes | Yes | No (multiple) | No (multiple) |
* Estimated cost when combining label detection, transcription, and object tracking at published per-minute rates. Actual costs vary by feature selection.
Check your quota
/v1/api/quotaRetrieve your current plan, included hours, credit balance, and reset date.
curl https://api.framequery.com/v1/api/quota \
-H "X-API-Key: fq_live_your_key_here"Response
200{
"data": {
"currentPlan": "pro",
"totalHours": 55,
"usedHours": 3,
"remainingHours": 52,
"includedHours": 50,
"creditsBalanceHours": 5,
"resetDate": "2026-03-01T00:00:00Z"
}
}Errors
When a request fails, the API returns a JSON body with an error string and an appropriate HTTP status code.
{
"error": "The provided API key is invalid or has been revoked."
}Common error responses
| Status | Description |
|---|---|
| 400 | Bad request: missing required fields or malformed body |
| 401 | Unauthorized: missing or invalid API key |
| 402 | Payment Required: insufficient credits or quota exceeded |
| 403 | Forbidden: missing scopes or permissions |
| 404 | Not found: the requested job or resource does not exist |
| 429 | Rate limited: too many requests, retry after the reset window |
| 500 | Internal error: something went wrong on our end |
Rate Limits
Rate limits protect the API from abuse and ensure fair usage across all accounts. Limits are applied per API key.
| Endpoint | Limit |
|---|---|
| General | 120 requests / minute (2/sec, burst 20) |
| Uploads | 20 concurrent active jobs |
| Key management | 10 requests / minute |
Rate limit information is included in response headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1708523400When rate limited, the API returns 429 Too Many Requests. Use the X-RateLimit-Reset header (Unix timestamp) to determine when you can retry.
Limits
The following limits apply to all accounts. Contact support if you need higher limits for your use case.
| Limit | Value |
|---|---|
| Max file size | 20 GB per upload |
| Max video duration | 12 hours per job (contact support@framequery.com for extensions) |
| Max batch size | 100 clips per batch request |
| Upload URL expiry | 15 minutes after creation |
Need help?
Having trouble with the API? Reach out and we'll get you sorted.