← Developers

TrackJet API v1

A read-only REST API over TrackJet's detection engine and carrier directory — the same logic that powers the website and the MCP server. Multi-vertical: air cargo (MAWB), ocean containers (ISO 6346), Bill of Lading (SCAC), parcels, and postal (UPU S10). EU-hosted. No scraping of third-party aggregators.

Base URL: https://trackjet.world/api/v1
OpenAPI 3.1: /api/v1/openapi.json
Import: point Postman, Bruno, Insomnia or any OpenAPI client at https://trackjet.world/api/v1/openapi.json — they generate an always-current collection from the live spec, so there is nothing hand-maintained to drift.
Rate limits: free: 60/min · pro: 600/min · enterprise: 6000/min (per API key). Responses carry X-RateLimit-* headers; over-limit returns 429.
Billing: disabled (all plans free).

Authentication

Send your API key as a Bearer token (the X-Api-Key header is also accepted):

Authorization: Bearer tjk_live_xxxxxxxxxxxxxxxxxxxxxxxx

Keys are issued by the TrackJet team. Treat a key like a password — it grants read access under your plan's rate limit. Every response includes an X-Request-Id you can quote in support requests.

Endpoints

MethodPathDescription
GET/api/v1/detectDetect a number's format/vertical. Query: number.
GET/api/v1/track/{number}Detect + resolve where to track (carrier URL + TrackJet URL).
GET/api/v1/carriersList production carriers. Query: type, limit, offset.
GET/api/v1/carrier/{id}A single carrier's metadata.
GET/api/v1/capabilitiesCoverage snapshot (formats, counts, MCP info).
GET/api/v1/carbonRough freight CO2e estimate (GLEC/ISO 14083). Query: mode, weight_kg, distance_km.
GET/api/v1/track/{number}/qrQR image (SVG/PNG) that opens the tracking page. No key required; embeddable in <img>.
GET/api/v1/track/{number}/streamServer-Sent Events live stream (text/event-stream). Emits status events; closes with event: closed + a reason (no_live_feed when the carrier has no licensed event feed — DHL Group numbers stream real updates).
GET·POST/api/v1/webhooksList / register webhooks (HMAC-signed deliveries). POST honours Idempotency-Key.
POST/api/graphqlGraphQL (alongside REST). Same key auth. Query: detect, carriers, carrier, capabilities.
GET/api/v1Service descriptor (no key required).

Response envelope

{
  "ok": true,
  "data": { "...": "endpoint-specific" },
  "meta": { "request_id": "…", "api_version": "v1" }
}

// errors:
{ "ok": false, "error": { "code": "unauthorized", "message": "…", "status": 401 }, "meta": { … } }

Examples

curl

curl -H "Authorization: Bearer $TRACKJET_API_KEY" \
  "https://trackjet.world/api/v1/detect?number=020-12345675"

JavaScript (fetch)

const res = await fetch("https://trackjet.world/api/v1/detect?number=020-12345675", {
  headers: { Authorization: `Bearer ${process.env.TRACKJET_API_KEY}` },
});
const { data } = await res.json();
console.log(data.primary, data.normalized);

PHP

$ch = curl_init("https://trackjet.world/api/v1/detect?number=020-12345675");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ["Authorization: Bearer " . getenv("TRACKJET_API_KEY")],
]);
$data = json_decode(curl_exec($ch), true);
echo $data["data"]["normalized"];

Python

import os, requests
r = requests.get(
    "https://trackjet.world/api/v1/detect",
    params={"number": "020-12345675"},
    headers={"Authorization": f"Bearer {os.environ['TRACKJET_API_KEY']}"},
)
print(r.json()["data"]["primary"])

GraphQL

curl -X POST "https://trackjet.world/api/graphql" \
  -H "Authorization: Bearer $TRACKJET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query":"{ detect(number:\"020-12345675\"){ normalized matched matches{ type label } } }"}'

EPCIS 2.0 repository

Minimal conformant subset of GS1 EPCIS 2.0 (honest scope: ObjectEvent JSON only — no XML, no subscriptions, no full query language). Events are isolated per API key. EPCs whose digit-run matches a tracked shipment appear on its public page as "EPCIS (partner)", separate from the carrier chain.

