Errors
Standard HTTP status codes with structured error bodies. Idempotency keys make safe retries; rate limits trigger 429 with a Retry-After header.
Error body shape
Every 4xx and 5xx response has a JSON body with this shape. The code field is machine-readable and stable across API versions; the error field is human-readable and may be rephrased over time.
{
"code": "invalid_request",
"error": "Invalid request body",
"issues": [
{ "path": "parcel.weightOz", "message": "Number must be greater than 0" }
]
}Two optional fields appear on certain responses:
field— single field path (dot-separated) for single-field errors (e.g.missing_field,invalid_field).issues[]— array of{ path, message }objects for multi-field validation failures.requiredScope— only on 403 responses; identifies the scope the token would need to call this endpoint.
HTTP status codes
- 200OKSuccessful synchronous operation.
- 201CreatedResource was synchronously created (webhook subscription, address verification result, etc.).
- 202AcceptedAsync operation accepted. Label is being purchased; subscribe to webhooks or poll for completion.
- 400Bad RequestRequest body wasn't valid JSON. Code: invalid_json. Field-level validation failures are 422, not 400.
- 401UnauthorizedMissing or malformed bearer token. Code: missing_authorization.
- 402Payment RequiredNo payment method on file. Code: no_payment_method. Add a card at /settings/billing and retry with the same idempotency key.
- 403ForbiddenToken lacks the scope this endpoint requires. Code: insufficient_scope. Body includes requiredScope.
- 404Not FoundResource id doesn't exist or doesn't belong to your account. Code: not_found.
- 409ConflictState conflict that prevents the operation - already revoked, refund in progress, duplicate webhook URL, etc.
- 422UnprocessableBody was valid JSON but semantically invalid. Codes: invalid_request, missing_field, invalid_field, no_rates, no_matching_service, invalid_event_type, subscription_limit, etc.
- 429Too Many RequestsRate limit exceeded. See the Retry-After header.
- 500Server ErrorSomething went wrong on our side. Safe to retry with the same idempotency key.
- 502Bad GatewayUpstream carrier API failed. Usually transient; retry.
- 503Service UnavailableWe're rate-limiting the upstream carrier on your behalf. See the Retry-After header.
Idempotency keys
Every mutating request that creates a resource takes an idempotencyKey in the body. A duplicate call with the same key + the same body returns the original response instead of creating a second resource.
Rules
- The key must be stable across retries. Use the customer order id, a UUID generated once at the start of your buy flow, or anything else you can regenerate from inputs.
- The key is scoped to your account + the endpoint. Calling
/labels/createwith keyorder-12345doesn't conflict with calling/refunds/createwith the same key. - A duplicate call with the same key + different body returns
409 idempotency_key_collision. This is a programmer error — you reused a key across distinct operations. - Keys are retained for 7 days. After that the dedup record is dropped; the same key can be reused for a new operation.
Don't mint UUIDs per-call
POST /labels/create with a fresh crypto.randomUUID() on every retry defeats idempotency — each retry gets a different key and creates a new label. Derive the key from a stable input (order id, shipment id, etc.) and pass it through every retry of the same logical operation.Retry semantics
Which errors are safe to retry, and how to retry them:
- 500 / 502 / 503 / 504: always safe to retry with the same idempotency key. Use exponential backoff with jitter — start at 500ms, double each attempt, cap at 30s, give up after 5 attempts.
- 429: retry after at least the duration in the
Retry-Afterheader. See Rate limits. - 402:retrying without fixing the underlying payment method just produces another 402. Surface the error to your operator + retry only after they've updated the card.
- 4xx generally: NOT safe to blindly retry — the request is malformed in some way and will fail again identically.
- Network timeouts / connection errors: safe to retry with the same idempotency key. The request may or may not have hit our servers; idempotency handles both cases.
Common error codes
Authentication + authorisation
missing_authorization— no Authorization header, or token doesn't match theshp_live_…format. Status: 401.insufficient_scope— token valid but missing the scope this endpoint requires. Body includesrequiredScope. Status: 403.
Request validation
invalid_json— body didn't parse as JSON at all. Status: 400.invalid_request— body parsed but failed schema validation. Body includesissues[]with field paths + messages. Status: 422.missing_field— a required field was missing. Body includes the field path. Status: 422.invalid_field— a field was present but semantically wrong (e.g. a 3-char country code, an unknown status filter, a US address without a state). Body includes the field path. Status: 422.
Labels
no_rates— carriers returned no rates for the supplied origin / destination / parcel. Status: 422.no_matching_service— rates came back but none matched theserviceCategory/carrier/servicehint. Response listsavailableServices[]. Status: 422.label_pending— onGET /labels/{id}/label.pdf, the rehosted PDF isn't ready yet. Retry in a few seconds. Status: 202.label_unavailable— the rehosted PDF object isn't in storage. Contact support with the label id. Status: 404.
Refunds
not_purchased— shipment isn't connected to a carrier label yet. Status: 409.refund_in_progress— a refund is already in flight on this label. Poll for the terminal state. Status: 409.already_voided— label is already in a void terminal state. Status: 409.ineligible_state— label state isn't in the refundable set (e.g. failed, pending). Status: 409.
Addresses
verifier_unavailable— upstream verifier is having a transient outage. Retry in a minute. Status: 503.verifier_not_configured— server-side configuration error (no upstream key). Contact support; retries won't help. Status: 500.verifier_error— verifier threw an unhandled error. Retry. Status: 502.
Webhooks
invalid_event_type— subscribing to an event type that doesn't exist. Body includesknownEvents[]. Status: 422.duplicate_endpoint— you already have an active subscription for this URL. Delete it or use a different URL. Status: 409.subscription_limit— 25 active subscriptions per account. Delete one or email support to raise the cap. Status: 422.
Billing
no_payment_method— no default card on file. Add one at/settings/billingand retry with the same idempotency key. Status: 402.