openapi: 3.0.3 info: title: Parcels API version: 1.0.0 description: 'Track packages and shipments worldwide through the Parcels API. Tracking is asynchronous: create a tracking request, then poll by UUID until the result is complete. Use the Mock API server in this documentation to try the flow without an API key or account limits.' contact: name: Parcels url: https://parcelsapp.com email: hello@parcelsapp.com servers: - url: https://parcelsapp.com/api/v3 description: Live API tags: - name: Account description: Subscription usage and account limits - name: Tracking description: Create tracking requests and read results - name: Webhooks description: Callbacks sent to your `webhookUrl` when tracking progresses or completes. Pass `webhookUrl` in `POST /shipments/tracking`; Parcels sends JSON `POST` requests to that URL. paths: /account: get: operationId: getAccountUsage summary: Check account usage description: Returns the current subscription plan, shipment limit, usage counter, and reset date. On the Mock API server this endpoint returns a fixed demo account and does not require `apiKey`. tags: - Account parameters: - name: apiKey in: query required: true description: API key from the Parcels dashboard. Not required on the Mock API server. schema: type: string example: pk_live_your_api_key responses: '200': description: Account information or API error. content: application/json: schema: oneOf: - $ref: '#/components/schemas/AccountInfo' - $ref: '#/components/schemas/ErrorResponse' examples: success: summary: Account usage value: plan: Pro limit: 3000 current: 142 resetDate: '2026-08-01T00:00:00.000Z' missingApiKey: summary: Missing API key value: error: MISSING_API_KEY description: No .apiKey found invalidApiKey: summary: Invalid API key value: error: INVALID_API_KEY x-codeSamples: - lang: curl label: cURL source: curl "https://parcelsapp.com/api/v3/account?apiKey=" - lang: JavaScript label: JavaScript source: 'const response = await fetch(''https://parcelsapp.com/api/v3/account?apiKey='' + encodeURIComponent(apiKey)); console.log(await response.json());' - lang: Python label: Python source: 'import requests response = requests.get(''https://parcelsapp.com/api/v3/account'', params={''apiKey'': api_key}) print(response.json())' - lang: PHP label: PHP source: '' /shipments/tracking: post: operationId: createTrackingRequest summary: Create tracking request description: 'Creates an asynchronous tracking request. The response usually contains a temporary `uuid`; use that UUID with `GET /shipments/tracking` to read results, or pass `webhookUrl` to receive callbacks. Cached results can be returned immediately in the `shipments` array: - If every requested shipment is already cached, the API returns `done: true`, `fromCache: true`, and populated `shipments` immediately. In this case there may be no `uuid`, because no new tracking request is needed. - If only some shipments are cached, the API returns a `uuid`, `fromCache: true`, and the cached subset in `shipments`. Continue polling by `uuid` or wait for webhook callbacks for the remaining shipments. - If nothing is cached yet, the API returns a `uuid` and an empty `shipments` array while tracking continues in the background.' tags: - Tracking requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/TrackingRequest' examples: usps: summary: USPS demo shipment value: apiKey: language: en shipments: - trackingId: '9400111206213785678901' destinationCountry: United States webhook: summary: Tracking request with webhook value: apiKey: language: en webhookUrl: https://example.com/parcels-webhook shipments: - trackingId: '9400111206213785678901' destinationCountry: United States description: Parcels will send webhook events to `webhookUrl` as shipments finish and when the full batch is complete. responses: '200': description: Tracking request, cached tracking result, or API error. Business errors are returned as JSON with an `error` field. content: application/json: schema: oneOf: - $ref: '#/components/schemas/TrackingResult' - $ref: '#/components/schemas/ErrorResponse' examples: created: summary: Request created value: uuid: demo_usps_delivered shipments: [] cached: summary: Completed from cache value: done: true fromCache: true shipments: - trackingId: '9400111206213785678901' status: delivered origin: United States destination: United States originCode: US destinationCode: US states: - state: Delivered, In/At Mailbox location: Austin, TX, United States date: '2026-06-25T18:42:00.000Z' carrier: 0 services: - slug: usps name: USPS detectedCarrier: slug: usps name: USPS description: All requested shipments were cached, so the response is already complete and no polling is needed. unconfirmedAccount: summary: Account not confirmed value: error: UNCONFIRMED_ACCOUNT description: Account email is not confirmed. confirmationUrl: https://parcelsapp.com/api/v3/account/confirm/user%40example.com/0123456789abcdef confirmationExpiresAt: '2026-07-08T12:00:00.000Z' invalidWebhookUrl: summary: Invalid webhook URL value: error: INVALID_WEBHOOK_URL description: HTTPS required for webhook URLs in production partialCached: summary: Partially completed from cache description: One or more shipments were returned immediately from cache; use `uuid` to read or receive the remaining results. value: uuid: demo_mixed_cache fromCache: true shipments: - trackingId: '9400111206213785678901' status: delivered origin: United States destination: United States originCode: US destinationCode: US states: - state: Delivered, In/At Mailbox location: Austin, TX, United States date: '2026-06-25T18:42:00.000Z' carrier: 0 - state: Arrived at Post Office location: Austin, TX, United States date: '2026-06-25T13:17:00.000Z' carrier: 0 services: - slug: usps name: USPS detectedCarrier: slug: usps name: USPS x-codeSamples: - lang: curl label: cURL source: "curl -X POST \"https://parcelsapp.com/api/v3/shipments/tracking\" \\\n -H \"Content-Type:\ \ application/json\" \\\n -d '{\"apiKey\":\"\",\"language\":\"en\",\"shipments\"\ :[{\"trackingId\":\"9400111206213785678901\",\"destinationCountry\":\"United States\"}]}'" - lang: JavaScript label: JavaScript source: "const response = await fetch('https://parcelsapp.com/api/v3/shipments/tracking', {\n\ \ method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n\ \ apiKey,\n language: 'en',\n shipments: [{ trackingId: '9400111206213785678901', destinationCountry:\ \ 'United States' }]\n })\n});\n\nconsole.log(await response.json());" - lang: Python label: Python source: "import requests\n\nresponse = requests.post('https://parcelsapp.com/api/v3/shipments/tracking',\ \ json={\n 'apiKey': api_key,\n 'language': 'en',\n 'shipments': [{'trackingId': '9400111206213785678901',\ \ 'destinationCountry': 'United States'}],\n})\nprint(response.json())" - lang: PHP label: PHP source: " $apiKey,\n 'language' => 'en',\n 'shipments'\ \ => [[\n 'trackingId' => '9400111206213785678901',\n 'destinationCountry' => 'United\ \ States'\n ]]\n]);\n\n$context = stream_context_create([\n 'http' => [\n 'method' => 'POST',\n\ \ 'header' => \"Content-Type: application/json\\r\\n\",\n 'content' => $payload\n ]\n\ ]);\n\n$response = file_get_contents('https://parcelsapp.com/api/v3/shipments/tracking', false,\ \ $context);\nprint_r(json_decode($response, true));\n?>" - lang: curl label: cURL with webhook source: "curl -X POST \"https://parcelsapp.com/api/v3/shipments/tracking\" \\\n -H \"Content-Type:\ \ application/json\" \\\n -d '{\"apiKey\":\"\",\"language\":\"en\",\"webhookUrl\"\ :\"https://example.com/parcels-webhook\",\"shipments\":[{\"trackingId\":\"9400111206213785678901\"\ ,\"destinationCountry\":\"United States\"}]}'" - lang: JavaScript label: JavaScript with webhook source: "const response = await fetch('https://parcelsapp.com/api/v3/shipments/tracking', {\n\ \ method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n\ \ apiKey,\n language: 'en',\n webhookUrl: 'https://example.com/parcels-webhook',\n\ \ shipments: [{ trackingId: '9400111206213785678901', destinationCountry: 'United States'\ \ }]\n })\n});\n\nconst result = await response.json();\nif (result.done) {\n console.log('Complete\ \ from cache', result.shipments);\n} else {\n console.log('Tracking request UUID', result.uuid,\ \ 'cached results', result.shipments);\n}" get: operationId: readTrackingResults summary: Read tracking results description: Polls a tracking request by UUID. Keep polling every few seconds until `done` is `true`. Do not pass a tracking number to this endpoint; use only the UUID returned by `POST /shipments/tracking`. tags: - Tracking parameters: - name: uuid in: query required: true description: Temporary tracking request UUID returned by create tracking request. schema: type: string example: demo_usps_delivered - name: apiKey in: query required: true description: API key from the Parcels dashboard. Not required on the Mock API server. schema: type: string example: pk_live_your_api_key responses: '200': description: Tracking result or API error. content: application/json: schema: oneOf: - $ref: '#/components/schemas/TrackingResult' - $ref: '#/components/schemas/ErrorResponse' examples: complete: summary: Completed tracking result value: uuid: demo_usps_delivered done: true shipments: - trackingId: '9400111206213785678901' status: delivered origin: United States destination: United States originCode: US destinationCode: US states: - state: Delivered, In/At Mailbox location: Austin, TX, United States date: '2026-06-25T18:42:00.000Z' carrier: 0 services: - slug: usps name: USPS detectedCarrier: slug: usps name: USPS pending: summary: Still running value: uuid: demo_usps_delivered done: false shipments: [] missingUuid: summary: Missing UUID value: error: MISSING_UUID description: No .uuid found notFound: summary: Request not found value: uuid: expired_uuid parcels: [] error: NO_REQUEST_FOUND x-codeSamples: - lang: curl label: cURL source: curl "https://parcelsapp.com/api/v3/shipments/tracking?uuid=&apiKey=" - lang: JavaScript label: JavaScript source: 'const response = await fetch(`https://parcelsapp.com/api/v3/shipments/tracking?uuid=${encodeURIComponent(uuid)}&apiKey=${encodeURIComponent(apiKey)}`); console.log(await response.json());' - lang: Python label: Python source: "import requests\n\nresponse = requests.get('https://parcelsapp.com/api/v3/shipments/tracking',\ \ params={\n 'uuid': uuid,\n 'apiKey': api_key,\n})\nprint(response.json())" - lang: PHP label: PHP source: '' /parcels-webhook: post: operationId: receiveTrackingWebhook summary: Receive tracking webhook description: 'This is the endpoint that you host, not a Parcels API endpoint. Provide its URL as `webhookUrl` when creating a tracking request. Parcels sends `POST` requests with `Content-Type: application/json`. Return any 2xx response quickly after accepting the payload. Network errors, timeouts, and HTTP 4xx/5xx responses are treated as failed deliveries and may be retried. Typical flow: create tracking with `webhookUrl`, save the returned `uuid`, then match incoming webhook events by `uuid`. For batches, expect zero or more `shipment_completed` events followed by one `batch_completed` event when all non-cached shipments finish. If the create response included cached shipments, keep those from the create response; webhook payloads cover the newly tracked remainder.' tags: - Webhooks servers: - url: https://example.com description: Your application server requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/WebhookPayload' examples: shipmentCompleted: summary: Single shipment completed value: event: shipment_completed uuid: demo_mixed_cache timestamp: '2026-07-07T06:45:00.000Z' done: false shipment: trackingId: '9400111206213785678901' status: delivered origin: United States destination: United States originCode: US destinationCode: US states: - state: Delivered, In/At Mailbox location: Austin, TX, United States date: '2026-06-25T18:42:00.000Z' carrier: 0 - state: Arrived at Post Office location: Austin, TX, United States date: '2026-06-25T13:17:00.000Z' carrier: 0 services: - slug: usps name: USPS detectedCarrier: slug: usps name: USPS batchCompleted: summary: Batch completed value: event: batch_completed uuid: demo_mixed_cache timestamp: '2026-07-07T06:46:10.000Z' done: true shipments: - trackingId: '9400111206213785678901' status: delivered origin: United States destination: United States originCode: US destinationCode: US states: - state: Delivered, In/At Mailbox location: Austin, TX, United States date: '2026-06-25T18:42:00.000Z' carrier: 0 - state: Arrived at Post Office location: Austin, TX, United States date: '2026-06-25T13:17:00.000Z' carrier: 0 services: - slug: usps name: USPS detectedCarrier: slug: usps name: USPS trackingError: summary: Tracking workflow error value: event: tracking_error uuid: demo_mixed_cache timestamp: '2026-07-07T06:46:10.000Z' done: true error: TRACKING_ERROR message: Failed to save tracking result for 9400111206213785678901 responses: '200': description: Return any 2xx status after accepting the webhook payload. The response body is ignored. x-codeSamples: - lang: JavaScript label: Express source: "app.post('/parcels-webhook', express.json(), (req, res) => {\n const event = req.body;\n\ \n if (event.event === 'shipment_completed') {\n console.log('Shipment finished', event.uuid,\ \ event.shipment.trackingId);\n }\n\n if (event.event === 'batch_completed') {\n console.log('Batch\ \ finished', event.uuid, event.shipments.length);\n }\n\n res.sendStatus(204);\n});" - lang: Python label: Flask source: "from flask import Flask, request\n\napp = Flask(__name__)\n\n@app.post('/parcels-webhook')\n\ def parcels_webhook():\n event = request.get_json()\n if event['event'] == 'shipment_completed':\n\ \ print('Shipment finished', event['uuid'], event['shipment']['trackingId'])\n if\ \ event['event'] == 'batch_completed':\n print('Batch finished', event['uuid'], len(event.get('shipments',\ \ [])))\n return ('', 204)" - lang: PHP label: PHP source: "" components: schemas: AccountInfo: type: object required: - plan - limit - current - resetDate properties: plan: type: string description: Current subscription plan name. example: Pro limit: type: integer description: Shipment limit for the current period. example: 3000 current: type: integer description: Shipments already used in the current period. example: 142 resetDate: type: string format: date-time description: UTC date when the usage counter resets. example: '2026-08-01T00:00:00.000Z' TrackingRequest: type: object required: - apiKey - shipments properties: apiKey: type: string description: API key from the Parcels dashboard. Not required on the Mock API server. example: pk_live_your_api_key language: type: string description: Two-letter language code for localized countries and events. Defaults to `en`. example: en shipments: type: array minItems: 1 description: Shipments to track. items: $ref: '#/components/schemas/TrackingItem' webhookUrl: type: string format: uri description: Optional public HTTPS endpoint that receives tracking callbacks. In production it must use HTTPS and cannot point to localhost or private networks. Parcels sends `POST` requests with a JSON `WebhookPayload` body. example: https://example.com/parcels-webhook TrackingItem: type: object required: - trackingId properties: trackingId: type: string description: Carrier tracking number. example: '9400111206213785678901' destinationCountry: type: string description: Full English destination country name. Required for most non-AWB parcel numbers. example: United States country: type: string description: Alias for `destinationCountry`. example: United States zipcode: type: string description: Postal code, if a carrier requires it. example: '78701' slugs: type: array description: Optional carrier slugs to force instead of auto-detection. items: type: string example: - usps TrackingResult: type: object properties: uuid: type: string description: Temporary tracking request UUID. It can be absent when the create response is fully served from cache and `done` is `true`. example: demo_usps_delivered done: type: boolean description: True when all shipments in the request have finished tracking. example: true fromCache: type: boolean description: 'True when the response includes cached tracking data. A create response can include both `uuid` and `fromCache: true` when only part of the request was cached.' example: true shipments: type: array description: Finished shipment results. On create, this can contain immediately available cached results; use `uuid` or webhooks for any remaining shipments. items: $ref: '#/components/schemas/Shipment' Shipment: type: object properties: trackingId: type: string description: Tracking number. example: '9400111206213785678901' status: type: string description: Current normalized shipment status. enum: - transit - arrived - pickup - delivered - returned - archive example: delivered origin: type: string description: Localized origin country. example: United States destination: type: string description: Localized destination country. example: United States originCode: type: string description: Two-letter origin country code. example: US destinationCode: type: string description: Two-letter destination country code. example: US states: type: array description: Tracking events in reverse chronological order. items: $ref: '#/components/schemas/Event' services: type: array description: Carriers queried for this tracking number. items: $ref: '#/components/schemas/Carrier' detectedCarrier: $ref: '#/components/schemas/Carrier' detected: type: array description: Indexes into `services` where tracking events were found. items: type: integer attributes: type: array description: Additional carrier metadata such as origin, destination, weight, or references. items: $ref: '#/components/schemas/Attribute' externalTracking: type: array description: Direct carrier tracking links. items: $ref: '#/components/schemas/OutgoingLink' Event: type: object properties: state: type: string description: Human-readable tracking event text. example: Delivered, In/At Mailbox location: type: string description: Event location. example: Austin, TX, United States date: type: string format: date-time description: UTC event timestamp. example: '2026-06-25T18:42:00.000Z' carrier: type: integer description: Index of the reporting carrier in the `services` array. example: 0 Carrier: type: object properties: slug: type: string description: Carrier identifier. example: usps name: type: string description: Carrier display name. example: USPS Attribute: type: object properties: l: type: string description: Attribute label. example: origin n: type: string description: Optional display name. example: Weight val: type: string description: Attribute value. example: United States code: type: string description: Optional code value. example: US OutgoingLink: type: object properties: slug: type: string example: usps url: type: string format: uri example: https://tools.usps.com/go/TrackConfirmAction?tLabels=9400111206213785678901 trackingId: type: string example: '9400111206213785678901' method: type: string enum: - GET - POST example: GET WebhookPayload: type: object description: JSON body Parcels sends to the `webhookUrl` from `POST /shipments/tracking`. A `shipment_completed` event contains `shipment`; a `batch_completed` event contains `shipments`; a `tracking_error` event contains `error` and `message`. properties: event: type: string enum: - shipment_completed - batch_completed - tracking_error example: batch_completed description: Webhook event type. `shipment_completed` means one newly tracked shipment finished. `batch_completed` means all newly tracked, non-cached shipments for the UUID finished. uuid: type: string example: demo_usps_delivered description: Tracking request UUID returned by `POST /shipments/tracking`. timestamp: type: string format: date-time example: '2026-06-25T18:43:00.000Z' done: type: boolean example: true description: '`true` when the whole tracking request is complete. Individual shipment events usually have `done: false`; the final batch event has `done: true`.' shipment: allOf: - $ref: '#/components/schemas/Shipment' description: 'Present on `shipment_completed`: the finished newly tracked shipment result.' shipments: type: array items: $ref: '#/components/schemas/Shipment' description: 'Present on `batch_completed`: finished results for the newly tracked, non-cached shipments in this UUID. Cached shipments, if any, were already returned in the create response.' error: type: string example: TRACKING_ERROR description: 'Present on `tracking_error`: stable error type.' message: type: string example: Failed to save tracking result description: 'Present on `tracking_error`: human-readable error details.' ErrorResponse: type: object required: - error properties: error: type: string description: Stable error code. enum: - MISSING_API_KEY - INVALID_API_KEY - UNCONFIRMED_ACCOUNT - SUBSCRIPTION_NOT_FOUND - SUBSCRIPTION_INACTIVE - SUBSCRIPTION_LIMIT_REACHED - INVALID_SUBSCRIPTION_STATE - INVALID_PARAMS - NO_SHIPMENTS - BUSY - INVALID_WEBHOOK_URL - MISSING_UUID - TIMEDOUT - NO_REQUEST_FOUND - SERVER_ERROR - FORBIDDEN example: INVALID_API_KEY description: type: string description: Human-readable error details when available. example: No .apiKey found uuid: type: string description: Tracking request UUID, when relevant. example: demo_usps_delivered confirmationUrl: type: string format: uri description: One-time account confirmation URL returned with `UNCONFIRMED_ACCOUNT`. example: https://parcelsapp.com/api/v3/account/confirm/user%40example.com/0123456789abcdef confirmationExpiresAt: type: string format: date-time description: Expiration time for `confirmationUrl`. example: '2026-07-08T12:00:00.000Z' x-tagGroups: - name: Guide tags: - Account - Tracking - Webhooks