API docs · Build
Job lifecycle and state
How a job moves from creation to a terminal state, what each stage reports, how to poll, and every field of the Job object.
States
Every Job object has a state. It is derived from the job's stages by one rule on the server, and it is the only field you should branch on.
state | Meaning | Terminal |
|---|---|---|
awaiting_upload | Created with POST /jobs; waiting for you to upload the file | no |
queued | A URL or batch job waiting for its fetch to start | no |
processing | At least one stage is running or still to run | no |
completed | Every stage the job needs has completed (or was skipped) | yes |
failed | A stage the job needs failed. error says which and why | yes |
cancelled | You cancelled it | yes |
expired | It was never uploaded, or did not finish in time | yes |
POST /jobs ──────────► awaiting_upload ──(upload lands)──┐
POST /jobs/from-url ─► queued ───────────(fetch starts)──┴─► processing ─┬─► completed
├─► failed
any non-terminal state ──(POST /jobs/{jobId}/cancel)──► cancelled └─► expiredIgnore the status field. It is the legacy internal pipeline status, kept for older clients; its values are not part of the contract and change without notice. The GET /jobs?state= filter takes the same values as state.
A job left in awaiting_upload expires about an hour after creation, when its upload URLs stop working. Jobs that stall in the pipeline also end as expired (or failed) rather than hanging forever.
Stages
stages always has three entries, ingest, vision and transcript, each with a status:
Stage status | Meaning |
|---|---|
pending | Not started |
running | In progress |
completed | Done; its results are readable |
failed | Did not complete; error holds {code, message}. Never charged |
skipped | Will not run: excluded by processingMode, no video stream (vision) or no audio stream (transcript). Never charged |
Stages also carry startedAt and completedAt (RFC 3339) once known. ingest probes and prepares the media; it is free. vision and transcript are the billable stages and run in parallel after ingest.
The derived rule:
- The job needs
ingest, plusvisionunless the mode istranscriptor the input has no video, plustranscriptunless the mode isvisionor the input has no audio. - Cancelled →
cancelled. Expired →expired. - Any needed stage
failed→failed, witherror.stagenaming it. - Every needed stage
completedorskipped→completed. - Otherwise
awaiting_upload,queuedorprocessing.
Partial results
Each stage's output is readable as soon as that stage completes, even while the other is still running and even if the job ends failed. If vision fails but the transcript completes, the job is failed, GET /jobs/{jobId}/transcript still works, and you pay for the transcript only. Use the stage statuses to decide what to fetch:
job = fq.jobs.get(job_id)
if job.stages.transcript.status == "completed":
transcript = fq.jobs.transcript(job_id)
if job.stages.vision.status == "completed":
scenes = fq.jobs.scenes(job_id)Polling
curl "https://api.framequery.com/v1/api/jobs/$JOB_ID?include=none" \
-H "Authorization: Bearer $FRAMEQUERY_API_KEY"- Use
include=none: without it the response embeds every scene and transcript segment. - Poll every 5 to 15 seconds, backing off for long jobs. When
estimatedCompletionTimeSecondsis present, polling at about a third of it is plenty. - GET requests are limited to 600 per minute per account (shared by all your keys). On
429, wait forRetry-After. - Better still, set
callbackUrlat creation and let a webhook tell you when the job is done. Keep a slow poll as a fallback.
The Job object
GET /jobs/{jobId} (and every create endpoint) returns this shape. Optional fields are omitted when unknown.
{
"jobId": "0b5e6c1e-6f43-4a0e-9d4c-2f7f3c1b9a10",
"state": "completed",
"status": "VISION_COMPLETED",
"processingMode": "all",
"format": { "tier": "standard", "container": "mov", "extension": "mov" },
"stages": {
"ingest": { "status": "completed", "startedAt": "2026-10-13T09:00:04Z", "completedAt": "2026-10-13T09:01:12Z" },
"vision": { "status": "completed", "startedAt": "2026-10-13T09:01:13Z", "completedAt": "2026-10-13T09:03:40Z" },
"transcript": { "status": "completed", "startedAt": "2026-10-13T09:01:13Z", "completedAt": "2026-10-13T09:04:02Z" }
},
"metadata": { "assetId": "A003_C008" },
"links": {
"self": "https://api.framequery.com/v1/api/jobs/0b5e6c1e-6f43-4a0e-9d4c-2f7f3c1b9a10",
"transcript": "https://api.framequery.com/v1/api/jobs/0b5e6c1e-6f43-4a0e-9d4c-2f7f3c1b9a10/transcript",
"scenes": "https://api.framequery.com/v1/api/jobs/0b5e6c1e-6f43-4a0e-9d4c-2f7f3c1b9a10/scenes"
},
"originalFilename": "A003_C008.mov",
"source": "api",
"durationSeconds": 1847.5,
"billing": { "tier": "standard", "currency": "USD", "billableSeconds": 1848, "estimatedCents": 77, "chargedCents": 77 },
"createdAt": "2026-10-13T08:59:41Z",
"updatedAt": "2026-10-13T09:04:03Z",
"completedAt": "2026-10-13T09:04:03Z",
"statusHistory": [{ "status": "PENDING_UPLOAD", "timestamp": "2026-10-13T08:59:41Z" }]
}| Field | Notes |
|---|---|
jobId | UUID |
state | See States |
status | Legacy, informational only |
processingMode | all, transcript or vision |
format | tier (standard or raw), and container / extension when detected |
stages | ingest, vision, transcript, each {status, startedAt?, completedAt?, error?} |
error | Present when state is failed: {code, message, stage}. Codes are listed under job error codes |
metadata | Your create-time metadata; always an object |
links | Absolute URLs: self, and transcript, scenes, cancel when they apply (cancel disappears once the job is terminal; a skipped stage has no link) |
originalFilename, source | source is api (upload), url or batch |
durationSeconds | Media duration, once probed |
billing | tier, currency (USD), billableSeconds (per second, 30 s minimum), estimatedCents (the current hold for this job), chargedCents (charged so far, completed stages only) |
createdAt, updatedAt, completedAt | RFC 3339 |
estimatedCompletionTimeSeconds | A hint while the job runs |
audioTrackCount, audioTracksCompleted, audioTrackNames, hasMultiTrackAudio | Multi-track audio jobs |
batchId, clipCount, continuousShoot | Batch jobs |
processedData | Embedded results (scenes, transcript, speakers, language, length) unless include=none. Prefer the results endpoints |
errorMessage | Legacy copy of error.message |
GET /jobs list items carry the common fields (jobId, state, status, processingMode, format, stages with status only, error, metadata, links, originalFilename, source, durationSeconds, batchId and the timestamps). Get the job for billing, processedData, statusHistory and the audio-track fields.