Access your Packy Tracking API dashboard.

Packy Tracking API (1.0.0)

Packy is a high-performance RESTful shipment tracking API and multicarrier tracking API designed to normalize delivery data across 1385+ carriers globally. Built for developers, e-commerce platforms, and logistics providers, it functions as a comprehensive package tracking API and parcel tracking API in a single integration.

Packy Tracking API offers a generous free tier (free package) for developers to start testing and tracking immediately with no credit card required.

Key features include carrier auto-detection, realtime updates via high-frequency webhooks, and unified delivery tracking API endpoints. Automate your post-purchase experience, reduce WISMO (Where Is My Order) tickets, and get normalized JSON status updates for any courier worldwide using our free plan or scalable commercial tiers.

How does your pricing work? What exactly is a "credit"?

1 credit = 1 unique tracking number.

Once you add a tracking number, that single credit covers the package's normal tracking lifecycle. This includes everything from the moment it's created, through transit, final delivery, potential returns (if things don't go as planned), and even a little extra buffer time afterward—just to ensure you have all the data you need.

Optional API operations can have their own price. In particular, every successful forced refresh (POST /v1/trackings/{tracking_number}/refresh) is charged separately at the current price of the tracking.refresh operation, even when the carrier reports no new events.

Parcel Statuses

Every parcel in the system is assigned a status that reflects the latest stage of its journey. As new tracking events arrive, the status is updated automatically. The possible statuses are:

Status Code Meaning
Awaiting pending Tracking is registered, but no carrier updates have been received yet.
Created create A shipping label/order has been created; the parcel is not yet moving.
Info Received info The carrier has received shipment information and is preparing to handle the parcel.
In Transit transit The parcel is on its way through the carrier's network.
Out for Delivery out-for-delivery The parcel is with a courier and scheduled for delivery today.
Delivered delivered The parcel has been successfully delivered to the recipient.
Failed Attempt fail A delivery attempt was made but was unsuccessful (e.g. recipient unavailable).
Exception exception A problem occurred during delivery that requires attention.
Cancelled cancel The shipment was cancelled.
Lost lost The parcel is reported as lost by the carrier.
Returning return The parcel is being returned to the sender.
Out for Delivery to Sender out-for-delivery-sender The returned parcel is out for delivery back to the original sender.
Delivered to Sender delivered-sender The returned parcel has been delivered back to the sender.
Tracking Ended end Tracking for this parcel has been closed; no further updates are expected.

Note: When a parcel has several events at once, the most significant status is shown (for example, Delivered takes precedence over In Transit).

MCP server

The same API is also exposed as an MCP (Model Context Protocol) server, so you can connect directly to a model or agent instead of writing HTTP calls yourself. The model acts on behalf of your account with exactly the rights of the regular Tracking API, and MCP is not billed separately — calls consume the same plan credits as the equivalent HTTP requests.

What Value
Channel URL https://mcp.parceltracking.app/mcp
Transport Streamable HTTP (POST)
Server name in the client packyapp-tracking-mcp
Dashboard https://lk.parceltracking.app
Access key Your account API key from the dashboard — the same key as for /v1

Two ways to authenticate

Method When to use How the model gets access
API key (Bearer) Cursor, Claude Desktop — any client where you can set request headers You paste the account API key into the client config
OAuth Claude Custom connector and similar hosted clients You sign in to the Packy dashboard in the browser; no key in the config

For the API-key method, authenticate every request with Authorization: Bearer <your-api-key>. The X-API-Key: <your-api-key> header is accepted as a fallback.

Every account has its own key. Do not share it in group chats or shared documents — if it leaks, deactivate it in the dashboard and issue a new one.

Cursor (API key)

Config file: project or user MCP settings (mcp.json).

{
  "mcpServers": {
    "packyapp-tracking-mcp": {
      "url": "https://mcp.parceltracking.app/mcp",
      "headers": {
        "Authorization": "Bearer <your-api-key>"
      }
    }
  }
}

Replace <your-api-key> with your account API key, then:

  1. Save mcp.json.
  2. Restart MCP / the agent the way Cursor requires.
  3. Confirm the Packy tool list appears in the client.

Claude Desktop (API key)

Config file:

OS Path
macOS ~/Library/Application Support/Claude/claude_desktop_config.json
Windows %APPDATA%\Claude\claude_desktop_config.json
{
  "mcpServers": {
    "packyapp-tracking-mcp": {
      "url": "https://mcp.parceltracking.app/mcp",
      "headers": {
        "Authorization": "Bearer <your-api-key>"
      }
    }
  }
}

Replace <your-api-key> the same way as above, then:

  1. Save claude_desktop_config.json.
  2. Fully quit and reopen Claude Desktop.
  3. Confirm the Packy channel tools are available.

Claude Custom connector (OAuth)

Prepare first, in the dashboard — not from the model client:

  1. Sign in to (or create) your account at https://lk.parceltracking.app.
  2. Create an API key and make sure it is active.
  3. Check that the key belongs to the account and plan you want the model to work under.

The OAuth connection runs under the key you pick: the model's calls and credits are attributed to it, it keeps working while that key is active, and it stops as soon as you deactivate the key. Without an account and at least one active key the browser sign-in can succeed while the model still ends up with no working tools.

Then connect:

  1. In Claude, add a Custom connector / MCP server with the URL https://mcp.parceltracking.app/mcp.
  2. Start the connection — Packy sign-in opens in the browser.
  3. Sign in to the dashboard. A short Continue authorization page then redirects you onward automatically; there is nothing to click. That link is single-use and expires after 2 minutes, so if the flow breaks, restart it from Claude instead of reopening the previous URL.
  4. On the Allow access? consent screen, choose the API key this connection will run under and press Allow. If the account has no active key, the screen says so and you cannot continue — go back to the preparation steps above.
  5. Return to Claude and confirm the status is Connected and the Packy tools are listed.

Access tokens are short-lived and refreshed automatically, so you do not need to reconnect periodically.

Available tools

The channel covers the core /v1 resources:

Tool Maps to
trackings_create POST /v1/trackings
trackings_list GET /v1/trackings
trackings_get GET /v1/trackings/{id}
trackings_delete DELETE /v1/trackings/{id}
couriers_list GET /v1/couriers
courier_detections_create POST /v1/courier-detections
webhooks_list GET /v1/webhooks
webhooks_create POST /v1/webhooks
webhooks_update PATCH /v1/webhooks/{id}
webhooks_delete DELETE /v1/webhooks/{id}

Credits are consumed exactly as they are over HTTP: a trackings_create call from the model costs the same credit as POST /v1/trackings, and a batch create spends one credit per tracking number in the batch. It is the only billable tool — the courier and webhook tools cost nothing, so the only calls worth running deliberately are creates.

Check that it works

Check Expected result
Tool list in the client The tools above appear
A read call such as couriers_list Succeeds within your plan's limits
Invalid or deactivated key The connection or the call is rejected with 401
Channel liveness (no key needed) GET https://mcp.parceltracking.app/health → {"status":"ok"}
Channel readiness (no key needed) GET https://mcp.parceltracking.app/ready → {"status":"ok","checks":{"api_gateway":"healthy"}}

Disconnect access

API-key connections. Deactivate or rotate the account API key in the dashboard, then remove or update the packyapp-tracking-mcp entry in the client config. Once the key is deactivated, the previous model connection stops working.

OAuth connections. Open Connected applications in the dashboard and revoke the application's access, then remove the connector in Claude. Deactivating the API key the connection runs under also stops it immediately.

Troubleshooting

Symptom What to do
No tool list appears Check the URL https://mcp.parceltracking.app/mcp, the server name packyapp-tracking-mcp, the key or a finished OAuth flow, then restart the client
401 Unauthorized Confirm the key is active in the dashboard; for OAuth, confirm the account has an active key and run the connection again
OAuth: Could not continue authorization Sign in with the same account, confirm it has an active key, and start the connection again from Claude — do not reuse the link from the previous attempt
Access must be cut right now Deactivate the key in the dashboard, then remove the entry or connector in the client

Trackings

Create, list, retrieve, refresh, and delete shipment trackings.

List the account's trackings

Returns a paginated list of the account's trackings.

Authorizations:
BearerAuthApiKeyHeader
query Parameters
page
integer >= 1
Default: 1

Page number.

limit
integer >= 1
Default: 20

Page size.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "meta": {
    }
}

