Skip to main content

Error Handling

Use error categories to decide what to inspect and what to do next.

Error envelope​

Every JSON operation (all of Orders v1, Rates, label purchase, and cancellation) returns the same failure shape:

{
"success": false,
"data": null,
"errors": [
{ "code": "...", "message": "...", "metaData": null }
]
}

Reading code​

code is the machine-readable identifier and the thing to branch on; message is readable prose whose wording may change, so do not parse it. Codes are dotted and namespaced by area — Validation.*, Rate.*, Label.*, Shipment.*, Auth.* — and a code, once published for a condition, is stable.

There is no exhaustive list of code values, and the contract publishes none. Some codes are composed at request time from the field they concern, so the set is not finite: a missing field produces Validation.<FieldName>Required, an invalid one Validation.<FieldName>Invalid, and so on. Write clients that match the codes they handle and fall back on the HTTP status for everything else, rather than switching over a closed set. The per-category pages below name the codes worth handling explicitly.

POST /api/v2/orders is the one exception — its data contains order, shipmentRates, status, and operation-specific errors. Download a Label answers with a flat error object instead of this envelope on every failure but 401 — inspect its HTTP status and Content-Type before parsing. See API Conventions for the full shape reference.

Statuses any operation can return​

These are decided before an operation's own logic runs, so every operation can answer with them. Each operation documents its own statuses on top of these in the API Reference.

StatusMeaning
400The request could not be read as sent — for example an X-SubAccount-Id header that is not a GUID.
401The credentials presented for the operation cannot authorize it: malformed, expired, or revoked.
403Authenticated, but not with an Open API integration token. Sent without a body — there is nothing to parse.
404The sub-account given in X-SubAccount-Id does not exist or belongs to another account.
409The sub-account given in X-SubAccount-Id is not active.
415The request body was not sent as application/json. Only the operations that send a body — every POST and PATCH — can answer this.
500The request could not be completed because of an unexpected server-side failure.

401 is about a credential that was evaluated, not about a missing field. Most operations carry their credential in the Authorization header. The three token operations carry it in the request body instead — apiKey on token exchange, refreshToken on refresh and revoke — and a body field is validated before any authentication is evaluated. On those operations a missing, empty, or whitespace-only credential field answers 422, not 401. The per-category pages below carry the exact split, and the per-operation pages remain authoritative for the statuses an operation returns.

The two anonymous token operations (refresh and revoke) never look a sub-account up, so they answer 400 for a malformed header but never 404 or 409 for one.

503 is not in this table: it is not raised by the shared pipeline. It comes only from operations that call a carrier — rates, label purchase and download (Label Errors), and Create an Order with Rates. It means the carrier, not Mailhub, is unavailable: nothing in the request is wrong, so correcting it changes nothing and only a later retry can succeed.

422 means something different on each operation​

422 is the status most likely to be mishandled by a single generic branch, because what it says and what it asks you to do both depend on the operation that returned it.

OperationWhat 422 meansWhat to do
CreateOrderThe addresses were readable but not usable — an address failed validationCorrect the address and send a corrected create
GetShipmentRatesThe Shipment is missing the address or parcel information rating needsCorrect the Shipment or Order data before requesting Rates again
CreateShipmentLabelCorrectable purchase input, including a Rate that is no longer usableFor a stale Rate, get new Rates and select a new rate.id; otherwise correct the request
CreateOrderV2Invalid Order input, or the carrier rejected the Shipment for a reason you can correctCorrect the reported input. Replay the whole request only under the original Idempotency-Key — see below

On CreateOrder, a 422 is an address-validation outcome, not a missing-structure one. A request that omits fromAddress, toAddress, shipments, or a shipment's parcel is rejected with 400 before validation reaches the addresses. Branch the two apart: 400 means the request could not be read as sent, 422 means it was read and an address was rejected.

On CreateOrderV2, a 422 does not tell you whether an Order exists. When no created Shipment returned a Rate, an Order may already have been created before the response was returned, and the response carries no orderId for it. Send an Idempotency-Key header on the original request and you can find out without risking a duplicate: repeating the same request with the same key returns the Order the first attempt created rather than creating a second one. Without a key the outcome stays ambiguous — do not replay the create, and do not assume an identifier you can read back. See Retries and Ambiguous Outcomes.

Branch on the operation and the returned code, not on the status alone. A 503 on the rating path is the contrasting case — the carrier is temporarily unavailable and nothing in the request needs correcting.

Recovery model​

Inspect, correct, retry cautiously, or stop.

  • Inspect the operation, environment, identifiers, and sanitized failure summary.
  • Correct missing or invalid business data before resubmitting.
  • Retry only when the documented operation behavior makes it appropriate.
  • Stop repeated attempts after an ambiguous state-changing result.
  • Record sanitized context for your application's operational process; the API does not define a recovery sequence for an ambiguous state-changing result.

Categories​

The public API does not currently declare a 429 rate-limit response or a specific requests-per-minute quota for any operation — see API Conventions.

Authentication recovery​

When an access token is no longer usable, attempt one refresh and retry the original request once only after refresh succeeds. If refresh fails, stop and authenticate again. See Authentication and Retries and Ambiguous Outcomes.

Next steps​

  • Choose the matching error category above and correct the reported input or authentication problem before retrying.
  • Review Retries and Ambiguous Outcomes before repeating a state-changing request.
  • Use Diagnostics to record safe, actionable evidence for an application-owned investigation.
  • Continue to Production Readiness before go-live.
  • Complete the Go-Live Checklist before relying on a production integration.