Idempotency-Key Support Across 9 Shipping APIs

We checked 9 carrier and multi-carrier shipping APIs for documented Idempotency-Key support on label purchase, with sources and dates.

Share

Shippo publishes a header with no stated expiry. Sendle publishes a header with a 72-hour window. Sendcloud ties replay safety to a field, not a header. UPS, by a third-party developer's account, has none at all. Those four sentences describe more variance in carrier API idempotency than you'd expect from a single HTTP mechanism that's existed in draft form since 2020.

This matters because label purchase is the one call in a carrier integration that spends money. A timeout or a 5xx on POST /shipments or its equivalent leaves you with a binary unknown: did the label get bought or not? If the platform doesn't document a replay-safe mechanism, your retry logic either guesses or it buys a second label. This post doesn't re-explain idempotency architecture — that's covered elsewhere on this blog — it supplies the sourced numbers: which of nine shipping APIs document an idempotency key TTL on the endpoint that actually charges for a label, and which don't.

Method

Each row below reflects public developer documentation for the shipment or label-creation endpoint only — not tracking, address validation, webhooks, or refunds — read in early November 2026. Where no public documentation page states a mechanism or a time-to-live, the table says "Not published." No inference, no assumed vendor replies, no estimates. Enterprise and B2B platforms that gate their API reference behind a login or partner agreement are marked accordingly rather than guessed at.

The table

Platform Documented mechanism Field/header Published TTL Source
Shippo Idempotency-Key header, required Idempotency-Key Not published Shippo docs
Sendcloud Business-key uniqueness, all POST shipments endpoints external_reference_id Not published Sendcloud FAQ
Sendle Idempotency-Key header, documented lifecycle Idempotency-Key 72 hours Sendle docs
DHL eCommerce Americas (Label API) Client-supplied unique package ID, rejected on duplicate packageId Not published DHL developer portal
EasyPost Not published — Not published EasyPost API reference
ShipEngine / ShipStation API Not published — Not published ShipEngine API reference
FedEx Ship API Not published — Not published FedEx developer portal
UPS Shipping API None, per third-party integration report — Not published Runtara PR #370
Cargoson / nShift / other TMS-middleware layer Not published (non-public API reference) — Not published Not publicly documented

Three platforms out of nine give you a real answer on the endpoint that spends money. The rest either use a different pattern entirely or simply don't say.

What each row actually means

  • Shippo: The Idempotency-Key header is required, described as a client-generated key that makes retries safe: repeating a request with the same key returns the original result instead of creating a duplicate. Repeating a live request with the same Idempotency-Key returns the original request's pointer, with status showing that request's current outcome. No TTL is stated on that reference page.
  • Sendcloud: All POST shipments endpoints use the external_reference_id field to maintain idempotency. That's a field on the payload, not a header, and the FAQ gives no expiry for it.
  • Sendle: the clearest published window in the set. After 72h, the key is cleared and can be reused, though Sendle advises you shouldn't write your system to rely on this. The docs are explicit that once a key's used, you can't use it for a different endpoint or different parameters.
  • DHL eCommerce Americas: each label requires a unique identifier called the packageId, and for every successful label that is created, the response will contain the DHL unique package identifier, called the dhlPackageId. The dedup enforcement shows up in the error catalogue: a "Duplicate Validation Failed" error occurs when the same Package ID is used in more than one successful Label request. No TTL, and this isn't a response replay — it's a rejection.
  • UPS: the bluntest finding in the table, and it comes from a developer building a UPS agent, not from UPS itself: UPS has no idempotency key, and agent steps are retried on transient errors, so a shipment request that timed out or got a 5xx could be sent again and buy a second label. That repo's fix wasn't a UPS-side key — it was to stop retrying writes whose outcome is unknown.

Header vs business key: two different guarantees

A true Idempotency-Key header with server-side response caching — Shippo, Sendle — replays the exact original response on retry. Call it twice with the same key and you get the same payload back, including the label URL and tracking number. A business-key uniqueness constraint — Sendcloud's external_reference_id, DHL's packageId — only stops a second object from being created. It typically returns an error on replay, as DHL's 400.0204006 does, not the original response. Your code still needs somewhere to store the first outcome, because the carrier won't hand it back to you a second time.

The IETF draft that standardises this header is explicit about which of those two patterns it expects: the resource server SHOULD define such expiration policy and publish it in the documentation. It also says the obligation sits with the server, not the convention: resources MUST publish an idempotency-related specification, and this specification MUST include expiration related policy if applicable. Measured against that bar, three of nine platforms in this table comply in any meaningful sense, and only one — Sendle — publishes an actual number.

Where fintech set the bar carriers haven't matched

Payments APIs standardised on this years ago, with published numbers to match. Qonto's idempotency keys expire after 30 minutes, and sending the same request with the same key after that period creates a new resource. The FusionFabric open banking standard goes further and states the rule as a server obligation: the server must treat a request as idempotent if it had received the first request with the same Idempotency-Key from the same Identity provider in the preceding 24 hours. It adds a plain instruction that most shipping APIs in this table haven't followed: publish the longevity of idempotency keys — keys are typically purged after 24 hours.

Shipping volumes are smaller per transaction than payments, but a double-bought label is still real money and a real support ticket. The gap here isn't technical difficulty — the pattern has been a stable IETF draft for six years — it's documentation discipline.

What this means for the middleware layer

If the carrier doesn't publish a dedup guarantee, your architecture owns it instead. That means an outbox table or idempotency cache keyed by tenant and operation, with a client-generated reference stamped into whichever field the carrier actually checks: an order ID into Sendcloud's external_reference_id, a UUID into DHL's packageId, a stable key held across retries for Shippo or Sendle. For UPS and FedEx, where nothing is published, that cache is the only thing standing between a timeout and a second label.

This is exactly the primitive that multi-carrier platforms are supposed to absorb so individual shippers don't rebuild it per carrier. Vendors operating in this layer — Cargoson, nShift, EasyPost, ShipEngine, Shippo, Sendcloud among them — vary in whether they expose that dedup guarantee to the calling application or simply bury it inside their own retry handling. If you're evaluating any of them for a label-purchase workflow, ask the direct question: what happens, concretely, when my client retries a label request after a timeout? If the answer isn't a documented key and a stated window, assume the answer is "nothing" and build the cache yourself.

What wasn't measured

No live testing was run against production endpoints — that would require funded sandbox accounts across nine platforms, and some, including DHL Express, gate their fuller API documentation behind partner agreements rather than publishing it openly. Scope was limited to the shipment or label-create call specifically; tracking, webhooks, and address validation endpoints were out of scope and may use different mechanisms, including ones not covered here. Where a cell says "Not published," that's a statement about public documentation as read in early November 2026, not a claim about what the vendor's engineering team has built internally. Carrier docs change without much notice — if you find a header this table missed, that's the nature of unversioned documentation, not a contradiction of the method.