# ShipmentTrackingAPI > A free HTTP API that tracks USPS, UPS, FedEx and DHL packages. One endpoint answers all four carriers with the same nine statuses. 1,000 lookups a month per account, no credit card. ShipmentTrackingAPI is not affiliated with, endorsed by or sponsored by USPS, UPS, FedEx or DHL, and they do not provide or approve this data. A status can be late, missing or wrong. The API in brief: - Base URL: `https://api.shipmenttrackingapi.com`. JSON over HTTPS. - Key: `Authorization: Bearer ` on `POST /v1/track`. Keys start with `sta_` and are free: sign in with Google at https://console.shipmenttrackingapi.com. One key per account. Keep the key on a server. - Request: `POST /v1/track` with the JSON body `{"carrier": "ups", "trackingNumbers": ["YOUR_TRACKING_NUMBER"]}`. - `carrier` is one of `usps`, `ups`, `fedex`, `dhl`, in lowercase. One carrier per request. The API does not work out the carrier from a number. - `trackingNumbers` is always an array, even for one number. Each number is 6 to 40 letters and digits with the spaces removed, and no number appears twice. USPS takes 35 at once; UPS, FedEx and DHL take 1. - Response: HTTP 200 with `{"success": true, "data": {"carrier": "ups", "results": [...]}}`. `results` has one entry for each number, in the order sent. - A result is `{trackingNumber, status, events, estimatedDelivery?, lastUpdated?}`, or `{trackingNumber, error: {code, message}}` when that one number has no answer. The HTTP status is 200 either way. - An event is `{timestamp, status, description, location?, timeUnknown?}`. Events are oldest first. `timestamp` is ISO 8601; `description` is the carrier's own words. - `status` is one of nine words: `pending`, `in_transit`, `out_for_delivery`, `delivered`, `failed`, `returned`, `cancelled`, `lost`, `unknown`. - `GET /v1/carriers` needs no key and returns `{id, name, website, maxTrackingNumbers}` for each carrier. - A failed request has an HTTP error status and the body `{"success": false, "error": {"code": "...", "message": "..."}}`. Switch on `code`. - Limits: 1,000 lookups a month and 30 requests a minute per account; 1,000 lookups a month per IP address, across every key. The month is the calendar month in UTC. - One request is one lookup, however many numbers it carries. It counts when at least one number comes back with tracking data or `TRACKING_NOT_FOUND`. Requests rejected with a 400, 429 or 503 are not counted. - A lookup is not instant: in our tests a USPS request took 24 to 28 seconds. Set the HTTP client timeout to 60 seconds. A 503 was not counted and can be sent again. Send requests one after another, not all at once. - There are no webhooks. To follow a package, ask again later. The nine statuses: - `pending`: A label exists, but the carrier does not have the package yet. - `in_transit`: Moving through the carrier’s network. Also a missed delivery that can still be picked up or redelivered. - `out_for_delivery`: On the vehicle for the final delivery. - `delivered`: Handed over at the destination. - `failed`: The delivery could not be completed. - `returned`: Going back to the sender, or already back. - `cancelled`: The shipment was cancelled before delivery. - `lost`: The carrier reports the package missing. - `unknown`: The carrier said something the table does not cover yet. Errors for one number, inside `results` with HTTP 200: - `TRACKING_NOT_FOUND`: The carrier has no record of that number. Makes the request count. - `INVALID_TRACKING_NUMBER`: Not a valid number for that carrier. Does not count. - `CARRIER_ERROR`: The carrier did not answer for that number. Send it again. Does not count. Errors for the whole request: - `INVALID_CARRIER` (400): The carrier is not one of usps, ups, fedex, dhl. - `INVALID_TRACKING_NUMBER` (400): A number is malformed, repeated, or not valid for that carrier. trackingNumbers is always an array. - `TOO_MANY_TRACKING_NUMBERS` (400): More numbers than the carrier takes in one request: 35 for USPS, 1 for the others. - `AUTH_MISSING_API_KEY` (401): There is no Authorization header. - `AUTH_INVALID_API_KEY` (401): The key does not exist. - `KEY_INACTIVE` (403): The key was turned off. - `NOT_FOUND` (404): There is no such endpoint. Check the path and the method. - `RATE_LIMIT_EXCEEDED` (429): More than 30 requests in a minute. Retry-After says when to come back. - `MONTHLY_QUOTA_EXCEEDED` (429): The account has used its 1,000 lookups for the month. - `IP_QUOTA_EXCEEDED` (429): The IP address has used its 1,000 lookups for the month, across every key. - `INTERNAL_ERROR` (500): Something unexpected on our side. Try again. - `SERVICE_UNAVAILABLE` (503): Something on our side. Try again. Response headers on `POST /v1/track`: - `X-RateLimit-Limit-Minute`: Requests your account can send in one minute. - `X-RateLimit-Remaining-Minute`: Requests left in the current minute. - `X-RateLimit-Limit-Month`: Lookups your account has for the month. - `X-RateLimit-Remaining-Month`: Lookups left this month. - `Retry-After`: Only with RATE_LIMIT_EXCEEDED: the seconds until the next minute starts. ## Docs - [API documentation](https://shipmenttrackingapi.com/docs): The full reference: the key, the request and the response, the nine statuses, errors, limits and retries, with code in cURL, JavaScript, Python, PHP and Go. - [OpenAPI description](https://shipmenttrackingapi.com/openapi.json): OpenAPI 3.1 for both endpoints. - [Playground](https://shipmenttrackingapi.com/playground): Send real requests from the browser, with or without a key. ## Usage notes - [Track 35 USPS packages in one request](https://shipmenttrackingapi.com/blog/bulk-usps-tracking-api): A USPS request carries up to 35 tracking numbers and still counts as one lookup. This note covers the request, the order of the results, partial failures and lists longer than 35. ## Optional - [Terms of service](https://shipmenttrackingapi.com/terms): The conditions for using the service, including acceptable use. - [Privacy policy](https://shipmenttrackingapi.com/privacy): What is collected and how long it is kept.