Idempotency
Send an Idempotency-Key header on mutations so a network retry cannot execute the same
operation twice. Use a fresh UUID v4 per logical operation — not per retry.
Where it is required
These high-risk logistics and money routes reject a request without the header with 400 IDEMPOTENCY_KEY_REQUIRED:
| POST /orders/{id}/ship | POST /orders/bulk-ship |
| POST /orders/{id}/shipment | POST /orders/bulk-cancel |
| POST /orders/{id}/cancel | POST /fulfillments |
| POST /returns/{id}/approve-refund |
On the bulk endpoints — POST /products/bulk and POST /inventory/bulk-update — the
header is optional but strongly recommended, in both their synchronous and asynchronous (job) modes: a key lets
a retried submission replay the original result instead of creating a duplicate batch. See
Bulk operations & jobs.
On every other mutation the header is optional but strongly recommended. When it is absent the response
carries X-MallPlus-Idempotency: absent so you can detect unprotected calls in your own logs.
Semantics
| Situation | Result |
|---|---|
| Same key, same body, after completion | The original response is replayed. The operation does not run again |
| Same key, different body | 422 IDEMPOTENCY_KEY_REUSED — the key is bound to the body it first saw |
| Same key, first request still running | 409 IDEMPOTENCY_KEY_IN_PROGRESS with Retry-After |
| Original returned 5xx or threw | The reservation is released; retrying with the same key re-executes |
Treat idempotency as strong protection, not a mathematical guarantee. Replay protection is scoped to a short processing window, so a very long-running synchronous request is not covered indefinitely. For large batches use the async path and reconcile against the job result rather than assuming exactly-once delivery.