Errors

Every error has the same shape:

{ "error": { "code": "not_found", "message": "UPS returned no track details for that number" } }

Branch on code; message is for people and can change.

HTTPcodeWhenRetry?
400bad_requestNo numbers, more than 100, or more than 35 for one carrierNo
400unknown_carrierNo carrier's format matches the number (or its check digit is wrong), or an unsupported carrierFix the number or pass carrier
401unauthorizedMissing, wrong or revoked key, or a disabled accountNo
402payment_requiredNo key, and no free lookup left (or a request for several numbers)Pay with MPP; see Pay per request
402credits_usedA credit key doesn't have enough credits left for the requestPay again without the key
404not_foundThe carrier has no record of the number, or no such key or routeLater; new labels can take a while to appear
429free_lookup_usedNo key, and this IP's free lookup for today is used (when pay per request is off)Sign up for a key, or after 00:00 UTC
429rate_limitedDaily limit reached, or the carrier is throttling (Spee-Dee)After 00:00 UTC, or in a minute for Spee-Dee
500internal_errorUnexpected failureYes
500not_configuredThe service is misconfiguredYes
502carrier_errorThe carrier failed or blocked the lookupYes

On GET /v1/track?numbers=, per-number errors (not_found, unknown_carrier, carrier_error, rate_limited) are returned inside results with a 200; only a malformed request, a bad key or the daily limit fails the whole call.

Over MCP, a failed tool call returns this same object as a result with isError: true.