API and webhooks
API quickstart
Create an API key, add shipments with one request and read their statuses. Base URL, authentication, limits and error codes.
Updated
The Business API lets your own systems add shipments to your workspace and read their statuses. It is on the Starter plan and above. It works on the shipments we already check for you, so reading from it never sends a fresh request to the courier; to be told about changes as they happen, add a webhook (see Verifying webhook signatures).
1. Create a key
Owners and Admins create keys under Developers → API keys. Creating a key asks for your authenticator code (see Two-step sign-in). Give the key a name and choose what it may do:
shipments:readto read shipments;shipments:writeto add, update, archive and refresh them;webhooks:manageto manage webhook endpoints.
The key, starting tp_live_, is shown once: store it somewhere safe, such as your server's secrets. You can archive a key, or rotate it, which creates a new key and keeps the old one working for 24 hours while you switch.
2. The basics
- Base address:
https://trackparcel.in/api/v1 - Send your key in the
x-api-keyheader on every request. - Requests and responses are JSON. Times are in IST with the offset (+05:30).
3. Add shipments
Send 1 to 100 shipments in one request. Only tracking_number is required; leave out courier and we detect it, or give it to be sure. Each shipment in the reply says whether it was created, existing (already tracked), restored (brought back from the archive) or invalid, with the reason.
curl -X POST https://trackparcel.in/api/v1/shipments \
-H "x-api-key: tp_live_your_key_here" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 9f1c2a7e-order-SO-1182" \
-d '{
"shipments": [
{
"tracking_number": "1234567890123",
"courier": "delhivery",
"order_id": "SO-1182",
"label": "Blue kurta",
"tags": ["priority"],
"customer": {
"name": "R Kumar",
"phone": "+919812345678",
"city": "Pune",
"state": "Maharashtra",
"pincode": "411001"
}
}
]
}'- Send an
Idempotency-Keyheader that is unique to the batch. If a network error makes you send the same request again with the same key within 24 hours, nothing is added twice. customeris optional: the details are kept with the shipment for your team, and the API returns them when you read it.
4. Read shipments
curl "https://trackparcel.in/api/v1/shipments?exception=stuck&limit=100" \
-H "x-api-key: tp_live_your_key_here"| Request | What it does |
|---|---|
GET /shipments | Lists shipments. Filter by status, courier, tag, exception, archived, updated_since (an ISO 8601 time) or search. Up to 100 a page; pass the cursor from one page to get the next. |
GET /shipments/{id} | One shipment, with its latest status, exceptions and expected delivery. |
GET /shipments/lookup?tracking_number=…&courier=… | Finds a shipment by its tracking number. |
PATCH /shipments/{id} | Updates the label, order ID, tags, customer or courier. Changing the courier checks it again. |
POST /shipments/{id}/archive | Archives it. /unarchive restores it. |
GET /shipments/{id}/events | The courier's updates, newest first. |
POST /shipments/{id}/refresh | Asks for a fresh check. Answers 202 and uses one of your manual refreshes. |
GET /usage | Your usage so far. Free to call, up to 30 times a minute. |
Reads (the list, a lookup, one shipment and its events) cover the shipments added within your plan's history: a year on Starter and three years on Growth, and all of them on Pro and Enterprise. An older shipment answers not_found with a message saying it was added before your plan's history window. Older shipments are kept and appear when you upgrade.
A shipment
{
"id": "6f1c…",
"tracking_number": "1234567890123",
"tracking_number_raw": "1234567890123",
"courier": {"id": "delhivery", "name": "Delhivery"},
"status": "out_for_delivery",
"status_label": "Out for delivery",
"status_text": "Out For Delivery",
"exceptions": [{"type": "ofd_overdue", "since": "2026-10-12T09:10:00+05:30"}],
"last_event": {"at": "2026-10-12T08:02:00+05:30", "location": "PUNE", "activity": "Out For Delivery"},
"eta": {"date": "2026-10-12", "source": "courier"},
"delivered_at": null,
"order_id": "SO-1182", "label": "Blue kurta", "tags": ["priority"],
"customer": {"name": "R Kumar", "phone": "+919812345678", "email": null, "city": "Pune", "state": "Maharashtra", "pincode": "411001"},
"archived": false,
"last_checked_at": "2026-10-12T10:40:11+05:30",
"created_at": "2026-10-10T17:22:05+05:30"
}status is one of the statuses in What statuses mean, status_label is how the app shows it, and status_text is the courier's own words. Tracking numbers are always returned in full; tracking_number_raw is exactly what was sent to us.
Shipments are archived, not deleted
HTTP/1.1 405 Method Not Allowed
{"error":{"code":"archive_only","message":"Shipments can be archived, not deleted"}}Limits
| Plan | Requests per minute, per key | Calls per monthly period |
|---|---|---|
| Starter | 30 | 1,000 |
| Growth | 60 | 10,000 |
| Pro | 120 | 50,000 |
| Enterprise | As agreed | As agreed |
Responses carry X-Quota-Limit, X-Quota-Used and X-Quota-Reset headers, so you can see where you stand without a separate call.
Errors
Every error carries a code and a readable message, with a matching HTTP status. The codes are:
| Code | Meaning |
|---|---|
invalid_tracking_number | Not a tracking number we can use (6–40 letters or digits). |
courier_required | We couldn't tell the courier from the number; send it. |
unknown_courier | We don't recognise the courier you sent. |
plan_limit_reached | Your plan's limit stops this request. |
quota_exceeded | You have used this period's API calls. |
rate_limited | Too many requests this minute; wait and retry. |
role_forbidden | That isn't allowed for this workspace or key. |
scope_missing | The key lacks the permission this request needs. |
not_found | No such shipment or webhook in your workspace, or a shipment added before your plan's history window. |
archive_only | Shipments can be archived, not deleted. |
idempotency_conflict | That Idempotency-Key was used for a different request. |
The full reference is in the Business API section of the API documentation, with an OpenAPI 3.1 file at /api-docs/openapi.json.