# Container Tracking API & MCP: Maersk, MSC, COSCO & More

URL: https://docs.lastscanned.com/container-tracking-api

Track containers from Maersk (including Hamburg Süd), MSC, COSCO, Evergreen, ONE and Yang Ming through the same [tracking API](/tracking) and [MCP tools](/mcp) as parcels, in the same normalized JSON. Carrier inference works where the number identifies the line; leased prefixes such as `TGHU` and `TCNU` require explicit carrier selection. [Container watches](/webhooks) recheck every 6 hours (hourly once a container is out for delivery).

No booking, bill of lading or shipping line account is needed: the container number is enough.

---

## Supported Shipping Lines

Every example below is a real container looked up in November 2026.

| Line | Slug (`?carrier=`) | Owner codes detected | Example containers | What you get |
|---|---|---|---|---|
| **[Maersk](/carriers/maersk)** (with Hamburg Süd) | `maersk` | `MSKU` `MRKU` `MRSU` `MNBU` `SUDU` | `MSKU7812341` · `MNBU0123457` | Every move with vessel and voyage, local times, ETA |
| **[MSC](/carriers/msc)** | `msc` | `MSCU` `MEDU` `MSDU` `MSMU` `MSNU` `MSBU` | `MEDU1234562` · `MSCU6789016` | Every move incl. transshipments, bill of lading, ETA |
| **[COSCO Shipping](/carriers/cosco)** | `cosco` | `CSNU` `CBHU` `CCLU` `CSLU` `FCIU` | `CSNU8123465` · `CSNU7123455` | Latest move only (COSCO's public limit) |
| **[Evergreen](/carriers/evergreen)** | `evergreen` | `EGHU` `EGSU` `EISU` `EITU` | `EGHU9123457` · `EGSU9123450` | Every move on the bill of lading, with vessel and voyage |
| **[ONE](/carriers/one)** (Ocean Network Express) | `one` | `ONEU` `KKFU` | `ONEU1234566` · `ONEU0123450` | Every move with exact UTC times, vessel and voyage, ETA |
| **[Yang Ming](/carriers/yangming)** | `yangming` | `YMMU` | `YMMU6123458` · `YMMU7123452` | Every move incl. rail legs, with vessel and voyage |

---

## 1. Container Numbers and Auto-Detection

A container number (ISO 6346) is the owner's 3-letter code plus `U`, six digits and a check digit: `MSKU 781234 1`. lastscanned checks the check digit and sends the number to the line that owns the code, so `GET /v1/track/MSKU7812341` goes to Maersk with no `?carrier=`.

**Leased containers need `?carrier=`.** About half the world's boxes belong to leasing companies (Textainer `TGHU`, Triton `TCNU` and `TRHU`, CAI `CAIU`, SeaCo `SEGU` and others) and sail with any line. Their owner code says nothing about the line, so name it:

```bash
curl -s "https://api.lastscanned.com/v1/track/TGHU9123451?carrier=msc" \
  -H "Authorization: Bearer $LASTSCANNED_API_KEY"
```

Without `?carrier=`, a leased container answers `400 unknown_carrier` with a message saying to name the line. It is not billed.

---

## 2. Statuses for a Container's Trip

Containers use the same seven statuses as parcels, mapped onto a container's trip:

| Status | Container move |
|---|---|
| `label_created` | Empty container released to the shipper |
| `in_transit` | Received full at the origin terminal, loaded, sailing, transshipped, discharged |
| `out_for_delivery` | Full container leaving the destination terminal for the consignee |
| `delivered` | Empty container returned: the trip is over |

The shipping lines don't report when the consignee unloads the goods, so `delivered` means the empty box is back. Watch for `out_for_delivery` if you need to know when the cargo left the port.

---

## 3. Times, ETAs and Bills of Lading

- **Times**: most lines give local port time without a zone (MSC and Evergreen give a date only), so `localTime` is set and `timestamp` is `null`. Never read a container's `localTime` as UTC. ONE is the exception: it gives both, so both are exact.
- **ETA**: Maersk's, MSC's and ONE's estimated arrival is `estimatedDelivery` (ONE's also as `estimatedDeliveryAt` in UTC). Planned moves are never mixed into `events`.
- **Bill of lading or booking**: MSC and Evergreen name the bill the container sails under in `message`; ONE names the booking.

---

## 4. Watches and Webhooks for Containers

A container trip runs for weeks, so polling is wasteful. Set your webhook once, then create a [watch](/webhooks) per container. lastscanned re-checks it every 6 hours (hourly once it is out for delivery) and posts to your webhook when its status, newest move or ETA changes:

```bash
curl -s -X PUT "https://api.lastscanned.com/v1/webhook" \
  -H "Authorization: Bearer $LASTSCANNED_API_KEY" \
  -H "content-type: application/json" \
  -d '{"url": "https://example.com/hooks/containers"}'

curl -s -X POST "https://api.lastscanned.com/v1/watches" \
  -H "Authorization: Bearer $LASTSCANNED_API_KEY" \
  -H "content-type: application/json" \
  -d '{"trackingNumber": "MEDU1234562", "metadata": {"po": "PO-1042"}}'
```

A watch ends by itself when the empty container is returned, or after 90 days. On Pro, a container counts once per billing period however often it is re-checked.

---

## 5. Limits Worth Knowing

- **COSCO** shows a container's latest move only, without the bill of lading. COSCO's firewall also blocks bursts, so a batch of COSCO containers is looked up one at a time.
- **Maersk and MSC** are read through a browser session (their sites refuse plain server requests), so a lookup takes several seconds. One session serves a whole batch.
- **Other lines** (CMA CGM, Hapag-Lloyd, HMM, ZIM) are not supported yet.

## Related Resources

- **Formats and check digits**: [Tracking number formats & auto-detection](/formats)
- **Freight by road**: [LTL freight & heavy parcel tracking](/ltl-freight-and-heavy-parcel-tracking-api)
- **Agents**: [MCP server](/mcp)
