Reference
Errors and retries
Every error is an HTTP status and one error object. Branch on its code, show its message, quote its request_id to support.
Error format
{
"error": {
"code": "invalid_request",
"message": "Invalid request body.",
"request_id": "req_0ee311b4334e4b118a94c7d1c48577fd",
"category": "validation",
"stage": "validation",
"retryable": false,
"retry_after_seconds": null,
"public_reason": "invalid_request",
"next_action": "fix_input",
"details": {
"errors": [
{
"path": "prompt",
"loc": [
"prompt"
],
"message": "must have required property 'prompt'",
"type": "required"
}
]
}
}
}| Field | Meaning |
|---|---|
code | Stable and machine-readable, one per cause. Branch on this, not on the message. |
message | One sentence for people. |
request_id | Also in the x-sume-request-id header. |
category, stage | Where it failed, such as validation, auth, quota, queue or job_state. |
retryable | true when the same request can succeed later, unchanged. |
retry_after_seconds | How long to wait first. Every retryable error also sends it as the retry-after header. |
next_action | fix_input, authenticate, add_funds, retry_later, poll_status, inspect_events or contact_support. |
details | Extra facts, such as the failing fields in details.errors[]. |
The status gives the class: 400 fix the request, 401 and 403 the key, 402 the balance, 404 nothing there, 409 the job's state, 429 slow down, 5xx Sume's side.
Codes
| Status | code | Meaning | Retry |
|---|---|---|---|
| 400 | bad_request | The body is not valid JSON. | No, fix the body |
| 400 | invalid_request | A field, query value or header is missing or wrong; details.errors[] names it. | No, fix it |
| 400 | unknown_parameter | The job list got a query parameter it does not take. | No, fix the query |
| 401 | unauthorized | No key, or the key is wrong or revoked. | No |
| 401 | session_not_accepted | A dashboard session was sent instead of a key. | No, send a key |
| 402 | insufficient_funds | The balance cannot cover the price; details has required_usd and available_usd. Not charged. | After a top-up, with a new Idempotency-Key |
| 403 | insufficient_scope | The key lacks the scope this route needs. | No |
| 403 | open_key_route_not_allowed | The route is outside the model API a Sume API key calls. | No |
| 403 | open_key_required | A billing read got a key that is not a Sume API key. | No, use one |
| 404 | not_found | No such route, job or model in your workspace. | No |
| 409 | idempotency_conflict | The Idempotency-Key was used for a different request. | No, use a new key |
| 409 | job_not_completed | The job is still queued or processing, so it has no result yet. | Yes, poll its status first |
| 409 | job_failed | The job failed; message says why. It has no result. | No |
| 409 | job_canceled | The job was canceled. It has no result. | No |
| 409 | job_generation_already_started | Generation has started, so the job can no longer be canceled. | No |
| 409 | job_not_cancelable | The job has already ended. | No |
| 409 | webhook_not_configured | The job was made without a webhook_url, so there is nothing to redeliver. | No |
| 409 | job_not_terminal | The job has not ended, so there is no webhook to redeliver yet. | Yes, once it ends |
| 409 | asset_upload_incomplete | The uploaded file has not arrived. | After the PUT finishes |
| 409 | top_up_in_dashboard | Top-ups are made in the dashboard, not through the API. | No |
| 413 | payload_too_large | The request body is too big. | No |
| 413 | asset_too_large | The upload is over the limit; details.max_bytes says it. | No |
| 415 | unsupported_media_type | The body is not application/json. | No |
| 429 | queue_full | The workspace has as many jobs in flight as it may. Not charged. | Yes, once a job ends, with the same Idempotency-Key |
| 429 | rate_limited | Too many requests for this key in the window. | Yes, after retry-after |
| 500 | internal_error | A fault on Sume's side. | Yes, with backoff |
| 501 | billing_in_dashboard | Transactions, receipts and usage are read in the dashboard. | No |
| 502 | provider_submission_failed | The model could not start the job. Refunded. | Only when retryable is true, with the same Idempotency-Key |
| 503 | provider_capacity_exceeded | The model is at capacity right now. Refunded. | Yes, after retry-after, with the same Idempotency-Key |
| 503 | provider_not_configured | This model is not available here. | No |
| 503 | database_busy | A short overload. | Yes, after retry-after |
| 503 | deploy_draining | This server is restarting for a deploy. | Yes, after retry-after |
| 503 | overloaded | This server has as many requests in flight as it takes. | Yes, after retry-after |
| 503 | replica_not_ready | This server has just started and cannot reach its database yet. | Yes, after retry-after |
| 503 | api_key_auth_unavailable | Keys could not be checked for a moment. | Yes, after retry-after |
Retrying safely
- Retry when
retryableistrue, afterretry-after. Back off exponentially, with jitter, if it fails again. - Retry reads (status, result, list) after a network error too.
- A retryable refusal frees its
Idempotency-Key: sendqueue_full(once a job ends),provider_capacity_exceededor a retryableprovider_submission_failedagain with the same key. A final refusal keeps its answer: afterinsufficient_funds, top up and send a new request with a new key. A refused submit is never charged. - Do not retry
401,403,404or a400you have not fixed.
Idempotency
A network error after a submit leaves you unsure whether the job exists. Send an Idempotency-Key on every generate request and reuse it on retry:
Idempotency-Key: 9816bc88-9791-43a1-86c2-876dc7c544e8- 1–255 printable ASCII characters. A UUID per request is the simple choice.
- The same key with the same body returns the original job with
idempotency_hit: true, charged once. - The same key with a different body answers
409 idempotency_conflict.
Failed jobs
A job that ends failed is not an HTTP error. The submit succeeded; the job's error object (same fields as above) says what went wrong, and its price is refunded. Reading its result answers 409 job_failed with the same reason. A model refusing a prompt on content grounds reports public_reason: "content_policy_rejected".