curl -X POST "https://trackjet.world/api/v1/epcis/capture" \
  -H "Authorization: Bearer $TRACKJET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type":"ObjectEvent","eventTime":"2026-06-11T09:00:00Z",
       "eventTimeZoneOffset":"+00:00","action":"OBSERVE",
       "bizStep":"urn:epcglobal:cbv:bizstep:shipping",
       "epcList":["urn:epc:id:sscc:0037000.0123456789"],
       "readPoint":{"id":"urn:epc:id:sgln:0037000.00729.0"}}'

curl -H "Authorization: Bearer $TRACKJET_API_KEY" \
  "https://trackjet.world/api/v1/epcis/events?EPC=urn:epc:id:sscc:0037000.0123456789"

Webhooks

Register an HTTPS endpoint to receive shipment.status_changed events. Each delivery is signed:

curl -X POST "https://trackjet.world/api/v1/webhooks" \
  -H "Authorization: Bearer $TRACKJET_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://you.example/hook","events":"shipment.status_changed"}'
# response includes a one-time "secret".

Verify each delivery's X-TrackJet-Signature over the timestamp and the raw body, then dedupe on X-TrackJet-Delivery — that id is stable across all retries of one event:

signed   = header["X-TrackJet-Timestamp"] + "." + raw_request_body
expected = "sha256=" + hmac_sha256(signed, your_secret)
if not hmac_compare(expected, header["X-TrackJet-Signature"]): reject()
if abs(now - int(header["X-TrackJet-Timestamp"])) > 300:      reject()
if seen_before(header["X-TrackJet-Delivery"]):                return 200

Sandbox

Reserved TJTEST-* tracking numbers return deterministic, fully-synthetic lifecycles (timestamps in 2030, sandbox:true), so you can build and pin integration tests without a real parcel — happy path and edge cases:

NumberScenario
TJTEST-DELIVEREDFull happy path ending delivered.
TJTEST-IN-TRANSITMid-journey, latest event in_transit.
TJTEST-EXCEPTIONCarrier exception (damaged label), no delivery.
TJTEST-CUSTOMS-HOLDHeld at customs awaiting import duties.
TJTEST-RETURNEDRefused at the door, returned to sender.
TJTEST-STALENo scans for 14 days — exercise your staleness logic.
TJTEST-NOT-FOUNDAlways "no data" — exercise your empty state.
curl -H "Authorization: Bearer $TRACKJET_API_KEY" \
  "https://trackjet.world/api/v1/track/TJTEST-DELIVERED"

Webhook dry-run — fire one signed sandbox.test event at your registered endpoint through the real delivery pipeline (same X-TrackJet-Signature, same retry/log path):

curl -X POST "https://trackjet.world/api/v1/webhooks/<id>/test" \
  -H "Authorization: Bearer $TRACKJET_API_KEY"

Transparency & verification

Every tracked chain's history is sealed into a SHA-256 hash chain (tjvt2), and all sealed heads are anchored in a public, append-only Merkle log (tjmt1). Three endpoints are public — no API key — because a transparency log is only credible when anyone can verify:

curl "https://trackjet.world/api/v1/shipments/<uuid>/proof"       # tjvt2 chain proof
curl "https://trackjet.world/api/v1/shipments/<uuid>/inclusion"   # tjmt1 Merkle inclusion proof
curl "https://trackjet.world/api/v1/transparency/roots"            # append-only root history

Verify offline with the single-file, dependency-free tjverify CLI.

Proof-of-Delivery

For disputes, insurance claims and refund decisions: a single call returns a signed, offline-verifiable Proof-of-Delivery (tjpod1) — the TrackJet Passport (Ed25519) scoped to a delivered shipment, with the recorded delivery event surfaced. Public, no API key (the UUID is the capability), and it carries no personal data:

curl "https://trackjet.world/api/v1/shipments/<uuid>/pod"          # tjpod1 signed proof-of-delivery

A POD is issued only for a shipment whose recorded status is delivered — a number still in transit returns 409 not_delivered, never a signed claim. Verify the embedded passport against the published key with the same tjverify CLI — no call back to TrackJet. It proves TrackJet signed this exact snapshot, not that the carrier's original scan was true.

Retention window. A POD is built from a shipment's event detail, which TrackJet keeps for ~30 days after the last update. Past that window the endpoint returns 410 pod_outside_retention (an honest, permanent "gone" — not a retryable error), and the sealed hash chain stays available at /proof. So pull and store the POD when a dispute opens, or near delivery. Insurance/claim windows are often 60-90 days; if you need POD coverage to match, ask us about extending retention for delivered shipments.

Notes