API v1

ShipScanner for developers

Container tracking over HTTPS. Send a container number, get back where it is, where it has been and when it is expected — as JSON, authenticated with a bearer token.

Connect SGL Advisor

SGL Advisor reads container tracking from ShipScanner. Tracking is the only thing the connection does for now, so its token needs the tracking scope and nothing else.

  1. 1

    Create a token for SGL Advisor

    In ShipScanner, open Account → API tokens. Name it “SGL Advisor”, tick the tracking scope only, and create it. The token is shown once — copy it then.

  2. 2

    Give SGL Advisor the base URL and the token

    Store the token as a server-side secret in SGL Advisor, never in browser code. Every request sends it in the Authorization header.

  3. 3

    Track a container

    Call the endpoint below with a container number. A 200 with status "ok" means the connection works end to end.

Base URL
https://shipscanner.co/api/v1
Header
Authorization: Bearer ssk_…
Token scope
tracking

Open API tokens — revoke the token there at any time and the connection stops on its next request.

Authentication

Every request carries an API token in the Authorization header. Tokens start with ssk_, belong to the account that created them and are shown once, at creation. A request counts against that account’s plan.

curl https://shipscanner.co/api/v1/tracking/containers/MSKU1234565 \
  -H "Authorization: Bearer ssk_your_token_here"

Keep tokens on a server. Anything shipped to a browser or a mobile app can be read by its user.

Track a container

GET /api/v1/tracking/containers/{number}

number is an ISO 6346 container number such as MSKU1234565: four letters, six digits and a check digit. The carrier is detected from the number; there is nothing else to send.

Response

The shape is exact; the values below are illustrative, not a real shipment.

{
  "status": "ok",
  "data": {
    "containerNumber": "MSKU1234565",
    "status": "IN_TRANSIT",
    "isoType": "45G1",
    "carrierScac": "MAEU",
    "vessel": {
      "name": "EXAMPLE VESSEL",
      "imo": "9000001",
      "voyage": "123E"
    },
    "origin": {
      "locode": "TRIST",
      "name": "Istanbul",
      "country": "TR",
      "lat": 41.01,
      "lng": 28.98
    },
    "destination": {
      "locode": "NLRTM",
      "name": "Rotterdam",
      "country": "NL",
      "lat": 51.95,
      "lng": 4.14
    },
    "departedAt": "2026-01-04 18:30:00",
    "arrivedAt": null,
    "eta": "2026-01-16 06:00:00",
    "events": [
      {
        "at": "2026-01-04 18:30:00",
        "atUtc": null,
        "description": "Vessel departure",
        "code": "VD",
        "actual": true,
        "location": {
          "locode": "TRIST",
          "name": "Istanbul",
          "country": "TR",
          "lat": 41.01,
          "lng": 28.98
        },
        "vessel": {
          "name": "EXAMPLE VESSEL",
          "imo": "9000001",
          "voyage": "123E"
        }
      }
    ]
  },
  "meta": {
    "requestId": "0b0f6a1e-2f0c-4a53-9d0e-5c1f3f1f7a10",
    "mode": "live"
  }
}
Field Type Meaning
containerNumber string The number as tracked: upper case, no spaces.
status string | null The carrier’s current status for the container.
isoType string | null ISO 6346 size and type code, for example 45G1.
carrierScac string | null SCAC of the operating carrier, when it was identified.
vessel object | null name, imo and voyage of the vessel carrying it.
origin, destination object locode (UN/LOCODE), name, country (ISO alpha-2), lat, lng. Any of them may be null.
departedAt string | null Actual departure from the port of loading. null until it has sailed.
arrivedAt string | null Actual arrival at the port of discharge. null until it has arrived.
eta string | null Estimated arrival at the port of discharge.
events[] array Every reported milestone, oldest first.
events[].at string | null Local time at the event’s location (the port’s wall clock).
events[].atUtc string | null The same instant in UTC, when it is known.
events[].actual boolean | null true has happened; false is planned or estimated.
events[].description, code string | null The carrier’s wording and event code.
events[].location, vessel object Same shapes as origin and vessel above.

A value nobody has reported is null, never an empty string. New fields may be added to v1; existing ones are not renamed, removed or retyped.

meta.asOf, when present, is the time (UTC) the carrier data in this answer was read. It appears only when the answer comes from a container ShipScanner is already watching for changes; an answer without it was read from the carrier feed for this request.

Errors

A failure has the same envelope whatever went wrong. Branch on code; message is for people and may be reworded.

{
  "status": "error",
  "code": "not_found",
  "message": "No tracking information was found for this container yet."
}
HTTP code When
400 invalid_request Not a valid ISO 6346 container number (4 letters, 7 digits, check digit).
401 unauthenticated No token, or the token is unknown, revoked or expired.
402 quota_exceeded The account’s plan has no tracking allowance left this month. details carries plan and usage.
403 insufficient_scope The token carries neither the tracking nor the read scope.
404 not_found No carrier has published tracking for this container yet.
429 rate_limited More than 30 lookups in a minute. Wait for Retry-After seconds.
502 upstream_error The tracking provider failed. Safe to retry.

Limits

  • 30 requests a minute per token. Each response reports the budget in X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset; a 429 adds Retry-After.
  • A monthly tracking allowance set by the account’s plan. Each container found uses one lookup; a 4xx answer, including a container that was not found, uses none.
  • Poll gently. Carriers update a container a few times a day at most, so asking about the same one more than hourly spends allowance without learning anything.

A client in a dozen lines

TypeScript, no dependencies. The same four branches cover every answer the endpoint gives.

const SHIPSCANNER_API = 'https://shipscanner.co/api/v1'

export async function trackContainer(number: string) {
  const res = await fetch(
    `${SHIPSCANNER_API}/tracking/containers/${encodeURIComponent(number)}`,
    { headers: { Authorization: `Bearer ${process.env.SHIPSCANNER_API_TOKEN}` } },
  )
  const body = await res.json()
  if (body.status === 'ok') return body.data

  // Branch on `code`, never on `message`.
  if (body.code === 'not_found') return null
  if (body.code === 'rate_limited') {
    throw new Error(`Retry in ${res.headers.get('Retry-After')}s`)
  }
  throw new Error(`ShipScanner ${body.code}: ${body.message}`)
}

Building something this page does not cover? Tell us what you need.