OpenAI Video API (/v1/videos, Sora 2)
Status: DOCUMENTED + DEPRECATED — the deprecations page (notice 2026-03-24) schedules removal of the Videos API and every Sora 2 model on 2026-09-24. On 2026-09-18 the endpoints still answer: GET /v1/videos, GET /v1/videos/{id}, GET /v1/videos/{id}/content LIVE_VERIFIED; POST /v1/videos, remix, edits, extensions, characters, DELETE DOCUMENTED/UNVERIFIED (not called: cost / destructive).
Sources: Videos reference · Video generation guide · Pricing · Deprecations · Changelog (2026-03-12 entries) · model pages sora-2, sora-2-pro · OpenAPI CreateVideo*Body, VideoResource, VideoCharacterResource.
Last verified: 2026-09-18.
Machine-readable: endpoints fragment openai-images-video-embeddings-moderation.json, parameters/openai-videos.json, objects/openai-media-objects.json, prices/openai-media.json.
Models
| Model | Alias → snapshot | Output | Sizes | Price / second (standard · batch) | Rate limit (RPM T1…T5) | Status |
|---|---|---|---|---|---|---|
sora-2 (speed, iteration) |
sora-2 → sora-2-2025-12-08 (older sora-2-2025-10-06) |
video + synced audio | 720×1280, 1280×720 | $0.10 · $0.05 | 25 / 50 / 125 / 200 / 375 | DEPRECATED → 2026-09-24 |
sora-2-pro (production) |
sora-2-pro → sora-2-pro-2025-10-06 |
video + audio | 720p $0.30 · $0.15; 1024×1792 / 1792×1024 $0.50 · $0.25; 1080×1920 / 1920×1080 $0.70 · $0.35 | 10 / 25 / 50 / 75 / 150 | DEPRECATED → 2026-09-24 |
Both models accept 4, 8, 12 s (spec enum) and, per guide/changelog 2026-03-12, 16 and 20 s. Cost = seconds × per-second rate (an 8 s 720p sora-2 clip ≈ $0.80).
Operations
| Method / path | Body | Returns | Status |
|---|---|---|---|
POST /v1/videos |
JSON or multipart: prompt*, model, seconds, size, input_reference, characters[] (guide only) |
Video job (status: queued) |
DOCUMENTED, UNVERIFIED |
GET /v1/videos?limit&after&order |
— | {object: list, data: Video[], first_id, last_id, has_more} |
LIVE_VERIFIED (200) |
GET /v1/videos/{video_id} |
— | Video |
LIVE_VERIFIED (200; unknown id → 404 Video with id '…' not found.) |
DELETE /v1/videos/{video_id} |
— | {id, deleted: true, object: "video.deleted"} |
DOCUMENTED (destructive, not run) |
| `GET /v1/videos/{video_id}/content?variant=video | thumbnail | spritesheet` | — |
POST /v1/videos/{video_id}/remix |
{prompt} |
Video (remixed_from_video_id) |
DOCUMENTED; being replaced by edits (changelog 2026-03-12: deprecated in 6 months) |
POST /v1/videos/edits |
JSON {video: {id}, prompt} or multipart video=@file, model, prompt |
Video |
DOCUMENTED, UNVERIFIED (upload path = eligible customers only) |
POST /v1/videos/extensions |
{video: {id}, prompt, seconds} (JSON or multipart) |
Video (seconds = stitched total) |
DOCUMENTED, UNVERIFIED |
POST /v1/videos/characters |
multipart video=@clip.mp4;type=video/mp4, name (1–80) |
{id, name, created_at} |
DOCUMENTED, UNVERIFIED |
GET /v1/videos/characters/{character_id} |
— | {id, name, created_at} |
DOCUMENTED, UNVERIFIED |
There is no list-characters operation: GET /v1/videos/characters?limit=1 → 400 invalid_value "Invalid 'video_id': 'characters'. Expected an ID that begins with 'video'." (routed as /videos/{video_id}).
SDKs: Python client.videos.create | retrieve | list | delete | download_content | remix | edit | extend | create_character | get_character | create_and_poll | poll; Node client.videos.create | retrieve | list | delete | downloadContent | remix | edit | extend | createCharacter | getCharacter.
Parameters of POST /v1/videos
| Param | Type | Default | Notes |
|---|---|---|---|
prompt * |
string 1–32 000 | — | describe shot type, subject, action, setting, lighting |
model |
sora-2 | sora-2-pro | snapshots |
sora-2 |
|
seconds |
string `"4" | "8" | "12"(+"16" |
size |
720x1280 | 1280x720 | 1024x1792 | 1792x1024 (+ 1080x1920 | 1920x1080 sora-2-pro, guide/pricing) |
720x1280 |
spec enum lacks 1080p |
input_reference |
multipart file or JSON {file_id} / {image_url} (URL or data URL ≤ 20 MiB) |
— | becomes the first frame; human faces rejected; Batch: JSON only |
characters |
[{id}], ≤ 2 |
— | guide only, absent from OpenAPI; name must appear verbatim in prompt; not with extensions |
Job lifecycle
POST /v1/videos ──► {status:"queued", progress:0}
│ poll GET /v1/videos/{id} every 10–20 s (exp. backoff) or webhook video.completed / video.failed
▼
in_progress (progress %) ──► completed (completed_at, expires_at) ──► GET …/content (mp4 | thumbnail | spritesheet)
└──► failed (error{code,message,misalignment?})- A render "may take several minutes"; longer/1080p jobs take materially longer.
expires_atmarks asset expiry: live job hadexpires_at − completed_at = 172 800 s = 48 h; batch-generated videos downloadable ≤ 24 h after the batch completes. Metadata stays listable after expiry (our 2-month-old job still lists ascompleted).- Webhook payload:
{"id":"evt_…","object":"event","created_at":…,"type":"video.completed"|"video.failed","data":{"id":"video_…"}}— id only, fetch details afterwards. - Batch API:
POST /v1/videosonly, JSON bodies, terminal statescompleted|failed|expired.
Video object (live sample)
{"id":"video_6a5e…a57a","object":"video","created_at":1784590069,"status":"completed","completed_at":1784590117,
"expires_at":1784762917,"model":"sora-2","progress":100,"prompt":"A simple blue geometric cube rotating on a white background.",
"remixed_from_video_id":null,"edited_from_video_id":null,"extended_from_video_id":null,"error":null,"seconds":"4","size":"720x1280"}edited_from_video_id and extended_from_video_id are LIVE_DISCOVERED (absent from reference and spec). error.misalignment{error_type, detailed_explanation, steer.message} reuses the safety-alert error types (see docs/openai/safety.md).
Remix, edit, extend, characters
- Remix (
/{id}/remix): re-render a completed video with a new prompt; superseded by edits. - Edits (
/videos/edits): targeted change keeping structure/continuity; one clear adjustment per edit; source = generated video id (model inferred) or uploaded MP4 (+ explicitmodel, eligible customers). - Extensions (
/videos/extensions): continue a completed clip using the whole source as context; each extension adds ≤ 20 s; ≤ 6 extensions, ≤ 120 s total; no characters / image references. - Characters: reusable non-human subject from a 2–4 s MP4 (16:9 or 9:16, 720p–1080p, match output aspect ratio); ≤ 2 per video; combinable with
input_reference; human likeness blocked by default (sales/eligibility).
Content policy and restrictions (guide)
Under-18-suitable content only (bypass "available in the future"); copyrighted characters/music rejected; real people incl. public figures cannot be generated; input images with human faces rejected; human-likeness character uploads blocked by default.
Examples and tests
examples/openai/video/ — list.sh|py|ts (LIVE_VERIFIED), create_poll_download.py|ts|sh (UNVERIFIED create; poll + download logic follows the guide). tests/openai/test_video.py: list/retrieve/404 shape unmarked (free); creation behind RUN_VIDEO_TESTS.