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

# Idempotency

> Safely retry write requests without creating duplicates, using the Idempotency-Key header.

Write endpoints — `POST /v1/payment-intents`, `POST /v1/evidence/provider`, `POST /v1/evidence/onchain`, and other state-changing routes — require an `Idempotency-Key` header. ReconLayer uses it to make retries safe: the **same key with the same request body** always replays the original result instead of executing again.

## The header

```http theme={null}
Idempotency-Key: 8a93a5b2-6ee6-4700-a3f9-b1ccac86b252
```

<ParamField header="Idempotency-Key" type="string" required>
  A unique string (UUID v4 recommended) that you generate **before** the first attempt of a logical write operation. 1–255 characters.
</ParamField>

If the header is missing on a route that requires it, ReconLayer rejects the request before doing any work:

```json 400 Missing Idempotency-Key theme={null}
{
  "error": "invalid_idempotency_key",
  "message": "Idempotency-Key header is required."
}
```

## The contract

<Steps>
  <Step title="Generate a key per logical operation">
    Before your first attempt at, say, "create payment intent for invoice-9182", generate one `Idempotency-Key`. Store it alongside the operation in your own system (e.g. next to the queued job or outbound request).
  </Step>

  <Step title="Send it on the first attempt">
    ```bash theme={null}
    curl -X POST https://api.reconlayer.com/v1/payment-intents \
      -H "Authorization: Bearer $RECONLAYER_API_KEY" \
      -H "Idempotency-Key: 8a93a5b2-6ee6-4700-a3f9-b1ccac86b252" \
      -H "Content-Type: application/json" \
      -d '{ "externalReference": "invoice-9182", ... }'
    ```
  </Step>

  <Step title="On timeout, network error, or 5xx: retry with the SAME key and SAME body">
    Do not generate a new key. Send the identical request body again, with the identical `Idempotency-Key`.
  </Step>

  <Step title="ReconLayer replays the original outcome">
    If the original request already completed, ReconLayer returns the **same status code and body** it returned the first time, without creating a second record. The response includes `Idempotency-Replayed: true`.
  </Step>
</Steps>

## How it works under the hood

For each idempotent write, ReconLayer keys a stored record by `(organizationId, operation, idempotencyKey)` and hashes the request body. When a request arrives:

1. **New key** — ReconLayer records the key as `processing`, executes the operation, then stores the response as `completed` (status code + body). The response includes `Idempotency-Replayed: false`.

2. **Same key, same body, original completed** — ReconLayer returns the stored status code and body verbatim. `Idempotency-Replayed: true`. Nothing is re-executed.

3. **Same key, different body** — ReconLayer rejects the request. The stored request hash doesn't match the new body's hash:

   ```json 409 Idempotency key reused with a different body theme={null}
   {
     "error": "idempotency_key_conflict",
     "message": "This Idempotency-Key was already used for a different request on this operation."
   }
   ```

4. **Same key, original still in flight (or never completed)** — another request with this key is currently being processed (or the prior attempt crashed before completing):

   ```json 409 Request still in progress theme={null}
   {
     "error": "idempotency_request_in_progress",
     "message": "A request with this Idempotency-Key is already being processed."
   }
   ```

   Retry after a short backoff — once the original attempt finishes, the same key will either replay its result or, if it had failed cleanly, be available to retry.

<Tip>
  If the original execution itself throws, ReconLayer removes the in-progress idempotency record so a retry with the same key can run cleanly rather than getting stuck in "in progress".
</Tip>

## Response header

Every successful response from an idempotent write endpoint carries:

```http theme={null}
Idempotency-Replayed: true | false
```

Use this to distinguish "this just happened" from "this was a replay of an earlier attempt" — useful for logging and for deciding whether to fire your own downstream side effects again.

## Scope: per organization and per operation

The idempotency key is scoped to `(organizationId, operation, idempotencyKey)`. The same key string used by two different organizations, or for two different operations (e.g. `payment_intents.create` vs. `evidence.provider.create`), is treated independently — there is no cross-operation collision. Even so, using a fresh, unique key per logical operation (e.g. a UUID v4) is the simplest way to stay correct.

## Idempotency vs. business-level uniqueness

`Idempotency-Key` deduplicates **the same request being retried**. It does not, by itself, deduplicate **two different requests that describe the same business entity** — that's handled separately via fields like `externalReference` on payment intents. See [API Design Principles → Business-level uniqueness](/api-reference/design-principles#business-level-uniqueness) for how the two interact: a brand-new `Idempotency-Key` with a previously-seen `externalReference` returns the existing payment intent with `"outcome": "reused"` rather than a duplicate.

## Next steps

* [API Design Principles](/api-reference/design-principles) — idempotency in the context of the wider write model.
* [Errors](/api-reference/errors) — the full error envelope and status code catalog.
* [Quickstart](/getting-started/quickstart) — a worked example that sets `Idempotency-Key` on a real `POST /v1/payment-intents` call.