Create tracking(s)

Creates one or more trackings. The body is either a single object or an array of objects. Up to 40 trackings can be created per request.

Specifying the courier is optional: if courier_code is omitted, the courier is detected automatically from the tracking number.

This operation consumes account credits.

Behavior depends on ?async:

  • async=true (default) — trackings are created and refreshed in the background; the response contains the created trackings without events yet.
  • async=false — legacy sync mode: polls the carrier first, then subscribes. One credit is charged per item in the request (including when courier_code is unknown). Re-tracking an existing number is allowed. Unknown courier_code returns invalid_courier in errors. The response includes normalized events and crawler couriers (with icon URLs) per created item.

The response is a {data, errors} envelope:

  • 201 Created — all items were created;
  • 207 Multi-Status — partial success (both data and errors present);
  • 422 Unprocessable Entity — nothing was created, only errors.
Authorizations:
BearerAuthApiKeyHeader
query Parameters
async
boolean
Default: true

true (default) — return immediately with a short summary (TrackingSummary). false — wait for crawler poll and return full TrackingDetail (Crawler Trackcode + id + courier_code).

Request Body schema: application/json
required
One of
tracking_number
required
string
courier_code
string

Courier code; if omitted, it is detected automatically.

Responses

Request samples

Content type
application/json
Example
{
  • "tracking_number": "9400111899562537683144",
  • "courier_code": "usps"
}

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "errors": [
    ]
}

