Idempotency & Concurrency
Idempotency-Key for safe retries; expected_version so two writers never silently overwrite each other.
Idempotency-Key
Use a fresh Idempotency-Key for every logical command. A key is 1–255 visible ASCII characters (! through ~), is case-sensitive, and is scoped to one integration. Shop Commander stores an encrypted replay response for seven days and reserves the key as a non-replayable tombstone for 30 days.
POST /v1/repair-orders
Idempotency-Key: fleet-wo-2026-000813
- Same key + same operation, target, and JSON body within seven days returns the original status/body plus
Idempotent-Replayed: trueandIdempotency-Original-Request-Id. - The JSON fingerprint ignores object-key order and insignificant whitespace. Omitted and explicit
nullare different bodies. - Same key + a different operation, target, or body within 30 days returns
409 idempotency_key_reused. - On days 8–30, the response is no longer retained; retry returns
409 idempotency_result_expiredand never executes again. After day 30 the key may be reused, but generating a new UUID-like key is safer. - The header is required for customer create/archive; vehicle create; appointment create/cancel/check-in/conversion; repair-order create/add-job; job actions; task create/complete; and webhook endpoint create/rotate/test.
- It is optional but honored on customer, vehicle, appointment, task, and webhook endpoint PATCH operations. External-reference PUT/DELETE operations are naturally idempotent and also honor a supplied key.
- Keys are scoped to your integration; two integrations can use the same key value independently.
- If another request still owns the key, retry
409 idempotency_in_progressafter theRetry-Afterdelay. A failed/rolled-back first command does not consume the key. A committed command whose HTTP response was lost is replayed safely.
Optimistic concurrency with expected_version
Mutable resources carry version (an integer that increments on every change). Include it in updates and domain actions:
PATCH /v1/customers/cus_…
{ "expected_version": 7, "email": "jane@example.com" }
If someone — a staff member in the app, another integration — changed the record since you read it, the API responds 409 conflict / version_conflict naming the current version. Re-read, merge, and try again. Omitting expected_version is allowed and means "last write wins"; use it whenever you edit a field a human might also edit.
Ordering and duplicates in webhooks
Webhook deliveries are at-least-once and unordered. De-duplicate on the event id and treat each delivery as a signal to GET the current object rather than as the object itself — see Webhooks.
Timeouts
Requests are cut off after 60 seconds at the edge. A timed-out command may or may not have committed: retry it with the same Idempotency-Key to find out safely.