A tracker follows one tracking number with one carrier. It keeps two clocks:
  1. events[]: an append-only log of what the carrier said, kept lossless. It holds the raw code, raw text and raw location.
  2. status: a projection over that log into a small canonical set, which may be lossy, plus status_detail for the reason.
Clients build on status. Auditors and support agents read events[]. All disagreement between carriers is quarantined in the mapping from carrier codes to canonical status, and the implementer owns that mapping. The diagram shows the two clocks: carrier data lands unchanged in events[], and only the implementer’s mapping turns it into status and status_detail.

1. The tracker object

2. Canonical status

Rules:
  • Status is allowed to move backwards. out_for_delivery → exception → out_for_delivery is normal, and so is delivered → in_transit, which happens when the carrier corrects a mis-scan. Clients MUST NOT assume the status only moves forward.
  • status equals the status of the most recent event by occurred_at, except that unknown events never override a known status.
  • delivered, returned and cancelled stop automatic refresh after 72 hours without new events. “Terminal” means exactly that. A carrier correction can still move a terminal status, as described above.
  • Every event carries both canonical and carrier fields. carrier_code is never dropped.
The state diagram shows the common paths between canonical statuses, including the backwards moves. It is not exhaustive, because status follows whatever the most recent event says.

status_detail

A finer reason within the status. Clients MAY ignore it, but they MUST tolerate unknown values. Why delivery_attempted is an exception. One carrier treats a failed attempt as a normal step and another treats it as an exception. The spec takes the recipient’s view: something needs attention. The retry is then a further out_for_delivery event.

3. Create a tracker

Idempotent by nature. Creating a tracker for a (carrier, tracking_number) that the account already tracks returns the existing tracker with 200 and messages[0].code = "tracker_exists". This holds even without an Idempotency-Key. tracking_number_not_found is not an error at create time. New labels are often unknown to the carrier for hours. The tracker is created with status: unknown, status_detail: not_found_yet, and keeps polling for up to 30 days before it stops with refresh.stopped_reason = "not_found". The sequence shows a tracker created for a number the carrier does not know yet.

4. Discard a tracker

This stops all polling and push subscriptions with the carrier, and deregisters carrier-side webhooks where they exist. The tracker and its events are then unreachable via the API. Implementers MAY retain them internally for 30 days for billing disputes. A tracker that is linked to a shipment cannot be discarded while the shipment is purchased (409 state_error / tracker_owned_by_shipment). Use POST /v1/trackers/{id} with refresh: { mode: "manual" } to stop polling instead. When the shipment is voided, its tracker stops automatically. The flowchart shows how a discard request is decided.

5. Refresh

This forces a synchronous carrier poll and returns the tracker. It is rate-limited to once per 5 minutes per tracker (429 otherwise). It is useful for “where is my order” pages. It is not useful as a polling loop. Use webhooks for that.

6. Events, list, search, update

tracker.events inlines the 50 most recent events. The full log is at /events.

7. Sources

tracking_event.source records where an event came from: Events from different sources are merged by occurred_at. Duplicates (same carrier_code, same occurred_at within 60 seconds, same location) are collapsed, and the first-received copy wins. The flowchart shows events from all five sources merging into one tracker log.

8. Proof of delivery

When delivered, proof_of_delivery MAY be { signed_by, signature_file, photo_files: ["file_..."], location: { latitude, longitude }, delivered_to: "front_door" }. The image files are file objects with purpose: "proof_of_delivery".

9. Events (webhooks)

tracker.created, tracker.updated (any new event), tracker.status_changed (canonical status changed, which is the one most clients want), tracker.delivered, tracker.exception, tracker.returned, tracker.stopped, tracker.deleted.