Public Tracking
Use Public Tracking to show a customer the current safe projection for one shipment. The endpoint is anonymous and accepts only a tracking_number.
Call the endpoint
| Method | Path | Response body |
|---|---|---|
GET | /public/tracking/{tracking_number}/ | JSON projection on 200; detail-only body on 404 or 429 |
HEAD | /public/tracking/{tracking_number}/ | No body; the same existence and rate-limit status as GET |
The complete URL is https://mexpress.nz/api/v1/public/tracking/{tracking_number}/. Encode the tracking number as one path segment. Do not substitute a package code, scan code, internal ID, or customer data.
curl --fail-with-body 'https://mexpress.nz/api/v1/public/tracking/MX123456789/'Use the response
The 200 object always contains these required keys. timeline may be an empty array, and nullable timestamps or context values may be null.
{
"tracking_number": "MX123456789",
"status": "out_for_delivery",
"milestone": "out_for_delivery",
"reason": null,
"status_label": "Out for delivery",
"package_count": 2,
"package_summary": {
"total": 2,
"created": 0,
"picked_up": 0,
"in_warehouse": 0,
"out_for_delivery": 2,
"exception": 0,
"returning": 0,
"delivered": 0,
"returned": 0,
"cancelled": 0,
"unresolved": 0
},
"projection_revision": 7,
"occurred_at": "2026-07-02T03:04:05.000000Z",
"timeline": []
}| Field | Contract |
|---|---|
tracking_number | Required string. The requested shipment identifier. |
status | Required enum: created, in_transit, out_for_delivery, exception, delivered, returned, cancelled, completed. |
milestone | Nullable enum: information_received, picked_up, at_transit_facility, out_for_delivery, delivery_exception, returning_to_sender, delivered, returned_to_sender, cancelled, delivered_and_returned, partially_delivered, partially_returned. |
reason | Nullable enum: recipient_unavailable, address_issue, access_issue, recipient_refused, damaged, other_delivery_issue. |
status_label | Required string intended for customer-facing display. |
package_count | Required non-negative integer. |
projection_revision | Required non-negative integer. Cache or compare it as a projection revision. |
occurred_at | Nullable RFC 3339 date-time string for the current projection. |
package_summary | Required object with exactly the fixed keys below; every value is a non-negative integer. |
timeline | Required array, possibly empty, of timeline events ordered oldest first. |
package_summary always uses this key order: total, created, picked_up, in_warehouse, out_for_delivery, exception, returning, delivered, returned, cancelled, unresolved. Do not add keys or infer package identity from counts.
Each timeline item has required status (enum created, picked_up, in_warehouse, loaded, out_for_delivery, delivery_attempted, delivered, exception, return_initiated, returned_to_sender, cancelled, completed), label (string), and nullable occurred_at (date-time). An empty array means no timeline event is available.
Import the runnable examples
The canonical regions handle 200, detail-only 404, 429, and positive Retry-After without putting tracking data in a browser credential flow.
export async function fetchTracking(
trackingNumber,
{ baseUrl = API_BASE_URL, fetchImpl = globalThis.fetch } = {},
) {
const response = await fetchImpl(
endpoint(baseUrl, `/public/tracking/${encodeURIComponent(trackingNumber)}/`),
{
method: "GET",
headers: { Accept: "application/json" },
},
);
if (response.status === 200) return { kind: "ok", data: await json(response) };
if (response.status === 404) return { kind: "not_found" };
if (response.status === 429) {
const retryAfter = Number.parseInt(header(response.headers, "Retry-After") ?? "", 10);
if (!Number.isInteger(retryAfter) || retryAfter < 1) {
throw new Error("Tracking 429 did not include a positive Retry-After.");
}
return { kind: "rate_limited", retryAfter, error: await json(response) };
}
throw new Error(`Tracking request failed with HTTP ${response.status}.`);
}def fetch_tracking(tracking_number: str, *, client, base_url: str = API_BASE_URL):
response = client.get(
_url(base_url, f"/public/tracking/{quote(tracking_number, safe='')}/"),
headers={"Accept": "application/json"},
)
if response.status_code == 200:
return {"kind": "ok", "data": response.json()}
if response.status_code == 404:
return {"kind": "not_found"}
if response.status_code == 429:
raw_retry_after = _header(response, "Retry-After")
try:
retry_after = int(raw_retry_after or "")
except ValueError as error:
raise ValueError("Tracking 429 did not include a positive Retry-After.") from error
if retry_after < 1:
raise ValueError("Tracking 429 did not include a positive Retry-After.")
return {"kind": "rate_limited", "retry_after": retry_after, "error": response.json()}
raise RuntimeError(f"Tracking request failed with HTTP {response.status_code}.")Handle failures and polling
404 is deliberately detail-only. Show one neutral unavailable result and do not reveal whether the number is unknown, hidden, malformed, or belongs to another customer.
429 has a JSON body with detail equal to Too many public tracking requests. Retry after the indicated delay. and code equal to public_tracking_rate_limited. It also has a positive integer Retry-After header. Wait at least that long, then use backoff and jitter.
The anonymous limit is five requests per second per source IP with a burst of thirty, plus a shared service limit. Poll only when the customer view needs refresh; once per minute is a conservative default for one tracking number. Prefer Webhooks for backend updates.
Do not expose recipient contact or address data, package scan codes, POD media, exact device location, driver identity, billing data, raw scan evidence, or internal audit and risk fields. The response is a customer projection, not an operations feed.
Verify the flow
- [ ]
GETrenders only the required documented fields. - [ ]
HEADis used only when no body is needed. - [ ]
404is one neutral unavailable outcome. - [ ]
429waits for a positiveRetry-After. - [ ] Polling has backoff and jitter with no tight loop.
Continue with Webhooks for event-driven updates or POD API for authenticated evidence.