Parse Retry-After as seconds or HTTP-date before retrying a Sume 429
A 21-line Python helper that reads retry-after as integer seconds or an HTTP-date, caps the wait, and falls back to exponential delay when the header is absent.

When Sume answers 429, wait for the value in the retry-after header if it is present, and fall back to exponential backoff if it is not. The Python helper below accepts the two forms that HTTP allows for that header, a count of seconds or an HTTP-date, so a retry loop does not crash on either one. Sume's docs say to use the header when present, but they do not document its format, so a parser that handles both is the safe choice.
This matters more this week than usual. With the Sora API gone since 2026-09-24, many teams are re-pointing a video pipeline at a new backend in one batch, and a batch is exactly what produces 429s. A retry helper you can paste in and test offline is worth more than a clever one.
What Sume says about 429
Sume returns two different 429 codes, and both can carry rate-limit headers. The table is from the errors page and the generation admission page, read 2026-10-08.
- Public API responses can include
ratelimit-limit,ratelimit-remaining,ratelimit-reset, andretry-after. - Do not retry an unsafe submit without an
Idempotency-Key. - Read and status endpoints can also be rate limited. Treat that as poll backpressure, not as a failed job.
| Code | Meaning | Documented client behavior |
|---|---|---|
| rate_limited | Too many requests in the current window | Back off. Use retry-after if present. |
| queue_full | Workspace concurrency and queue capacity are both full | Wait for jobs to finish or cancel queued ones, then retry with the same idempotency key. |
The helper
The function returns None when the header is missing or unreadable, so the caller can choose its own fallback. The wait is capped at 120 seconds in delay, which is a policy choice of this sketch and not a Sume rule. Change it to suit your deadline.
import email.utils, time
def retry_after_seconds(value, now=None):
if not value:
return None
value = value.strip()
if value.isdigit():
return int(value)
try:
parsed = email.utils.parsedate_to_datetime(value)
except (TypeError, ValueError):
return None
now = time.time() if now is None else now
return max(0.0, parsed.timestamp() - now)
def delay(attempt, header):
hinted = retry_after_seconds(header)
if hinted is not None:
return min(hinted, 120)
return min(2 ** attempt, 60)
print(retry_after_seconds("7"))
print(retry_after_seconds("Thu, 08 Oct 2026 12:00:30 GMT", now=1791460800))
print(delay(3, None), delay(1, "oops"))Checking it without a network
The last three lines run offline. The first prints 7, the second prints 30.0 because the example date is 30 seconds after the fixed now of 1791460800 (2026-10-08 12:00:00 UTC), and the third prints 8 2: attempt 3 with no header gives 2 to the power 3, and attempt 1 with an unreadable header gives 2 to the power 1.
Keep the helper separate from the HTTP call. A function that takes a header string and returns a number is easy to test; a function that sleeps inside a request loop is not.
Using it in a submit loop
Pair the delay with the same Idempotency-Key on every attempt. If a 429 or a timeout leaves you unsure whether Sume accepted the job, resending the same key returns the original job instead of billing a second one. A different payload under the same key is rejected with 409 idempotency_conflict, so build the key from the order, not from a counter that changes on retry.
A queue_full response is a capacity signal, not a rate signal. Sleeping for the header value is fine, but also check the generation_limits snapshot in the error details and stop adding work until at least one job is terminal. See Generation admission for the formula.
Sources
Related posts
More in Developers
- Save a Sume job's result artifacts in Python by content type
Fetch GET /v1/jobs/:id/result and save each artifact with an extension from content_type, not the URL. Standard library only, with a text-result guard.
- Python: submit, poll and download one Omni Flash clip (v1/videos)
A short Python script that submits a Gemini Omni Flash 1.1 request to Sume's /v1/videos, polls the job until it completes and saves the MP4.
- Python urllib: retry a Sume video submit on 429 and 503, no requests
A standard-library Python function that retries a Sume video submit on 429 and 503, reusing one Idempotency-Key and reading Retry-After. Tested on a stub.
- A Python Sume webhook verifier for the two-signature rotation header
In a 24-hour secret rotation Sume sends two sume-v1 signatures. This 25-line Python verifier accepts either, refuses an empty secret, and prints its own test.
Written by Sume