An Idempotency-Key exists so that a retry is safe. You send one with a job, the network times
out, you retry with the same key, and you get the same job back instead of a second charge.
That part worked. The part that did not was what happened when the key came from somewhere else.
#The shape of the bug
Idempotency is usually described as a property of a request: same key, same result. What that description leaves out is whose key it is.
Consider two services in the same account, each with its own API key, each generating idempotency keys from something that felt unique enough — a job id from an internal queue, say, or a hash of the prompt. Sooner or later they collide.
The old behaviour was to look up the idempotency key, find a job, and return it. Both services got
a 200. Both believed they had submitted work. One of them received the other's render.
Nothing errored. Nothing was logged. The second service's job simply never existed, and the evidence that it never existed was a successful response describing a job that looked exactly like the one it had asked for.
#What it does now
An idempotency key belongs to the API key that first used it.
{
"error": "idempotency_key_owned",
"docs": "https://auray.ai/docs/api/errors#idempotency_key_owned",
"request_id": "54b4754a-3e17-4629-adb1-7d3f067e97d3"
}Same API key, same idempotency key: the original job, at whatever status it has now, and no second charge. Different API key: refused. There is no third outcome.
A 409 is not a nicer failure than a 200 — it is a visible one. The collision was always
happening; the only question was whether anybody found out.
#Why this is the right trade
The obvious objection is that a 409 breaks a caller who was previously fine. It does, and that
caller was fine by luck: it was reading another service's job and would have kept doing so until
somebody noticed the renders did not match the prompts.
The second objection is that scoping keys per API key makes them less useful across a fleet. That is true and it is the point. If two processes genuinely need to share an idempotency key, they should share the API key that owns it — which is a decision somebody makes on purpose, rather than a collision they discover in production.
#What to do on your side
Generate an idempotency key per submit, not per user and not per prompt. A UUID is the simple answer:
curl -X POST https://queue.auray.run/auray-ai/krea-2-turbo/text-to-image \
-H "Authorization: Bearer $AURAY_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{ "prompt": "…", "aspect_ratio": "16:9", "resolution": 1024 }'A key derived from something stable will collide the first time the same input is submitted twice on purpose — and then hand back the first job instead of generating anything.
Full detail in Idempotency.
Filed under Engineering on All posts