Sora jobs in flight at shutdown: reconcile, then resubmit to Sume
OpenAI's page gives the removal date but not in-flight jobs. Mark every unfinished Sora row, then resubmit its prompt to Sume with a key built from the old id.

Treat every Sora row that never reached completed as lost, mark it, and resubmit its saved prompt to Sume once, using an Idempotency-Key built from the old Sora id so a re-run of your script cannot bill the same video twice. The deprecations page lists September 24, 2026 as the removal date for the Videos API and says nothing about what happens to jobs queued or running on that day, so do not assume any will finish.
I read OpenAI's deprecations page and video guide on 2026-10-08; neither describes in-flight handling or a grace period for downloads.
Classify the rows first
Before you spend anything, sort your table into the groups below. Only the first group needs new generation.
| Row state at shutdown | What you know | Action |
|---|---|---|
| queued or in_progress, no file stored | page is silent on in-flight jobs | resubmit the prompt to Sume |
| completed, file copied to your storage | download URLs lived at most 1 hour | nothing to do |
| completed, only the Sora id stored | the id cannot be fetched after removal | resubmit if the clip is still needed |
| failed | no output was produced | resubmit only if a person still wants it |
The reconcile script
This reads a local SQLite table and resubmits each unfinished row. Keying on the old id means a crash halfway through is safe: the second run gets the first job back for rows already sent. Check your Sume balance first, since each submit reserves the list price times 1.25 from the workspace USD balance and answers 402 insufficient_credits when it is short.
import json, os, sqlite3, urllib.request
KEY = os.environ["SUME_API_KEY"]
db = sqlite3.connect("jobs.db")
db.execute("create table if not exists jobs (id text primary key, prompt text, status text, sume_id text)")
def submit(prompt, idem):
req = urllib.request.Request(
"https://api.sume.com/v1/videos", method="POST",
data=json.dumps({"model": "sume/auto", "prompt": prompt}).encode(),
headers={"Authorization": "Bearer " + KEY, "Content-Type": "application/json",
"Idempotency-Key": idem})
return json.load(urllib.request.urlopen(req))["id"]
def main():
rows = db.execute("select id, prompt from jobs "
"where status in ('queued', 'in_progress') and sume_id is null").fetchall()
for old_id, prompt in rows:
new_id = submit(prompt, "migrate-" + old_id)
db.execute("update jobs set status = 'resubmitted', sume_id = ? where id = ?", (new_id, old_id))
db.commit()
print(old_id, "->", new_id)
main()After the resubmit
The new clip will not be frame-identical to anything Sora would have produced, because you are on a different model. Tell whoever asked for it. If you also need to cap the spend, submit in waves and respect the plan queue; the Sume docs list concurrency, queue and accepted capacity per plan, from 1, 5 and 6 on Free to 20, 100 and 120 on Scale.
The shutdown runbook has the full six steps, and the post on what to store in place of Sora ids covers the column design.
Sources
Related posts
More in Developers
- Measure your own render time on Sume from job event timestamps
No published Sora timing carries over. Read GET /v1/jobs/{id}/events, diff created, started and completed, and keep your own queue and render split per model.
- Sora to Sume pull request review: eight lines to check before merge
A reviewer's checklist for a Sora-to-Sume PR: duration and resolution types, status words, the 302 download, the webhook body, idempotency and removed fields.
- Sora API gone: port to Sume with Python urllib, no extra packages
Replace the OpenAI videos calls with a stdlib-only Python script: submit to Sume /v1/videos, poll, then fetch the 302 artifact URL without sending your key.
- Speech-to-text for short voice notes: one request, sync mode
Send a voice note's public URL to Sume STT with mode sync and a 30-second wait. A cent per minute, plus the poll fallback when the job outlasts the wait.
Written by Sume