Trackers
Track labels generated outside SendIt with normalized statuses and webhooks, whatever the carrier.
Trackers give you tracking for labels not generated through SendIt. Register any supported carrier’s tracking number. You get the same normalized statuses, event history, and webhooks as a SendIt shipment.
Labels purchased through SendIt are tracked automatically and free of charge. Trackers are only for external numbers.
Endpoints
| Method | Path | Role and scope | Description |
|---|---|---|---|
POST |
/v1/trackers |
OPERATOR+ · trackers:write |
Register an external number (may charge overage) |
GET |
/v1/trackers |
VIEWER+ | List trackers |
GET |
/v1/trackers/:id |
VIEWER+ | One tracker with its full event history |
DELETE |
/v1/trackers/:id |
OPERATOR+ · trackers:write |
Permanently stop tracking (irreversible) |
List filters: search (partial tracking-number match), status, carrier, origin, isFinalized, createdAfter, and createdBefore. It also accepts livemode and is paginated.
isFinalized accepts true or false. Send isFinalized=false to list only the trackers still being polled.
Register an external number
Body parameters
trackingNumberstring
The external label's tracking number.
stringcarrier?string
Optional carrier hint (DHL | FEDEX | ESTAFETA today).
stringmetadata?object
Your own key-value pairs; returned as-is.
objectcurl -X POST https://api.sendit.mx/v1/trackers \
-H "X-API-Key: sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"trackingNumber": "1234567890123456",
"carrier": "DHL",
"metadata": { "orderId": "ORD-2026-001" }
}'
{
"success": true,
"data": {
"id": "trk_a1b2c3d4e5f6",
"trackingNumber": "1234567890123456",
"carrier": "DHL",
"status": "UNKNOWN",
"events": [],
"metadata": { "orderId": "ORD-2026-001" },
"createdAt": "2026-07-18T10:00:00.000Z"
}
}
carrieris optional. SendIt infers it only when the number format identifies one carrier unambiguously. If the format is ambiguous, you receive400 INVALID_INPUT: send the request again with acarrierhint.- An unsupported carrier returns
422 CARRIER_NOT_SUPPORTED; one that needs account credentials returns422 CARRIER_CREDENTIALS_REQUIREDuntil you configure that integration. - The response arrives with
status: "UNKNOWN"and an emptyeventsarray. The first carrier poll happens within about a minute. From then on, every status change fires atracker.updatedwebhook. metadatais returned as-is, never interpreted.
Duplicates are free
Registering a number that already has an active tracker in your organization returns the existing tracker with meta.deduplicated: true, and is never charged again. This applies to the same number and carrier within the last 3 months. Client-side retries are always safe: this endpoint needs no Idempotency-Key, though one is honored if sent.
Statuses
UNKNOWN → PRE_TRANSIT → IN_TRANSIT → OUT_FOR_DELIVERY → DELIVERED | RETURNED | FAILED
Exception scans (customs holds, failed delivery attempts, damage) appear in events[] with isException: true, without necessarily changing the status.
Polling cadence and finalization
Carrier polling adapts to the status: ~6 h before first movement, ~2 h in transit, ~30 min out for delivery, backing off to ~12 h after 5 days without new scans.
A tracker finalizes in the cases below. On finalization, isFinalized becomes true, polling stops, and the record stays queryable:
| Condition | finalizedReason |
|---|---|
| Delivered / returned / failed | DELIVERED / RETURNED / FAILED |
| 45 days without leaving pre-transit | TTL_PRE_TRANSIT (fires tracker.expired) |
| 60 days without any new event | TTL_NO_UPDATES (fires tracker.expired) |
Manual DELETE /v1/trackers/:id |
CANCELLED |
Webhooks
Subscribe your endpoint to tracker.created, tracker.updated, or tracker.expired. They use the same signatures and retries as every SendIt webhook. There are deliberately no separate delivered or exception events: read data.object.status inside tracker.updated.
Pricing
| Plan | Included trackers / month | Overage per tracker (MXN) |
|---|---|---|
| Free | 100 | $0.80 |
| Growth | 2,000 | $0.50 |
| Scale | 10,000 | $0.30 |
| Enterprise | Unlimited | — |
- Only external registrations count; SendIt-generated labels never consume quota.
- Over quota, the overage is debited from your wallet at registration time (
402 INSUFFICIENT_BALANCEif it can’t be covered). There is no hard cap. - The amount charged is returned as
overageChargedon the tracker.
In test mode
Registrations with a sk_test_ key never charge overage or consume quota. Test trackers progress to DELIVERED on their own with simulated data, and their webhooks carry livemode: false.
Handle errors
| Code | When it happens | How to resolve it |
|---|---|---|
400 INVALID_INPUT |
The number does not identify one carrier unambiguously | Send a compatible carrier hint |
402 INSUFFICIENT_BALANCE |
The record exceeds quota and the wallet lacks funds | Fund the wallet and try again |
422 CARRIER_NOT_SUPPORTED |
SendIt cannot poll that carrier | Use a supported carrier |
422 CARRIER_CREDENTIALS_REQUIRED |
The carrier needs a configured account | Configure the carrier credentials |