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=<YOUR_API_KEY>"
- 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: '<?php
$response = file_get_contents(''https://parcelsapp.com/api/v3/account?apiKey='' . urlencode($apiKey));
print_r(json_decode($response, true));
?>'
/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: <YOUR_API_KEY>
language: en
shipments:
- trackingId: '9400111206213785678901'
destinationCountry: United States
webhook:
summary: Tracking request with webhook
value:
apiKey: <YOUR_API_KEY>
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\":\"<YOUR_API_KEY>\",\"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: "<?php\n$payload = json_encode([\n 'apiKey' => $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\":\"<YOUR_API_KEY>\",\"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=<UUID>&apiKey=<YOUR_API_KEY>"
- 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: '<?php
$url = ''https://parcelsapp.com/api/v3/shipments/tracking?uuid='' . urlencode($uuid) . ''&apiKey=''
. urlencode($apiKey);
$response = file_get_contents($url);
print_r(json_decode($response, true));
?>'
/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: "<?php\n$event = json_decode(file_get_contents('php://input'), true);\n\nif ($event['event']\
\ === 'shipment_completed') {\n error_log('Shipment finished ' . $event['uuid'] . ' ' . $event['shipment']['trackingId']);\n\
}\n\nif ($event['event'] === 'batch_completed') {\n error_log('Batch finished ' . $event['uuid']\
\ . ' ' . count($event['shipments'] ?? []));\n}\n\nhttp_response_code(204);\n?>"
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