Idempotency¶
The ingest API has two idempotency keys that work together. Confusing them is the most common cause of duplicate state.
Surface |
Key |
Behavior on retry |
|---|---|---|
Request |
|
Returns the same response shape; no re-processing. |
Item |
|
Updated if |
Item (versioned) |
|
A submitted item with this triple already accepted returns |
Request-level idempotency¶
Every request carries a correlation_id — a UUID v4 or v7 generated by the caller:
POST /wms-ingest/v1/master/skus?mode=upsert
X-Correlation-Id: 01J7Y6K1NQ3W2C0X4V0R5T6E7N
{ "items": [ ... ] }
A second request with the same (partner_id, correlation_id) returns the original response without re-processing the body. This makes retries on transient errors safe — the caller can replay the same request after a 500 or a connection timeout without risking duplicate inserts.
The same correlation_id reused across two different logical requests is a caller-side bug. Use a fresh UUID per logical action.
Item-level idempotency¶
Items inside a batch are deduplicated on (partner_id, source_id) regardless of the request’s correlation_id. This means:
Two requests pushing the same SKU with the same
source_version→ the second isREPLAY. No mutation.Two requests pushing the same SKU with a higher
source_versionon the second → the second updates. The first staysACCEPTED.Two requests pushing the same SKU with a lower
source_versionon the second → the second isREPLAY(out-of-order delivery is safe).
How the two interact¶
Picture a batch of 100 SKUs sent twice (network hiccup, caller retried):
First request — all 100 items normalize; 95
ACCEPTED, 5QUARANTINED. The response body is stored under(partner_id, correlation_id).Second request with the same
correlation_id— the stored response body is returned verbatim. No items are re-processed. The 5 quarantines are not re-evaluated.Second request with a fresh
correlation_id— items are re-evaluated. All 95 already-ACCEPTEDitems returnREPLAY. The 5 previously-quarantined items are re-evaluated and may now succeed if the underlying dependency was fixed in the interim.
This dual-key behavior is intentional. Request-level idempotency makes raw network retries safe. Item-level idempotency lets you re-send a batch with a fresh correlation_id to retry quarantines without forcing the caller to filter out already-accepted items.
Generating correlation_id¶
UUID v7 is recommended:
Sorts by time, which makes operational triage easier (the first 8 bytes encode milliseconds since epoch).
128 bits of identifier space, no collisions in practice.
UUID v4 is acceptable — fully random — and is what most existing language libraries default to. Both are supported.
import uuid
correlation_id = str(uuid.uuid4()) # or uuid7() in libraries that provide it
String correlationId = UUID.randomUUID().toString();
Retention¶
The receiving side retains idempotency state for 30 days by default. A correlation_id re-submitted after 30 days is treated as a fresh request — the original response is no longer cached. Within the 30-day window, the original response is returned verbatim.
source_id + source_version retention is permanent for non-tombstoned items. Tombstoned (INACTIVE) items retain their full history for audit and may be archived after the audit-log retention window.
Common mistakes¶
Regenerating
correlation_idon retry. The retry layer should reuse the original. Generating a new UUID per attempt breaks request-level idempotency and causes the receiver to re-process the body.Reusing
correlation_idacross logically distinct requests. A second logical batch needs a fresh UUID. Reuse is a bug.Trusting
correlation_idalone for deduplication. Item-level dedup happens regardless. A newcorrelation_idwith the same items is safe and is the right pattern when retrying after the original request errored at the items layer.Submitting an unchanged item without
source_version. Withoutsource_version, the second submit overwrites the first. Withsource_version, the second is a no-opREPLAY. Usesource_versionwhenever the upstream can produce one.
Idempotency and async jobs¶
Submitting the same (partner_id, correlation_id) ?mode=bulk request twice returns the same job_id. Polling /jobs/{job_id} after the original has completed returns the completed state without re-running.
A new correlation_id on the same payload, however, creates a new job — the prior bulk records are not re-checked for REPLAY at the request layer (only at the item layer). For idempotent bulk submission, keep the same correlation_id across retries.