Retry by error code, not by 5xx: Sume's 502 is your input
Sume's 502 attachment_fetch_failed means fix your URL, not retry. A retry policy keyed on status and code, with a table of which create errors repeat safely.

Do not retry Sume create errors by status class alone. A blanket rule of retry every 5xx and no 4xx gets the most visible case wrong: 502 attachment_fetch_failed is a bad input, and Sume sets its next_action to fix_input even though the status is 5xx. Retry by the pair of status and code. The Formats error page states the principle: branch on the HTTP status first, then on code, and remember that a 4xx at create means nothing ran and nothing was charged.
Which create errors are worth repeating
Only a few create errors repeat safely. 409 idempotency_key_in_use is flagged retryable: true and clears in about a second. 429 rate_limited clears after retry-after. 503 studio_agent_upstream_unavailable is a Sume-side outage, and the docs say to retry later with the same Idempotency-Key. Everything else at create is a statement about your request, and sending it again returns the same answer.
The expensive repeat is 403 insufficient_scope. A key's scopes are fixed when it is minted, so no number of retries will make a formats:read key able to create runs; the docs call looping on it the most common and most expensive mistake. Likewise 402 insufficient_credits returns the same answer until someone tops up in the dashboard.
| Create error | Cause | Retry? |
|---|---|---|
| 400 invalid_request, unknown_parameter, output_schema_invalid | Fix the body | Never as-is |
| 401 unauthorized | Fix the header; two credentials at once also fails | Never as-is |
| 402 insufficient_credits | Top up in the dashboard | After funds only |
| 403 insufficient_scope | Mint a new key | Never as-is |
| 409 idempotency_key_in_use | Another request holds the key | Wait about 1 s, same key |
| 409 format_run_in_progress | on_active_run was reject | Wait, or change the option |
| 429 rate_limited | Budget spent, details.scope names it | Wait retry-after, same key |
| 502 attachment_fetch_failed | Your URL is unreachable | Fix the URL first |
| 503 studio_agent_upstream_unavailable | Sume-side outage | Retry later, same key |
The policy in code
The function below encodes that table. The special case for the 502 sits first so the generic rules never see it, and the final fallback follows the docs for format_run_failed_to_start: retry once, then contact support with the request id. It runs as-is.
def retry_policy(status: int, code: str, retryable=None) -> str:
if status == 502 and code == "attachment_fetch_failed":
return "fix_input" # a 5xx that is your fault
if status == 409 and code == "idempotency_key_in_use":
return "wait_1s_same_key"
if status == 409:
return "never" # conflict, in_progress, inactive...
if status == 429:
return "wait_retry_after_same_key"
if status == 503 and code == "studio_agent_upstream_unavailable":
return "retry_later_same_key"
if 400 <= status < 500:
return "never" # nothing ran; fix the call
if retryable is True:
return "retry_later_same_key"
return "retry_once_then_contact_support"
for s, c in [(502, "attachment_fetch_failed"), (409, "idempotency_key_in_use"),
(409, "format_run_in_progress"), (403, "insufficient_scope"),
(429, "rate_limited"), (503, "studio_agent_upstream_unavailable")]:
print(s, c, "->", retry_policy(s, c))What the SDK already does
If you use the TypeScript SDK, createSumeClient retries 408, 429 and 5xx and transport failures twice with exponential backoff and jitter, honours retry-after, and retries a POST only when an Idempotency-Key is present. That is a bounded safety net, not your policy. A 502 attachment failure will get its two quick retries and then surface as an error; your code still has to read the code and stop. The typed error subclasses help: authentication (401), insufficient credits (402), permission (403), conflict (409) and rate limit (429) each have their own class.
Log the decision
Record the status, code, request id and the policy's verdict on every failed create. A day of those lines tells you quickly whether failures are mostly bad inputs, which means a validation gap in your pipeline, or mostly capacity, which means pacing.
Fixing the attachment cause
A failed fetch usually means the URL is not publicly reachable: a signed link that expired, a private bucket, a host that blocks unknown clients, or a redirect to a login page. details.index names which attachment failed, so fix that one and leave the others. Keep attachment URLs valid for longer than your slowest retry loop, and prefer uploading the file so the Format receives an asset id. Two sibling errors are easy to confuse with it: 400 invalid_attachment means the item itself is malformed or the shared media budget is exceeded, and 413 attachment_too_large means an image over 30 MB or a set over 500 MB. None of the three is fixed by a retry.
Sources
Related posts
More in Developers
- 9:16 story image from Sume, then resize to 1080x1920 with Pillow
The v1 image API does not serve explicit pixel sizes, so ask for 9:16, then cover-crop to 1080x1920 in Pillow. Includes the script, models and prices.
- ADK 2.11 abort_signal and /run_sse cancel: Sume job keeps billing
Google ADK 2.11.0 can abort a run and cancels /run_sse on disconnect. A Sume job started by that run is not cancelled by it.
- ADK 2.11 tool nodes pause for approval: gate Sume paid tools
Google ADK 2.11.0 tool nodes can pause for user approval via RequestInput. Use it so a Sume paid tool call waits for a person before it spends.
- Agent crashed after submitting: find the Sume runs it started
List a Format's runs, read trigger.idempotency_key, and rebuild which rows already ran after a fresh agent session or crash, without paying for a duplicate.
Written by Sume