# Idempotency > Send an Idempotency-Key on every write and a retry can never create a duplicate, even when you have no idea whether the first attempt landed. Source: https://getlaunchlist.com/help/docs/api/idempotency --- The hardest failure in any API is the one where you do not know what happened: you sent a write, the connection dropped, and you cannot tell whether it landed. An idempotency key removes that question. ## How to use it Send any unique string on a mutating request: ```bash curl https://api.getlaunchlist.com/v1/contacts \ -H "Authorization: Bearer llk_live_8xKq3Rp9Wm2Nz7Vt6yL1cG5dH0jA4sE8uNqB2fXk" \ -H "Idempotency-Key: signup_8f3a2b" \ -H "Content-Type: application/json" \ -d '{ "project": "prj_7Qk2mN4pR8", "email": "ada@lovelace.dev" }' ``` Retry with the **same key** and you get the **original response** back, same status and same body, rather than a second contact. ## The rules **The window is 24 hours.** After that the key is forgotten and the same request would be treated as new. **Only successful responses are stored.** If the first attempt returned an error, nothing was recorded and the retry runs the request properly rather than replaying the failure. This is on purpose: a `429` and a `403` both reflect something that changes, and freezing either for 24 hours would turn a temporary refusal into a permanent one and lose the signup. **Only mutating requests need it.** `POST`, `PATCH`, `PUT` and `DELETE` are covered. `GET` is already idempotent and the header is ignored there. **The key is scoped to the exact request.** We fingerprint the method, the full path including its query string, and the body. Reusing a key on a genuinely different request is caught rather than answered from the cache, which is the whole point of the header. **A different request with the same key returns `409 idempotency_key_reused`.** That combination is a caller-side bug, and accepting it silently would make the guarantee worthless. **A retry that arrives while the first is still running returns `409 idempotency_key_in_flight`.** It is refused immediately rather than queued behind the first request, so nothing runs twice and your client is not left holding an open connection. Treat it as "ask again shortly": wait a moment and resend the same key, and once the first attempt has finished you get its response. **Keys are 255 characters or fewer.** A longer one is a `400 idempotency_key_too_long`. ## Recognizing a replay A replayed response carries a header the original did not: ```text Idempotent-Replayed: true ``` Useful in logs, and useful for telling "my retry was answered from the record" apart from "my retry did the work". ## Choosing a key Anything unique to the _operation_, not to the attempt: - A UUID generated once per logical operation and reused across retries - A deterministic id from your own system, such as `submission_{id}` The mistake is generating a fresh key on every retry, which turns each attempt into a new operation and defeats the whole thing. > [!WARNING] Generate the key before the first attempt, not per attempt > `Idempotency-Key: uuid()` inside a retry loop creates a new key each time. The key has to be > decided once, outside the loop, and reused. ## Dedupe is not the same thing `POST /v1/contacts` **also** deduplicates by identifier: submitting an address that is already a contact returns `200` with the existing contact rather than creating a second. The two protect different things. Deduplication stops the same _person_ appearing twice. Idempotency stops the same _request_ being processed twice, which matters for writes that are not identity-based, like updates and deletes. Use both. ## Next - [Errors](/help/docs/api/errors): including what a `409` looks like - [Create a contact](/help/docs/api/contacts-create) ## Common questions ### How do I safely retry a failed API request? Send an Idempotency-Key header with the original request. If you retry with the same key within 24 hours, you get the original response back rather than a second contact. ### What should I use as an idempotency key? Anything unique to the operation: a UUID you generate once per operation, or a deterministic id from your own system such as the form submission id. What matters is that a retry of the same logical operation reuses the same value. ### What happens if I reuse an idempotency key with a different body? You get a 409 with the code idempotency_key_reused. Reusing a key for different content is a bug on the caller's side, and silently accepting it would make the guarantee meaningless.