service_account_idempotency_required 400: send an Idempotency-Key
Some service-account keys require an Idempotency-Key on every paid submit. The 400 means the header is missing; add a stable key per intent and resend.

service_account_idempotency_required is a 400 with the message "Service account policy requires an Idempotency-Key header." It appears when the key's policy sets requireIdempotencyKey and a paid generation arrives without an Idempotency-Key header. Add one and resend. Nothing was reserved or billed for the refused call.
What a good key looks like
One key names one intent, such as one clip for one order line, and it is reused only when you retry that same intent. The jobs and results guide says it directly: do not submit a new paid job for the same intent, retry the submit with the same key so that it returns the existing job. The generation admission page uses the pattern avatar-batch-001-item-001, which is a batch id plus an item id.
- Derive the key from your own stable ids, not from a timestamp or a random value created on each attempt.
- Keep it under your control: store it with the order or task before you send.
- Reuse it across network retries and process restarts.
- Use a different key for a different request body. Sending the same key with a different body is a conflict, not a new job.
Where it sits among the policy checks
The order in the source is: key usable, model allowed, operation allowed, then idempotency, attribution metadata and callback domain. A missing key therefore surfaces only after the earlier checks passed. If you fix this and see a 403, the next gate is a different one. Each refusal is recorded as a policy denial before it is returned.
Add it once, in a wrapper
Generate the key outside the retry loop, not inside it. This sketch runs as is and shows the shape of the call without sending it:
import hashlib, json
def idem_key(order_id: str, line: int, model: str) -> str:
raw = f"{order_id}:{line}:{model}".encode()
return "ord-" + hashlib.sha256(raw).hexdigest()[:24]
def request_parts(order_id, line, model, body):
headers = {
"Authorization": "Bearer <SUME_API_KEY from env>",
"Idempotency-Key": idem_key(order_id, line, model),
"Content-Type": "application/json",
}
return headers, json.dumps(body)
h, _ = request_parts("A-1001", 2, "image-1.0", {"prompt": "test"})
print(h["Idempotency-Key"])Test it before production
Send the same submit twice in staging with the same key and confirm that you get the same job id back, not two jobs. Then send it once without the header and confirm that you see this 400, so that your wrapper's error handling is proven on the failure path as well. Finally, kill the process between the submit and the response, restart it and resend, because that crash window is the case the key exists for.
What it does not do
The header makes a retry safe. It does not lift a cap or a rate limit, and a new key for the same work is a new intent that meets the same limits. If the key was already used by a request that is still starting, the API may answer idempotency_key_in_use with a short retry delay, which is the one 409 on these surfaces that is worth retrying.
Sources
Related posts
More in Developers
- Service-account 403: key revoked, account revoked or disabled
Three 403 codes stop a service-account key before any other check: key_revoked, revoked and disabled. None is retryable; create or request a replacement.
- service_account_metadata_required 400: which source headers to send
The 400 lists missing fields in details.missing and the header for each in details.headers. Five x-sume-source headers exist; send the ones named.
- service_account_model_not_allowed 403: the key's model allowlist
The 403 says the model, or the operation, is not on the service-account key's allowlist. details.model or details.operation names the value to add or avoid.
- service_account_policy_unavailable 503: the message says temporary
The message says temporarily unavailable, but the envelope marks this 503 retryable false with contact_support. Retry cautiously and alert on repeats.
Written by Sume