Skip to content

Idempotency

Creating a shipment is a billable, irreversible carrier call — a duplicate means a real charge and a real second shipment. The v2 create endpoint (POST /shipments) is therefore idempotent and crash-safe by design: safe to retry, and safe against a mid-request crash, without ever re-billing the carrier.

The Idempotency-Key header

Send an optional Idempotency-Key header on a create request:

Idempotency-Key: 9c1a5f4e-2b6d-4a3e-8f10-7d0c2b3a4e5f

Use a fresh unique value (a UUID is ideal) per logical shipment. If the same create is retried with the same key, the API returns the original shipment instead of creating a second one.

The key is scoped to your resolved license, and it is bound to the request body (see the conflict rule below).

What happens without a key

The header is optional. When you omit it, the API derives a body fingerprint and dedupes on that instead. The fingerprint is computed from the fields that define a shipment's identity under the license:

  • service direction (ship_data.type) and return mode (ship_data.return)
  • the order reference (order.id, falling back to order.number)
  • recipient phone (ship_data.contact_phone)
  • destination — ship_data.street, ship_data.number, ship_data.city, and pickup point (ship_data.pickup)
  • package count (ship_data.packages)

Two creates with the same values for all of these are treated as the same shipment and deduped. Two creates that differ in any of them — for example the same order and phone but a different address — are correctly treated as distinct shipments and both go through.

TIP

An explicit Idempotency-Key is the more precise mechanism. The body fingerprint is a safety net for clients that don't send one — but because it ignores fields outside the identity set, prefer an explicit key when you need exact retry semantics.

Retrying is safe

On a create, the API first checks for a cached completed result for this key/fingerprint under your license. If one exists, the original shipment is returned immediately — no lock, no carrier call. Concurrent retries are additionally serialized behind a short lock; if a second identical create arrives while the first is still in flight, it does not create a duplicate — it returns:

json
{
  "error": {
    "code": "duplicate_request",
    "message": "A shipment for this request is already being created. Please retry shortly.",
    "status": 409
  }
}

Retry after a moment and you'll get the completed shipment.

Reusing a key with a different body — 409 idempotency_key_conflict

An Idempotency-Key is bound to the exact request body of its first use. Reusing the same key with a different body is a client error, not a silent return of the old shipment:

json
{
  "error": {
    "code": "idempotency_key_conflict",
    "message": "This Idempotency-Key was already used with a different request body.",
    "status": 409
  }
}

This protects you from accidentally masking a genuinely different shipment behind a stale key. To create a different shipment, use a new key. (This body binding applies only when you send an explicit Idempotency-Key; the fingerprint path has no equivalent conflict because a different identity simply produces a different fingerprint.)

Crash-safe recovery — never re-bill the carrier

The create path records the carrier result before persisting the shipment row. The carrier call is the billable, irreversible step, so writing its result to the cache first means a crash after the carrier succeeded but before (or during) the local save cannot re-charge the carrier on retry:

  1. Carrier call succeeds → the tracking code and raw carrier response are cached (keyed by your idempotency key / fingerprint, under your license) before anything is persisted.
  2. The shipment row is persisted, then its uuid is written back into the same cache record.

If a crash interrupts the request after step 1, the next retry (serialized under the lock) recovers instead of re-calling the carrier:

  • If the shipment was actually persisted (only the uuid write-back was lost), it is located by its cached tracking code and reused — no duplicate row, no carrier call.
  • If persistence never happened, the shipment row is re-created from the cached carrier result — still no second carrier call.

Either way, the retry converges on the single shipment the carrier already created. The cached record lives for one hour, which is the recovery window for a retry.

Summary

SituationResult
Same key (or same body fingerprint), completedReturns the original shipment.
Same key, still in flight409 duplicate_request — retry shortly.
Same key, different body409 idempotency_key_conflict.
Different key / different identityCreates a new shipment.
Crash after carrier successRecovered on retry without re-billing the carrier.