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.
CLI kullanıcıları
Bu yönergeler doğrudan HTTP API istemcileri içindir. Resmi Chainabit CLI, desteklenen değişiklik komutları için idempotency kimliklerini otomatik olarak oluşturur ve yeniden kullanır; CLI'ye anahtar vermeyin.
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:
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:
| Endpoint | Why |
|---|---|
POST /billing/checkout | Prevents duplicate charges |
POST /billing/discounts/redeem | Prevents 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
Checkout işlemi ayrıca yakın zamanda passkey ile yeniden kimlik doğrulama gerektirir. Uygun bir kanıt olmadan yapılan ilk isteğe API, error.details.challenge içeren 403 STEP_UP_REQUIRED yanıtını verir; checkout oluşturulmaz. Sunulan WebAuthn doğrulamasını tamamlayın, ardından aynı idempotency anahtarıyla aynı checkout isteğini yineleyin. Aşağıdaki örnekler hemen başarılı olan bir checkout'u değil, istek biçimini gösterir.
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 '{
"variantId": "3f2b9c1e-4d5a-4b6c-8d7e-9f0a1b2c3d4e"
}'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({
variantId: "3f2b9c1e-4d5a-4b6c-8d7e-9f0a1b2c3d4e",
}),
});
const { data } = await response.json();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={"variantId": "3f2b9c1e-4d5a-4b6c-8d7e-9f0a1b2c3d4e"},
)
data = response.json()["data"]Yeniden kimlik doğrulamadan önceki yanıt (ID'ler her istekte değişir):
{
"error": {
"code": "STEP_UP_REQUIRED",
"message": "Billing management requires recent authentication",
"details": {
"code": "STEP_UP_REQUIRED",
"message": "Billing management requires recent authentication",
"challenge": {
"challengeId": "<challenge-id>",
"actionClass": "billing.manage",
"minimumAssurance": "phishing_resistant",
"acceptableMethods": ["webauthn"],
"maxAgeSeconds": 300
}
}
},
"meta": { "requestId": "<request-id>" }
}Checkout is created for the account the request acts in (selected with the x-workspace-id or x-account-id header). It requires the owner, admin, or billing role in that account; other members receive 403. Pass variantId (a variant UUID from the products list) or variantSystemKey.
If another checkout for the same plan and account is being created at the same moment, the request returns 409 with error code billing_checkout_in_progress; retry the request.
Example: Redeem a Discount
The discount is redeemed for the account the request acts in. This requires the owner, admin, or billing role in that account.
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"
}'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();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:
# Linux / macOS
uuidgen// Node.js 19+ / modern browsers
crypto.randomUUID()
// Older Node.js
require('crypto').randomUUID()import uuid
print(uuid.uuid4())Rules
One key per logical operation. Generate a new key for each distinct user action. Do not reuse keys across different operations.
Same key = same request body. If you retry with the same idempotency key but a different request body, the server returns a
422 Unprocessable Entityerror.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.
Keys are scoped to your account. Two different accounts can use the same key value without conflict.
Error Responses
| Status | Meaning |
|---|---|
200 | Idempotent replay -- returning the original response |
409 Conflict | A request with this key is currently being processed |
422 Unprocessable Entity | The key was already used with a different request body |
Conflict Example
If a previous request with the same key is still in progress:
{
"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.