# Idempotency Keys

Use the `Idempotency-Key` header on any supported `POST` endpoint when you need to safely retry a request after a timeout, dropped connection, or uncertain client-side failure.

When Fynd receives the same authenticated request with the same idempotency key, it returns the original response instead of processing the request again.

## When to use it

- Network timeouts where your system is not sure whether the request reached Fynd.
- Client retries after a `5xx`, transport error, or interrupted connection.
- Any workflow where exactly-once execution matters more than blindly submitting again.

:::note
`POST /api/v0/orders` also supports an optional persisted `clientReference`. If you already have your own stable identifier for the order, `clientReference` may be a better fit than an idempotency key. See [Supported endpoints](#supported-endpoints).
:::

## Header

Send a non-empty string up to 512 characters:

```bash
Idempotency-Key: 2bc0d9df-8b33-4ec7-8db0-7ee2f07a0d5c
```

UUIDs are recommended, but any sufficiently unique value generated by your system is accepted.

## How matching works

An idempotency key is scoped to:

- the authenticated API client
- the specific endpoint (method + path)
- the request fingerprint

The request fingerprint is derived from the fields in the request body. The fingerprint is **key-order sensitive** — the JSON fields must be in the same order across retries for the fingerprint to match.

That means retries must reuse the same `Idempotency-Key` and send the same request payload with the same field ordering.

## Replay behavior

If you resend the same request with the same idempotency key, Fynd replays the original response.

- The request is not processed again.
- The response body matches the original response.
- The response status matches the original response.
- The response includes `X-Idempotent-Replay: true`.

This applies to **all** response status codes — including errors. If the original request returned a `400`, `409`, or `5xx`, the same response will be replayed on retry with the same key.

## Lifecycle

Idempotency keys expire **30 days** after first use. Before expiry, a key produces the same replayed response for the same request.

Once a key expires, the record is released. A subsequent request using the same key is treated as a completely fresh first use — the key is reserved anew and the request is processed normally. This means:

- If you retry an identical request after 30 days, it will be executed again (not replayed).
- If you reuse a key for a different request after 30 days, it is accepted as a fresh reservation.

There is no manual release mechanism; expiry is the only way a key becomes reusable.

Keys are stored internally as a SHA-256 hash of the value you provide, trimmed to a fixed length for efficient indexing.

## Request lifecycle diagram

```mermaid
sequenceDiagram
    participant C as Client
    participant API as Fynd API

    C->>API: POST /api/v0/...\nIdempotency-Key: abc123

    alt First request (key not seen before)
        API->>API: Reserve key (status: IN_PROGRESS)
        API->>API: Process request
        API->>API: Store response (status: COMPLETED)
        API-->>C: 200 OK (or 202 Accepted)
    else Retry with same key and same body
        API->>API: Key found with COMPLETED status
        API-->>C: Replayed original response\nX-Idempotent-Replay: true
    else Retry while original is still processing
        API->>API: Key found with IN_PROGRESS status
        API-->>C: 409 Conflict\n"A request with this Idempotency-Key is already in progress."
    else Retry with same key but different body
        API->>API: Key found, request fingerprint mismatch
        API-->>C: 409 Conflict\n"The supplied Idempotency-Key has already been used for a different request."
    else Key has expired (any body)
        API->>API: Key found but expired — delete record
        API->>API: Reserve key (status: IN_PROGRESS)
        API->>API: Process request
        API->>API: Store response (status: COMPLETED)
        API-->>C: 200 OK (or 202 Accepted)
    end
```

## HTTP status codes

| Status                | When it occurs                                                                                                                            |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `200 OK`              | Request completed synchronously within the requested timeout.                                                                             |
| `202 Accepted`        | Request accepted, processing asynchronously.                                                                                              |
| `400 Bad Request`     | Request body or headers are invalid. Also returned on replay if the original response was a 400.                                          |
| `401 Unauthorized`    | Missing or invalid API key. Authentication is evaluated before idempotency.                                                               |
| `409 Conflict`        | One of the following idempotency-specific conditions: key is in progress, key used with different body, or the key could not be reserved. |
| Any status (replayed) | On replay, the exact original status code is returned, along with `X-Idempotent-Replay: true`.                                            |

## Conflict behavior

If you reuse an idempotency key with a different request body, Fynd rejects the request with `409 Conflict`:

```json
{
  "success": false,
  "error": "The supplied Idempotency-Key has already been used for a different request."
}
```

## Supported endpoints

Idempotency keys are accepted on the following `POST` endpoints:

- `POST /api/v0/orders` — create an order. The request fingerprint is derived from the `product` and `titleId` fields (in that order).
  :::info
  `clientReference` is an optional client utility for order creation. If you already have a stable unique identifier for the order in your own system, pass it as the `clientReference` field.

  This helps reduce your risk of accidentally creating duplicate orders because Fynd requires `clientReference` to be unique per organisation. It also gives you a client-owned lookup key you can use later with `GET /api/v0/orders`, so you do not need to persist Fynd's generated `orderId` just to find the order again.

  Choose `clientReference` when you control a stable order identifier up front. Choose `Idempotency-Key` when you need to safely retry the same HTTP request after a timeout, dropped connection, or similar transport uncertainty.
  :::

## Example

```bash
curl 'https://api.fynd.au/api/v0/orders' \
  -X POST \
  -H 'Authorization: Bearer sk_live_...' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: 2bc0d9df-8b33-4ec7-8db0-7ee2f07a0d5c' \
  -d '{
    "product": "title",
    "titleId": "<title-id-from-get-api-v0-titles>"
  }'
```

If your client is unsure whether that request succeeded, retry the same call with the same headers and body.

## Best practices

- Generate a new idempotency key for each new logical request.
- Reuse that same key only for retries of the same logical request.
- Persist the key in your job or request state until you receive a definitive response.
- Do not reuse keys across different requests.
- If you change the request body, generate a new key.

## Notes

- The header is optional. If omitted, the request is processed normally and retries may produce duplicates.
- Authentication is evaluated before idempotency handling, so missing or invalid API credentials still return authentication errors first.
- Idempotency keys are stored as internal hashes — the raw key value you provide is never persisted.
- Idempotency helps with request retries; once a request is accepted, use [Webhooks](/webhooks) or the relevant `GET` endpoint to track its outcome.
