Idempotency
Make POST/PATCH/PUT requests safe to retry with the Idempotency-Key header.
POST, PATCH, and PUT endpoints accept an Idempotency-Key header that makes retries safe. A duplicate request with the same key and the same body returns the original cached response only after the same authenticated actor is verified (API key or Auth0 JWT), instead of running the operation twice.
Key format and scope
- Format: 1–255 characters, alphanumeric only (
[a-zA-Z0-9]+). - Scope: keys are scoped per actor (distributor / partner / organization), so two actors using the same key do not collide.
- Cache: the first response is cached for 24 hours.
- Methods: only
POST,PATCH, andPUT.
The header is auto-injected on every write operation in the OpenAPI spec — you don't need to look it up per route. Routes that intentionally opt out set the x-bp-skip-idempotency: true extension on their operation.
The middleware fails open: if the cache layer is unavailable the request proceeds without idempotency protection.
How it works
- First request — the API processes the request and caches the response under your key for 24 hours.
- Duplicate (same key, same body) — after the same authenticated actor is established (before the cache lookup), the API returns the cached response without re-running the operation.
X-Idempotency-Replayed: trueis set on the response. - Conflict (same key, different body) —
409 Conflict. - In-flight (same key, original still running) —
429 Too Many RequestswithRetry-After. Problemtypeishttps://api.benjipays.com/problems/idempotency-processing(not the rate-limit URI).
Header reference
| Header | Direction | Description |
|---|---|---|
Idempotency-Key | Request | Unique key. Required for retry safety on POST/PATCH/PUT. Omitting it leaves the operation un-protected. |
X-Idempotency-Replayed | Response | Present and set to true only when the response is a cached replay. |
Generating keys
Use UUID v4 with hyphens stripped, or any 1–255-character alphanumeric value tied to the logical operation you're performing:
import { v4 as uuidv4 } from 'uuid';
const idempotencyKey = uuidv4().replace(/-/g, '');
// e.g. 550e8400e29b41d4a716446655440000Example: first request
POST https://api.benjipays.com/v2/partners
Authorization: Bearer YOUR_ACCESS_TOKEN
Idempotency-Key: createPartnerAbc123
Content-Type: application/json
{
"name": "Acme MSP",
"externalId": "acme-001"
}HTTP/1.1 201 Created
Content-Type: application/json
{
"data": {
"partnerId": "6022f98854f2c41d7ef2b1fe",
"name": "Acme MSP",
"externalId": "acme-001"
}
}Example: duplicate request (cached replay)
Same key, same body:
HTTP/1.1 201 Created
Content-Type: application/json
X-Idempotency-Replayed: true
{
"data": {
"partnerId": "6022f98854f2c41d7ef2b1fe",
"name": "Acme MSP",
"externalId": "acme-001"
}
}No new partner was created.
Example: conflict
Same key, different body returns 409 Conflict:
HTTP/1.1 409 Conflict
Content-Type: application/problem+json
{
"type": "https://api.benjipays.com/problems/idempotency-key-conflict",
"title": "Bad Request",
"status": 409,
"detail": "Idempotency key conflict: request body does not match previous request with the same key",
"instance": "/v2/partners"
}When you see this, generate a new idempotency key and retry as a new operation.
Example: in-flight
Same key while the original request is still running:
HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 30
{
"type": "https://api.benjipays.com/problems/idempotency-processing",
"title": "Too Many Requests",
"status": 429,
"detail": "A request with this idempotency key is currently being processed. Please retry after a moment.",
"instance": "/v2/partners",
"retryAfterSeconds": 30
}This is distinct from a rate-limit 429, which uses type https://api.benjipays.com/problems/rate-limit-exceeded.
Error reference
| Status | Reason | What to do |
|---|---|---|
400 Bad Request | Invalid key format (type …/idempotency-key-invalid). | Fix the key (1–255 alphanumeric characters). |
409 Conflict | Same key, different request body. | Use a new key. |
429 Too Many Requests | Another request with the same key is still processing (type …/idempotency-processing). | Wait the Retry-After duration and retry with the same key. |
Best practices
- Send an idempotency key on every
POST,PATCH, andPUT. - Generate the key once per logical operation and reuse it across retries of that operation.
- On
409 Conflict, generate a fresh key for the new operation. - Inspect
X-Idempotency-Replayedif you need to distinguish "the work just happened" from "the work happened before".
Updated 25 days ago
