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.
| Status | Meaning |
|---|---|
400 | The request could not be read as sent — for example an X-SubAccount-Id header that is not a GUID. |
401 | The credentials presented for the operation cannot authorize it: malformed, expired, or revoked. |
403 | Authenticated, but not with an Open API integration token. Sent without a body — there is nothing to parse. |
404 | The sub-account given in X-SubAccount-Id does not exist or belongs to another account. |
409 | The sub-account given in X-SubAccount-Id is not active. |
415 | The request body was not sent as application/json. Only the operations that send a body — every POST and PATCH — can answer this. |
500 | The 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.
| Operation | What 422 means | What to do |
|---|---|---|
CreateOrder | The addresses were readable but not usable — an address failed validation | Correct the address and send a corrected create |
GetShipmentRates | The Shipment is missing the address or parcel information rating needs | Correct the Shipment or Order data before requesting Rates again |
CreateShipmentLabel | Correctable purchase input, including a Rate that is no longer usable | For a stale Rate, get new Rates and select a new rate.id; otherwise correct the request |
CreateOrderV2 | Invalid Order input, or the carrier rejected the Shipment for a reason you can correct | Correct 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
- Authentication Errors
- Validation Errors
- Order State Errors
- Rate Errors
- Label Errors — purchase, binary download, and cancellation
- Create an Order with Rates documents its own per-shipment error array; it is not a separate error category here
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.