Skip to content

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, or anonymous when auth is disabled)
  • company_id
  • organization_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

  1. Send live request with Idempotency-Key: ERP-WO-20260807-000001.
  2. On timeout, retry with the same key and same payload.
  3. If first request completed, receive replayed logical response (Idempotency-Replayed: true).
  4. If still processing, wait for Retry-After and retry.
  5. Never reuse the same key for a different work-order payload.