Get a tracking (full Trackcode)

Returns crawler Trackcode JSON with subscription overlay (id, courier_code). Same crawler blocks as webhook (events, source, couriers, info, …).

Authorizations:
BearerAuthApiKeyHeader
path Parameters
id
required
string

Tracking identifier.

Responses

Response samples

Content type
application/json
{
  • "id": "2f8b1c4d5e6f7a8b9c0d1e2f3a4b5c6d",
  • "courier_code": "usps",
  • "trackcode": "9400111899562537683144",
  • "status": "transit",
  • "updatedAt": "2026-05-26 14:30:00",
  • "origin_country": "string",
  • "destination_country": "string",
  • "info": {
    },
  • "events": [
    ],
  • "source": [],
  • "couriers": []
}

Delete a tracking

Deletes a tracking.

Authorizations:
BearerAuthApiKeyHeader
path Parameters
id
required
string

Tracking identifier.

Responses

Response samples

Content type
application/json
{
  • "code": "unauthorized",
  • "message": "Invalid or missing API key"
}

Force-refresh an existing tracking

Immediately polls the carrier for a tracking that already belongs to the authenticated account and returns the freshly obtained TrackingDetail, including events and couriers, in the same response.

This operation does not create, subscribe, or re-create a tracking. If the tracking number does not belong to the account, the response is 404 Not Found. Use POST /v1/trackings first when you want to start tracking a new number.

Every successful response is charged at the current price configured for the tracking.refresh billing operation, even if the carrier reports no new events. A normal GET /v1/trackings/{id} only reads saved data and is billed according to its own operation; it does not trigger or charge a forced refresh.

Each tracking has a dedicated rate limit within an account. Exceeding it returns 429 for that tracking without blocking refreshes of other trackings, create, GET, or other API operations for the account.

Authorizations:
BearerAuthApiKeyHeader
path Parameters
tracking_number
required
string
Example: 9400111899562537683144

Tracking number of an existing tracking owned by the account.

Responses

Response samples

Content type
application/json
{
  • "id": "2f8b1c4d5e6f7a8b9c0d1e2f3a4b5c6d",
  • "courier_code": "usps",
  • "trackcode": "9400111899562537683144",
  • "status": "transit",
  • "updatedAt": "2026-05-26 14:30:00",
  • "origin_country": "string",
  • "destination_country": "string",
  • "info": {
    },
  • "events": [
    ],
  • "source": [],
  • "couriers": []
}

Couriers

List supported couriers and detect a courier by tracking number.

List supported couriers

Returns the list of supported couriers.

Authorizations:
BearerAuthApiKeyHeader

Responses

Response samples

Content type
application/json
[]

Detect courier by tracking number

Returns the list of possible couriers for the given tracking number.

