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
| Method | Endpoint | Returns | Notes |
|---|---|---|---|
| Single URL | POST /api/v1/videos | 202 with video_id, job_id, created | Publicly fetchable http(s) URL. Optional title. |
| Single file | POST /api/v1/videos/upload | 202 with video_id, job_id, storage_path | Multipart upload. Same async flow as the URL route. |
| Batch of URLs | POST /api/v1/videos/batch with kind: "url" | 201 with batch_id, counts and one items[] row per URL | Up to 500 rows per request. Each row carries its own video_id / job_id / status / error in request order. |
| Batch of files | POST /api/v1/videos/batch/upload | 202 with batch_id and accepted / skipped / failed counts | Recommended 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 URLs | POST /api/v1/videos/batch/upload-sheet | 201 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 feed | Registered from the dashboard; managed with GET /api/v1/mrss-feeds and friends | Feed items that become videos | The feed is polled on a schedule; each ingested video keeps the feed item's guid as external_content_id. |
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
| Identifier | Scope | Stability |
|---|---|---|
video_id | One 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_id | One 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_id | A submission receipt grouping many URLs or files. | Stable, but it only records submission outcomes, never indexing progress. |
external_content_id | Your 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.
status | Meaning |
|---|---|
pending | Queued; the pipeline has not started. |
running | Pipeline in flight. done_scenes / total_scenes track per-scene progress. |
done | Indexing succeeded. The video is indexed and searchable. |
failed | Indexing failed. Read reason_code for the stable cause. |
total_scenesintegerNumber of scenes detected for this attempt. 0 until scene detection has run.
done_scenesintegerScenes whose analysis has finished so far.
reason_codestring | nullStable, 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 | nullRaw 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 | nullWall-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.
| Field | Meaning |
|---|---|
status | Indexing 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_code | Why indexing failed. Only meaningful when status is failed; null otherwise. |
indexed_at | When 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_trigger | What started that attempt: initial or reindex. null when no attempt has finished yet. |
job_id | The job behind indexed_at. An attempt still in progress does not replace it. |
reindex_run_id | Identifier of the re-index run that produced the attempt; null outside a run. |
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 AIsummary,genre, IAB labels, thegarm_videoassessment,scene_markersfor every scene, the first page ofscenes, and whole-videoaggregations.- The video's scenes are searchable through
POST /api/v1/searchand usable by ad matching. indexed_at,index_triggerandjob_idare 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 pattern | Endpoint | Behaviour |
|---|---|---|
| Live, as analysis completes | GET /api/v1/videos/{video_id}/scenes/stream | NDJSON. 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 done | GET /api/v1/videos/{video_id}/scenes | Paged by offset / limit (max 200); total is the scene count. |
| With the video detail | GET /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 status | Meaning |
|---|---|
submitted | Accepted and handed to the pipeline. Not "finished indexing". |
skipped | Rejected before submission (for example a malformed or duplicate URL). |
failed | Submission itself errored. |
pending | Synthetic. 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.
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.
POST /api/v1/videos/{video_id}/custom-summarieswithinstructions(required, up to 2000 characters), optionalcast(the only person names the summary may use; without it, no person names are written) andlanguage(a language tag such asenorko, defaulten; it overrides any language named ininstructions). Returns202withcustom_summary_id,instruction_id,base_versionand astatusofqueued, orrejectedimmediately when the same instructions were already judged out of scope.- Poll
GET /api/v1/videos/{video_id}/custom-summaries/{custom_summary_id}.
status | Meaning |
|---|---|
queued | Being prepared. summary is null. |
ready | summary holds the sections logline, tone_pacing, setting, themes (each a string or null; notes explains a null section). |
rejected | The instructions were out of scope; message says why. |
failed | Generation 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}returnscountson two axes: submission (pending/submitted/failed) and indexing (indexed_done/indexed_running/indexed_failed).GET /api/v1/mrss-feeds/{feed_id}/itemsitems carrystatus(submission),index_status,index_reason_codeand the resultingvideo_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.