Idempotency
Este conteúdo não está disponível em sua língua ainda.
Network failures happen. A request may time out before you receive a response, leaving you unsure whether the server processed it. Resending the request without protection could create a duplicate ticket, charge a customer twice, or fire a notification a second time.
The BenchKey API supports idempotency keys on all mutating requests (POST, PUT, PATCH, DELETE). Send the same key on a retry and you get back the original response with no side effects repeated.
How it works
Section titled “How it works”Add an Idempotency-Key header containing a unique string to your request:
POST /api/v1/ticketsAuthorization: Bearer bk_live_acme_xxxxxxxxxxxxBenchKey-Version: 2026-06-13Content-Type: application/jsonIdempotency-Key: idem_01J8X4abc123def456- The server records the key + response when the request first completes. The fingerprint includes the HTTP method, path, and query string, a retry must be identical on all three to match.
- Completed responses are normally retained for 24 hours. An identical retry in that window replays the stored response without repeating the write.
- Some operations also keep a durable command or business-event identity. Do not assume a key becomes reusable after 24 hours; use a new key for a new action.
- An unfinished request can remain reserved while its outcome is reconciled. Retry the identical request with its original key; do not create another submission to bypass a pending result.
The replay is returned with the same HTTP status code and body as the original response, plus the header:
Idempotent-Replayed: trueGenerating a key
Section titled “Generating a key”Use a cryptographically random string. A UUID v4 is the simplest choice:
curl
KEY=$(python3 -c "import uuid; print('idem_' + str(uuid.uuid4()).replace('-',''))")
curl -X POST https://app.benchkey.com/api/v1/tickets \ -H "Authorization: Bearer $BENCHKEY_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $KEY" \ -d '{ "customer_email": "jane@example.com", "customer_name": "Jane Smith", "device_name": "iPhone 14", "service_name": "Screen replacement", "notes": "Cracked screen" }'Node.js
import crypto from "node:crypto";
const idempotencyKey = "idem_" + crypto.randomBytes(16).toString("hex");
const res = await fetch("https://app.benchkey.com/api/v1/tickets", { method: "POST", headers: { Authorization: `Bearer ${process.env.BENCHKEY_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": idempotencyKey, }, body: JSON.stringify({ customer_email: "jane@example.com", customer_name: "Jane Smith", device_name: "iPhone 14", service_name: "Screen replacement", notes: "Cracked screen", }),});
const ticket = await res.json();console.log(ticket);Retry pattern
Section titled “Retry pattern”Generate and persist the key once with the operation before sending, then pass that saved key to the helper on every attempt, including after a process restart. Keep the request body unchanged. Completed responses are normally replayable for 24 hours; beyond that window, check the saved result and resource state before resubmitting.
async function postWithIdempotency(path, body, key) { if (typeof key !== "string" || !key) { throw new Error("Pass the operation's persisted idempotency key"); } const url = `https://app.benchkey.com/api/v1${path}`; const options = { method: "POST", headers: { Authorization: `Bearer ${process.env.BENCHKEY_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": key, // same key for every attempt }, body: JSON.stringify(body), };
let backoff = 1000; for (let attempt = 0; attempt < 5; attempt++) { let res; try { res = await fetch(url, options); } catch (err) { if (attempt === 4) throw err; await new Promise((r) => setTimeout(r, backoff + Math.random() * 200)); backoff = Math.min(backoff * 2, 30_000); continue; }
if (res.ok) return res.json();
const payload = await res.json().catch(() => null); const failure = new Error(payload?.error ? `[${payload.error.code}] ${payload.error.message}` : `HTTP ${res.status}`); const retryable = res.status === 408 || res.status === 429 || res.status >= 500; if (!retryable || attempt === 4) throw failure;
const retryAfter = res.headers.get("Retry-After")?.trim(); const retryDate = Date.parse(retryAfter ?? ""); const wait = /^\d+$/.test(retryAfter ?? "") ? Number(retryAfter) * 1000 : Number.isFinite(retryDate) ? Math.max(0, retryDate - Date.now()) : backoff; await new Promise((r) => setTimeout(r, wait + Math.random() * 200)); backoff = Math.min(backoff * 2, 30_000); }}Conflict errors
Section titled “Conflict errors”If you reuse a key with a different request body or URL, the API returns 409 Conflict with code idempotency_key_reuse:
{ "error": { "type": "conflict_error", "code": "idempotency_key_reuse", "message": "An idempotency key was reused with a different request body.", "request_id": "req_01J8X4zzzzzz" }}This is always a bug in the caller. Generate a new key for each logically distinct write.
For 409 Conflict with code idempotency_in_progress, wait and retry with the same key and unchanged body. If the API returns idempotency_outcome_unknown, stop automatic write retries and reconcile the saved result before resubmitting. Do not use a fresh key to bypass the conflict.
Scope and limits
Section titled “Scope and limits”| Detail | Value |
|---|---|
| Applies to | POST, PUT, PATCH, DELETE |
| Key TTL | 24 hours |
| Key min length | 8 characters |
| Key max length | 255 characters |
| Header name | Idempotency-Key |
The general idempotency namespace includes the tenant and key environment label. Replay still checks the caller’s current authority, and some commands, including webhook-secret rotation, bind the key to the acting principal. An environment label does not isolate workspace data.
An Idempotency-Key is required for ticket creation, payment recording, invoice and payment refunds, customer consent writes, and webhook-secret rotation. Payment-recording keys allow 8 to 220 characters; the other listed operations allow up to 255. Use a UUID or another durable identifier and retain it with your submission.
When to use idempotency keys
Section titled “When to use idempotency keys”Use them on every write that has side effects you can’t easily reverse:
- Creating tickets, invoices, or estimates
- Recording a payment
- Sending an SMS or email notification
- Any
POSTthat charges money or modifies inventory
Use the documented method for each resource: ticket edits use PATCH /tickets/:id. Keep the same key and body when retrying the same edit.
Best practices
Section titled “Best practices”- Persist the key before sending: store it alongside your pending operation so you can recover it after a crash and retry with the same key.
- Use meaningful prefixes: e.g.,
idem_ticket_oridem_pmt_to aid debugging in logs. - Never reuse across unrelated operations: one key, one logical operation.
- Combine with exponential backoff: retries on
429or5xxshould always carry the original idempotency key.