usage_reservation_unavailable: a Sume job failed before it started
The usage reservation could not be placed, so Sume failed the queued job and gave back any hold. Nothing ran. Resubmit with the same Idempotency-Key.

usage_reservation_unavailable is an error code on a failed job, with the message "The usage reservation could not be placed. Retry the request." It means the job row was created, but the step that holds the money for it could not complete, so Sume failed the row itself and no generation ran. It is not a verdict on your prompt or your balance.
What happens inside
A paid submit is recorded first, and then a usage reservation is placed against it. If that reserve stops, for instance because a connection pool is exhausted, or because it answered with something like budget_exceeded or spend_approval_queue_full and left the row untouched, nothing would ever submit the recorded row, and the background claim scan skips rows that have no task. So the API schedules a repair. It fails that still-queued row with this code and gives back a hold that the reserve may have committed, once the pool recovers.
The repair only touches a row that is still queued with no task. A row that another path already failed, for example a quota refusal, is left as it is.
How to react
Treat it as a failed job whose cost is returned. The jobs and results guide is the reference for reading a terminal job and for resubmitting.
| Question | Answer |
|---|---|
| Did generation run? | No. The row was failed before any task was submitted |
| Was I charged? | Any hold the reserve committed is given back by the repair |
| Is the request wrong? | No. Send it again unchanged |
| Which key to use? | The same Idempotency-Key for the same intent, per the jobs guide |
| Where do I see it? | GET /v1/jobs/:id/status and GET /v1/jobs/:id/events |
A resubmit rule
Do not submit a different intent just because one job failed. Read the job, and only if the error code is this one, resubmit the same body. A short backoff helps if the cause was pool pressure:
import json, time
JOB = '{"status": "failed", "error": {"code": "usage_reservation_unavailable"}}'
def should_resubmit(job: dict, attempt: int) -> float | None:
code = (job.get("error") or {}).get("code")
if job.get("status") == "failed" and code == "usage_reservation_unavailable" and attempt < 3:
return 2.0 * (attempt + 1)
return None
delay = should_resubmit(json.loads(JOB), 0)
print("resubmit after", delay, "seconds" if delay else "- do not resubmit")
time.sleep(0)Limits
If you see the same code several times in a row, stop and read the job's events at GET /v1/jobs/:id/events before you try again, because a persistent reserve failure is a platform condition, and more submits only add rows. Log each job id, so that support can follow the repair. The general envelope is on the Errors and credits page.
Sources
Related posts
More in Developers
- Use Decimal, not float, to reconcile Sume video costs to the cent
Sume bills list x 1.25 and rounds each video job up to the cent. Python Decimal with ROUND_CEILING reproduces the bill exactly, where floats drift by a cent.
- 'Use either first/end frame fields or reference_*_urls, not both'
A first frame pins the opening shot; references only guide it. Video Router refuses to mix them, so pick one mode per job and chain jobs if you need both.
- /v1/videos 409 job_failed vs job_not_completed in retry loops
On /v1/videos/{id}/content, 409 job_not_completed is retryable and means keep polling; 409 job_failed is not retryable. Branch on the code, not the 409.
- /v1/videos canonical_slug: the stable model id to store
Store canonical_slug (or id) from GET /v1/videos/models, not the display name. On Sume ids are bare, like seedance-2, with no org prefix.
Written by Sume