Idempotency¶
Purpose¶
Idempotency-Key prevents the same ERP business operation from being applied more than once when the client retries because of network errors, timeouts, or transient failures.
Applies to:
text
POST /api/v1/inbound/work-orders
The test endpoint POST /api/v1/inbound/work-orders/test is a public format-validation route. It does not require Idempotency-Key, does not acquire/replay, and does not write idempotency records. See Inbound Work Orders.
Consistency guarantee¶
This service provides effectively-once request processing under normal operation, with safe retry and natural-key reconciliation after interrupted processing.
It does not claim absolute exactly-once semantics on MongoDB standalone deployments, because business writes and idempotency completion are not wrapped in a multi-document transaction.
HTTP contract¶
Required header (live)¶
http
Idempotency-Key: ERP-WO-20260807-000001
Rules:
| Rule | Value |
|---|---|
| Required | Yes when IDEMPOTENCY_ENABLED=true and IDEMPOTENCY_REQUIRED=true |
| Length | 8–128 characters |
| Characters | A-Z a-z 0-9 . _ : - |
| Whitespace | Leading/trailing whitespace rejected |
| Case | Case-sensitive |
| Duplicates | Multiple Idempotency-Key headers rejected |
Scope¶
Uniqueness is scoped by:
client_id(authenticated principal, oranonymouswhen auth is disabled)company_idorganization_id- HTTP method
- route
idempotency_key
The same key under a different tenant or client is independent.
Response headers¶
| Header | First request | Replay |
|---|---|---|
X-Request-ID |
Current attempt id | New current attempt id |
Idempotency-Key |
Echo of key | Echo of key |
Idempotency-Replayed |
false |
true |
Idempotency-Original-Request-ID |
Same as current | First processed request id |
Envelope request_id always equals the current HTTP attempt id.
Concurrent processing¶
If another request owns an active processing lease:
- HTTP
409 - code
IDEMPOTENCY_REQUEST_IN_PROGRESS - header
Retry-After: 2
Payload conflict¶
Same scope/key with a different canonical payload:
- HTTP
409 - code
IDEMPOTENCY_KEY_CONFLICT - message:
The Idempotency-Key has already been used with a different request payload.
Canonical request hash¶
Idempotency compares canonical_request_sha256, computed after decode, normalize, validation, and tenant resolution.
Included:
- resolved
company_id/organization_id - normalized projects contract
- endpoint contract version
Excluded:
request_id, timestamps, Authorization, tokens, remote IP, User-Agent, Accept
JSON and XML payloads that are semantically equivalent after normalization produce the same hash. Accept format is not part of the hash, so a JSON original can be replayed with Accept: application/xml.
A separate raw_body_sha256 is stored for archive purposes only.
Processing lease¶
Config: IDEMPOTENCY_PROCESSING_LEASE_SECONDS (default 300, min 30, max 3600).
While state=processing and the lease is active, other same-key requests receive in-progress. After lease expiry, a matching-hash retry may take ownership and increment attempt_count.
Retryable failures¶
MongoDB unavailable / unexpected 5xx mark the record failed_retryable. A later same-key/same-hash request may acquire ownership and retry. Deterministic completed business responses (including 409 NO_PROJECT_PROCESSED) are stored and replayed.
Retention¶
IDEMPOTENCY_RETENTION_HOURS (default 720 = 30 days) sets expires_at. MongoDB TTL index uses expireAfterSeconds: 0.
ERP retry example¶
- Send live request with
Idempotency-Key: ERP-WO-20260807-000001. - On timeout, retry with the same key and same payload.
- If first request completed, receive replayed logical response (
Idempotency-Replayed: true). - If still processing, wait for
Retry-Afterand retry. - Never reuse the same key for a different work-order payload.