Skip to content

Errors and retries

Branch on the HTTP status and the shape of the body, never on the wording of detail. Messages may change; status codes and fields will not.

Status How to recognise it Meaning What your code should do
200 state: "ROUTED" Shipped Store shipment_id, tracking_number, carrier and the label
400 Body has missing: [...] Contact details missing for every tier that fits Fix the data (ask the buyer for email or phone). Don’t retry unchanged.
401 detail only Missing or invalid API key Configuration error. Alert a human, don’t retry.
404 detail only Unknown id, or not your shipment Check the id you stored
422 detail is an array of field errors The request failed validation, e.g. country is not FI, a field is missing or has the wrong type A bug in your mapping. Fix the code.
422 detail is a string, no shipment_id No routing rule fits this parcel: too heavy or large for every allowed tier, or PICKUP only Not shippable as specified. Broaden delivery_method or route to a human. Don’t retry.
422 Has shipment_id and state: "UNROUTABLE" Every eligible carrier rejected the parcel Not shippable as specified. Don’t retry.
502 Has shipment_id and state: "FAILED" Every carrier errored (temporary) Retry later with the same Idempotency-Key. The same shipment_id is retried.
Timeout, connection error, other 5xx No usable body Outcome unknown Retry with the same Idempotency-Key

A 422 is a verdict on the parcel; a 502 is a verdict on the carriers.

400 — contact details missing
{
"detail": "this parcel's routing tier requires recipient_email, recipient_phone",
"field": "recipient_contact",
"missing": ["recipient_email", "recipient_phone"]
}
422 — validation error
{ "detail": [{ "type": "literal_error", "loc": ["body", "country"], "msg": "Input should be 'FI'" }] }
422 — no route
{ "detail": "no routing rule matched this parcel" }
422 — every carrier rejected the parcel
{ "detail": "all carriers rejected the shipment", "shipment_id": 42, "state": "UNROUTABLE" }
502 — every carrier unavailable
{ "detail": "all carriers unavailable", "shipment_id": 43, "state": "FAILED" }

Send an Idempotency-Key header on every single-shipment create. If the response is lost (a timeout, a dropped connection), retrying with the same key returns the original shipment instead of creating, and paying for, a second one.

  • Scope: per customer account. Length: up to 128 characters (longer returns 422).
  • The key identifies the parcel; the body is not compared. Reusing a key for a different parcel silently returns the first parcel’s shipment. Never share keys between parcels.
  • After ROUTED, repeating the key returns the same shipment, tracking number and label. No second consignment is created.
  • After a 502, repeating the key runs the carriers again for the same shipment_id.
  • After an “all carriers rejected” 422, repeating the key replays that 422. If you change the parcel (for example a different delivery_method), use a new key.
  • After a 400, or a “no route” or validation 422, nothing was stored. Fix the request and retry with the same key.
  • The batch endpoint does not support idempotency keys.

For a 502 or an unknown outcome, retry after 5 s, 30 s, 2 min and 10 min, then hand the parcel to a human queue. In a web request or job worker, enqueue the retries rather than sleeping inline. Never retry a 4xx unchanged.