/api/v1/sandbox/…Try it — live sandbox
Run a real request against the API right now — no API key or sign-up needed. The sandbox returns fixed sample data (it doesn't touch your account or live carriers), so you can see the exact request and response shape before you integrate.
https://traqocontainer.com/api/v1/sandbox/container/MRSU6859427?sealine=MAEUSandbox data is illustrative and fixed. Create a free account and enable developer mode to track real shipments with a live key.
Authentication
All API requests must include your API key as a Bearer token in the Authorization header. You can generate and manage API keys from the Developer section of your dashboard.
Authorization: Bearer YOUR_API_KEY
Base URL
All endpoints are relative to the following base URL:
https://traqocontainer.com/api/v1
/api/v1/openapi.json — feed it to openapi-generator, orval, Swagger UI or Redoc. It also describes the webhook events. Prefer Postman? Import the ready-made Postman collection, set the apiKey variable, and every endpoint is one click from running. Errors
All error responses return JSON with a consistent structure:
{
"statusCode": 401,
"statusMessage": "Invalid or missing API key"
}| Status | Meaning |
|---|---|
200 | Success |
400 | Bad request — check required parameters |
401 | Invalid or missing API key |
402 | Payment required — either your shipment limit is reached (data.error: "shipment_limit_reached") or your payment is overdue past the grace period (data.error: "payment_overdue"). Branch on data.error. Sends a Retry-After header — stop retrying and fix billing / upgrade; re-hammering the same call won't succeed. |
403 | Developer mode not enabled — enable it from your dashboard settings |
404 | Resource not found |
429 | Rate limit exceeded (per API key). Includes X-RateLimit-Limit / -Remaining / -Reset on every response and a Retry-After (seconds) on the 429 — wait that long before retrying. See API rate limits. |
502 | Upstream tracking API error — retry after a moment |
A 402 includes a structured data object so you can react programmatically. When data.error is "payment_overdue", tracking is paused because a payment failed and the grace period ended — update billing at data.manageUrl to resume (your existing shipments are unaffected):
{
"statusCode": 402,
"statusMessage": "Payment overdue — API access is paused. Update your billing to resume tracking.",
"data": {
"error": "payment_overdue",
"overdueDays": 9,
"graceDays": 7,
"plan": "business",
"manageUrl": "https://traqocontainer.com/dashboard/billing"
}
}Shipment limits
Two independent limits apply: a request rate limit per API key (see API rate limits) and a shipment slot limit — how many shipments your account can track simultaneously, set by your plan.
Each call to /api/v1/container/:number, /api/v1/bl/:number checks whether the shipment is already in your account. If it is, the call succeeds without consuming a slot. If it's new and you have remaining slots, it's added. If you've reached your limit, the API returns 402.
/api/v1/vessel/track and /api/v1/voyage/schedules endpoints do not consume shipment slots — they are purely lookup calls. API rate limits
Every authenticated request is rate-limited per API key, in a fixed one-minute window. The default is 120 requests per minute (some plans allow more — check the headers below for your actual limit).
Every response carries your current budget, so you never have to guess:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Requests allowed in the current window (your key's limit). |
X-RateLimit-Remaining | Requests left in the current window. |
X-RateLimit-Reset | Unix time (seconds) when the window resets and the count returns to the full limit. |
Retry-After | On a 429 only — how many seconds to wait before retrying. |
X-Traqo-Refresh-Hint | On the live-fetch endpoints (/container, /bl) — a reminder that they re-fetch from the carrier and are slow. For repeat status checks use GET /shipments/{id} (stored data, no re-fetch, no slot); for changes poll ?updated_since= or subscribe to webhooks. |
Exceed the limit and you get a 429 with a Retry-After. Back off for that many seconds — retrying sooner just burns another 429:
{
"success": false,
"statusCode": 429,
"message": "Rate limit exceeded — 120 requests per minute. Retry after 42s.",
"data": { "error": "rate_limit_exceeded", "limit": 120, "retryAfter": 42 }
}/api/v1/shipments in a tight loop to spot changes — you'll hit the rate limit fast. Fetch on a sensible interval (or use the upcoming delta / webhook features). /api/v1/container/:numberTrack a container
Returns full tracking data for a container number — status, route, ETA, port events, and vessel info. Pass the container number directly in the URL. The shipment is automatically saved to your account in the background.
403.Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
number | string | Yes | Container number (e.g. MSCU1234567) |
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
sealine | string | Yes | 4-character SCAC code of the shipping line (letters and/or digits, e.g. MSCU). Required — omitting returns a 400 error. |
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://traqocontainer.com/api/v1/container/MRSU6859427?sealine=MAEU"
Response
/api/v1/bl/:numberTrack a bill of lading
Returns full tracking data for a Bill of Lading number. Identical response structure to the container endpoint. The shipment is automatically saved to your account in the background.
sealine (SCAC) query parameter is mandatory — the API cannot auto-detect it. Omitting it returns 400 "sealine is required".403.Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
number | string | Yes | Bill of Lading number, 3–50 characters, exactly as the carrier printed it. No format is imposed: digits-only BLs, and BLs containing hyphens, slashes or dots, are all accepted (URL-encode a / as %2F). Surrounding whitespace is trimmed. |
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
sealine | string | Yes | 4-character SCAC code of the shipping line (letters and/or digits, e.g. CMDU). Required — omitting returns a 400 error. |
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://traqocontainer.com/api/v1/bl/SHZ8037930?sealine=CMDU"
Response
/api/v1/trackBulk track shipments
Track up to 50 containers or bills of lading in a single request — the batch form of the container and BL endpoints. Each item needs its 4-character SCAC (sealine). New shipments are saved to your account and consume a slot each; ones you already track are re-fetched for free.
200 even when some items fail — branch on each results[].ok. The whole request is only rejected up front for auth (401/403), rate limit (429), a malformed body (400), or a payment past the grace period (402).sealine (SCAC) is mandatory — see Carriers for the list of valid codes.Request body
| Field | Type | Required | Description |
|---|---|---|---|
shipments | array | Yes | 1–50 items. |
shipments[].type | string | Yes | container or bl. |
shipments[].number | string | Yes | Container number (4 letters + 7 digits, ISO 6346 check digit verified) or BL number (3–50 characters, as printed by the carrier — separators are fine). |
shipments[].sealine | string | Yes | 4-character SCAC of the carrier. |
Example request
curl -X POST https://traqocontainer.com/api/v1/track \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"shipments":[{"type":"container","number":"MRSU6859427","sealine":"MAEU"},{"type":"bl","number":"MEDUFR123456","sealine":"MSCU"}]}'Response
/api/v1/shipmentsList tracked shipments
Returns a paginated list of all shipments saved to your account — containers and bills of lading — with their current status, route, and ETA. Useful for building dashboards and monitoring multiple shipments at once.
403.Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
page | integer | No | Page number, default 1 |
pageSize | integer | No | Results per page, default 20, max 100 |
updated_since | string | No | ISO 8601 timestamp. Delta mode — returns only shipments whose tracking data changed at or after this time (by last_synced_at), so you can sync changes instead of polling the whole list. Supersedes pagination; capped at 200 results. Response shape becomes { success, updated_since, count, data }. |
last_synced_at timestamp — the last time we refreshed its tracking. Save the newest one you see and pass it back as updated_since on your next call to fetch just what changed.curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://traqocontainer.com/api/v1/shipments?page=1&pageSize=20"
Response
Showing 2 of 4 items for brevity. Flat objects — no nested arrays.
/api/v1/shipments/{id}Get a shipment
Returns the current summary of a single shipment you already track — status, route, ETA and last_synced_at — straight from Traqo's stored data. Unlike /container and /bl, this never re-fetches from the carrier and never consumes a shipment slot, so it's the right call for cheap status checks and reconciliation. The {id} is the shipment id returned by /api/v1/shipments.
404.Example request
curl https://traqo.io/api/v1/shipments/MSCU1234567 \ -H "Authorization: Bearer YOUR_API_KEY"
Response
When predictive ETA is enabled for your plan, the data object also carries predictive_eta and demurrage_risk — see Predictive ETA.
/api/v1/shipments/{id}Untrack a shipment
Removes a shipment from your account and frees the slot it occupied, exactly like removing it from your dashboard. This is a soft delete — the shipment stops counting toward your monthly limit and disappears from /api/v1/shipments.
404 — nothing changed.Example request
curl -X DELETE https://traqo.io/api/v1/shipments/MSCU1234567 \ -H "Authorization: Bearer YOUR_API_KEY"
Response
{
"success": true,
"deleted": true,
"shipment_id": "MSCU1234567"
}/api/v1/vessel/trackTrack a vessel
Returns real-time AIS position, speed, heading, and voyage information for a vessel. Both imo (7 digits) and mmsi (9 digits) are required.
403.Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
imo | string | Yes | 7-digit IMO number |
mmsi | string | Yes | 9-digit MMSI number |
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://traqocontainer.com/api/v1/vessel/track?imo=9811000&mmsi=636022327"
Response
/api/v1/voyage/schedulesVoyage schedules
Returns sailing schedules between two ports for a given date, across available carriers.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
origin | string | Yes | Origin port UN/LOCODE (e.g. CNSHA) |
destination | string | Yes | Destination port UN/LOCODE (e.g. NLRTM) |
date | string | Yes | Date in YYYY-MM-DD format |
week_range | integer | No | Number of weeks to search, default 1 |
date_type | string | No | "departure" (default) or "arrival" |
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://traqocontainer.com/api/v1/voyage/schedules?origin=INMUN&destination=AEJEA&date=2026-06-01&week_range=2&date_type=departure"
Response
Showing 2 of many results for brevity.
/api/v1/ports/:locode/congestionPort congestion
Returns the latest congestion score for a port (UN/LOCODE) plus its 90-day score history: an explainable 0–100 score, a bucket (fluid / normal / moderate / high / critical), a data-sufficiency tier (A/B/C), a 7-day trend, and the per-signal components (dwell, anchorage wait, ETA-slip, schedule deviation, bunching) with each signal's raw value, baseline and z-score.
Requires port-congestion to be enabled on your plan. Reads Traqo's own analytics — no upstream call, so it never consumes a shipment slot.
Predictive ETA on container tracking
When predictive ETA is enabled for your account, /api/v1/container/:number responses include a predictive_eta object: p50 and p80 timestamps, a source (model / blend / carrier), a confidence (high / medium / low), and computed_at. It's derived from live vessel progress plus port congestion — a more accurate arrival estimate than the raw carrier ETA, which goes stale.
/api/v1/ports/congestionCongestion board
Returns the current congestion reading for every scored port in one call — the same score / bucket / tier as the per-port endpoint, plus a 7-day trend_7d, a 30-day calls_30d volume, and coordinates. Use it to build a map or a watchlist without polling ports one at a time.
Same gating as Port congestion: requires port-congestion on your plan. Reads Traqo's own analytics — no upstream call, no shipment slot.
Example request
curl https://traqo.io/api/v1/ports/congestion \ -H "Authorization: Bearer YOUR_API_KEY"
Response
Showing 1 of many ports for brevity.
/api/v1/portsSearch ports
A directory lookup against Traqo's port database — resolve a UN/LOCODE, port name, or city to canonical metadata (locode, name, city, country, region, coordinates). Handy for turning free-text origin/destination into the locodes the congestion and schedule endpoints expect. Local read, no upstream call.
?search= must be at least 2 characters, or the endpoint returns 400. Results are capped at 25, exact LOCODE matches first, then busiest ports.Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
search | string | Yes | A UN/LOCODE, port name, or city — minimum 2 characters (e.g. rotterdam or NLRTM) |
Example request
curl "https://traqo.io/api/v1/ports?search=rotterdam" \ -H "Authorization: Bearer YOUR_API_KEY"
Response
/api/v1/carriersList supported carriers
The directory of every ocean carrier Traqo can track, each with its 4-character scac and name. This is the lookup you need before calling /container, /bl or /track: the scac returned here is exactly what you pass as the mandatory sealine parameter. Local read — never consumes a shipment slot.
?search= (≥2 chars) to filter by SCAC, name or slug.Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
search | string | No | Case-insensitive filter by SCAC, name or slug — minimum 2 characters (e.g. maersk or MAEU). |
Example request
curl "https://traqocontainer.com/api/v1/carriers?search=maersk" \ -H "Authorization: Bearer YOUR_API_KEY"
Response
→ your endpoint URLWebhooks
Instead of polling, subscribe to events and we'll POST them to your server as they happen — a shipment's status or ETA changes, or carrier auto-discovery recovers a shipment that first failed to track. Register and manage endpoints from the Webhooks tab of your dashboard; you can subscribe each endpoint to specific events or to all of them.
Every delivery is a JSON POST with the same envelope — an event name, a created unix timestamp (seconds), and an event-specific data object:
{
"event": "shipment.updated",
"created": 1754170000,
"data": {
"shipment_id": "SHP-000481",
"reference_number": "MSCU1234567",
"carrier": "MSCU",
"status": "IN_TRANSIT",
"previous_status": "BOOKED",
"eta": "2026-08-14T09:00:00.000Z",
"previous_eta": "2026-08-12T09:00:00.000Z"
}
}Request headers
| Header | Description |
|---|---|
X-Traqo-Event | The event name (also in the body), so you can route without parsing. |
X-Traqo-Delivery | Unique id for this delivery attempt's delivery record — use it to dedupe (deliveries are at-least-once). |
X-Traqo-Signature | HMAC signature of the body — see Verifying signatures. |
User-Agent | Traqo-Webhooks/1 |
Delivery & retries
Acknowledge a delivery by responding with any 2xx status within 10 seconds — respond first, then do your processing asynchronously. Any non-2xx response, or a timeout, is retried with exponential backoff (≈30s, 1m, 2m, 4m … capped at 6h) up to the configured attempt limit, after which the delivery is marked failed.
2xx never reached us). Make your handler idempotent by de-duplicating on X-Traqo-Delivery.Webhook events
Subscribe an endpoint to any of these events, or to * for all of them.
| Event | Fires when |
|---|---|
shipment.updated | A tracked shipment's status changes (any transition other than arrival). |
shipment.arrived | A tracked shipment's status becomes delivered / completed. |
eta.changed | The carrier ETA for a tracked shipment changes. |
discovery.recovered | Carrier auto-discovery found the carrier for a shipment that first failed to track — it's now live. |
The three shipment events share the data shape shown in the envelope above (status/previous_status carry the transition; eta/previous_eta the ETA move). discovery.recovered carries the recovered carrier:
{
"reference_number": "MSCU1234567",
"type": "Container",
"carrier": "MSCU",
"carrier_name": "MSC",
"shipment_id": "SHP-000481"
}Verifying signatures
Every delivery is signed so you can confirm it came from Traqo and wasn't tampered with. The X-Traqo-Signature header has a timestamp and a signature:
X-Traqo-Signature: t=1754170000,v1=5f3b1a…c9d2
v1 is the HMAC-SHA256, as lowercase hex, of the string <t>.<raw request body>, keyed with your endpoint's signing secret (whsec_…, shown once when you create or rotate the endpoint). To verify: recompute the HMAC over t + "." + the raw body, compare it to v1 in constant time, and reject anything whose t is more than a few minutes (300s) from now to stop replay.
JSON.parse. Re-serializing the parsed object can reorder keys or change whitespace and the signature won't match.import { createHmac, timingSafeEqual } from 'node:crypto'
// Capture the RAW body for this route (do NOT let a JSON parser consume it first):
// app.post('/webhooks/traqo', express.raw({ type: 'application/json' }), handler)
function verify(rawBody, header, secret, toleranceSec = 300) {
const parts = Object.fromEntries(header.split(',').map(s => s.split('=')))
const t = Number(parts.t)
if (!t || !parts.v1) return false
if (Math.abs(Math.floor(Date.now() / 1000) - t) > toleranceSec) return false
const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex')
const a = Buffer.from(parts.v1, 'hex'), b = Buffer.from(expected, 'hex')
return a.length === b.length && timingSafeEqual(a, b)
}
app.post('/webhooks/traqo', (req, res) => {
const raw = req.body.toString('utf8')
if (!verify(raw, req.get('X-Traqo-Signature'), process.env.TRAQO_WEBHOOK_SECRET)) {
return res.sendStatus(400)
}
const { event, data } = JSON.parse(raw)
res.sendStatus(200) // ack fast, then process asynchronously
// … handle `event` (dedupe on the X-Traqo-Delivery header) …
})