---
title: "Retrying safely"
description: "A request that timed out may still have succeeded. `Idempotency-Key` is how you retry without doing it twice."
canonical: "https://boltclaw.ai/docs/idempotency"
last-updated: "2026-11-05"
---

# Retrying safely

A request that timed out may still have succeeded. `Idempotency-Key` is how you retry without doing it twice.

A request that timed out may still have succeeded. To retry a `POST` without
creating a second page, post or message, send an `Idempotency-Key` header: any
string of 1 to 255 printable characters, one per logical operation (a UUID is
the easy choice).

```
Idempotency-Key: 5b8f6e2c-1d4a-4f7b-9c3e-8a2d6f0b4e17
```

- **The same key and the same request** within 24 hours gets the first answer
  again, word for word, with `Idempotent-Replay: true`. Nothing is done twice.
- **The same key with a different request** is `409 IDEMPOTENCY_KEY_CONFLICT`.
- **The same key while the first request is still running** is
  `409 IDEMPOTENCY_KEY_IN_FLIGHT`. Wait a moment and retry.
- **Only a success is kept.** After a `4xx`, fix the request and retry with
  the same key; the corrected request runs.

## Two requests, one page

**A create that can be retried** — answers `201`

Any string of 1 to 255 printable characters. A UUID is the easy choice, one per logical operation.

```bash
curl -X POST https://api.boltclaw.ai/v1/pages \
  -H "Authorization: Bearer bc_live_EXAMPLExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 5b8f6e2c-1d4a-4f7b-9c3e-8a2d6f0b4e17" \
  -d '{
  "slug": "acme-staging",
  "title": "Acme, staging"
}'
```

```js
const res = await fetch("https://api.boltclaw.ai/v1/pages", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.BOLTCLAW_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({
    "slug": "acme-staging",
    "title": "Acme, staging"
  }),
});

// 201
const data = await res.json();
```

**The same request again, with the same key** — answers `201`

The first answer, word for word, with `Idempotent-Replay: true`. No second page was created, and the slug is not reported as taken.

```bash
curl -X POST https://api.boltclaw.ai/v1/pages \
  -H "Authorization: Bearer bc_live_EXAMPLExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 5b8f6e2c-1d4a-4f7b-9c3e-8a2d6f0b4e17" \
  -d '{
  "slug": "acme-staging",
  "title": "Acme, staging"
}'
```

```js
const res = await fetch("https://api.boltclaw.ai/v1/pages", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.BOLTCLAW_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({
    "slug": "acme-staging",
    "title": "Acme, staging"
  }),
});

// 201
const data = await res.json();
```

## Which operations take it

**Every `POST` accepts the header.** Four refuse the request without it:
`POST /v1/keys`, `POST /v1/keys/{id}/rotate`, `POST /v1/webhooks` and
`POST /v1/webhooks/{id}/rotate-secret`. All four mint a credential, and a credential is the one thing where a retried
network timeout cannot be told apart from a request that never landed: without
a key the only safe move is to issue a second one, and then nobody knows which
of the two their system is holding.

Keys are scoped to your workspace and to the operation, so reusing one
elsewhere is harmless. A replay of `POST /v1/media` answers `"upload": null`:
the upload URL is a credential too, it is never kept, so ask for a new upload
if you still need one.

## Request ids

Every response, success or error, also carries **`X-Request-Id`**, the same
value as an error's `request_id`. Send your own (a UUID) and it is used
instead, so your logs and ours share one id.

<!-- https://boltclaw.ai/docs/idempotency as markdown. Generated from openapi.yaml; API version 1.22.0. -->
<!-- Every /docs page answers as markdown: add .md to its path, or send Accept: text/markdown. -->