Authorizations:
BearerAuthApiKeyHeader
Request Body schema: application/json
required
tracking_number
required
string

Responses

Request samples

Content type
application/json
{
  • "tracking_number": "MGRAE0003506539YQ"
}

Response samples

Content type
application/json
[]

Webhooks

Manage webhook subscriptions for delivery updates, and the contract for the deliveries we send to your endpoint.

Delivery contract

When a tracking you are subscribed to receives an update, we send an HTTP POST to every enabled subscription whose account owns that tracking. The request body is the tracking payload, forwarded verbatim — the same JSON, byte-for-byte, that GET /v1/trackings/{id} returns for the parcel. No wrapper object and no event field are added, so you must sign/verify against the raw request body (see below).

{
  "trackcode": "RA123456789CN",
  "status": "delivered",
  "origin_country": "CN",
  "destination_country": "UA",
  "events": [],
  "source": [
    {
      "code": "ukrposhta",
      "name": "Ukrposhta",
      "track": "RA123456789CN",
      "external_link": "https://track.ukrposhta.ua/tracking_RA123456789CN"
    }
  ]
}

Headers

Header Value
Content-Type application/json
X-Webhook-Id Delivery id. Stable across every retry of the same delivery — use it to deduplicate.
X-Webhook-Timestamp Unix time (seconds) of the delivery attempt.
X-Webhook-Signature sha256=<hex(HMAC-SHA256(secret, raw_body))>. Sent only when the subscription has a secret.

Signature verification

Compute the expected signature over the raw request body (before any deserialization, since the body is re-sent byte-for-byte) and compare in constant time:

expected = "sha256=" + hex(HMAC_SHA256(secret, raw_request_body))
compare(expected, X-Webhook-Signature)   // constant-time

Delivery, retries & idempotency

Any 2xx response is treated as a successful delivery. Any other status code, or a network/timeout error, triggers a retry. Because X-Webhook-Id is stable across retries, treat deliveries as at-least-once and deduplicate on that id.

List webhook subscriptions

Returns the account's webhook subscriptions.

Authorizations:
BearerAuthApiKeyHeader

Responses

Response samples

Content type
application/json
[
  • {}
]

Create a subscription

Creates a webhook subscription. url must be https. secret is never returned — its presence is surfaced via the has_secret field.

Authorizations:
BearerAuthApiKeyHeader
Request Body schema: application/json
required
url
required
string <uri>

Must use https.

secret
string

Secret used to sign deliveries (never returned in responses).

enabled
boolean
Default: true

Responses

Request samples

Content type
application/json
{}

Response samples

Content type
application/json
{}

Update a subscription

Partial update. Only the fields to be changed are sent. An empty secret clears the secret (has_secret=false).

Authorizations:
BearerAuthApiKeyHeader
path Parameters
id
required
integer <int64>

Subscription identifier.

Request Body schema: application/json
required
url
string <uri>
secret
string

An empty string clears the secret.

enabled
boolean

Responses

Request samples

Content type
application/json
{
  • "enabled": false
}

Response samples

Content type
application/json
{}

Delete a subscription

Authorizations:
BearerAuthApiKeyHeader
path Parameters
id
required
integer <int64>

Subscription identifier.

Responses

Response samples

Content type
application/json
{
  • "code": "unauthorized",
  • "message": "Invalid or missing API key"
}

Send a test delivery

Delivers a mock tracking payload to the subscription's endpoint and waits for the answer. Nothing is persisted and nothing is retried — use it to verify connectivity and signature checking after setting a subscription up.

The request looks like a real delivery (same headers, same Trackcode body, signed with the subscription's secret when it has one) with two markers so it can be told apart: the header X-Webhook-Test: true and an extra top-level "test": true field in the body. X-Webhook-Id is 0, since a test has no delivery row.

Disabled subscriptions can be tested too. A delivery that fails is still reported with HTTP 200 here — check ok and http_code in the body. Not billed.

Authorizations:
BearerAuthApiKeyHeader
path Parameters
id
required
integer <int64>

Subscription identifier.

Responses

Response samples

Content type
application/json
{
  • "ok": true,
  • "http_code": 200,
  • "duration_ms": 143,
  • "error": "http 500"
}