Sora API Alternative: Migrating Your Video Pipeline After the September 24 Shutdown
sora-2, sora-2-pro and dated snapshot models on September 24, 2026, and its deprecations page lists no recommended replacement. If your product called POST /v1/videos, the closest like-for-like replacement you can switch to today is the ZSky video API: the same asynchronous pattern (create a job, poll it, download the file), text-to-video and image-to-video, synchronized audio on every clip, clips from 1 to 10 seconds at up to 2560 px on a side, and a monthly spending cap you set instead of an open-ended per-second meter. It is included with the Max plan ($99 a month, or $79 a month billed annually) with a monthly allowance of videos and images; usage beyond it is pay-per-use up to your cap. The field mapping and a working Python port are below.
What replaced the Sora API?
Nothing from OpenAI. The Videos API and the sora-2 models were removed on September 24, 2026, and OpenAI's deprecations page leaves the recommended-replacement column empty, so every team that built on it picks its own successor. For developers who want the same create, poll and download job shape rather than a new architecture, the ZSky video API is the closest match available today: text-to-video and image-to-video, synchronized audio on every clip, clips of 1 to 10 seconds at up to 2560 px, and a monthly spending cap you set. Teams that need 12-second clips, completion webhooks or Sora's remix endpoints will not find them here; the rest of the migration is a handful of renamed fields, mapped below.
What OpenAI removed on September 24
On March 24, 2026, OpenAI told developers using the Videos API that the API and every Sora 2 model alias and snapshot would be removed on September 24, 2026. The consumer Sora app and website closed earlier, on April 26. The OpenAI deprecations page lists the removals as:
- the Videos API itself (
POST /v1/videos,GET /v1/videos/{id},GET /v1/videos/{id}/contentand the remix endpoint), sora-2andsora-2-pro,- the dated snapshots
sora-2-2025-10-06,sora-2-2025-12-08andsora-2-pro-2025-10-06.
The "recommended replacement" column for every one of those rows is empty. This is not a version bump. There is no Sora 3 model ID to swap in, no grace period, and no migration guide from OpenAI. Any request that names a sora-2 model now fails, and if your application stored Sora video IDs rather than the downloaded files, those downloads are no longer reachable through the API. You choose the replacement, and you port the calls.
What a replacement has to give you
Most Sora integrations are small. They submit a prompt, poll a job until it finishes, download an MP4, and store or serve it. A replacement needs to cover the same surface without a regression your users will notice:
- The same asynchronous shape. Create a job, poll a status, fetch a file. If the replacement forces a synchronous request that blocks for a minute, your queue and timeouts change.
- Text-to-video and image-to-video. Sora's
input_referenceanchored a clip to a still image. If you used it, the replacement needs an equivalent. - Audio in the clip. Sora 2 generated synchronized sound. Several video APIs return silent MP4s, which is a visible regression for anything social.
- Durations and resolutions that cover your product. Sora offered 4, 8 or 12 seconds at 720p, and 1080p-class sizes on the Pro model.
- A spend ceiling you control. Per-second metering with no cap of its own made a busy month expensive and hard to predict.
- A safety layer with a clear status. You need to know when a job was refused rather than failed, so you can tell the user.
- Ownership and retention controls. Who owns the output, how long the file is kept, and whether you can opt out of storage.
The ZSky video API covers each of those. The table below puts the two APIs side by side so you can check it against your own code.
Sora 2 API vs ZSky video API
| Item | Sora 2 API (removed) | ZSky video API |
|---|---|---|
| Status today | Removed September 24, 2026 | Live |
| Create a video | POST https://api.openai.com/v1/videos | POST https://zsky.ai/api/v1/videos/generate |
| Authentication | Authorization: Bearer | X-API-Key header |
| Model selection | model: sora-2 or sora-2-pro | None. One pipeline, no model parameter. |
| Prompt | prompt | prompt, up to 2,000 characters |
| Duration | seconds: "4", "8" or "12" (a string) | duration: any integer 1 to 10 seconds (default 5) |
| Resolution | size: 1280x720 or 720x1280; larger sizes on the Pro model | width and height, 256 to 2560 px each (default 1280×720) |
| Image to video | input_reference, a multipart image upload | init_image_base64: PNG, JPEG or WebP up to 12 MiB, base64-encoded |
| Reproducible output | Not documented | seed (integer) |
| Poll status | GET /v1/videos/{id} → queued, in_progress, completed, failed | GET https://zsky.ai/api/v1/jobs/{job_id} → queued, processing, completed, failed, blocked |
| Download | GET /v1/videos/{id}/content | result_url in the completed job: a signed download link valid for 24 hours |
| Completion webhooks | Yes | No. Poll; most clips finish in about 30 seconds. |
| Audio | Generated with the clip | Synchronized audio on every clip, included |
| Lip-sync to your own audio | No | Yes: send audio_base64 (up to 10 MB) with lip_sync: true |
| Remix and character endpoints | Existed | Not offered |
| Price | sora-2: $0.10 per second of video. sora-2-pro: $0.30 to $0.70 per second depending on resolution. | Included with Max ($99 a month, or $79 a month billed annually) with a monthly allowance of videos and images; usage beyond it is pay-per-use, only if you opt in, with a monthly cap you set ($5 to $1,000). Current rate card: zsky.ai/api-docs. |
| Rate limits | Varied by account tier | 20 requests a minute per key, 3 concurrent jobs, and a soft cap of 50 videos and 200 images a day (lifted on request) |
| Retention | Account data deleted after the shutdown | Results kept 7 days unless you delete them earlier. Send X-ZSky-Store: 0 to get the bytes inline and store nothing. |
| Content safety | Moderation errors | Same safety pipeline as the web app. A refused job returns status: "blocked" with a message. |
| Public feed | n/a | API outputs never appear in ZSky's public Explore feed |
| Visible watermark | None | None on Max outputs (an invisible provenance mark only) |
Two things stand out for a migration. The job lifecycle is the same, so your polling loop, retries and storage code survive with renamed fields. And spend has a ceiling: Sora billed every second with no cap of its own, while ZSky's pay-per-use stops at the monthly cap you set.
Parameter mapping
| Sora 2 field | ZSky field | Notes |
|---|---|---|
model | (drop it) | ZSky has no model parameter. |
prompt | prompt | Natural-language prompts port unchanged. Limit 2,000 characters. |
seconds: "8" | duration: 8 | Integer seconds, 1 to 10. A 12-second Sora job becomes a 10-second clip. |
size: "1280x720" | width: 1280, height: 720 | Each side 256 to 2560 px. Portrait: 720 by 1280. |
input_reference (file) | init_image_base64 (string) | Base64 of a PNG, JPEG or WebP up to 12 MiB. Image URLs are not accepted. |
| — | seed | Optional integer for reproducible runs. |
| — | X-ZSky-Store: 0 (header) | Optional zero-retention mode. |
id (video_…) | job_id | Returned by the create call together with poll_url. |
status: in_progress | status: processing | Terminal states: completed, failed, blocked. |
GET /videos/{id}/content | result_url | Plain HTTPS GET on the signed URL; no API key needed for the download itself. |
The port, in Python and curl
Here is a typical Sora 2 integration using the OpenAI SDK, and the same job on ZSky using requests. The ZSky version is the same number of lines; only the field names and the download step change.
Before (Sora 2, OpenAI SDK):
import time
from openai import OpenAI
client = OpenAI()
video = client.videos.create(
model="sora-2",
prompt="A lighthouse beam sweeping across a black sea at night, cinematic",
seconds="8",
size="1280x720",
)
while video.status in ("queued", "in_progress"):
time.sleep(5)
video = client.videos.retrieve(video.id)
content = client.videos.download_content(video.id)
content.write_to_file("clip.mp4")
After (ZSky video API):
import os, time, requests
BASE = "https://zsky.ai/api/v1"
HEADERS = {"X-API-Key": os.environ["ZSKY_API_KEY"]}
job = requests.post(f"{BASE}/videos/generate", headers=HEADERS, json={
"prompt": "A lighthouse beam sweeping across a black sea at night, cinematic",
"duration": 8,
"width": 1280,
"height": 720,
}).json()
# {"job_id": "8a1...", "status": "queued", "kind": "video", "poll_url": "/v1/jobs/8a1..."}
while True:
state = requests.get(f"{BASE}/jobs/{job['job_id']}", headers=HEADERS).json()
if state["status"] in ("completed", "failed", "blocked"):
break
time.sleep(5)
if state["status"] == "completed":
# result_url is a signed link, valid for 24 hours; download it once and keep the file
open("clip.mp4", "wb").write(requests.get(state["result_url"]).content)
else:
print(state["status"], state.get("error"))
The same two calls with curl:
curl -s -X POST https://zsky.ai/api/v1/videos/generate \
-H "X-API-Key: $ZSKY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt":"A lighthouse beam sweeping across a black sea at night, cinematic","duration":8,"width":1280,"height":720}'
# {"job_id":"8a1...","status":"queued","kind":"video","poll_url":"/v1/jobs/8a1..."}
curl -s https://zsky.ai/api/v1/jobs/8a1... -H "X-API-Key: $ZSKY_API_KEY"
# {"job_id":"8a1...","status":"completed","kind":"video","result_url":"https://...","expires_in":86400}
Image-to-video replaces Sora's multipart input_reference with a base64 string in the same JSON body:
import base64
frame = base64.b64encode(open("frame.png", "rb").read()).decode()
job = requests.post(f"{BASE}/videos/generate", headers=HEADERS, json={
"prompt": "slow push-in, wind moving through the trees",
"duration": 5,
"init_image_base64": frame,
}).json()
Three details worth handling in production. Every 429 carries a plain-English error field naming the cause: the per-minute limit, the concurrency limit and the daily soft cap also send a Retry-After header, while an exhausted monthly allowance does not, because nothing changes until the counters reset or you enable pay-per-use. One handler that reads error and honors Retry-After when present covers all four. A blocked status means the safety filter refused the prompt or the output; show the user the error text rather than retrying. And the signed result_url expires after 24 hours, so download the file when the job completes and keep your own copy.
What Sora charged, and how ZSky bills
OpenAI published Sora 2 API rates per second of generated video: $0.10 for sora-2 at 720p, and $0.30, $0.50 or $0.70 for sora-2-pro at 720p, 1024p and 1080p. A batch tier halved those rates in exchange for up to 24-hour turnaround. An 8-second sora-2 clip was $0.80; the same clip on sora-2-pro at 1080p was $5.60, and a month's bill had no ceiling other than your own rate limiting.
ZSky's API comes with the Max plan ($99 a month, or $79 a month billed annually), which includes a monthly allowance of videos and images. Usage beyond the allowance is pay-per-use, and only if you opt in: you set a monthly cap between $5 and $1,000 in Settings, generation stops at the cap, and unused cap budget rolls over. The current allowance and rate card are on the API docs page. The practical difference from Sora is that your worst-case month is a number you chose in advance, not a function of how many seconds your users requested. For sustained volume beyond the daily soft cap, email [email protected].
Every limit, in one place
- Plan: the API is a Max feature. Max is $99 a month, or $79 a month billed annually.
- Included each month: a monthly allowance of videos and images (300 videos and 1,000 images as this is written; the current figures are on the docs page). The counters reset on the 1st of each month, UTC.
- Beyond the allowance: pay-per-use, only if you opt in, with a monthly cap you set between $5 and $1,000. Rates are on the docs page. Unused cap budget rolls over.
- Rate limit: 20 requests a minute per API key.
- Concurrency: 3 jobs in flight per key. A fourth returns
429withRetry-After: 5. - Daily soft cap: 50 videos and 200 images a day; email [email protected] to lift it.
- Clip length: 1 to 10 seconds (
duration), default 5. - Resolution:
widthandheightfrom 256 to 2560 px each, default 1280×720. - Prompt: up to 2,000 characters.
- Image input: PNG, JPEG or WebP up to 12 MiB, base64 in the JSON body. Audio for lip-sync: up to 10 MB.
- Result link: signed URL valid 24 hours. Storage: 7 days, or none with
X-ZSky-Store: 0.DELETE /api/v1/jobs/{job_id}purges early. - Safety: every request passes the same prompt and output checks as the web app; refused work returns
blocked. - Ownership: you own the output. Max includes an explicit commercial license, and API outputs never appear in the public Explore feed.
How to get a key today
- Subscribe to Max ($99 a month, or $79 a month billed annually).
- Open Settings → API + MCP and create your key. It is shown once; rotating it invalidates the previous key immediately.
- Confirm it works:
curl https://zsky.ai/api/v1/usage -H "X-API-Key: zsky_live_..."returns your current-month counters.
If you want to see the output before you pay, the same generation pipeline runs free in the browser at zsky.ai/create with a free account and no card. Type the prompts your Sora integration sends, and judge the clips yourself.
What ZSky does not replace
A migration guide that only lists wins is not one you can plan against. The gaps are:
- 12-second clips. The API tops out at 10 seconds per clip.
- Completion webhooks. There is no callback; you poll. Jobs usually finish in about 30 seconds, so a 5-second poll interval is fine.
- Remix and character endpoints. Sora's
/remixand character-upload features have no equivalent. - A batch tier. There is no discounted slow lane.
- 4K through the API. The API caps each side at 2560 px. 4K output is a web-app feature on Max.
- SDK compatibility. ZSky is not an OpenAI-compatible endpoint. The port is a handful of renamed fields, as shown above, not a base-URL swap.
Using it from an AI agent
The same key also drives ZSky's MCP adapter, a local stdio server for MCP clients such as Claude Code and Claude Desktop. It exposes tools to generate images and videos, animate a local image, check job status and read usage, and it runs against the same /api/v1 endpoints with the same allowance and limits. If your Sora integration lived inside an agent workflow rather than a product, that is the shorter path. Setup steps are on the API and MCP docs page.
Frequently asked questions
Is the Sora API really gone, or only deprecated?
Gone. Deprecation was announced on March 24, 2026, and removal happened on September 24, 2026. OpenAI's deprecations page lists the Videos API and every sora-2 model ID with no recommended replacement.
Is there a free Sora API alternative?
Not from ZSky as an API. ZSky's web app is free and unlimited for people, but the developer API is a Max feature at $99 a month with a monthly included allowance. The free browser tier is a good way to evaluate the output before subscribing.
Does the ZSky API generate audio like Sora 2 did?
Yes. Every clip comes with synchronized audio at no extra cost, and you can supply your own audio track for lip-synced video with audio_base64 and lip_sync: true.
Can I keep my existing Sora prompts?
Yes. Prompts are plain natural language on both APIs. The only hard limit on ZSky is 2,000 characters per prompt.
How long does a video take?
Typically about 30 seconds from submission to a completed status. Poll the job every few seconds; the response carries a terminal flag when it is done.
What happens when I use up the monthly allowance?
The next request returns 429 with a message naming the allowance. If you have opted into pay-per-use in Settings, generation continues at the current rates until your monthly cap is reached; if not, it resumes when the counters reset on the 1st.
Who owns the output?
You do. Max includes an explicit commercial license, API outputs never appear in the public Explore feed, and you can opt out of storage entirely with the X-ZSky-Store: 0 header.
Is there an OpenAI-compatible endpoint I can point the SDK at?
No. The request and response fields differ, as the mapping table shows. Porting a typical integration is a matter of renaming a few fields and downloading from result_url instead of a content endpoint.
Switch your video pipeline today
Max includes API and MCP access with a monthly allowance, pay-per-use beyond it up to a cap you set, and a key you create yourself in Settings.
Get API access with Max → Read the API overviewSources: OpenAI API deprecations page (Videos API and sora-2 model removals, September 24, 2026); OpenAI Help Center, "What to know about the Sora discontinuation" (app closure April 26, 2026; API discontinuation September 24, 2026); OpenAI's published Sora 2 API per-second rates as summarized by CostGoat and eesel; ZSky API limits and response fields from the ZSky API documentation, checked September 26, 2026.