> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dueflow.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Idempotency

> Why every write needs a key, and exactly what a reused key does.

Every `POST` requires an `Idempotency-Key` header. Without one, the request is
rejected before the handler runs:

```json theme={"system"}
{
  "error": {
    "code": "invalid_request",
    "message": "This endpoint requires an Idempotency-Key header so a retried request can't apply twice. Use a UUID per logical operation.",
    "param": "Idempotency-Key"
  }
}
```

This is mandatory rather than optional because the failure it prevents is
silent: a request that times out in transit has still been applied, and a client
that retries without a key creates a second member, a second group, a second
charge. Making the key required means a retry is always safe.

## Sending a key

Generate one UUID per *logical operation* — not per HTTP attempt. All retries of
the same operation reuse the same key.

```bash theme={"system"}
curl -X POST https://api.dueflow.co/v1/members \
  -H "Authorization: Bearer $DUEFLOW_TOKEN" \
  -H "Idempotency-Key: 8f14e45f-ceea-467a-9c6a-9c0b3f39b41c" \
  -H "Content-Type: application/json" \
  -d '{"firstName":"Ada","lastName":"Lovelace","email":"ada@example.com"}'
```

## What a reused key does

| Situation                                  | Result                                                                                           |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------ |
| Same key, same body                        | The original response is replayed, with `Idempotent-Replayed: true`. Nothing runs a second time. |
| Same key, different body                   | `409 conflict`. The key already means something else.                                            |
| Same key, original request still in flight | `409 conflict`. Wait and retry; the first attempt is still deciding.                             |
| Same key, original attempt failed          | The key is released, so the retry runs normally.                                                 |
| Same key, more than 24 hours later         | Keys expire after 24 hours; the key is reusable.                                                 |

A replay returns the *stored* response — the same body and the same status:

```
HTTP/1.1 201 Created
Idempotent-Replayed: true

{ "id": "S_QZS1bcVIsrxWJHTKbIM", "firstName": "Docs", "lastName": "Example", ... }
```

Note that it is `201`, not `200`. The response describes the original operation,
not the act of replaying it, so a client can handle both identically.

## Keys are scoped to your token

Two tokens can use the same key without colliding. A key from a revoked token
carries nothing over to its replacement — after rotating, an in-flight retry
runs as a fresh request, so make sure the operation is also naturally
deduplicated (a duplicate email returns `409 conflict`) if that matters to you.

## Body comparison ignores key order

The fingerprint is taken over a canonicalized body: object keys sorted at every
depth, `undefined` dropped. So these are the *same* request, and the second
replays rather than conflicting:

```json theme={"system"}
{ "firstName": "Ada", "lastName": "Lovelace" }
{ "lastName": "Lovelace", "firstName": "Ada" }
```

Without that, any client library that serializes keys in a different order would
get a spurious `409` on a legitimate retry.

<Warning>
  Don't reuse a key across different operations, and don't derive keys from
  something that repeats — "member-import-row-1" every night will replay
  yesterday's response for 24 hours instead of importing today's row.
</Warning>
