Idempotency keys for Sume batches: item index plus payload hash
A deterministic Idempotency-Key makes a rerun return the original jobs, and a changed payload gets a new key, avoiding 409 idempotency_conflict.

Build the Idempotency-Key from three things: the batch label, the item number, and a short hash of the canonical request body. A rerun of the same batch then sends the same keys, and Sume returns the original jobs instead of billing new ones.
The rule from the docs is to use the same key again only for the same operation and payload. A key reused with a different operation or payload returns 409 idempotency_conflict (read 2026-10-10, Generation admission). Hashing the payload into the key turns that rule into code.
What each part of the key does
Random UUIDs are the usual advice, and they fail the one case that matters: the process crashes after the request left but before you stored the key. A restart makes a new UUID and a second paid job. A deterministic key survives a restart because you can recompute it.
| Part | Example | Prevents |
|---|---|---|
| batch label | hero-2026-10-10 | Collisions between two batches with the same prompt. |
| item index | i007 | Two different items sharing one key. |
| take counter | t1 | Being stuck with the old job when you deliberately want a re-roll. |
| payload hash | 16 hex chars of SHA-256 | 409 idempotency_conflict after you edit a prompt. |
The helper
Canonical JSON means sorted keys and no extra spaces, so two equal dictionaries always hash the same. The sample runs as is and prints four keys, with the first two identical:
import hashlib, json
def idem_key(batch: str, item: int, payload: dict, take: int = 1) -> str:
"""Same batch + item + payload + take -> same key, so a retry is an exact retry."""
canon = json.dumps(payload, sort_keys=True, separators=(",", ":"))
digest = hashlib.sha256(canon.encode()).hexdigest()[:16]
return f"{batch}-i{item:03d}-t{take}-{digest}"
body = {"prompt": "matte bottle on marble", "mode": "async"}
print(idem_key("hero-2026-10-10", 7, body))
print(idem_key("hero-2026-10-10", 7, body)) # identical: safe retry
print(idem_key("hero-2026-10-10", 7, body, take=2)) # new key: you want a new job
edited = dict(body, prompt="matte bottle on black slate")
print(idem_key("hero-2026-10-10", 7, edited)) # new payload, new keyRetries and re-rolls
A retry after a timeout, a network drop or a 5xx should reuse the key, and the docs say the same for queue_full and provider_capacity_exceeded. After a sync wait ends without a terminal state, the response is still 2xx with a job id; poll it, and if you must resubmit the request itself, send the same key.
A re-roll is different. Bump take, and you get a new key and a new paid job. If you instead edit the prompt, the hash changes and the key changes with it. In both cases store the key next to the job id, because GET /v1/jobs returns idempotency_key per job, which lets you map your own key back to a job after a crash.
- Do not put secrets or signed URLs in the key; it is returned in job records.
- Keep the key readable; you will grep for it during incidents.
- A key does not make a changed request safe, it makes an identical request cheap.
Sources
Related posts
More in Developers
- Bun 1.4.3 ERR_PROXY_TUNNEL: is it a Sume error or your proxy?
Bun 1.4.3 rejects fetch with ERR_PROXY_TUNNEL on a failed CONNECT. A Sume error always carries error.request_id; use it to tell the two apart.
- Bun 1.4.3 fake timers: test a Sume poll loop with request timeouts
Bun 1.4.3 fixes advanceTimersByTime spinning with AbortSignal.timeout. Test a Sume job poll loop that honors next_poll_after_seconds without real waits.
- Bun 1.4.3 fetch Content-Length check: send a Sume JSON body as bytes
Bun 1.4.3 rejects fetch when a declared Content-Length mismatches a stream body. For a Sume submit, skip the header and pass the encoded JSON.
- bun run --check: type errors stop before a Sume job submits
Bun 1.4.3 adds bun check and a --check flag on bun run. Use it so a type error in your Sume script fails before a paid generation request is sent.
Written by Sume