docs/api-reference/store-api/idempotency.mdx
The Store API supports idempotency keys on mutating endpoints. This lets you safely retry requests without risking duplicate side effects — for example, adding an item to cart twice or creating duplicate payments due to network timeouts.
Include an Idempotency-Key header with a unique value (e.g., a UUID) on any supported request. The API will:
422 error to prevent misusecurl -X POST https://api.mystore.com/api/v3/store/carts/cart_xxx/items \
-H "Authorization: Bearer pk_xxx" \
-H "X-Spree-Token: abc123" \
-H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-H "Content-Type: application/json" \
-d '{"variant_id": "variant_xxx", "quantity": 1}'
If the client retries this exact request with the same idempotency key, the API returns the original response with an Idempotent-Replayed: true header — without adding the item again.
| Endpoint | Actions |
|---|---|
POST /carts | Cart creation |
PATCH /carts/:id | Cart updates |
POST /carts/:id/complete | Order completion |
POST /carts/:id/items | Adding items to cart |
POST /carts/:id/payment_sessions | Payment session creation |
PATCH /carts/:id/payment_sessions/:id/complete | Payment completion |
POST /carts/:id/coupon_codes | Applying coupon codes |
POST /carts/:id/store_credits | Applying store credits |
Idempotency keys are ignored on GET, DELETE, and other non-supported actions.
When a cached response is replayed, the API sets:
Idempotent-Replayed: true
You can use this header to detect replayed responses in your application or logging.
If you send a request with an idempotency key that was previously used with different request parameters (different body, different path), the API returns a 422 error:
{
"error": {
"code": "idempotency_key_reused",
"message": "This Idempotency-Key has already been used with different request parameters."
}
}
Keys longer than 255 characters return a 400 error:
{
"error": {
"code": "invalid_request",
"message": "Idempotency-Key must be 255 characters or less."
}
}
5xx responses are not cached. If the server fails to process your request, you can safely retry with the same idempotency key and the request will be re-executed.
The Spree SDK supports idempotency keys via the idempotencyKey option:
import { createClient } from '@spree/sdk'
const client = createClient({
baseUrl: 'http://localhost:3000',
publishableKey: 'pk_xxx',
})
// Add item to cart with idempotency key
await client.carts.items.create(cartId, {
variant_id: 'variant_xxx',
quantity: 1,
}, {
idempotencyKey: '550e8400-e29b-41d4-a716-446655440000',
})
The Store API also uses optimistic locking via the state_lock_version field on orders. These mechanisms complement each other:
| Mechanism | Prevents | Scope |
|---|---|---|
| Idempotency keys | Duplicate operations from retries | Per-request deduplication |
Optimistic locking (state_lock_version) | Concurrent modifications | Conflict detection between clients |
Use both together for robust checkout flows: idempotency keys protect against network retries, while state_lock_version detects when another client has modified the order.