Skip to main content

Videos and jobs

A video is the unit you index. Every submission creates (or reuses) a video_id and starts an indexing job identified by a job_id. The job runs asynchronously through scene detection, captioning, embedding and moderation; when it finishes, the video's scenes, summary and labels are available and the video is searchable.

submit ─▶ job: pending ─▶ running ─▶ done ──▶ video indexed (scenes, summary, labels, search)
└▶ failed ─▶ video.status = failed, reason_code set

Ways to submit​

MethodEndpointReturnsNotes
Single URLPOST /api/v1/videos202 with video_id, job_id, createdPublicly fetchable http(s) URL. Optional title.
Single filePOST /api/v1/videos/upload202 with video_id, job_id, storage_pathMultipart upload. Same async flow as the URL route.
Batch of URLsPOST /api/v1/videos/batch with kind: "url"201 with batch_id, counts and one items[] row per URLUp to 500 rows per request. Each row carries its own video_id / job_id / status / error in request order.
Batch of filesPOST /api/v1/videos/batch/upload202 with batch_id and accepted / skipped / failed countsRecommended for about 10 files or fewer per request; a 413 backstop rejects more than 500. Pass a pre-created batch_id to spread uploads across requests.
Sheet of URLsPOST /api/v1/videos/batch/upload-sheet201 with counts, rejected_rows and items[].csv or .xlsx with a url column and optional title column. 10 MB and 500 valid rows per sheet.
MRSS feedRegistered from the dashboard; managed with GET /api/v1/mrss-feeds and friendsFeed items that become videosThe feed is polled on a schedule; each ingested video keeps the feed item's guid as external_content_id.
note

Resubmitting a URL that is still being indexed for you does not start a second pipeline. The URL routes return the existing video_id / job_id with created: false (still 202), and batch rows do the same with created: false on a submitted row. File uploads have no natural dedup key, so resending a file indexes it twice.

Identifiers​

IdentifierScopeStability
video_idOne video in your account. Used by every downstream endpoint (/videos/{video_id}, /scenes, search filters, ad matching).Stable for the lifetime of the video. Store this one.
job_idOne indexing attempt for a video.Replaced by every re-index. The video's job_id field always points at the most recent finished attempt.
batch_idA submission receipt grouping many URLs or files.Stable, but it only records submission outcomes, never indexing progress.
external_content_idYour own content id, set when a video was ingested from an MRSS feed. null otherwise.Filter the video list with content_id= (exact match).

Job status​

Poll GET /api/v1/jobs/{job_id} until status is done or failed.

statusMeaning
pendingQueued; the pipeline has not started.
runningPipeline in flight. done_scenes / total_scenes track per-scene progress.
doneIndexing succeeded. The video is indexed and searchable.
failedIndexing failed. Read reason_code for the stable cause.
total_scenesinteger

Number of scenes detected for this attempt. 0 until scene detection has run.

done_scenesinteger

Scenes whose analysis has finished so far.

reason_codestring | null

Stable, machine-readable failure cause. Values seen in the docs: source_forbidden, url_unreachable, media_unreadable, processing_stalled, internal_error. Treat unknown values as opaque.

errorstring | null

Raw error text. A finished job can still carry an older attempt's error, so branch on reason_code instead of parsing this.

started_at / completed_atdate-time | null

Wall-clock bounds of the attempt.

Video status and indexing history​

GET /api/v1/videos/{video_id} and GET /api/v1/videos carry a status string plus four history fields describing the most recent finished indexing attempt.

FieldMeaning
statusIndexing state of the video. The spec references done (indexed) and failed; it is also accepted as a filter on the list endpoint. A video that was already done keeps that status even if a later re-index fails, so check the outcome through job_id.
reason_codeWhy indexing failed. Only meaningful when status is failed; null otherwise.
indexed_atWhen the most recent finished attempt ended, successful or not. A changed value means a new attempt has ended. null while the first indexing is still running.
index_triggerWhat started that attempt: initial or reindex. null when no attempt has finished yet.
job_idThe job behind indexed_at. An attempt still in progress does not replace it.
reindex_run_idIdentifier of the re-index run that produced the attempt; null outside a run.
tip

