# Idempotency (/developers/idempotency)

Every v1 write needs an `Idempotency-Key` header. Today that is the five `POST` endpoints: create a
checkout session, cancel it, reissue its link, cancel a subscription and send a test event. If a
request fails on the network, you send it again with the same key, and Fianto answers with the
first response instead of doing the work twice.

This matters most for creating a checkout session: its `url` comes back only in the create response.
A retry under the same key returns that stored response, `url` included.

## The key [#the-key]

* 1 to 255 printable ASCII characters, with no spaces. Keys are separate per application.
* Kept for 24 hours. Within that time, the same key with the same request (method, path, body and query) returns the stored
  response, with the header `Idempotent-Replayed: true`.
* If the first request failed with an error, the key is released, and you can use it again.

```bash
curl https://api.fianto.xyz/v1/checkout-sessions/SESSION_ID/cancel \
  --user "$FIANTO_APP_ID:$FIANTO_APP_SECRET" \
  -H "Idempotency-Key: cancel-SESSION_ID-1" \
  -X POST
```

## In the SDK [#in-the-sdk]

The SDK sends a key with every `POST` on its own: a `crypto.randomUUID()` made once per call and
reused on every automatic retry of that call. That covers a lost response while your process keeps
running.

If your process can crash between creating a session and saving its `url`, derive the key from data
you already have, so a restarted process sends the same key:

```ts
import { Fianto } from '@fianto/sdk';

const fianto = new Fianto();

export async function createCheckout(orderId: string, attempt: number) {
  return fianto.checkoutSessions.create(
    {
      mode: 'payment',
      order_id: orderId,
      amount: '12.50',
      description: 'Coffee beans, 1 kg',
      success_url: 'https://shop.example/thank-you',
      cancel_url: 'https://shop.example/cart',
    },
    // The same order and attempt always send the same key.
    { idempotencyKey: `checkout:${orderId}:${attempt}` },
  );
}
```

When the SDK gives up on a network failure, the `ConnectionError` or `TimeoutError` carries the
`idempotencyKey` it used. Retry the same call with that key to recover:

```ts
import { ConnectionError, Fianto, TimeoutError } from '@fianto/sdk';
import type { CheckoutSessionCreateParams } from '@fianto/sdk';

const fianto = new Fianto();

export async function createWithRecovery(params: CheckoutSessionCreateParams) {
  try {
    return await fianto.checkoutSessions.create(params);
  } catch (err) {
    if ((err instanceof ConnectionError || err instanceof TimeoutError) && err.idempotencyKey) {
      return fianto.checkoutSessions.create(params, { idempotencyKey: err.idempotencyKey });
    }
    throw err;
  }
}
```

> **A new key is a new request:**
>
> Retrying with a fresh key runs the request again. For a create whose first attempt did succeed, the
> second call finds the order's open session and returns it with `url: null`, so you lose the link
> until you reissue it. Reuse the key whenever you repeat a write.

## Troubleshooting [#troubleshooting]

| You see | Why | Fix |
|---|---|---|
| 400 `idempotency_key_required` | A `POST` was sent without an `Idempotency-Key` header, or with an empty one. | Add the header. The SDK does this for you. |
| 400 `idempotency_key_invalid` | The key is longer than 255 characters, or has spaces or non-printable characters. | Use 1 to 255 printable ASCII characters with no spaces. |
| 422 `idempotency_key_reused` | The key was already used with a different request. | Use a new key for a new request. The SDK does not retry this error. |
| 409 `idempotency_request_in_progress` | The first request with this key is still running. | Retry with the same key in a few seconds. The SDK retries this one. |
| 409 `idempotency_response_unreadable` | The request already completed, but its saved response can no longer be read. | Fetch the resource to see the result, for example `GET /v1/orders/lookup?order_id=`. Do not resend under a new key without checking first. |

## See also [#see-also]

- [Errors](/developers/errors): Which errors the SDK retries, and how.

- [Checkout sessions](/developers/checkout-sessions): Why the url makes the create call special.

- [Pagination and rate limits](/developers/pagination-and-rate-limits): 429s, Retry-After and list cursors.