---
url: /docs/learn/idempotency.md
description: Retry a mutating request safely with the Idempotency-Key header.
---

Pass an `Idempotency-Key` header on a mutating request to retry it safely, without risking a duplicate action, such as creating two payouts because a network timeout made you resend the same call.

This is unrelated to the `idempotency_key` field on [terms of service acceptance](/docs/learn/terms-of-service). That field is a UUID you generate and send in the request body of `POST /v1/e/instances/{instance_id}/tos`; reusing it there returns its own `400 idempotency_key_already_exists` error and only guards that one endpoint. The `Idempotency-Key` header described on this page applies across the API and has its own separate set of errors.

## Header

| Header | Required | Max length | Notes |
| --- | --- | --- | --- |
| `Idempotency-Key` | No | 255 characters | Any string up to 255 characters. A UUID is a good default, but no specific format is enforced. |

## Which requests it applies to

* Any `POST`, `PUT`, `PATCH`, or `DELETE` request under `/v1/*`.
* Only authenticated requests. Unauthenticated routes, such as `/v1/e/*` endpoints and account bootstrap, ignore the header.
* Not `multipart/form-data` requests, such as file uploads via [Upload](/docs/learn/upload). Multipart bodies get a fresh random boundary on every encode, so an honest retry can never hash identically to the original; the header is silently ignored on these requests.

A key is scoped to the exact combination of your credentials, the HTTP method, and the request path including its query string. The same key value sent to a different path, a different query string, or under different credentials is treated as an entirely different key, not a conflict.

## Matching retries

A retry only replays if the request body matches byte-for-byte: the check is a SHA-256 hash of the raw body, not a semantic JSON comparison. Reformatting the body (different key order, different whitespace) before retrying produces a different hash and is treated as a conflicting reuse of the key, not a matching retry. Send the exact same bytes on every retry.

## What happens on retry

| Outcome | Response |
| --- | --- |
| Key longer than 255 characters | `400` `idempotency_key_invalid` |
| Same key, same body, original request already finished | The original response is replayed with an `Idempotency-Replayed: true` header. The handler is not re-run, so side effects (database writes, webhooks, provider calls) are not repeated. |
| Same key, different body | `422` `idempotency_key_payload_mismatch` |
| Same key, same body, original request still in progress | `409` `idempotency_key_in_flight`, with a `Retry-After` header (seconds) telling you how long to wait before retrying. |

Only responses in the 2xx or 4xx range with a JSON body are stored and eligible for replay. A `5xx` response, or a non-JSON response, releases the key instead of storing it, so a retry with the same key runs as a fresh attempt rather than replaying the failure.

## Retention

A completed request's idempotency key is kept for 24 hours. Reuse the key after it expires and it is treated as brand new: the request runs again.

## Reliability

Idempotency protection is best-effort, not absolute. If BlindPay's idempotency store is temporarily unavailable when you send the header, the request still runs, exactly as if you had not sent the header at all: a store outage never blocks a mutating call, but a duplicate submission during that window is not deduplicated.

## Example

```bash [cURL]
curl --request POST \
  --url https://api.blindpay.com/v1/instances/in_000000000000/payouts/evm \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Idempotency-Key: a1b2c3d4-e5f6-7890-abcd-ef1234567890' \
  --header 'Content-Type: application/json' \
  --data '{ ... }'
```

## Related

* [Upload](/docs/learn/upload): the header is ignored on multipart uploads
* [Terms of Service](/docs/learn/terms-of-service): a separate, endpoint-specific `idempotency_key` body field
* [API keys](/docs/learn/api-keys): authenticate the requests you retry
