Skip to content

Follow a job and get its outputs

GET /v1/jobs/{id}

Progress is refreshed on every request while the job runs. Rather than polling, listen for the webhooks: job.succeeded, job.failed and job.canceled carry the job as this request shows it.

queued → processing → succeeded | failed | canceled

A job waits in queued while your organization already runs as many jobs as its plan allows (max_concurrent_jobs); it starts as soon as one finishes. progress (0–100) is the whole job’s.

tasks lists everything being produced, each with its status (pending, running, done, stopped), progress (0–100, or null while it runs without a measurable percentage) and times:

type
import The copy from input.url, with bytes and total_bytes.
upload A progressive upload arriving, with bytes and total_bytes.
analysis Reading the source.
video One per quality (resolution). Qualities advance together, since they are encoded from the same frames.
audio One per language: track, language, name, and mode: copy (the source’s audio reused, no quality lost) or encode.
subtitles count tracks.
mp4, mp3, poster, storyboard Each deliverable.
streaming The HLS and DASH manifests (formats).
Field
queued_seconds Waiting for room in your plan.
import_seconds Copying from input.url, if the job had one.
processing_seconds From the start of the analysis to the outputs being ready, without the queue or the upload. When the video was processed while it arrived, it counts from the end of the upload or copy: it is the wait once the file is in.
speed Seconds of video per second of processing: 33.5 means 33.5× real time.
stages analysis_seconds, encoding_seconds, assembly_seconds.
timeline When each stage started and ended (created_at, dispatched_at, import_started_at, imported_at, analysis_started_at, analyzed_at, encoded_at, completed_at), to draw them on one axis.
"outputs": {
"streaming": {
"hls_url": "https://…/master.m3u8",
"dash_url": "https://…/manifest.mpd",
"variants": [{ "resolution": "1080p", "width": 1920, "height": 1080, "size_bytes": 612345678 }],
"size_bytes": 640123456,
"audio_size_bytes": 27000000
},
"mp4": [{ "resolution": "720p", "width": 1280, "height": 720, "url": "https://…", "size_bytes": 301234567 }],
"mp3": { "url": "https://…", "size_bytes": 57600000 },
"posters": [{ "url": "https://…", "size_bytes": 84512 }],
"storyboard": { "image_url": "https://…/sprite-1-000.jpg", "image_urls": ["…"], "vtt_url": "https://…/storyboard.vtt", "size_bytes": 1203344 },
"audio_tracks": [{ "language": "spa", "name": "Español", "default": true }],
"subtitles": [{ "language": "spa", "name": "Español (forzados)", "forced": true, "url": "https://…/subtitles-0.vtt" }],
"quality": { "metric": "vmaf", "model": "vmaf_v0.6.1", "sampled_seconds": 96, "variants": [{ "resolution": "1080p", "vmaf": 95.8, "vmaf_min": 93.1 }] },
"total_size_bytes": 999123456,
"source_size_bytes": 4812345678
}

width and height are what came out, after fit and rotation. A long storyboard is split into several images (sheets of 10 rows); image_url is the first, image_urls all of them, and the WebVTT maps each moment to its tile.

Every output has size_bytes, what it weighs as published. In streaming, size_bytes is the whole folder: HLS and DASH share their segments, so they count once; each variant’s is its video, and audio_size_bytes is the audio they all share. total_size_bytes is everything published for the job; source_size_bytes, the source.

With quality_metrics: true, outputs.quality scores how each variant looks next to the source, from 0 to 100, on samples spread over the video (the whole video if it is short): vmaf is the mean of the samples, vmaf_min the weakest one. From about 93 the difference is hard to see; 80–93 is good; below 60, poor. Variants are compared at 1080p (or the source’s size, if smaller) with the standard vmaf_v0.6.1 model. Variants with a watermark are not scored, since the mark would count as a defect. Without quality_metrics it is null.

  • source: what the source is (duration, size, frame rate, bitrate, codecs, audio_tracks, subtitle_tracks).
  • adjustments: corrections applied so the outputs play everywhere: deinterlaced, frame_rate_normalized and frame_rate_capped (with from and to), rotation_applied (degrees), anamorphic_corrected, hdr_tone_mapped.
  • skipped: qualities left out because they were larger than the source (on_upscale: "skip").
  • skipped_tracks: requested tracks that didn’t come out: not_found (an optional pick), image_subtitles, too_many.
  • request: the settings the job was created with, defaults applied: what a retry starts from.
  • billed_minutes: the job’s billed minutes per category, once it succeeded (only in GET /v1/jobs/{id}).

error says whose the problem is and whether trying again can help:

"error": { "code": "unreadable_source", "category": "source", "retryable": false, "message": "…" }
code category retryable What happened, what to do
unsupported_source source no The file can’t give what was asked: no audio and an MP3 asked, smaller than a quality with on_upscale: "fail", a required track that isn’t there, an unsupported video. Change the request or the file.
unreadable_source source no The file is damaged, incomplete (an MP4 without its index, cut off mid-upload) or not audio or video. Upload it again, whole.
source_import_failed source yes It couldn’t be copied from input.url (HTTP 4xx or 5xx, timeout, too large…). Check the URL and retry.
upload_incomplete source no A progressive upload stopped arriving for 30 minutes. Upload the file again.
validation_error request no The settings don’t work for this file (a watermark that can’t be downloaded, say). Fix them.
timeout, launch_failed internal yes Ours, and temporary: retry.
processing_error internal no Ours: we are already looking into it. The message is generic on purpose.

Momentary failures (network, capacity) are retried inside the job and never reach failed.

POST /v1/jobs/{id}/retry
POST /v1/jobs/{id}/cancel
DELETE /v1/jobs/{id}
  • Retry creates a new job from the same source (no new upload) with the same settings, except those in the body: outputs, audio, subtitles, on_upscale, quality_metrics, webhook_url, metadata, and input.clip (null for the whole video). It also makes a variation of a finished job. The new job’s retry_of points at the original. Sources are kept 7 days: after that, one brought from a URL is copied from it again, and an uploaded one answers 409 (upload it again).
  • Cancel stops a queued or running job.
  • Delete removes a finished job’s files. The job stays listed, with deleted: true, for your history and billing.
GET /v1/jobs?status=processing&limit=20

Newest first. Pass the page’s next_cursor as cursor for the next one; it is null on the last. Pages stay consistent while new jobs arrive.