Sora API is gone: replace the calls with POST /v1/videos on Sume
OpenAI removed the Videos API and sora-2 on 2026-09-24. Point video code at Sume POST /v1/videos: new model ids, duration, resolution, polling, download.

OpenAI's deprecations page says the Videos API and the Sora 2 model aliases and snapshots were removed from the API on September 24, 2026, and it lists no replacement. If your app still has a video job path that called them, the fix on Sume is POST /v1/videos with a catalog model id such as seedance-2.5 or wan-3.0, then a poll and a download.
This is a rewrite of the request body, not a base-URL swap. The submit, poll, and download shape stays the same, and the model, size, and result handling change.
What changes in your code
Model ids are bare catalog ids, with no provider prefix. Ask GET /v1/videos/models for the live list instead of hard-coding one. Length is duration in whole seconds, and the range depends on the model: seedance-2.5 takes 4-30 and wan-3.0 takes 2-30.
Output shape is resolution plus aspect_ratio, for example 720p and 9:16. A size string like 1280x720 is rejected with 400 unsupported_parameter, because every v1 model reports supported_sizes: null. Audio comes with the model, and generate_audio is optional.
| Step | Sume call | Note |
|---|---|---|
| Create | POST /v1/videos | Returns id, polling_url, status pending |
| Check | GET /v1/videos/{id} | pending, in_progress, completed, failed, cancelled |
| Download | GET /v1/videos/{id}/content?index=0 | Send the API key header |
| Retry safely | Idempotency-Key on the create | A replay returns the original job |
| Callback | callback_url on the create | HTTPS only, signed, terminal events |
A working replacement
The script submits, polls every 15 seconds, and saves the clip. It reads unsigned_urls[0], which points at the Sume content endpoint, so the download call sends the API key too.
import os, time, requests
BASE = "https://api.sume.com/v1/videos"
H = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}
job = requests.post(BASE, timeout=30,
headers={**H, "Idempotency-Key": "sora-port-demo-1"},
json={"model": "seedance-2.5", "prompt": "A paper boat on a rainy street",
"duration": 10, "resolution": "720p", "aspect_ratio": "16:9"})
job.raise_for_status()
poll_url = job.json()["polling_url"]
while True:
s = requests.get(poll_url, headers=H, timeout=30).json()
if s["status"] in ("completed", "failed", "cancelled"):
break
time.sleep(15)
if s["status"] != "completed":
raise SystemExit(f"job ended {s['status']}: {s.get('error')}")
video = requests.get(s["unsigned_urls"][0], headers=H, timeout=120)
open("clip.mp4", "wb").write(video.content)Things that do not carry over
Prompts were tuned for one model, and a different model will read them differently. Run a short test per model before you move traffic. Length limits also differ, so check supported_durations before you submit.
Keep the job id in your own store. The same job can be read at GET /v1/jobs/{id}/status and GET /v1/jobs/{id}/result, which is useful when you want queue state or a webhook receipt.
Sources
Related posts
More in Developers
- Restyle avatar clip captions with source_caption_id, no re-transcribe
To try a second caption look on an avatar video, send source_caption_id instead of the video URL. Sume reuses the word timings. Cost, errors and a worked flow.
- Retry a failed 30-second video job on Sume without paying twice
Reuse the same Idempotency-Key when you retry a seedance-2.5 or wan-3.0 submit on Sume; a replay returns the original job. Failed status and error field.
- Retry a failed Format create with the same Idempotency-Key
After a 402 or 503 on a Format create, Sume releases the Idempotency-Key. Fix the cause, then retry with the same key instead of minting a new one.
- Roda: verify a Sume webhook with r.body.read in config.ru
A Roda app in one config.ru that verifies a Sume webhook with OpenSSL HMAC and a length-checked secure compare. Aborts on an empty secret. Tested on Ruby 4.0.
Written by Sume