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

# Idempotency

> Safely retry POST requests without creating duplicate resources

# Idempotency

Every `POST` request to the Journeybee API must include a unique
`Idempotency-Key` header. Retries with the same key return the cached
response from the first successful attempt, so a network blip or
client retry never creates a duplicate lead, deal, or subscription.

## Quick start

Generate a UUID per logical operation and send it with your request:

```bash theme={null}
curl -X POST https://api.journeybee.io/v1/leads \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: 7f3a9b2c-4e8d-4a5b-9c1d-8e5f2a3b4c5d" \
  -H "Content-Type: application/json" \
  -d '{ "first_name": "Jane", "email": "jane@acme.test", "partnership_uuid": "<partnership-uuid>" }'
```

If the network times out and you retry with the **same key and body**,
you get the same response — no duplicate lead is created.

## Rules

| Condition                                      | Response                                         |
| ---------------------------------------------- | ------------------------------------------------ |
| First request with a new key                   | Runs the handler, caches the response for 24h    |
| Retry with same key + same body                | Replays the cached response (same status + body) |
| Retry with same key + **different** body       | `422` — key was reused for a different request   |
| Retry while the first request is still running | `409` — tells you to back off and retry          |
| 5xx failure (server error)                     | Not cached — your retry gets a fresh attempt     |
| Missing header on any POST                     | `400` — header is required                       |

## Key format

* 1–255 characters
* Alphanumeric, underscore, hyphen (`[A-Za-z0-9_-]`)
* UUIDs recommended — they're collision-free and easy to generate
* Keys are scoped per company, so two companies using the same key string
  will not collide

## Retention

Each key's response is cached for **24 hours**. After that, the same
key becomes fresh again.

## What to use as a key

Pick a value that uniquely identifies the **logical** operation the
client intends, not the physical HTTP request:

* ✅ `crm-lead-7841-sync` — one key per lead you're syncing.
* ✅ A UUID generated when the user clicked "Save".
* ❌ `Date.now()` — changes on every retry, defeats dedup.
* ❌ A constant string — every request collides.

## Client examples

### Node.js

```javascript theme={null}
import { randomUUID } from "node:crypto";

async function createLead(payload) {
  const key = randomUUID();

  for (let attempt = 0; attempt < 3; attempt++) {
    const res = await fetch("https://api.journeybee.io/v1/leads", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.JOURNEYBEE_API_KEY}`,
        "Idempotency-Key": key,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(payload),
    });

    if (res.status === 409) {
      // in flight — back off and retry with the SAME key
      await new Promise((r) => setTimeout(r, 500 * 2 ** attempt));
      continue;
    }
    return res.json();
  }
  throw new Error("createLead: exhausted retries");
}
```

### Python

```python theme={null}
import uuid
import time
import requests

def create_lead(payload, api_key):
    key = str(uuid.uuid4())
    for attempt in range(3):
        r = requests.post(
            "https://api.journeybee.io/v1/leads",
            headers={
                "Authorization": f"Bearer {api_key}",
                "Idempotency-Key": key,
            },
            json=payload,
        )
        if r.status_code == 409:
            time.sleep(0.5 * 2 ** attempt)
            continue
        return r.json()
    raise Exception("exhausted retries")
```

## Scope today

Idempotency is required on **all `POST` endpoints**. `PATCH` and
`DELETE` are not currently required to carry the header — most `PATCH`
requests are naturally idempotent (setting a field to the same value
twice has the same effect as once) and `DELETE` is idempotent by
definition.

## Related

* [Authentication](/guides/authentication) — API key setup
* [Errors](/guides/errors) — error response shapes