To catch up after missing notifications, list videos with reindexed_since=<timestamp with UTC offset> or reindex_run_id=<uuid>. Videos that were only indexed once match neither filter.

What "indexed" means​

A video is indexed once its job reaches done. At that point:

  • GET /api/v1/videos/{video_id} returns the AI summary, genre, IAB labels, the garm_video assessment, scene_markers for every scene, the first page of scenes, and whole-video aggregations.
  • The video's scenes are searchable through POST /api/v1/search and usable by ad matching.
  • indexed_at, index_trigger and job_id are populated.

See Scenes and analysis for the fields themselves.

When scenes become available​

Scenes do not wait for the whole job. Each scene is pushed as soon as its caption is ready:

Access patternEndpointBehaviour
Live, as analysis completesGET /api/v1/videos/{video_id}/scenes/streamNDJSON. scene events arrive in completion order (not scene_index order); the stream closes with a done event carrying job_status (done, failed, or null). Connecting to an already-indexed video replays every scene, then closes.
After the job is doneGET /api/v1/videos/{video_id}/scenesPaged by offset / limit (max 200); total is the scene count.
With the video detailGET /api/v1/videos/{video_id}?scene_limit=First scene_limit scenes (default 50, max 200) plus lightweight scene_markers for all scenes.

Batch receipts are not progress​

GET /api/v1/videos/batch/{batch_id} is a submission receipt. Each item's status is written once at submission time and never updated, so polling it waits forever.

Item statusMeaning
submittedAccepted and handed to the pipeline. Not "finished indexing".
skippedRejected before submission (for example a malformed or duplicate URL).
failedSubmission itself errored.
pendingSynthetic. Appears only in counts: slots declared at batch creation that no upload has filled. It never drains by itself.

error on a row is a stable reason such as duplicate_url, blocked_target, dispatch_failed, link_failed or submit_failed. Sheet rows dropped during parsing are listed in rejected_rows with codes such as duplicate_url or url_too_long.

warning

A dispatch_failed row already has a committed video row. For URL batches and sheets, resubmit it (its job is marked failed, so nothing was indexed). For file batches, do not resend it, or the file is indexed twice.

For indexing outcome, follow the per-item ids: video_id to GET /api/v1/videos/{video_id}, job_id to GET /api/v1/jobs/{job_id}. On the batch endpoint job_id is the latest job including one still in progress, so it can differ from the video endpoints' value during a re-index.

Custom summaries​

Once a video has its standard summary, you can request additional summaries written to your own instructions. The standard result is never changed.

  1. POST /api/v1/videos/{video_id}/custom-summaries with instructions (required, up to 2000 characters), optional cast (the only person names the summary may use; without it, no person names are written) and language (a language tag such as en or ko, default en; it overrides any language named in instructions). Returns 202 with custom_summary_id, instruction_id, base_version and a status of queued, or rejected immediately when the same instructions were already judged out of scope.
  2. Poll GET /api/v1/videos/{video_id}/custom-summaries/{custom_summary_id}.
statusMeaning
queuedBeing prepared. summary is null.
readysummary holds the sections logline, tone_pacing, setting, themes (each a string or null; notes explains a null section).
rejectedThe instructions were out of scope; message says why.
failedGeneration failed; message carries a short reason. Request again.

Each request counts as one summary against your organization's summary quota (summaries in GET /api/v1/usage), not against the video quota, including one that ends rejected; a failed request is returned to the quota. A video without a standard summary yet answers 422 with code: custom_summary_no_base_summary.

MRSS feeds​

Feeds are registered in the dashboard. The API lets you list them, read per-feed item counts, adjust enabled, poll_interval_s (3600, 21600 or 86400 seconds) and label, delete a feed, and page through its items.

  • GET /api/v1/mrss-feeds/{feed_id} returns counts on two axes: submission (pending / submitted / failed) and indexing (indexed_done / indexed_running / indexed_failed).
  • GET /api/v1/mrss-feeds/{feed_id}/items items carry status (submission), index_status, index_reason_code and the resulting video_id.
  • Deleting a feed keeps already-indexed videos in your library with their external_content_id; re-registering the same URL later treats every item as new.