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

# Idempotency

> Use the Idempotency-Key header to replay a POST safely. Covers what MoonPay Enterprise caches, what it does not, and why a retry after an error runs again.

Every `POST` under `/api` requires an `Idempotency-Key` header. The key lets you replay a request that timed out or failed in transit without creating the resource twice.

## Header: `Idempotency-Key`

Send a random UUID:

```bash theme={null}
Idempotency-Key: 123e4567-e89b-12d3-a456-426614174000
```

The header name is case-insensitive, so `IDEMPOTENCY-KEY` and `idempotency-key` both work. The API reference writes it as `IDEMPOTENCY-KEY`.

A `POST` without the header returns `400` with the message `Missing Idempotency-Key header`. This check runs ahead of authentication, so it fires before an invalid API key is reported.

<Warning>
  Idempotency applies to `POST` only. `PUT`, `PATCH`, and `DELETE` are not deduplicated and do not require the header. Sending one on those methods has no effect.
</Warning>

## What MoonPay Enterprise Stores

MoonPay Enterprise fingerprints each request as a hash of the **request body, the full path including the query string, and your identifying headers** (`Authorization`, `X-API-Key`, `X-Auth-Token`, `X-Sub-Partner-Id`, `X-API-Version`). The fingerprint is stored against your key alongside the response. Change any one of those parts on a replay and it is a different request.

<Steps>
  <Step title="First request">
    MoonPay Enterprise runs the request. If the response status is `2xx`, MoonPay Enterprise stores the status code and response body against the key.
  </Step>

  <Step title="Replay with the same key and same body">
    MoonPay Enterprise returns the stored status and body without running the operation again.
  </Step>

  <Step title="Replay with the same key and a different body">
    MoonPay Enterprise returns `409 Conflict`. The response carries no explanatory body.
  </Step>
</Steps>

<Danger>
  **MoonPay Enterprise stores successful responses only.** If the first attempt returns `4xx` or `5xx`, nothing is stored, and a retry with the same key runs the operation from scratch. Retrying a failed request is not a safe replay.

  This is how duplicate records get created. On `POST /api/customers/{id}/identifications/v2`, a partner who retries after an error can end up with several identifications for one customer, each one resetting the customer's status. Read the retry rules in [Onboarding Lifecycle](/onboarding) and [KYB](/kyb#resubmitting-and-correcting-a-submission) before you add retry logic to an onboarding flow.
</Danger>

## Key Considerations

* **One key per operation.** Generate a fresh UUID for each new operation, and reuse that same key only when retrying that exact request.
* **A new key means a new operation.** Retrying with a fresh UUID bypasses deduplication entirely and creates a second resource. This is the most common cause of duplicates.
* **Keys never expire.** There is no time window. A key you used last year still returns its stored response today.
* **The query string is fingerprinted.** Two requests to the same path with the same body but different query parameters are different requests. Replaying one key across both returns `409 Conflict`.
* **Idempotency does not make an endpoint an upsert.** It stops one request from running twice. It does not stop two different requests from creating two records.

## Example: Creating an Autoramp

### Request

<CodeGroup>
  ```bash theme={null}
  curl -X POST "https://api.sandbox.iron.xyz/api/autoramps" \
    -H "Content-Type: application/json; charset=utf-8" \
    -H "Idempotency-Key: 123e4567-e89b-12d3-a456-426614174000" \
    -H "X-API-Key: <your-api-key>" \
    -d '{
      "source_currencies": [{
        "type": "Fiat",
        "code": "EUR"
      }],
      "destination_currency": {
        "type": "Crypto",
        "token": "USDC",
        "blockchain": "Ethereum"
      },
      "recipient_account": {
        "type": "Crypto",
        "chain": "Ethereum",
        "address": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
      },
      "customer_id": "123e4567-e89b-12d3-a456-426614174000",
      "source_is_third_party": false
    }'
  ```
</CodeGroup>

### Response (First Request)

<CodeGroup>
  ```json theme={null}
  {
    "id": "f8f9a8c7-d8b1-4f1a-8f15-7e6c8a1a8e50",
    "status": "Authorized",
    "kind": "Onramp",
    "created_at": "2025-01-20T12:34:56Z"
  }
  ```
</CodeGroup>

### Response (Retry With the Same Key and Body)

Byte for byte the stored response, including the original `created_at`:

<CodeGroup>
  ```json theme={null}
  {
    "id": "f8f9a8c7-d8b1-4f1a-8f15-7e6c8a1a8e50",
    "status": "Authorized",
    "kind": "Onramp",
    "created_at": "2025-01-20T12:34:56Z"
  }
  ```
</CodeGroup>

<Note>
  A replayed response is served from the cache, so it carries the stored status and body without response headers. It has no `X-API-Version` echo. See [API Versioning](/versioning).
</Note>

## Handling Errors Correctly

Because failures are never stored, decide what to do based on the status you got back:

| Response | What happened | What to do |
| - | - | - |
| `2xx` | Stored against your key | Safe to replay with the same key |
| `400`, `409`, `422` | Rejected, nothing stored | Fix the request. Do not blind-retry a create |
| `5xx` or a timeout | Unknown. The operation ran or it did not | Read current state with a `GET` before retrying |

<Warning>
  On a timeout or `5xx` against a creating endpoint, check state before you retry. Poll `GET /api/customers/{id}/identifications` or the relevant collection to see whether the record already exists. A retry after a partial failure runs the operation again and creates a second record.
</Warning>
