Developers

API Reference

Track ocean and air shipments programmatically — start trackings, follow live milestones, and read full container timelines over a simple REST API.

Base URL  https://api.navlo.ioAPI  v1Format  JSON

01 · Overview

The shipment tracking API

Navlo exposes the same tracking engine that powers the app through a public REST API: start tracking a container, B/L, booking or AWB number, list your shipments, and read full detail — containers or air waybills, ports, dates and the merged event timeline. Requests authenticate with organization-level keys created in Settings → API by an organization admin.

Quick start

  1. 1Create a key. In the app, open Settings → API and create a key with the REST API scope. The full key is shown once — store it in your secret manager.
  2. 2Start tracking. POST /v1/shipments with the identifier. Navlo detects the carrier and begins pulling data.
  3. 3Read the shipment. Poll GET /v1/shipments/{id} to follow progress — status moves from discovering to active as carrier data arrives.

02 · Authentication

Authentication

Every request carries your key in the X-API-Key header.

  • Base URL: https://api.navlo.io — all paths on this page are relative to it, and all request and response bodies are JSON.
  • REST keys start with nvl_ and are created in the API keys section of the app. The full key is shown once, at creation.
  • Requests act as the key's creator: shipments started through the API appear in the app under their name and draw from the organization's credit pool.
  • Revoking a key blocks it immediately.

Example

curl https://api.navlo.io/v1/shipments \
  -H "X-API-Key: nvl_your_key_here"

03 · Rate limit & errors

Rate limit & errors

60 requests per minute per key. Errors are JSON with a single error field.

400Missing or unknown mode, or an empty / oversized batch — {"error": "mode is required: OCEAN or AIR"} and the like.
401Missing, invalid or revoked key — {"error": "Invalid or revoked API key"}
404Shipment id doesn't exist or belongs to another organization (empty body).
429More than 60 requests in the current minute — {"error": "Rate limit exceeded (60 requests/minute)"}. Back off and retry the next minute.

04 · REST endpoints

Start tracking

POST/v1/shipmentsStart tracking an identifier

Body fields

identifierrequiredContainer, B/L, booking or AWB number.
moderequired"OCEAN" or "AIR". Missing or anything else answers 400 {"error": "mode is required: OCEAN or AIR"}. An unambiguous identifier still wins: an AWB with a known airline prefix and a valid check digit is tracked as AIR, an ISO 6346 container number as OCEAN — the mode in the response tells you which.
carrieroptionalCarrier code (e.g. MAERSK, MSC) or the carrier in your own wording, like "Hapag Lloyd" or "MAERSK LINE A/S" — known names resolve instantly, Navlo AI maps new wordings, and anything unresolvable is still accepted and auto-detected from the identifier (never rejected). For AIR shipments the field is informational only — the carrier is detected from the AWB prefix.
  • Idempotent: re-posting an identifier you already track returns your existing shipment — no duplicate, no extra charge.
  • A new identifier uses one credit from the organization pool (free when a teammate already tracks the same identifier).

Request

curl -X POST https://api.navlo.io/v1/shipments \
  -H "X-API-Key: nvl_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"identifier": "DEMO1234567", "mode": "OCEAN"}'

Response — 201 Created

{
  "id": "6b1f0c9a-4d2e-4f7b-9a3c-8e5d21f0a4b7",
  "identifier": "DEMO1234567",
  "mode": "OCEAN",
  "status": "discovering",
  "carrier": null,
  "createdAt": "2026-08-01T09:12:44Z"
}

carrier fills in once carrier data arrives — poll the detail endpoint to follow progress.

04 · REST endpoints

Batch start tracking

Bulk-push up to 50 shipments in one call — built for integration platforms. The whole batch counts as a single request against the rate limit.

POST/v1/shipments/batchStart tracking up to 50 identifiers

Body fields

shipmentsrequiredArray of shipments to start tracking — 1 to 50 items. An empty array or more than 50 items returns 400.
shipments[].identifierrequiredContainer, B/L, booking or AWB number.
shipments[].moderequired"OCEAN" or "AIR" — same rules as the single endpoint. An item without it fails alone with error "mode is required: OCEAN or AIR".
shipments[].carrieroptionalCarrier code or the carrier in your own wording — resolved exactly like the single-shipment endpoint; unresolvable wordings are still accepted and the carrier is auto-detected from the identifier.

