OpenAI Video Format
POST /v1/videos and GET /v1/videos/{task_id} are the OpenAI video dialect of the same asynchronous task API described in Create a video task and Get a video task. The submit body uses the same fields; the difference is the response: both the submit response and the GET return an OpenAI video object rather than the task record. This is the format Sora models use, and it is what an OpenAI SDK’s video client expects.
Endpoints
Section titled “Endpoints”| Method | Path | Purpose |
|---|---|---|
POST | /v1/videos | Submit a generation task |
GET | /v1/videos/{task_id} | Read the task as an OpenAI video object |
GET | /v1/videos/{task_id}/content | Download the finished video bytes |
POST | /v1/videos/{video_id}/remix | Create a task derived from an existing video |
Submit
Section titled “Submit”curl https://api.routeapi.ai/v1/videos \ -H "Authorization: Bearer $ROUTEAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "sora-2", "prompt": "A calico cat playing a piano on stage", "size": "1280x720", "seconds": "4" }'The request fields are the ones on Create a video task: model, prompt, seconds / duration, size, input_reference, images, and metadata. input_reference is honored by the models that support a reference image. multipart/form-data is accepted as well, so a file can be uploaded directly:
curl https://api.routeapi.ai/v1/videos \ -H "Authorization: Bearer $ROUTEAPI_KEY" \ -F "model=sora-2" \ -F "prompt=A calico cat playing a piano on stage" \ -F "size=1280x720" \ -F "seconds=4" \ -F "input_reference=@cat.jpg"Submit response
Section titled “Submit response”{ "id": "task_G2RdWGt4t1nck5fYqTjwREKJo8Ze4RAV", "task_id": "task_G2RdWGt4t1nck5fYqTjwREKJo8Ze4RAV", "object": "video", "model": "sora-2", "status": "queued", "progress": 0, "created_at": 1792048458}id is the RouteAPI task ID. task_id is a compatibility alias and may be absent in future responses; read id. Always address the task by this id; identifiers echoed inside the stored upstream payload are not the value to poll or download with.
The submit response also carries an X-New-Api-Other-Ratios header with the billing multipliers that were applied, for example {"seconds":5,"size":1} for a Sora request.
Get the video object
Section titled “Get the video object”GET /v1/videos/{task_id}curl https://api.routeapi.ai/v1/videos/task_G2RdWGt4t1nck5fYqTjwREKJo8Ze4RAV \ -H "Authorization: Bearer $ROUTEAPI_KEY"{ "id": "task_G2RdWGt4t1nck5fYqTjwREKJo8Ze4RAV", "task_id": "task_G2RdWGt4t1nck5fYqTjwREKJo8Ze4RAV", "object": "video", "model": "seedance-2.0", "status": "completed", "progress": 100, "created_at": 1792048458, "completed_at": 1792048569, "metadata": { "url": "https://...mp4" }}| Field | Description |
|---|---|
id | RouteAPI task ID |
object | Always "video" |
model | Model that ran the task |
status | queued, in_progress, completed, or failed |
progress | Integer 0–100 |
created_at / completed_at | Unix seconds. Some adaptors fill completed_at with the row’s last update time, so decide completion from status, not from this field |
expires_at, size, seconds, remixed_from_video_id | Present only when the provider reports them |
metadata.url | Video URL for providers that report one through the video object. Use /v1/videos/{task_id}/content if you want the bytes regardless of provider |
error.code / error.message | Set when the task failed, for example expired or cancelled for Seedance |
The upstream payload’s own fields (such as expires_at or size) appear only when the provider returns them; the set above is what RouteAPI guarantees to map. On Gemini and Vertex channels the task’s status is refreshed from the provider before this object is built.
The task is scoped to the API Key’s user: an id that belongs to another account is reported as task_not_exist, exactly as on the task-record endpoint.
Download the content
Section titled “Download the content”GET /v1/videos/{task_id}/contentReturns the video bytes, streamed through RouteAPI. The response carries Cache-Control: public, max-age=86400 and the upstream Content-Type. The route accepts an API Key or a console session and only serves tasks owned by the caller. See Download the video for the error codes and a curl example.
POST /v1/videos/{video_id}/remixCreates a new task from an existing video. RouteAPI looks up video_id among your tasks, derives the model from it, and pins the request to the channel that produced the original video so the result stays compatible.
curl https://api.routeapi.ai/v1/videos/task_G2RdWGt4t1nck5fYqTjwREKJo8Ze4RAV/remix \ -H "Authorization: Bearer $ROUTEAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "prompt": "The same cat, now wearing a tiny hat" }'Remix is meaningful for the Sora family (sora-2, sora-2-pro), where the request is forwarded to the upstream’s own remix endpoint and prompt is required. For other platforms the request is still accepted, the model and channel are resolved from the origin task, and the task is built by that model’s adaptor — but the upstream never receives a remix instruction, so the new task is an ordinary generation, not a derivative of video_id.
Remix inherits the origin task’s billing multipliers where the adaptor recorded them, so the new task is priced like the original rather than from defaults.
Sora models use this endpoint family directly; the parameters that matter are size and seconds.
| Model | Accepted sizes |
|---|---|
sora-2 | 720x1280, 1280x720 |
sora-2-pro | 720x1280, 1280x720, 1792x1024, 1024x1792 |
- An unsupported size returns
400with codeinvalid_sizebefore anything is reserved. - Defaults are
720x1280and 4 seconds. seconds(orduration) is validated the same way as every other video model: negative values and values above 3600 are rejected with400 invalid_seconds.input_referenceis forwarded as the reference image; JSON and multipart submissions both work.- Billing is computed from
secondsandsize: a1792x1024or1024x1792output costs a size multiplier (1.666667) over the base size, on top of the per-second rate. The applied multipliers are returned inX-New-Api-Other-Ratios. - Remix is supported on this family.
Availability and price are per account — confirm the model appears in GET /v1/models and check the model detail page.
Errors
Section titled “Errors”The task error shape applies once routed:
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | Missing or blank prompt, malformed body, or a field with the wrong type |
| 400 | invalid_seconds | duration / seconds is negative or above 3600 |
| 400 | invalid_size | The model does not accept this size (for example an unsupported Sora size) |
| 400 | task_not_exist | The video_id in a remix is unknown or belongs to another account |
| 400 | task_source_unavailable | The origin task’s channel was deleted or disabled, or its adaptor is missing |
| 400 | asset_remix_conflict | A remix of a non-asset task tried to reference an asset:// material |
| 429 | — | Upstream capacity exhausted; the message is replaced with a generic retry notice |
| 500 | build_request_failed, do_request_failed | The task could not be converted or the upstream connection failed |
| 500 | convert_to_openai_video_failed | The stored task data could not be converted into a video object |
| 501 | not_implemented | GET /v1/videos/{task_id} on a platform whose adaptor has no OpenAI video conversion |
| any | fail_to_fetch_task | The upstream answered the submit with a non-200 status; the HTTP status is the upstream’s |
Requests rejected before routing use the standard {"error": {"message": ..., "type": "error", "code": ...}} body instead.
Next steps
Section titled “Next steps”- Create a video task — the shared request envelope and submission errors.
- Get a video task — the task-record dialect, statuses, and the content download endpoint.
- Seedance, Hailuo, Wan — model-specific parameters.