Skip to content

How to Use Idempotency Keys

Idempotency keys prevent duplicate operations when a request is retried due to network failures, timeouts, or client-side errors. This is critical for endpoints that create resources or trigger financial transactions.

Why Idempotency Matters

Without idempotency protection, retrying a failed request can result in:

  • Duplicate charges -- a customer is billed twice for the same purchase
  • Duplicate resources -- two identical discounts are redeemed
  • Inconsistent state -- parallel retries create conflicting records

Idempotency keys solve this by letting the server recognize repeated requests and return the original response instead of executing the operation again.

Environment Variables

Set these variables before running any example on this page:

bash
export BASE_URL="https://api.chainabit.com/api/v1"
export TOKEN="your-access-token"

Which Endpoints Require It

The following endpoints require an Idempotency-Key header:

EndpointWhy
POST /billing/checkoutPrevents duplicate charges
POST /billing/discounts:redeemPrevents redeeming a discount code twice

Other POST endpoints accept the header but do not require it. It is good practice to include it on any mutating request where a duplicate would cause problems.

How to Use It

Send the Idempotency-Key header with a unique identifier. A UUID v4 is recommended.

Example: Checkout

bash
curl -s -X POST "https://api.chainabit.com/api/v1/billing/checkout" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "productCode": "pro-monthly"
  }'
javascript
const BASE_URL = process.env.BASE_URL;
const TOKEN = process.env.TOKEN;

const response = await fetch(`${BASE_URL}/billing/checkout`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${TOKEN}`,
    "Idempotency-Key": crypto.randomUUID(),
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    productCode: "pro-monthly",
  }),
});

const { data } = await response.json();
python
import requests
import uuid
import os

BASE_URL = os.environ["BASE_URL"]
TOKEN = os.environ["TOKEN"]

response = requests.post(
    f"{BASE_URL}/billing/checkout",
    headers={
        "Authorization": f"Bearer {TOKEN}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={"productCode": "pro-monthly"},
)

data = response.json()["data"]

Response:

json
{
  "data": {
    "checkoutId": "chk_01HQX...",
    "checkoutUrl": "https://checkout.chainabit.com/session/chk_01HQX...",
    "expiresAt": "2026-03-17T11:00:00.000Z"
  }
}

If you send the exact same request with the same idempotency key, the server returns the original response with a 200 status code instead of creating a new checkout session.

Example: Redeem a Discount

bash
curl -s -X POST "https://api.chainabit.com/api/v1/billing/discounts:redeem" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "code": "LAUNCH2026"
  }'
javascript
const BASE_URL = process.env.BASE_URL;
const TOKEN = process.env.TOKEN;

const response = await fetch(`${BASE_URL}/billing/discounts:redeem`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${TOKEN}`,
    "Idempotency-Key": crypto.randomUUID(),
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    code: "LAUNCH2026",
  }),
});

const { data } = await response.json();
python
import requests
import uuid
import os

BASE_URL = os.environ["BASE_URL"]
TOKEN = os.environ["TOKEN"]

response = requests.post(
    f"{BASE_URL}/billing/discounts:redeem",
    headers={
        "Authorization": f"Bearer {TOKEN}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={"code": "LAUNCH2026"},
)

data = response.json()["data"]

Generating Idempotency Keys

Use any UUID v4 generator. Here are a few options:

bash
# Linux / macOS
uuidgen
javascript
// Node.js 19+ / modern browsers
crypto.randomUUID()

// Older Node.js
require('crypto').randomUUID()
python
import uuid
print(uuid.uuid4())

Rules

  1. One key per logical operation. Generate a new key for each distinct user action. Do not reuse keys across different operations.

  2. Same key = same request body. If you retry with the same idempotency key but a different request body, the server returns a 422 Unprocessable Entity error.

  3. Keys expire after 24 hours. After expiry, the same key can be used for a new operation. Do not rely on this -- always generate fresh keys.

  4. Keys are scoped to your account. Two different accounts can use the same key value without conflict.

Error Responses

StatusMeaning
200Idempotent replay -- returning the original response
409 ConflictA request with this key is currently being processed
422 Unprocessable EntityThe key was already used with a different request body

Conflict Example

If a previous request with the same key is still in progress:

json
{
  "statusCode": 409,
  "message": "A request with this idempotency key is already being processed",
  "error": "Conflict"
}

Wait briefly and retry. The original request will complete and subsequent retries will return the cached response.

Built with purpose.