Skip to main content
A video renders for minutes, so POST /v1/media/videos does not wait: it answers 202 with a media job in queued, and you read the job back until it is completed or failed. The finished clip is a file. Images are the synchronous case — see Images. The same job is what an agent’s generate_video tool submits; the catalogue is shared, and it is its own — a video model is never an agent’s model.

The media job object

string
med_….
string
Always media_job.
string
video. Other async media kinds share the object and the routes below.
string
queued → processing → completed | failed.
string | null
The model rendering it.
object
The request as accepted.
object | null
On completed: { files: [{ file_id, content_type, width, height, duration_seconds }] }. Dimensions are null where the model did not report them.
object | null
On failed: { code, message } — the reason, safe to repeat, so a refusal and an outage read differently.
integer | null
What was debited, in integer micro-USD, once the job finished: null while it runs, 0 on a failure — a job that fails is not billed.
string | null
The session whose agent submitted it; null for a job submitted through this API.
string
Submission time.
string | null
When it reached a terminal status.

Generate a video

POST /v1/media/videos → 202 Accepted — scope agents:write, Idempotency-Key required.
string
required
A model id from GET /v1/media/models?kind=video. Video models publish no price, so there is no default to pick for you.
string
required
What to render.
string[]
One public http(s) image, used as the first frame the video animates from. A private or loopback host is validation_failed.
integer
Target duration, 1–60. Models honour it as they can — each renders a set of lengths of its own, and clamps to what it supports.
string
Such as 16:9 or 9:16. Passed to the model as-is.
integer
Repeat an earlier result, on models that honour a seed.
That is the whole request. Not on it today: a resolution or size, a last frame, style or subject reference images beyond the first frame, an audio on/off switch, a completion callback URL, and any per-model or per-provider passthrough. A body that carries one of them is validation_failed, not quietly dropped. Completion is read by polling the job or, for an agent’s job, from the session’s events; a job submitted here does not fire a webhook — clip jobs do, video jobs do not yet.

Retrieve a job

GET /v1/media/{id} → 200 OK — scope agents:read. Poll it; a job left rendering for over thirty minutes is failed and not billed. Any med_ reads here, a clip job included — the same object GET /v1/media/clips/{id} answers.
Response
Read the clip with GET /v1/files/{file_id}?download=true. The file carries source.type = video_generation and source.job_id = this job. A job an agent submitted with generate_video carries its session_id, and that session’s event stream announces it: media.job.queued, then media.job.completed or media.job.failed. A job submitted here has no session, so poll it.

List jobs

GET /v1/media → 200 OK, cursor-paginated — scope agents:read. See Pagination.
string
video (or another async kind).
string
queued, processing, completed or failed.
string
Jobs an agent submitted from one session.
number
Page size, 1–100.
string
The next_cursor of the previous page.

Billing

A job is billed what it actually cost, on the media tier, when it completes; nothing is quoted or held beforehand, and there is no balance check at submit — a low balance goes negative by the cost of the clip rather than turning the job away. The debit settles under the job id at completion, exactly once. A job that fails, is refused, or is abandoned is not billed.

In a session

An agent submits with the generate_video tool — the same job, the same catalogue, the same fields (model?, prompt, image_urls?, seconds?, aspect_ratio?, seed?). The tool does not block: it returns the job’s handle in the same turn and the agent carries on; when the job finishes, the file is saved and the session is woken with its file_id. Three differences from calling the route yourself:
  • model may be left to the agent’s configuration. tools.configs.generate_video.config.models on the agent names the model that runs when the agent does not choose one. It is a default, not an allow-list; with none pinned and none named, the agent is asked to choose, because there is no price to pick the cheapest by.
  • Completion is an event, not a poll. The session’s event stream announces media.job.queued, then media.job.completed or media.job.failed; the job also reads back here with its session_id set.
  • The bill lands on the agent. The debit carries the session_id and settles against the agent’s budget as the media component; a job through this route carries no session and settles against the organization.

Errors