Stripe replays a saved 500 for the same key; Sume releases it
Stripe saves the first result for a key, even a 500, and replays it. Sume replays a finished receipt, but a create that fails with 402 or 503 releases its key.

If you retry a Stripe request with the same idempotency key after the first attempt returned a 500, you get that 500 back. Stripe saves the status code and body of the first request once execution begins, including server errors, and replays it. On Sume, the same retry behaves differently: a create that failed with 402 or 503 releases its key, so you can resend with the same key.
The two rules side by side
Stripe's idempotent requests page says results are saved only after an endpoint begins executing. Validation failures and concurrent conflicts are not saved, so those can be retried. Keys can be removed from the system automatically once they are at least 24 hours old, and a reused key after that creates a new request. Keys are up to 255 characters.
| Situation | Stripe | Sume |
|---|---|---|
| First request finished | Saved result replayed | 200 with the original receipt and idempotency_hit true, no second charge |
| First request failed after execution began | Saved error, including a 500, replayed | Failed create (402, 503): key released, retry with the same key |
| Same key, different body | Error for parameter mismatch | 409 idempotency_conflict |
| Same key while the first is still running | Not saved, retry later | 409 idempotency_key_in_use, retryable after about 1 second |
| Retention | Pruned after at least 24 hours | Docs I read state no retention window |
The trap when you port the retry loop
A Stripe-style loop that reuses one key forever works because Stripe pins the outcome. On Sume, a failed run is a different matter from a failed create. The Formats docs say a run that failed must be retried with a new key, because the old key still points at the failed run. Only a create that never started (402, 503) frees its key.
Derive the key from the thing you are making, such as an order id plus a version, not from a fresh uuid per attempt. That way a retry after a lost response finds the original run instead of paying twice, and a deliberate redo bumps the version.
- 409 idempotency_key_in_use: wait about a second and resend the same body.
- 409 idempotency_conflict: you changed the body. Decide whether you meant a new key.
- 402 or 503 at create: resend with the same key.
- Run failed: send a new key.
What I could not confirm
Sume's docs do not publish how long a key is remembered, so do not assume Stripe's 24-hour figure carries over. If your retries can arrive days later, check the run by id before you resubmit.
A short retry policy
Put the rules in one place. On a timeout or a lost response, resend the same key and body, since a finished run replays and a running one answers 409 idempotency_key_in_use. On 402 or 503 at create, resend the same key. On a failed run, mint a new key from the same business id plus a counter.
Never retry with a changed body under the same key, because Sume answers 409 idempotency_conflict and Stripe returns a mismatch error. Log the idempotency_hit flag in the response so you can see how often replays actually happen. A creating call that fails validation with a 4xx costs nothing on Sume, so there is no billing reason to be hesitant about retrying after a fix.
Sources
Related posts
More in Developers
- Stripe thin events: fetch the object, vs a Sume webhook receipt
Stripe thin events are GA for v1 resources in Endive: you fetch the object. A Sume webhook carries the receipt; an oversized one points to result_url.
- Sume 503: provider_capacity_exceeded vs provider_not_configured
Sume 503s differ: provider_capacity_exceeded: retry later, same key; provider_not_configured is no hard retries, job_ledger_not_configured is an outage
- Sume API errors: a 13-line function that says retry or fix
Map a failed Sume response to out-of-credit, wait, back off, fix the key, retry with the same key, or fix the request, using the documented error envelope.
- sume/auto for an image series: why to pin a model id instead
sume/auto never tells you which model ran, and job.model stays sume/auto. For a series that must match, send one catalog id such as google/nano-banana-2.
Written by Sume