---
title: Idempotency
description: How to retry safely. Idempotency-Key header, 24h dedup window, large-upload behavior.
---

# Idempotency

Send an `Idempotency-Key` header on every mutating request and ARCNM
dedupes safe retries — a dropped connection or client retry never bills
the same calculation twice.

```http
POST /api/v1/parts/calculations/quote
Idempotency-Key: 9c5f0a40-5a8b-4d18-9f4e-c4e1f6d8b6e9
```

The first request with a given key succeeds normally. A subsequent
request with the **same key and body** within the window replays the
original response — without re-running the work, without re-charging.

---

## How it works (requests up to 1 MB)

1. Client generates a UUIDv4 idempotency key per logical operation.
2. The server fingerprints `(method, path, caller, org, body)` and looks
   it up in a short-lived cache.
3. **Cache hit, completed, same body:** we replay the original status
   code, headers, and body, and add `Idempotent-Replayed: true`.
4. **Cache hit, in-flight:** we return `409` (`idempotency_in_flight`)
   so you don't double-create while the original is still processing.
5. **Same key, different body:** we return `409`
   (`idempotency_conflict`) — the server refuses to overwrite a
   different operation with the same key. Pick a new key.
6. **Cache miss:** we execute, store the response, and write the cache
   entry with a **24h** TTL.

### What's in the fingerprint

- `method` + `path`
- `caller` — your API key (or user) identity
- `org` — your `X-Org-Slug` / tenant context
- `body` — exact bytes, including whitespace

---

## Large uploads (CAD / multipart > 1 MB)

Bodies larger than **1 MB** — i.e. CAD uploads to
`/upload-and-quote` — are too big to fingerprint or cache, so they use a
**lock-only** mode:

- The first request claims a short lock and executes.
- A concurrent or fast-retry request with the same key gets `409`
  (`idempotency_in_flight`) — it does **not** receive a cached body.
- On a `4xx` the lock is released so you can fix and resend.
- On success or `5xx` the lock is held for a short window (≈60 s); a
  retry inside that window still gets `409`, not a replay.

In other words: for big uploads, idempotency protects you from
double-execution, but you won't get the original response replayed —
poll `GET /parts/calculations/{id}` to find the result instead.

---

## Which verbs honour it

`Idempotency-Key` is honoured on `POST`, `PUT`, `PATCH`, and `DELETE`
whenever the header is present. `GET` / `HEAD` ignore it (reads don't
mutate). A mutation **without** a key still works — we just can't
dedupe. Send one anyway.

---

## Key format

- Any string up to a few hundred bytes; UUIDv4 is conventional.
- Must be **globally unique per logical operation**. If you generate one
  key per "shipment of CAD files to ARCNM", every retry of that
  shipment reuses the same key.
- Treat it as opaque.

A naming convention that works well with upstream ids:

```
<system>-<verb>-<external_id>
e.g.  erp-quote-rfq-2026-05-9012
```

---

## Concurrency

A second request with the same key arriving **while the first is still
in flight** gets:

```json
{
  "error": {
    "code": "idempotency_in_flight",
    "message": "Request with this Idempotency-Key is currently being processed."
  }
}
```

Wait briefly and retry. Once the first request completes, a same-body
retry of a small request replays the original response (with
`Idempotent-Replayed: true`); a large-upload retry returns `409` until
the lock expires.

---

## Example

```bash
curl -X POST https://api.arcnm.io/api/v1/parts/calculations/quote \
  -H "X-API-Key: $ARCNM_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: erp-quote-rfq-2026-05-9012" \
  -d '{ "part_revision_id":"…", "costing_environment_id":"…", "lot_size":50 }'
```

---

## See also

- [Errors](./errors.md) — `idempotency_in_flight`, `idempotency_conflict`.
- [Rate limits](./rate-limits.md) — pair idempotency with retries.
- [Webhooks](./webhooks.md) — the event `id` is the dedup key for
  delivery-side consumers.
