> ## Documentation Index
> Fetch the complete documentation index at: https://docs.instantcompliance.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Idempotency

> Make every retry safe.

Network calls fail. Lambda timeouts mid-write. Zapier retries on its own.
The `Idempotency-Key` header tells us *"if you have already seen this
request, replay the original response — do not run the work again."*

## When to use it

Include `Idempotency-Key` on every `POST` and `PATCH`. For `GET`
requests it has no effect (reads are naturally idempotent).

## How to generate one

Use any unique-enough string per request, ≤ 255 characters. A UUID
works perfectly:

```javascript theme={null}
const idempotencyKey = crypto.randomUUID();
```

Or hash the source event id:

```javascript theme={null}
const idempotencyKey = `hubspot-contact-${event.objectId}-${event.occurredAt}`;
```

Use the **same key** for every retry of the same logical request.

## How it works

```http theme={null}
POST /v1/customers
Authorization: Bearer ic_live_...
Idempotency-Key: 7f9a1c2d-...

{ "externalId": "crm-7741", "fullName": "Jane Doe", "email": "jane@example.com" }
```

* **First request** with that key: the work runs, the response is
  stored, and the response is returned.
* **Second request** with the same key (and the same body): the stored
  response is **replayed verbatim**, including the original HTTP status
  code. A `Idempotent-Replayed: true` header is added so you can tell.
* **Repeat with a different body**: returns **409 idempotency\_conflict**.
  This catches a real client bug — never silently shadows a prior
  result.

## TTL

Cached responses are kept for **24 hours**. After that, the next request
with that key runs the work again. Make sure your retry window fits
inside 24 hours.

## What counts as "the same request"

A request is matched by the tuple `(organization, method, path, key)`.
That means:

* You can reuse the same idempotency key across `POST` and `PATCH`
  without collision.
* Two different organisations can use the same key independently — they
  are isolated.
* `POST /v1/customers` with key `X` and `PATCH /v1/customers/abc` with
  key `X` do not interfere.

## Idempotency vs `externalId`

These are two complementary safeties:

* **`Idempotency-Key`** — protects against duplicate calls within a 24h
  retry window. Operational concern.
* **`externalId`** — protects against duplicate records across all of
  time. Domain concern. The same `externalId` reused on `POST` performs
  a *deliberate* upsert.

Use both. They cover different failure modes.

## Example: safe retry loop

```javascript theme={null}
async function ingestCustomerWithRetry(customer, opts = {}) {
  const key = opts.idempotencyKey ?? crypto.randomUUID();
  for (let attempt = 0; attempt < 5; attempt++) {
    try {
      const res = await fetch('https://app.instantcompliance.ai/api/v1/customers', {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${process.env.IC_API_KEY}`,
          'Content-Type': 'application/json',
          'Idempotency-Key': key
        },
        body: JSON.stringify(customer)
      });
      if (res.status < 500) return res.json();
    } catch (err) {
      // transient — retry with same key
    }
    await new Promise((r) => setTimeout(r, 2 ** attempt * 500));
  }
  throw new Error('Gave up after 5 attempts');
}
```