Request

curl -X POST https://api.navlo.io/v1/shipments/batch \
  -H "X-API-Key: nvl_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"shipments": [
    {"identifier": "MEDU1234567", "mode": "OCEAN", "carrier": "MSC"},
    {"identifier": "BKNG9876543", "mode": "OCEAN", "carrier": "Hapag Lloyd"},
    {"identifier": "176-12345678", "mode": "AIR"}
  ]}'

Response — 200 OK

{
  "submitted": 3, "created": 2, "existing": 1, "errors": 0,
  "results": [
    { "identifier": "MEDU1234567", "status": "created", "id": "9f4b1c2e-demo-uuid" },
    { "identifier": "BKNG9876543", "status": "existing", "id": "5a3d8e0f-demo-uuid" },
    { "identifier": "176-12345678", "status": "created", "id": "c7e2a941-demo-uuid" }
  ]
}

Per-item status

createdNew shipment started — uses one credit from the organization pool.
existingYou already track this identifier — no charge; the id of your existing shipment is returned.
errorThe item failed — error says why (e.g. "Insufficient credits"). Items fail individually; the rest of the batch is unaffected.

04 · REST endpoints

List shipments

GET/v1/shipmentsPaginated shipment list

Query parameters

pageoptionalZero-based page index. Default 0.
sizeoptionalPage size. Default 20, maximum 100.

Request

curl "https://api.navlo.io/v1/shipments?page=0&size=20" \
  -H "X-API-Key: nvl_your_key_here"

Response — 200 OK

{
  "page": 0,
  "size": 20,
  "totalElements": 143,
  "totalPages": 8,
  "shipments": [
    {
      "id": "8f41c2e5-5b0a-4a4e-9d3b-2f6a8e9c1d07",
      "identifier": "DEMO7654321",
      "mode": "OCEAN",
      "status": "active",
      "carrier": "MSC",
      "createdAt": "2026-07-18T14:03:11Z"
    }
  ]
}

04 · REST endpoints

Shipment detail

GET/v1/shipments/{id}Full shipment detail

Path parameters

idrequiredThe shipment UUID from the create or list response.

Ocean shipments carry containers with their dates and ports; air shipments carry airShipments — the waybill with its airports, dates, pieces, weight and flights. Both share the merged event timeline, oldest first (up to 200 events per container). Branch on mode: the other list is always empty.

Request

curl https://api.navlo.io/v1/shipments/8f41c2e5-5b0a-4a4e-9d3b-2f6a8e9c1d07 \
  -H "X-API-Key: nvl_your_key_here"

Response — 200 OK

{
  "id": "8f41c2e5-5b0a-4a4e-9d3b-2f6a8e9c1d07",
  "identifier": "DEMO7654321",
  "mode": "OCEAN",
  "status": "active",
  "carrier": "MSC",
  "containers": [
    {
      "containerNumber": "DEMO7654321",
      "containerType": "40HC",
      "status": "DISCHARGED",
      "etd": "2026-07-02T18:00:00Z",
      "atd": "2026-07-02T21:47:00Z",
      "eta": "2026-07-28T06:00:00Z",
      "ata": "2026-07-27T22:15:00Z",
      "origin": { "code": "CNSHA", "name": "Shanghai" },
      "destination": { "code": "NLRTM", "name": "Rotterdam" },
      "currentVessel": "MSC MARTINA"
    }
  ],
  "events": [
    {
      "timestamp": "2026-07-02T21:47:00Z",
      "type": "ACTUAL",
      "status": "DEPARTED",
      "location": "Shanghai",
      "locationCode": "CNSHA",
      "vessel": "MSC MARTINA",
      "voyage": "428W"
    },
    {
      "timestamp": "2026-07-27T22:15:00Z",
      "type": "ACTUAL",
      "status": "ARRIVED",
      "location": "Rotterdam",
      "locationCode": "NLRTM",
      "vessel": "MSC MARTINA",
      "voyage": "428W"
    },
    {
      "timestamp": "2026-07-28T08:31:00Z",
      "type": "ACTUAL",
      "status": "DISCHARGED",
      "location": "Rotterdam",
      "locationCode": "NLRTM",
      "vessel": null,
      "voyage": null
    },
    {
      "timestamp": "2026-07-30T10:00:00Z",
      "type": "EXPECTED",
      "status": "GATE_OUT",
      "location": "Rotterdam",
      "locationCode": "NLRTM",
      "vessel": null,
      "voyage": null
    }
  ]
}

