# Idempotency

> Send an Idempotency-Key header so a request retried after a timeout returns the first result instead of creating a second journal entry or invoice.

A network error does not tell you whether the server created the entry. Send an `Idempotency-Key` with the request, and you can retry it without risking a duplicate.

```bash title="cURL" {3}
curl -X POST https://api.numina.app/v1/journal-entries \
  -H "Authorization: Bearer $NUMINA_API_KEY" \
  -H "Idempotency-Key: 5f8d2a3e-6c1b-4b7e-9a0f-2d4c6e8b1a3f" \
  -H "Content-Type: application/json" \
  -d @entry.json
```

Supported on [Create journal entry](https://developers.numina.app/api/journal-entries/create-journal-entry.md), [Create invoice](https://developers.numina.app/api/invoices/create-invoice.md) and [Send invoice by email](https://developers.numina.app/api/invoices/send-invoice-by-email.md).

## How it behaves

- Generate one key per logical request, such as a UUID, and reuse it only for retries of that request. Keys are 1 to 255 characters.
- A retry with the same key and body returns the original status and body, with the header `Idempotent-Replayed: true`. Replays still count toward [rate limits](https://developers.numina.app/rate-limits.md).
- Keys belong to the company, shared by every key and app connected to it, and are remembered for 24 hours from the first request.
- A failed request (`4xx` or `5xx`) frees the key, so you can fix the body and try again with the same key.
- The same key with a different body or on another endpoint returns `422 idempotency_key_reused`. A retry while the first request is still running returns `409 idempotency_key_in_use`; wait and retry. A request that never finished frees its key after 5 minutes.

> [!WARNING]
> **When a retry runs again**
>
>
> Because a failure frees the key, two calls can still act twice. A `5xx` from Send invoice by email can come after the email provider accepted the message, so a retry can send the email again. Create invoice with `book: true` keeps the draft when booking fails, so look for it with `GET /v1/invoices?status=draft` before you retry, or create the draft and book it in two calls.

## Other writes

- Booking a journal entry or an invoice is safe to repeat: one that is already booked comes back unchanged with `200`.
- `POST /v1/attachments` recognises a file the company already has and returns that document with `200` and `duplicate: true`.
- Uploading to an entry that already has a document returns `409 attachment_exists`, and adding files to a document that has some returns `409 attachment_not_empty`.
- `POST /v1/contacts` is not protected, so a retry can create a second contact. Search for it first.
- `POST /v1/products` refuses a repeat only when its `product_number` is already in use.

> [!NOTE]
> **External references**
>
>
> Apps installed as a Numina integration can also send `external_reference`, their own ID for a journal entry, of up to 255 characters. It is unique for the integration in each company: a duplicate returns `409 conflict` with the existing entry's `id`, and `GET /v1/journal-entries?external_reference=` finds it. API keys cannot send one.