Response — 200 OK (air shipment)

{
  "id": "3bb31ae0-54be-46c5-abe8-86b4dbf5a681",
  "identifier": "871-54934946",
  "mode": "AIR",
  "status": "active",
  "carrier": "SUPARANA_AIRLINES",
  "containers": [],
  "airShipments": [
    {
      "awbNumber": "871-54934946",
      "status": "DELIVERED",
      "origin": { "code": "CKG", "name": "Chongqing Jiangbei International Airport" },
      "destination": { "code": "AMS", "name": "Amsterdam Airport Schiphol" },
      "currentAirport": { "code": "AMS", "name": "Amsterdam Airport Schiphol" },
      "etd": null,
      "atd": "2026-09-14T13:44:00Z",
      "eta": null,
      "ata": "2026-09-16T01:52:00Z",
      "pieces": 342,
      "weight": 4574.0,
      "weightUnit": "KILOGRAM",
      "chargeableWeight": 4574.0,
      "volume": 22.14,
      "volumeUnit": null,
      "flights": [
        {
          "flightNumber": "Y87931",
          "from": { "code": "CKG", "name": "Chongqing Jiangbei International Airport" },
          "to": { "code": "AMS", "name": "Amsterdam Airport Schiphol" },
          "etd": "2026-09-14T13:30:00Z",
          "atd": "2026-09-14T13:44:00Z",
          "eta": "2026-09-14T18:17:00Z",
          "ata": null,
          "status": "BOOKED"
        }
      ]
    }
  ],
  "events": [
    {
      "timestamp": "2026-09-14T14:00:00Z",
      "type": "ACTUAL",
      "status": "Departed",
      "location": "Chongqing Jiangbei International Airport",
      "locationCode": "CKG",
      "vessel": null,
      "voyage": null,
      "flight": "Y87931",
      "pieces": 342,
      "weight": 4574.0
    },
    {
      "timestamp": "2026-09-16T06:43:00Z",
      "type": "ACTUAL",
      "status": "Delivered",
      "location": "Amsterdam Airport Schiphol",
      "locationCode": "AMS",
      "vessel": null,
      "voyage": null,
      "flight": null,
      "pieces": 342,
      "weight": 4574.0
    }
  ]
}

Field notes

containers[].statusCurrent milestone code for the container (e.g. GATE_OUT, DISCHARGED).
events[].statusNavlo's normalized status when the carrier event is mapped; otherwise the carrier's own status text.
events[].typeACTUAL — the event has happened. EXPECTED — a carrier projection that may still change. Null when the carrier doesn't say.
airShipments[]AIR only — one entry per air waybill: status (e.g. IN_TRANSIT, ARRIVED, DELIVERED), origin / destination / currentAirport as IATA code + name, ETD/ATD/ETA/ATA, pieces, weight (weightUnit KILOGRAM or POUND, null reads as kg), chargeable weight, volume, and the booked or flown flights in order. Empty for ocean shipments.
events[].flight / pieces / weightAIR only — the flight an event refers to and the pieces/weight it covers (split consignments report a partial count per leg). Null on ocean events; vessel/voyage are null on air events.
origin / destinationPort code (UN/LOCODE for ocean, IATA for air) and port name. Fields are null until the carrier reports them.

05 · Statuses & timestamps

Statuses & timestamps

What the status values mean, and how to read date-times.

Shipment status

discoveringNavlo is identifying the carrier and pulling the first data. Shipments stay here while no carrier data has been found yet.
resolvingCarrier found — the first full dataset is being resolved.
activeTracked and refreshed on Navlo's polling schedule.
completedThe shipment reached its final milestone; polling has ended.
stoppedTracking was stopped — by a user, or by Navlo support for identifiers that turn out to be untrackable.
Reading timestamps. All timestamps are ISO-8601. createdAt is UTC. Carrier-sourced times — container ETD/ATD/ETA/ATA and event timestamps — carry the carrier's reported local port time with a Z suffix: display them verbatim and don't shift them into the viewer's timezone.

Questions or missing capability? contact@navlo.io