# Container Tracking: MSC, Maersk Cargo Updates, Demurrage Alerts (`rich_minds/multi-carrier-tracking-ai`) Actor

First 25 container updates free. Container tracking for any carrier, pay per qualified container update — junk never charged by us. Where is my container, container status alerts and ETA slips in one format. Free demo on any plan. Replaces 15 single-carrier scrapers; MCP-ready.

- **URL**: https://apify.com/rich_minds/multi-carrier-tracking-ai.md
- **Developed by:** [Rich Minds](https://apify.com/rich_minds) (community)
- **Categories:** Automation, Integrations, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.70 / 1,000 container update (rules only)s

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Container Tracking: MSC, Maersk Cargo Updates, Demurrage Alerts

**Every container, any carrier, one table — only the changes in your inbox daily.**

⚡ First **25 container updates free** · 💵 ≈ $0.014 per container update all in, the carrier source lookup included — $0.0037 of it is ours · 🤖 AI ≈ $0.020 all in · ⏱️ demo in seconds

![Sample output — one row per container update: carrier, status, what changed, ETA slip, last free day, the note](https://api.apify.com/v2/key-value-stores/DytILlSa5kpBItmj6/records/shortlist.svg)

> **Try it in 30 seconds.** Click **Try it** — the form holds 5 sample containers; the demo runs on any plan.
> → [What a run costs](#-pricing--what-a-run-really-costs) · [What the AI adds](#-what-the-ai-tier-adds) · [Run it weekly](#-run-it-weekly)

### ⚡ At a glance

| | |
|---|---|
| **What you get** | one row per container in ONE format — status, milestone, ports, vessel, ETA, ETA slip, last free day, a note |
| **You provide** | container, B/L or booking numbers — any carriers; or paste an e-mail, or link a Google Sheet |
| **Typical run** | 40 containers, 4 carriers → 40 rows: $0.55 rules only · $0.82 with the AI, lookups included; then only those that moved (≈ 6 a day) |
| **Per update, all in** | ≈ $0.014 rules only · ≈ $0.020 with the AI (the carrier lookup on your account included) |
| **Free tier** | your first 25 container updates, once per Apify account |
| **Keys / setup** | Starter plan and up include Apify model access: AI on every update, no key · Free plan: your own key — free Gemini = 1–3 AI verdicts per run, paid = every update |
| **Which plan** | Free demo and your own results (`list`, [template](https://api.apify.com/v2/key-value-stores/DytILlSa5kpBItmj6/records/list-template.json)): any plan · live tracking and the e-mail digest: a plan that runs Store Actors |
| **Carriers** | MSC, Maersk, CMA CGM · in beta — free here, the carrier lookup still ≈ $0.01 on your account: COSCO, OOCL, ONE, Hapag-Lloyd, Evergreen, HMM, Yang Ming, ZIM, Wan Hai, PIL, SITC, Emirates Shipping |
| **Works with** | Schedules, webhooks, Google Sheets, Slack, Teams, Telegram, Make / n8n / Zapier, MCP |

### 🎯 What this Actor does

- **Replaces 15 single-carrier scrapers** — one list, any carrier, routed by its prefix (`MSCU…` → MSC).
- **One milestone format** — DCSA codes (`LOAD`, `DEPA`, `ARRI`, `DISC` …), one status, the ETA in port time and
  UTC.
- **Demurrage in dollars** — the last free day, the box first 3 days before it, the money at risk in the digest.
- **Only changes, fewer paid lookups** — unchanged containers and typos (ISO 6346 check digit) are free; finished and
  far-off boxes are looked up less.

#### 🚢 Multi carrier container tracking and bill of lading tracking

A container is routed by its owner code, a B/L by its prefix (`MEDU…`); a booking needs the carrier
(`254839107, Maersk, PO-4411, 5`); a pasted e-mail works too.

#### ⏰ Container status alerts — the ETA slip in days

`etaSlipDays` = days the ETA moved since the last run (+ = later); ≥ 1 day sets `eta_slipped` / `eta_earlier` and
ranks the row first.

#### 💰 Demurrage alerts — the last free day and the money at risk

`freeTimeDays`: discharge day + free days = `lastFreeDay`; `dd_risk` 3 days before it. `demurrageAtRiskUsd` = (days
past it + 3) × `demurrageRatePerDay` ($150); the digest opens with "≈ $1,350 of demurrage at risk — 3 boxes".

### 🚀 How to use it

1. **Click `Try it`** — the free demo: 5 sample containers from 4 carriers and one typo.
2. **Paste your numbers** (or link your Google Sheet) — they replace the sample and start the live lookup on your
   Apify account, capped by "Max source spend". Leave "AI assessment" on.
3. **Press Start** — demurrage risks, ETA slips and discharges come first. Export, or wire a webhook.
4. **Schedule it daily** with `notifyEmail` — only containers that changed are delivered and charged.

![The Consignee alerts view — the line to forward and the next step](https://api.apify.com/v2/key-value-stores/DytILlSa5kpBItmj6/records/alerts.svg)

📚 **Learn more:** [Tutorial — track containers from any carrier with this Actor](https://api.apify.com/v2/key-value-stores/DytILlSa5kpBItmj6/records/tutorial.html)

### 🚢 Track by carrier, one table

#### MSC cargo tracking

`MSCU`, `MEDU` containers and B/Ls.

#### Maersk tracking

Maersk, Sealand, Hamburg Süd (`MAEU`, `MSKU`, `SUDU`) in DCSA codes.

#### CMA CGM, COSCO, OOCL and Wan Hai container tracking

CMA CGM's DCSA events as they are; COSCO, OOCL and Wan Hai in beta — free here, the lookup still ≈ $0.01 on your
account.

### 🆚 Why this instead of running each carrier scraper yourself?

| | [MSC Cargo Tracking Scraper](https://apify.com/muhammetakkurtt/msc-cargo-tracking-scraper) + its 14 siblings | **This Actor** |
|---|---|---|
| **Your time per 100 containers** | ≈ 1.5 h a check (estimate, not timed yet) — merge 4 shapes, re-read every row | ≈ 5 min (estimate) — read the 20 changed rows |
| **Same 100 containers, 4 carriers, all in** | $1.00 a check: 100 unsorted rows | $1.37 rules only · $2.03 with the AI memo — 100 ranked rows, junk costs $0 here |
| **Next check, 20 of 100 moved** | $1.00 again | the same lookups + $0.07 here rules only · + $0.21 with the AI |

**How that compares** — the lookups are the same $0.01 either way; here you add $0.0037 per update that changed, and
one replaces ≈ 1 raw lookup row you would read by hand: $0.0037 per raw-row equivalent.

#### vs other container tracking Actors and free tools

| | 100 containers | What it lacks |
|---|---|---|
| **This Actor** | $1.37 rules · $2.03 AI | — |
| [Container Tracking API](https://apify.com/nabeelbaghoor/container-tracking-api) (`nabeelbaghoor`) | $1.50 + $0.005 a milestone + Vizion contract | your own Vizion key; no change-only monitor |
| [MSC Container Tracking Scraper](https://apify.com/brii3343/msc-container-tracking-scraper) (`brii3343`) | $0.20 | other carriers, alerts |
| Carriers' own pages · [track-trace.com](https://www.track-trace.com/container) | free | one table, export, API, alerts |

### 💵 Pricing — what a run really costs

| Event | When it is charged | Price |
|---|---|---|
| `free-tier` | your first 25 container updates on this Actor, in any mode | **$0.00** |
| `qualified-container-update-basic` | AI off — the one-format row, flags, the note | **$0.0037** |
| `qualified-container-update-ai` | AI on — plus the AI codes, note and alert / extension request | **$0.0092** |

**Never charged here:** invalid numbers, unknown carriers, failed lookups, unchanged containers, beta-carrier rows
(free here; their lookup still bills your account), rows below `minScore`, your exclude list. **The lookups are not
ours to waive:** each carrier's Actor bills your account $0.01 a result ($0.004 ONE, Wan Hai) + $0.001 per run,
capped by `maxDiscoveryChargeUsd`.

**Worked example** — 40 containers on 4 carriers, AI on, first run: lookups 40 × $0.01 + 4 × $0.001 = $0.40 on your
account + 40 × $0.0092 + 40 × $0.0011 tokens = **$0.82, ≈ $0.020 per container update** ($0.59 with your first 25
free). Daily after that, ≈ 6 move and ≈ 20 are looked up (smart polling): $0.20 lookups + 6 × $0.0092 + 6 ×
$0.0011 tokens = **$0.26 a day**; rules $0.22.

**At volume** — a forwarder, 300 containers weekly, ≈ 75 % moving: $3.00 lookups + 225 × $0.0037 = **$3.83 a week**
rules only (≈ $17 a month); AI on: $3.00 + 225 × $0.0092 + 225 × $0.0011 tokens = $5.32.

**AI tier, all in:** $0.0092 + ≈ $0.0011 tokens (1,961 input + 214 output per update, gemini-3.5-flash-lite at
OpenRouter's rates, measured 2026-10-24) ≈ $0.010 per container update — 2.8× the basic price; ≈ $0.020 all in with
the lookup.

### 🤖 What the AI tier adds

The same container update, AI **off**:

```json
{"trackingNumber": "MSCU7421055", "status": "DISCHARGED", "lastFreeDay": "2026-10-26", "flags": ["dd_risk", "arrived", "milestone_changed"], "recommendedNextStep": "Book the pickup at ROTTERDAM, NL (NLRTM) before 2026-10-26, or ask MSC for a free-time extension today."}
```

AI **on** (`qualified-container-update-ai`, $0.0092) — the own-key AI run of the sample (INPUT.ai.json), 2026-10-24:

```json
{
  "trackingNumber": "MSCU7421055", "status": "DISCHARGED", "lastFreeDay": "2026-10-26", "score": 95,
  "assessedBy": "ai", "aiModel": "google:gemini-3.5-flash-lite",
  "scoreReasons": ["status is DISCHARGED at NLRTM", "ddRisk is true with lastFreeDay on 2026-10-26", "milestone is DISC on 2026-10-22"],
  "generatedText": "Request free-time extension or urgent pickup slot for MSCU7421055 at ROTTERDAM, NL before last free day 2026-10-26."
}
```

The AI writes the extension / pickup request for a box at demurrage risk — one avoided $150 day pays for ≈ 16,000 AI
updates — and reads the events ("Vessel skipped the Singapore call" → "a risk of a missed connection"). It saves ≈ 3
min each on 4 sample containers — read by hand vs the AI rows, 2026-10-24.

**How much AI do I get on my plan?** AI on every update with Apify's model access (Starter and up) or a paid
own key; a free Gemini key (aistudio.google.com/apikey) gives 1–3 AI verdicts per run; else the demo shows sample verdicts.

### ⚙️ Input

| Field | Default | What it does |
|---|---|---|
| `trackingNumbers` | — | Numbers or pasted text; `NUMBER, carrier, reference, free days` per line |
| `numbersSheetUrl` | — | A shared Google Sheet (or CSV link) read every run |
| `freeTimeDays` | `0` | Free days at the POD → `lastFreeDay` and the `dd_risk` flag; 0 = off |
| `sourceMode` | `live` | The mode that runs = this field, except `{}` = the free demo; `list` / `dataset` read results |
| `demurrageRatePerDay` | `150` | Your demurrage per box per day → `demurrageAtRiskUsd` and the digest's total |
| `maxDiscoveryChargeUsd` | `0.5` | Hard cap on the lookups on your account |
| `targetFlags` | — | Only updates with one of these flags (`dd_risk`, `arrived`, `eta_slipped` …) |
| `minScore` | `0` | 0 = every new or changed container; higher = only those needing attention |
| `enableAi` | `true` | AI codes, note and the alert / extension request on/off |
| `clientName`, `brandName` | — | White-label: your client's and your company's name on the REPORT and digest |
| `notifyEmail` | — | The digest by e-mail after every run with changes |

Also in the **Input** tab: `pollPolicy` (`smart`), `itemsList`, `datasetId`, `goalDescription`, `etaSlipAlertDays` (1), the AI model and key,
`suppressionList`, `dedupeAcrossRuns`, `includeEvents`, `sendAlertsTo`; `allowList`, `dedupeTtlDays`, `webhookSecret`,
API-only fields and every flag (`etaSlipped` works too) in the API reference (Integrations).

### 📤 Output

One item per new or changed container:

```json
{
  "url": "https://www.maersk.com/tracking/MSKU6104820", "itemId": "maersk:MSKU6104820",
  "shipmentKey": "maersk:MSKU6104820:2026-10-05", "dedupeKey": "maersk:MSKU6104820:38b303d007",
  "carrier": "Maersk", "status": "IN_TRANSIT", "milestone": "DEPA", "eta": "2026-11-02T06:00:00", "etaUtcOffset": "+00:00",
  "etaUtc": "2026-11-02T06:00:00Z", "etaSlipDays": 3, "changeType": "eta", "score": 90, "unverifiedCarrier": false,
  "note": "MSKU6104820 (Maersk) — in transit; last event: vessel departed (export) at MYTPP on 2026-10-06; ETA FELIXSTOWE, GB (GBFXT) 2026-11-02, 3 days later than at the last check."
}
```

`eta` = local time at the port; `etaUtc` for comparisons (the port's time zone by UN/LOCODE). **More fields**: `name`, `numberType`,
`carrierScac`, `routedBy`, `milestoneDescription`, `milestoneLocation`, `milestoneTime`, `milestoneIsActual`,
`daysToLastFreeDay`, `demurrageAtRiskUsd`, `problem`, `problemDetail`, `sourceActor`, `targetFlagsMatched`, `label`,
`chargedEvent`, `billedAs`, `checkedAt`.

**Upsert key:** `shipmentKey` (a reused box on its next voyage gets a new key, `changeType: new_shipment`); `itemId`
\= the box. **Sort by `score`; threshold and trend across runs on `ruleScore`** (`fitScore` = the AI's). `OUTPUT` holds
the run record (`trackingSummary`, `pollSkipped`, `numbersExtracted`); a **`DIGEST`** and a forwardable **`REPORT`**
sit next to it. Views: `Shortlist`, `Morning
table`, `Consignee alerts`, `Score breakdown`, `Export`.

⭐ **Found it useful? A review on the Store helps others find it** — it takes a minute on the Actor's page.

### 🔁 Run it weekly

1. **Open the saved task and click Schedule** after your first real run (`OUTPUT.scheduleTaskUrl`); `notifyEmail`
   e-mails the digest of every run with changes.
2. Leave **Only changes** on — an unchanged container is free. **Tracking for clients?** Save one task per client:
   its numbers, `clientName` + `brandName` (the REPORT and digest carry both) and its own `monitorStoreName`.
3. **Your list stays in your Google Sheet** (`numbersSheetUrl`) and the results come back into it: the
   [Apps Script](https://api.apify.com/v2/key-value-stores/DytILlSa5kpBItmj6/records/sheet-writeback.txt) writes status, ETA, last free day and the note next to each number.
4. **Send alerts to**: a Slack, Teams or Telegram link gets the digest; `webhookUrl` the rows — start from the
   [tracker sheet](https://api.apify.com/v2/key-value-stores/DytILlSa5kpBItmj6/records/tracker.csv). `historyDatasetName` keeps the log across months.

It renews by itself: ≈ 15 % of containers move a day. 40 containers weekly, ≈ 30 moving: $0.40 lookups + 30 ×
$0.0092 + 30 × $0.0011 tokens ≈ **$0.71 a week** with the AI, $0.51 rules only.

#### 🎯 Try it for your niche

Pick one in **Start from a ready set-up** (`preset`) and press Start.

| Use case | What it does | Start |
|---|---|---|
| Forwarder: morning table | every container, rules only | `forwarder-morning-table` |
| Importer: demurrage and delay alerts | `dd_risk` + `arrived` + `eta_slipped` ≥ 2 days, 5 free days, AI on | `importer-arrival-alerts` |
| TMS / ERP feed | every change with its event list | `tms-webhook-feed` |

### 🔌 Container tracking API for any carrier, integrations

**Tested calls and the full item:** [API reference](https://api.apify.com/v2/key-value-stores/DytILlSa5kpBItmj6/records/api-reference.md); `OUTPUT.mode` says what ran (`demo`, `live`, `list`, `dataset`).

<details><summary>Run outcomes — what your integration sees</summary>

| Outcome | Run status | `OUTPUT` | Charged? |
|---|---|---|---|
| Free demo / success / nothing changed | SUCCEEDED | `ok: true`, `outcome: ok` / `empty` | per changed update, demo never |
| Your plan cannot run the source Store Actor | SUCCEEDED — check `OUTPUT.ok` (Console: the free sample; schedule / API: your channels alerted) | `ok: false`, `outcome: source_refused`, `sourceRefused: plan` | no — [list-mode rows](#list-mode-rows--what-a-pasted-record-needs) |
| Invalid input · every lookup failed · crash | FAILED | — · `outcome: source_failed` · `outcome: error` | no · no · rows before it |
| One carrier failed or timed out | SUCCEEDED | `sourceStatus.carriers` / `partial` | the others' |
| AI unavailable | SUCCEEDED | `aiError` | basic after the free tier |

</details>

- **Webhook** — `webhookUrl` POSTs `{"event": "container_update.qualified", "container_update": {…}, "runId": "…"}`
  per update; `webhookBatchSize` > 1 sends `{"event": "container_updates.qualified", "container_updates": [{…}, …], "runId": "…"}`;
  a 429 / 5xx is retried once, a failed POST is re-sent next run (never charged again) and never fails the run.
- **Which delivery field wins** — every URL is posted once; a per-service field (API-only) wins for its service; `webhookUrl` gets the rows, the others the digest.
- **Google Sheets / HubSpot / Pipedrive** — a Make / Zapier "create or update" step: record = `reference` +
  `trackingNumber` + `carrier`, note = `note` + `recommendedNextStep`, upsert on `shipmentKey` (`dedupeKey` = one update).
- **Bill of lading tracking API** — a B/L in `trackingNumbers` returns every box on it, `shipmentKey` = that B/L.

<details><summary>Python and JavaScript</summary>

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")
run = client.actor("rich_minds/multi-carrier-tracking-ai").call(
    run_input={
        # free first call: {} (the demo) or {"sourceMode": "list", "itemsList": [...]}
        # the mode you send is the mode that runs (autoSwitchToLive is Console-only)
        "sourceMode": "live",
        "trackingNumbers": ["MSCU0203658", "MAEU3402103", "254839107, Maersk, PO-4411, 5"],
        "maxQualified": 100,
        "maxDiscoveryChargeUsd": 0.5,  # spend cap for the carrier lookups
        "failOnSourceError": True,
    },
    timeout_secs=3240,  # see "How long does a run take?"
    # build="0.1.4",  # pin the build your pipeline was tested with (Changelog)
)
out = client.key_value_store(run["defaultKeyValueStoreId"]).get_record("OUTPUT")["value"]
if not out.get("ok", True):  # a plan refusal ends SUCCEEDED with 0 rows
    raise RuntimeError(out.get("outcome"), out.get("sourceError"))
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["url"], item["shipmentKey"], item["score"], item["status"], item["etaUtc"], item["lastFreeDay"])
```

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_API_TOKEN>' });
const run = await client.actor('rich_minds/multi-carrier-tracking-ai').call({
    sourceMode: 'live', // the mode you send is the mode that runs; {} = the free demo
    trackingNumbers: ['MSCU0203658', 'MAEU3402103', '254839107, Maersk, PO-4411, 5'],
    maxQualified: 100,
    maxDiscoveryChargeUsd: 0.5, // spend cap for the carrier lookups
    failOnSourceError: true,
}, { timeout: 3240 /* , build: '0.1.4' — pin the build you tested */ });
const out = (await client.keyValueStore(run.defaultKeyValueStoreId).getRecord('OUTPUT')).value;
if (out.ok === false) throw new Error(`${out.outcome}: ${out.sourceError}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map((i) => [i.url, i.shipmentKey, i.score, i.status, i.etaUtc, i.lastFreeDay]));
```

</details>

**MCP:** `{"mcpServers": {"apify": {"url": "https://mcp.apify.com/?actors=rich_minds/multi-carrier-tracking-ai"}}}`.
**Minimal agent input** — the memory off, so the same question asked twice answers both times:

```json
{"sourceMode": "live", "trackingNumbers": ["MSCU0203658", "MAEU3402103"], "enableAi": false, "dedupeAcrossRuns": false, "maxDiscoveryChargeUsd": 0.1}
```

Any plan, your own rows: `{"sourceMode": "list", "itemsList": [{"trackingNumber": "MSCU0203658", "events": [...]}]}` · demo `{}`.

<details><summary>List-mode rows — what a pasted record needs</summary>

#### List-mode rows — what a pasted record needs

`sourceMode: "list"` runs on any plan; MSC, Maersk and CMA CGM rows are read as they are, any other shape through
these fields (template linked under At a glance).

| Field | Needed? | What it enables |
|---|---|---|
| `trackingNumber` | required | the row, carrier, `url` (or `containerNumber` …); alone = looked up live |
| `events` | optional | status, milestone, ETA (`description`, `time`, `location`, `unlocode`, `isActual`) |
| `carrier`, `reference` | optional | routing when the prefix does not say it; your reference |
| `pol`, `pod`, `finalDestination`, `vessel`, `voyage`, `eta`, `ata` | optional | those fields |
| `freeDays` · `blNumber` | optional | `lastFreeDay` and `dd_risk` · `shipmentKey` |

</details>

### 👥 Who is it for?

| You are… | You run it to… | Start with |
|---|---|---|
| Freight forwarder / customs broker | one morning table of every container, ETA slips first; a branded report per client | `forwarder-morning-table`, daily |
| Exporter / importer | "warn me before demurrage starts, and when my containers arrive" | `importer-arrival-alerts` |
| Trade platform / TMS | milestone webhooks into the shipment state machine | `tms-webhook-feed` + `webhookUrl` |

### 🧠 How the AI works

- **Grounded.** The model sees one container's row and only the DCSA codes; a note naming a fact the row does not
  hold is dropped for the template one, as is a failed call.
- **The model.** `google/gemini-3.5-flash-lite` through Apify's model access (not measured on the platform yet);
  `google:gemini-3.5-flash-lite` with your own key (measured 2.7–12.2 s and 214 output tokens per update).

### 🔒 Data, compliance and limits

- Public tracking data only, no login. Some carriers show no ETA (`no_eta`) or vessel.
- Beta carriers: mapped from their Actors' documented fields until real rows are captured (`unverifiedCarrier:
  true`) — free here, the carrier lookup still ≈ $0.01 on your account. No vessel position (AIS) yet.

### ❓ FAQ

**How much will one run cost me?** The lookups ($0.01 a container, + $0.001 per carrier) plus $0.0037 per update here
— 40 containers, lookups included: $0.55 rules only, $0.82 in all AI on.

**Is this an alternative to the carriers' own tracking pages or track-trace.com?** They are free and authoritative —
use them for one number; for 20+ containers they lack one table, an API and change alerts.

**Already using the Maersk tracking scraper or the MSC one?** Paste the same numbers — or read its last dataset.

**Where is my container — for every carrier at once?** Yes: one row per box with its last milestone, place and ETA.

<details><summary>How long does a run take?</summary>

**How long does a run take?**

| Mode | Time | `timeoutSecs` |
|---|---|---|
| Free demo (5 sample containers) | under 1 s here, measured 2026-10-24 (+ the platform's start) | 120 |
| Live lookup, rules only | one carrier run ≈ 57 s (an MSC run, measured 2026-09-03); carriers in parallel | 600 |
| AI via Apify's model access | not measured on the platform yet | 1800 |
| AI with your own key | measured 2026-10-24 with a free google:gemini-3.5-flash-lite key: ≈ 2.7–12.2 s per container update (2.7 s locally; 4.6, 7.3 and 12.2 s on Apify) — 50 updates ≈ 790 s | 960 |

Own-key throughput: a free Gemini key ≈ 5–22 container updates per minute (measured 2026-10-24). Worst case with a
slow own key: ≈ 21 min per 100 container updates — the run-time budget caps the AI phase.

</details>

**Which Apify plan do I need?** Any plan: the demo and your own results; live lookups and the e-mail digest: a plan
that runs Store Actors.

<details><summary>More questions</summary>

**How is this different from a visibility platform (Vizion, Terminal49, project44)?** No subscription or contract —
pay per update on your Apify account.

**What happens when nothing changed?** 0 rows, nothing charged; the status says how many were unchanged.

**Will I be charged for the same container update twice?** No — the same state is never delivered or charged again;
adding a number keeps the memory.

</details>

### 🧩 More Actors from the same developer

- **[OFAC Sanctions Screening](https://apify.com/rich_minds/counterparty-check-ai)** — Screen every consignee with one clear / review / block verdict before you ship.

### 🆘 Support

A carrier field or status missing? Open an issue on the Actor's page — buyer requests ship first.

### 📝 Changelog

- **0.1.4** (2026-10-24) — The form now shows what a run really costs: at most $0.92 here + ≈ $0.11 of AI tokens + your $0.50 lookup cap at the defaults, and beta carriers say plainly that their lookup still costs ≈ $0.01 on ….
- **0.1.3** (2026-10-24) — the page says plainly how much AI you get on your plan; the measured own-key speed.
- **0.1.2** (2026-10-24) — demurrage alerts, `shipmentKey`, smart polling, e-mail and Google Sheet input.
- **0.1.1** (2026-10-24) — initial release: 15 carriers, one DCSA-style format, change-only monitor.

**Coming next:** 2026-10-31 — AI without model access or a key · 2026-11-07 — real rows for every beta carrier, the
Maersk DCSA API · 2026-11-14 — monitoring and demurrage-alert events.

# Actor input Schema

## `preset` (type: `string`):

Pick a ready set-up and press Start: it replaces the sample search (the untouched prefill counts as empty), fills the fields you left empty (a value you typed always wins) and tracks its example numbers live, spend-capped by <code>Max source spend</code>. <b>Custom</b> = fill the fields yourself.

## `trackingNumbers` (type: `array`):

One per line — container, B/L or booking (<code>12345678, Maersk, PO-4411, 5</code> = carrier, reference, free days), or a pasted e-mail. Typed next to the free demo they start the live lookup — <b>affects what you pay</b>: one Store Actor lookup each on your Apify plan; a plan that cannot run Store Actors gets the free sample.

## `goalDescription` (type: `string`):

In plain words, what needs your attention — e.g. 'flag anything late into Rotterdam by 2+ days'. The AI weighs each update against it; container numbers written here are tracked when the list above is empty.

## `notifyEmail` (type: `string`):

The digest of every run with a change (counts, ETA slips, demurrage risks, the top 10 with links) is e-mailed here. Sent by Apify's send-mail Actor, so it needs a plan that runs Store Actors — on other plans put a Slack / Teams / Telegram link in <b>Send alerts to</b> (any plan). Never sent for the free demo.

## `minScore` (type: `integer`):

0 = every new or changed container is delivered (recommended). Higher = only the updates that need attention (an ETA slip, an arrival, a discharge score 70+); updates below it are dropped and <b>not charged</b> — affects what you pay.

## `maxQualified` (type: `integer`):

Hard cap on results — and the ceiling of what <b>this Actor</b> charges: at most this many × $0.0092 with AI on (+ ≈ $0.0011 of AI tokens each), × $0.0037 with AI off. Worst case at the default 100: $0.92 here + ≈ $0.11 tokens + your $0.50 lookup cap. The updates that need attention are output first.

## `maxDiscoveryChargeUsd` (type: `number`):

Hard cap on what the carrier tracking Actors may charge your account for the lookups — $0.50, also for an API call that leaves it out (≈ 45 numbers at ≈ $0.011 each: $0.01 a result + $0.001 a run). Split over your carriers; numbers the cap cannot cover get free `lookupFailed` rows. Affects what you pay.

## `enableAi` (type: `boolean`):

AI codes for unmapped carrier texts, the status note and, for a box at demurrage risk, the extension / pickup request. <b>On = $0.0092 per container update + ≈ $0.0011 AI tokens; off = $0.0037</b>. Apify's model access (Starter and up) runs google/gemini-3.5-flash-lite — measured with an own key, not measured on the platform yet.

## `freeTimeDays` (type: `integer`):

Your free days at the port of discharge. Each discharged box then gets its <code>lastFreeDay</code>, and 3 days before it the <code>dd_risk</code> flag ranks it first with the pickup / extension request — demurrage runs $75–300 a day after it. A line's own 4th part wins (<code>MSCU0203658, MSC, PO-1, 7</code>). 0 = off.

## `demurrageRatePerDay` (type: `integer`):

Your line's demurrage rate. A box at demurrage risk gets <code>demurrageAtRiskUsd</code> = (days past its last free day + 3) × this rate, and the digest opens with the total at risk ("≈ $1,350 at risk, 3 boxes"). Typical $75–300; 0 = off. Changes no price.

## `numbersSheetUrl` (type: `string`):

A Google Sheet shared as 'Anyone with the link' (or any CSV link), read on every run: columns <code>number</code>, <code>carrier</code>, <code>reference</code>, <code>freeDays</code> (or the number in the first column). Its numbers are tracked next to the ones above — the sheet stays your list. <b>Affects what you pay:</b> one lookup per number on your Apify plan; a plan that cannot run Store Actors gets the free sample instead.

## `targetFlags` (type: `array`):

Deliver only the updates with at least one of these flags — e.g. ETA slipped + arrived for a consignee alert. Leave empty to keep everything. An API call passes the flag names from the README's flag table, e.g. <code>eta_slipped</code> (<code>etaSlipped</code> works too); an unknown name stops the run at once with the list of valid ones.

## `etaSlipAlertDays` (type: `integer`):

An ETA that moved later (or earlier) by at least this many days since the last check gets the <code>eta_slipped</code> / <code>eta_earlier</code> flag, ranks first and is counted in the digest. 1 = every slip of a day or more.

## `suppressionList` (type: `array`):

Names, URLs or ids to never output. Suppressed container updates are skipped before any processing and never charged.

## `allowList` (type: `array`):

API only — hidden on the form. Names, URLs or ids no free rule may drop (a container update a rule skipped as off-type last run — see <code>OUTPUT.skips</code> for the names). They still need to pass <code>minScore</code>.

## `llmProvider` (type: `string`):

<b>Apify (no keys)</b> — the AI runs through Apify's built-in OpenRouter proxy; tokens are billed to your Apify account at OpenRouter's rates. <b>My own key</b> — your OpenAI / Anthropic / Gemini / Groq key instead. Measured 2026-10-24 with a free google:gemini-3.5-flash-lite key: ≈ 2.7–12.2 s per container update (2.7 s locally; 4.6, 7.3 and 12.2 s on Apify) — set the timeout from the slowest: 50 updates ≈ 790 s. With a free-tier key expect 1–3 AI checks per run (the rest keep their rule score); a paid Groq / Gemini key (billing on) assesses every container update.

## `llmModel` (type: `string`):

Leave empty for the default: <code>google/gemini-3.5-flash-lite</code> through Apify (the model measured with an own key; not measured on the platform yet), <code>google:gemini-3.5-flash-lite</code> with your own key (measured). Apify mode takes an OpenRouter slug; own-key mode takes <code>provider:model</code>.

## `llmApiKey` (type: `string`):

Required when <b>AI model access</b> is <i>My own API key</i>. Stored encrypted by Apify, never logged.

## `aiCandidateMultiplier` (type: `integer`):

API only — hidden on the form. How many of the best rule-scored container updates get the AI pass, as a multiple of <b>Max qualified</b>. Higher = more thorough, slower, more tokens.

## `sendAlertsTo` (type: `string`):

Paste one or more URLs, one per line — the service is detected from the address: a Slack or Discord channel webhook, a Teams Workflows or Google Chat space webhook, your Telegram bot's send link (<code>https://api.telegram.org/bot\<token>/sendMessage?chat_id=@yourchannel</code>) or any other https URL (Zapier, Make, n8n, your CRM), which gets the container updates as JSON. After every run with at least one new container update, the digest (how many are new, the top 10 with links) is posted there. Never sent for the free demo. Headers, batching and signing: <i>Delivery details (advanced)</i>.

## `dedupeAcrossRuns` (type: `boolean`):

The monitor: on means a container is skipped only while it is unchanged — every new milestone or ETA move is delivered. remembers each container's last state (status, milestone, ETA) in a named key-value store on your account; a container in the same state as last run is skipped and <b>not charged</b>, a changed one comes back with <code>changeType</code> and the ETA slip in days.

## `monitorStoreName` (type: `string`):

Names the monitor's memory — the last known state of every container — on your Apify account. Empty = one memory per list of numbers (adding a number keeps it). Running it for several clients, type one name per client so their containers never mix.

## `clientName` (type: `string`):

White-label: "Container updates for <client>" heads the forwardable REPORT page and the digest. Running it for several clients, save one task per client with its numbers, this name and its own <b>Monitor name</b>.

## `brandName` (type: `string`):

White-label: "by <your company>" next to the client name on the REPORT page and the digest your client receives.

## `includeEvents` (type: `boolean`):

Every row carries all its events in one format (DCSA code, text, place, UN/LOCODE, time, actual or estimated, vessel). Off = only the last milestone — smaller rows for a TMS webhook.

## `pollPolicy` (type: `string`):

<b>Smart</b> (default): a delivered or empty-returned box is checked once more, then never looked up again; a box in the ocean with its ETA more than 7 days out is looked up every 3rd run — the lookups you pay the carrier Actors for drop by about half on a daily schedule. <b>Every run</b>: every number, every time. Affects what you pay.

## `dedupeTtlDays` (type: `integer`):

API only — hidden on the form. 0 = a container in the same state is never sent again. A number of days re-sends an unchanged container after that many days (a weekly 'still in transit' line) — charged again as a new update.

## `historyDatasetName` (type: `string`):

Optional. Type a name (e.g. <code>multi-carrier-tracking-ai-seen-log</code>) and every run appends its new container updates with the date to that named dataset on your account — one list across months to connect to Google Sheets or your BI tool once. Empty = off. Costs nothing extra.

## `sourceMode` (type: `string`):

The mode that runs = this field, except <code>{}</code> over the API = the free demo (nothing charged). <b>live</b> — look up <code>trackingNumbers</code> with each carrier's Store tracking Actor on your account, billed separately. <b>dataset</b> — an existing Apify dataset of carrier results or of numbers. <b>list</b> — carrier results (or numbers) you paste in, no lookup, any plan. The mode you choose always runs as chosen; only the untouched sample next to numbers you typed on the Console form switches to the live lookup (spend-capped).

## `autoSwitchToLive` (type: `boolean`):

API only — hidden on the form. On (default, Console runs only): search terms typed next to the untouched sample records start the live search (spend-capped; <code>OUTPUT.switchedToLive</code> names them). Off: the mode you send is the mode that runs — the sample stays the free demo and the status names the terms it did not search. Over the API the mode you send is always the mode that runs.

## `failOnSourceError` (type: `boolean`):

Off (default): a plan that cannot run the source Store Actor ends SUCCEEDED with 0 rows and <code>OUTPUT.ok: false</code>. On: the same run ends FAILED with the same message, so a schedule's or an API pipeline's failed-run alert fires. Turn it on for every schedule.

## `datasetId` (type: `string`):

Only for <b>dataset</b> mode. Pick the dataset so the Actor is granted read access to it.

## `itemsList` (type: `array`):

Only for <b>list</b> mode. A JSON array: carrier tracking results in any carrier Actor's shape (MSC, Maersk, CMA CGM / DCSA, or a flat <code>events</code> list — README → List-mode rows), or rows with only <code>trackingNumber</code> (+ <code>carrier</code>, <code>reference</code>), which are looked up like <code>trackingNumbers</code>.

## `discoveryInput` (type: `object`):

API only — hidden on the form. Extra fields passed to every carrier tracking Actor next to <code>trackingNumbers</code>; leave it empty.

## `maxToProcess` (type: `integer`):

API only — hidden on the form. Upper bound on how many container updates are scored in one run (controls run time). Default = 3 × max output.

## `webhookUrl` (type: `string`):

Qualified container updates are POSTed here as JSON (Zapier, Make, n8n, your CRM). For Google Sheets / Slack you can also use Apify's built-in Integrations tab. Stored encrypted — Zapier / Make hook URLs carry their secret in the path.

## `webhookHeaders` (type: `object`):

API only — hidden on the form. Extra HTTP headers for the webhook, e.g. <code>{"Authorization": "Bearer …"}</code>. Stored encrypted by Apify, never logged.

## `webhookBatchSize` (type: `integer`):

API only — hidden on the form. 1 = one POST per container update the moment it is ready. Higher = one POST per N container updates.

## `webhookSecret` (type: `string`):

API only — hidden on the form. Any random string. Every webhook POST then carries <code>X-Signature: sha256=\<HMAC-SHA256 of the raw body with this secret></code>, so your endpoint can verify it came from this Actor. Stored encrypted by Apify, never logged.

## `dedupeStoreName` (type: `string`):

API only — hidden on the form. Key-value store used for cross-run memory. Left at the default, the memory is kept per search (source and search terms): rewording <code>goalDescription</code> keeps it, so a scheduled run never re-delivers what you already have, and another search starts its own list. Running it for several clients on one account, type one name per client.

## `discoveryTimeoutSecs` (type: `integer`):

API only — hidden on the form. How long the source Store Actor may run before its partial results are used — never more than 60 % of the run's time left, so the AI, the charges and the digest keep the rest. Raise it (and the run timeout) when the status says the source stopped early.

## `slackWebhookUrl` (type: `string`):

API only — hidden on the form, where <code>sendAlertsTo</code> takes it. Slack incoming-webhook URL for the digest.

## `discordWebhookUrl` (type: `string`):

API only — hidden on the form, where <code>sendAlertsTo</code> takes it. Discord channel webhook URL for the digest.

## `teamsWebhookUrl` (type: `string`):

API only — hidden on the form, where <code>sendAlertsTo</code> takes it. Teams Workflows webhook URL for the digest.

## `googleChatWebhookUrl` (type: `string`):

API only — hidden on the form, where <code>sendAlertsTo</code> takes it. Google Chat space webhook URL for the digest.

## `telegramUrl` (type: `string`):

API only — hidden on the form, where <code>sendAlertsTo</code> takes it. The bot's sendMessage link with its chat_id.

## Actor input object example

```json
{
  "preset": "custom",
  "trackingNumbers": [],
  "minScore": 0,
  "maxQualified": 100,
  "maxDiscoveryChargeUsd": 0.5,
  "enableAi": true,
  "freeTimeDays": 0,
  "demurrageRatePerDay": 150,
  "etaSlipAlertDays": 1,
  "llmProvider": "apify",
  "aiCandidateMultiplier": 2,
  "dedupeAcrossRuns": true,
  "monitorStoreName": "",
  "clientName": "",
  "brandName": "",
  "includeEvents": true,
  "pollPolicy": "smart",
  "dedupeTtlDays": 0,
  "historyDatasetName": "",
  "sourceMode": "list",
  "autoSwitchToLive": true,
  "failOnSourceError": false,
  "itemsList": [
    {
      "tracking_number": "MSKU6104820",
      "reference": "PO-44120 sample",
      "origin": {
        "terminal": "TANJUNG PELEPAS TERMINAL",
        "city": "TANJUNG PELEPAS",
        "country_code": "MY",
        "location_code": "MYTPP"
      },
      "destination": {
        "terminal": "FELIXSTOWE TERMINAL",
        "city": "FELIXSTOWE",
        "country_code": "GB",
        "location_code": "GBFXT"
      },
      "containers": [
        {
          "container_num": "MSKU6104820",
          "container_size": "40",
          "container_type": "DRY",
          "locations": [
            {
              "city": "Tanjung Pelepas",
              "country_code": "MY",
              "location_code": "MYTPP",
              "events": [
                {
                  "activity": "GATE-IN",
                  "actfor": "EXP",
                  "event_time": "2026-10-03T08:10:00.000",
                  "stempty": false
                },
                {
                  "activity": "LOAD",
                  "actfor": "EXP",
                  "event_time": "2026-10-05T21:40:00.000",
                  "stempty": false,
                  "vessel_name": "MAERSK SAMPLE BAY",
                  "voyage_num": "442W"
                },
                {
                  "activity": "CONTAINER DEPARTURE",
                  "actfor": "EXP",
                  "event_time": "2026-10-06T04:00:00.000",
                  "stempty": false,
                  "vessel_name": "MAERSK SAMPLE BAY",
                  "voyage_num": "442W"
                }
              ]
            },
            {
              "city": "Felixstowe",
              "country_code": "GB",
              "location_code": "GBFXT",
              "events": [
                {
                  "activity": "DISCHARG",
                  "actfor": "IMP",
                  "event_time": "2026-11-02T06:00:00.000",
                  "event_time_type": "EXPECTED",
                  "stempty": false,
                  "vessel_name": "MAERSK SAMPLE BAY",
                  "voyage_num": "442W"
                }
              ]
            }
          ]
        }
      ],
      "sampleChange": {
        "changeType": "eta",
        "etaSlipDays": 3
      }
    },
    {
      "tracking_number": "MSCU7421055",
      "tracking_type": "Container",
      "reference": "PO-44107 sample",
      "bill_of_ladings": [
        {
          "BillOfLadingNumber": "MEDUSAMPLE01",
          "NumberOfContainers": 1,
          "GeneralTrackingInfo": {
            "ShippedFrom": "SANTOS, BR",
            "ShippedTo": "ROTTERDAM, NL",
            "PortOfLoad": "SANTOS, BR",
            "PortOfDischarge": "ROTTERDAM, NL",
            "Transshipments": [],
            "FinalPodEtaDate": ""
          },
          "ContainersInfo": [
            {
              "Events": [
                {
                  "Order": 1,
                  "Date": "21/09/2026",
                  "Location": "SANTOS, BR",
                  "UnLocationCode": "BRSSZ",
                  "Description": "Export received at CY",
                  "Detail": [
                    "LADEN"
                  ]
                },
                {
                  "Order": 2,
                  "Date": "24/09/2026",
                  "Location": "SANTOS, BR",
                  "UnLocationCode": "BRSSZ",
                  "Description": "Export Loaded on Vessel",
                  "Detail": [
                    "LADEN"
                  ],
                  "Vessel": {
                    "Name": "MSC SAMPLE ARIA"
                  }
                },
                {
                  "Order": 3,
                  "Date": "22/10/2026",
                  "Location": "ROTTERDAM, NL",
                  "UnLocationCode": "NLRTM",
                  "Description": "Import Discharged from Vessel",
                  "Detail": [
                    "LADEN"
                  ],
                  "Vessel": {
                    "Name": "MSC SAMPLE ARIA"
                  }
                }
              ]
            }
          ]
        }
      ],
      "sampleChange": {
        "changeType": "milestone"
      }
    },
    {
      "tracking_number": "CMAU5382066",
      "reference": "PO-44131 sample",
      "events": [
        {
          "eventType": "EQUIPMENT",
          "eventClassifierCode": "ACT",
          "eventDateTime": "2026-10-12T09:30:00Z",
          "equipmentEventTypeCode": "GTIN",
          "emptyIndicatorCode": "LADEN",
          "transportCall": {
            "UNLocationCode": "CNSHA",
            "modeOfTransport": "TRUCK"
          }
        },
        {
          "eventType": "EQUIPMENT",
          "eventClassifierCode": "ACT",
          "eventDateTime": "2026-10-15T17:05:00Z",
          "equipmentEventTypeCode": "LOAD",
          "emptyIndicatorCode": "LADEN",
          "transportCall": {
            "UNLocationCode": "CNSHA",
            "modeOfTransport": "VESSEL",
            "vessel": {
              "vesselName": "CMA CGM SAMPLE STAR"
            },
            "exportVoyageNumber": "0MX3EW1MA"
          }
        },
        {
          "eventType": "TRANSPORT",
          "eventClassifierCode": "ACT",
          "eventDateTime": "2026-10-16T02:00:00Z",
          "transportEventTypeCode": "DEPA",
          "transportCall": {
            "UNLocationCode": "CNSHA",
            "modeOfTransport": "VESSEL",
            "vessel": {
              "vesselName": "CMA CGM SAMPLE STAR"
            },
            "exportVoyageNumber": "0MX3EW1MA"
          }
        },
        {
          "eventType": "TRANSPORT",
          "eventClassifierCode": "EST",
          "eventDateTime": "2026-11-03T08:00:00Z",
          "transportEventTypeCode": "ARRI",
          "transportCall": {
            "UNLocationCode": "USLAX",
            "modeOfTransport": "VESSEL",
            "vessel": {
              "vesselName": "CMA CGM SAMPLE STAR"
            }
          }
        }
      ]
    },
    {
      "tracking_number": "CBHU7719301",
      "carrier": "COSCO",
      "reference": "PO-44139 sample",
      "pol": "Ningbo, CN",
      "pod": "Hamburg, DE",
      "events": [
        {
          "description": "Gate-In at First POL",
          "location": "Ningbo, CN",
          "unlocode": "CNNGB",
          "time": "2026-10-08 10:20",
          "isActual": true
        },
        {
          "description": "Loaded on vessel",
          "location": "Ningbo, CN",
          "unlocode": "CNNGB",
          "time": "2026-10-10 23:45",
          "isActual": true,
          "vessel": "COSCO SAMPLE PRIDE",
          "voyage": "071W"
        },
        {
          "description": "Vessel omitted the port call, cargo rolled to the next sailing",
          "location": "Singapore, SG",
          "unlocode": "SGSIN",
          "time": "2026-10-19 06:00",
          "isActual": true
        },
        {
          "description": "Vessel arrival at POD",
          "location": "Hamburg, DE",
          "unlocode": "DEHAM",
          "time": "2026-11-12 05:00",
          "isActual": false
        }
      ]
    },
    {
      "trackingNumber": "MSCU1234560",
      "reference": "PO-44150 sample (typo in the number)"
    }
  ],
  "webhookBatchSize": 1,
  "dedupeStoreName": "multi-carrier-tracking-ai-seen",
  "discoveryTimeoutSecs": 3600
}
```

# Actor output Schema

## `qualified` (type: `string`):

All qualified container updates as JSON, best-scoring first.

## `sheet` (type: `string`):

The same container updates as a spreadsheet.

## `runSummary` (type: `string`):

JSON record with the funnel (loaded -> filtered -> AI-assessed -> qualified), per-reason skip counts, charged events by type, free-tier container updates used and remaining, whether a budget limit was reached, webhook delivery counts and the dedupe store size.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "trackingNumbers": [],
    "maxDiscoveryChargeUsd": 0.5,
    "sourceMode": "list",
    "itemsList": [
        {
            "tracking_number": "MSKU6104820",
            "reference": "PO-44120 sample",
            "origin": {
                "terminal": "TANJUNG PELEPAS TERMINAL",
                "city": "TANJUNG PELEPAS",
                "country_code": "MY",
                "location_code": "MYTPP"
            },
            "destination": {
                "terminal": "FELIXSTOWE TERMINAL",
                "city": "FELIXSTOWE",
                "country_code": "GB",
                "location_code": "GBFXT"
            },
            "containers": [
                {
                    "container_num": "MSKU6104820",
                    "container_size": "40",
                    "container_type": "DRY",
                    "locations": [
                        {
                            "city": "Tanjung Pelepas",
                            "country_code": "MY",
                            "location_code": "MYTPP",
                            "events": [
                                {
                                    "activity": "GATE-IN",
                                    "actfor": "EXP",
                                    "event_time": "2026-10-03T08:10:00.000",
                                    "stempty": false
                                },
                                {
                                    "activity": "LOAD",
                                    "actfor": "EXP",
                                    "event_time": "2026-10-05T21:40:00.000",
                                    "stempty": false,
                                    "vessel_name": "MAERSK SAMPLE BAY",
                                    "voyage_num": "442W"
                                },
                                {
                                    "activity": "CONTAINER DEPARTURE",
                                    "actfor": "EXP",
                                    "event_time": "2026-10-06T04:00:00.000",
                                    "stempty": false,
                                    "vessel_name": "MAERSK SAMPLE BAY",
                                    "voyage_num": "442W"
                                }
                            ]
                        },
                        {
                            "city": "Felixstowe",
                            "country_code": "GB",
                            "location_code": "GBFXT",
                            "events": [
                                {
                                    "activity": "DISCHARG",
                                    "actfor": "IMP",
                                    "event_time": "2026-11-02T06:00:00.000",
                                    "event_time_type": "EXPECTED",
                                    "stempty": false,
                                    "vessel_name": "MAERSK SAMPLE BAY",
                                    "voyage_num": "442W"
                                }
                            ]
                        }
                    ]
                }
            ],
            "sampleChange": {
                "changeType": "eta",
                "etaSlipDays": 3
            }
        },
        {
            "tracking_number": "MSCU7421055",
            "tracking_type": "Container",
            "reference": "PO-44107 sample",
            "bill_of_ladings": [
                {
                    "BillOfLadingNumber": "MEDUSAMPLE01",
                    "NumberOfContainers": 1,
                    "GeneralTrackingInfo": {
                        "ShippedFrom": "SANTOS, BR",
                        "ShippedTo": "ROTTERDAM, NL",
                        "PortOfLoad": "SANTOS, BR",
                        "PortOfDischarge": "ROTTERDAM, NL",
                        "Transshipments": [],
                        "FinalPodEtaDate": ""
                    },
                    "ContainersInfo": [
                        {
                            "Events": [
                                {
                                    "Order": 1,
                                    "Date": "21/09/2026",
                                    "Location": "SANTOS, BR",
                                    "UnLocationCode": "BRSSZ",
                                    "Description": "Export received at CY",
                                    "Detail": [
                                        "LADEN"
                                    ]
                                },
                                {
                                    "Order": 2,
                                    "Date": "24/09/2026",
                                    "Location": "SANTOS, BR",
                                    "UnLocationCode": "BRSSZ",
                                    "Description": "Export Loaded on Vessel",
                                    "Detail": [
                                        "LADEN"
                                    ],
                                    "Vessel": {
                                        "Name": "MSC SAMPLE ARIA"
                                    }
                                },
                                {
                                    "Order": 3,
                                    "Date": "22/10/2026",
                                    "Location": "ROTTERDAM, NL",
                                    "UnLocationCode": "NLRTM",
                                    "Description": "Import Discharged from Vessel",
                                    "Detail": [
                                        "LADEN"
                                    ],
                                    "Vessel": {
                                        "Name": "MSC SAMPLE ARIA"
                                    }
                                }
                            ]
                        }
                    ]
                }
            ],
            "sampleChange": {
                "changeType": "milestone"
            }
        },
        {
            "tracking_number": "CMAU5382066",
            "reference": "PO-44131 sample",
            "events": [
                {
                    "eventType": "EQUIPMENT",
                    "eventClassifierCode": "ACT",
                    "eventDateTime": "2026-10-12T09:30:00Z",
                    "equipmentEventTypeCode": "GTIN",
                    "emptyIndicatorCode": "LADEN",
                    "transportCall": {
                        "UNLocationCode": "CNSHA",
                        "modeOfTransport": "TRUCK"
                    }
                },
                {
                    "eventType": "EQUIPMENT",
                    "eventClassifierCode": "ACT",
                    "eventDateTime": "2026-10-15T17:05:00Z",
                    "equipmentEventTypeCode": "LOAD",
                    "emptyIndicatorCode": "LADEN",
                    "transportCall": {
                        "UNLocationCode": "CNSHA",
                        "modeOfTransport": "VESSEL",
                        "vessel": {
                            "vesselName": "CMA CGM SAMPLE STAR"
                        },
                        "exportVoyageNumber": "0MX3EW1MA"
                    }
                },
                {
                    "eventType": "TRANSPORT",
                    "eventClassifierCode": "ACT",
                    "eventDateTime": "2026-10-16T02:00:00Z",
                    "transportEventTypeCode": "DEPA",
                    "transportCall": {
                        "UNLocationCode": "CNSHA",
                        "modeOfTransport": "VESSEL",
                        "vessel": {
                            "vesselName": "CMA CGM SAMPLE STAR"
                        },
                        "exportVoyageNumber": "0MX3EW1MA"
                    }
                },
                {
                    "eventType": "TRANSPORT",
                    "eventClassifierCode": "EST",
                    "eventDateTime": "2026-11-03T08:00:00Z",
                    "transportEventTypeCode": "ARRI",
                    "transportCall": {
                        "UNLocationCode": "USLAX",
                        "modeOfTransport": "VESSEL",
                        "vessel": {
                            "vesselName": "CMA CGM SAMPLE STAR"
                        }
                    }
                }
            ]
        },
        {
            "tracking_number": "CBHU7719301",
            "carrier": "COSCO",
            "reference": "PO-44139 sample",
            "pol": "Ningbo, CN",
            "pod": "Hamburg, DE",
            "events": [
                {
                    "description": "Gate-In at First POL",
                    "location": "Ningbo, CN",
                    "unlocode": "CNNGB",
                    "time": "2026-10-08 10:20",
                    "isActual": true
                },
                {
                    "description": "Loaded on vessel",
                    "location": "Ningbo, CN",
                    "unlocode": "CNNGB",
                    "time": "2026-10-10 23:45",
                    "isActual": true,
                    "vessel": "COSCO SAMPLE PRIDE",
                    "voyage": "071W"
                },
                {
                    "description": "Vessel omitted the port call, cargo rolled to the next sailing",
                    "location": "Singapore, SG",
                    "unlocode": "SGSIN",
                    "time": "2026-10-19 06:00",
                    "isActual": true
                },
                {
                    "description": "Vessel arrival at POD",
                    "location": "Hamburg, DE",
                    "unlocode": "DEHAM",
                    "time": "2026-11-12 05:00",
                    "isActual": false
                }
            ]
        },
        {
            "trackingNumber": "MSCU1234560",
            "reference": "PO-44150 sample (typo in the number)"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("rich_minds/multi-carrier-tracking-ai").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "trackingNumbers": [],
    "maxDiscoveryChargeUsd": 0.5,
    "sourceMode": "list",
    "itemsList": [
        {
            "tracking_number": "MSKU6104820",
            "reference": "PO-44120 sample",
            "origin": {
                "terminal": "TANJUNG PELEPAS TERMINAL",
                "city": "TANJUNG PELEPAS",
                "country_code": "MY",
                "location_code": "MYTPP",
            },
            "destination": {
                "terminal": "FELIXSTOWE TERMINAL",
                "city": "FELIXSTOWE",
                "country_code": "GB",
                "location_code": "GBFXT",
            },
            "containers": [{
                    "container_num": "MSKU6104820",
                    "container_size": "40",
                    "container_type": "DRY",
                    "locations": [
                        {
                            "city": "Tanjung Pelepas",
                            "country_code": "MY",
                            "location_code": "MYTPP",
                            "events": [
                                {
                                    "activity": "GATE-IN",
                                    "actfor": "EXP",
                                    "event_time": "2026-10-03T08:10:00.000",
                                    "stempty": False,
                                },
                                {
                                    "activity": "LOAD",
                                    "actfor": "EXP",
                                    "event_time": "2026-10-05T21:40:00.000",
                                    "stempty": False,
                                    "vessel_name": "MAERSK SAMPLE BAY",
                                    "voyage_num": "442W",
                                },
                                {
                                    "activity": "CONTAINER DEPARTURE",
                                    "actfor": "EXP",
                                    "event_time": "2026-10-06T04:00:00.000",
                                    "stempty": False,
                                    "vessel_name": "MAERSK SAMPLE BAY",
                                    "voyage_num": "442W",
                                },
                            ],
                        },
                        {
                            "city": "Felixstowe",
                            "country_code": "GB",
                            "location_code": "GBFXT",
                            "events": [{
                                    "activity": "DISCHARG",
                                    "actfor": "IMP",
                                    "event_time": "2026-11-02T06:00:00.000",
                                    "event_time_type": "EXPECTED",
                                    "stempty": False,
                                    "vessel_name": "MAERSK SAMPLE BAY",
                                    "voyage_num": "442W",
                                }],
                        },
                    ],
                }],
            "sampleChange": {
                "changeType": "eta",
                "etaSlipDays": 3,
            },
        },
        {
            "tracking_number": "MSCU7421055",
            "tracking_type": "Container",
            "reference": "PO-44107 sample",
            "bill_of_ladings": [{
                    "BillOfLadingNumber": "MEDUSAMPLE01",
                    "NumberOfContainers": 1,
                    "GeneralTrackingInfo": {
                        "ShippedFrom": "SANTOS, BR",
                        "ShippedTo": "ROTTERDAM, NL",
                        "PortOfLoad": "SANTOS, BR",
                        "PortOfDischarge": "ROTTERDAM, NL",
                        "Transshipments": [],
                        "FinalPodEtaDate": "",
                    },
                    "ContainersInfo": [{ "Events": [
                                {
                                    "Order": 1,
                                    "Date": "21/09/2026",
                                    "Location": "SANTOS, BR",
                                    "UnLocationCode": "BRSSZ",
                                    "Description": "Export received at CY",
                                    "Detail": ["LADEN"],
                                },
                                {
                                    "Order": 2,
                                    "Date": "24/09/2026",
                                    "Location": "SANTOS, BR",
                                    "UnLocationCode": "BRSSZ",
                                    "Description": "Export Loaded on Vessel",
                                    "Detail": ["LADEN"],
                                    "Vessel": { "Name": "MSC SAMPLE ARIA" },
                                },
                                {
                                    "Order": 3,
                                    "Date": "22/10/2026",
                                    "Location": "ROTTERDAM, NL",
                                    "UnLocationCode": "NLRTM",
                                    "Description": "Import Discharged from Vessel",
                                    "Detail": ["LADEN"],
                                    "Vessel": { "Name": "MSC SAMPLE ARIA" },
                                },
                            ] }],
                }],
            "sampleChange": { "changeType": "milestone" },
        },
        {
            "tracking_number": "CMAU5382066",
            "reference": "PO-44131 sample",
            "events": [
                {
                    "eventType": "EQUIPMENT",
                    "eventClassifierCode": "ACT",
                    "eventDateTime": "2026-10-12T09:30:00Z",
                    "equipmentEventTypeCode": "GTIN",
                    "emptyIndicatorCode": "LADEN",
                    "transportCall": {
                        "UNLocationCode": "CNSHA",
                        "modeOfTransport": "TRUCK",
                    },
                },
                {
                    "eventType": "EQUIPMENT",
                    "eventClassifierCode": "ACT",
                    "eventDateTime": "2026-10-15T17:05:00Z",
                    "equipmentEventTypeCode": "LOAD",
                    "emptyIndicatorCode": "LADEN",
                    "transportCall": {
                        "UNLocationCode": "CNSHA",
                        "modeOfTransport": "VESSEL",
                        "vessel": { "vesselName": "CMA CGM SAMPLE STAR" },
                        "exportVoyageNumber": "0MX3EW1MA",
                    },
                },
                {
                    "eventType": "TRANSPORT",
                    "eventClassifierCode": "ACT",
                    "eventDateTime": "2026-10-16T02:00:00Z",
                    "transportEventTypeCode": "DEPA",
                    "transportCall": {
                        "UNLocationCode": "CNSHA",
                        "modeOfTransport": "VESSEL",
                        "vessel": { "vesselName": "CMA CGM SAMPLE STAR" },
                        "exportVoyageNumber": "0MX3EW1MA",
                    },
                },
                {
                    "eventType": "TRANSPORT",
                    "eventClassifierCode": "EST",
                    "eventDateTime": "2026-11-03T08:00:00Z",
                    "transportEventTypeCode": "ARRI",
                    "transportCall": {
                        "UNLocationCode": "USLAX",
                        "modeOfTransport": "VESSEL",
                        "vessel": { "vesselName": "CMA CGM SAMPLE STAR" },
                    },
                },
            ],
        },
        {
            "tracking_number": "CBHU7719301",
            "carrier": "COSCO",
            "reference": "PO-44139 sample",
            "pol": "Ningbo, CN",
            "pod": "Hamburg, DE",
            "events": [
                {
                    "description": "Gate-In at First POL",
                    "location": "Ningbo, CN",
                    "unlocode": "CNNGB",
                    "time": "2026-10-08 10:20",
                    "isActual": True,
                },
                {
                    "description": "Loaded on vessel",
                    "location": "Ningbo, CN",
                    "unlocode": "CNNGB",
                    "time": "2026-10-10 23:45",
                    "isActual": True,
                    "vessel": "COSCO SAMPLE PRIDE",
                    "voyage": "071W",
                },
                {
                    "description": "Vessel omitted the port call, cargo rolled to the next sailing",
                    "location": "Singapore, SG",
                    "unlocode": "SGSIN",
                    "time": "2026-10-19 06:00",
                    "isActual": True,
                },
                {
                    "description": "Vessel arrival at POD",
                    "location": "Hamburg, DE",
                    "unlocode": "DEHAM",
                    "time": "2026-11-12 05:00",
                    "isActual": False,
                },
            ],
        },
        {
            "trackingNumber": "MSCU1234560",
            "reference": "PO-44150 sample (typo in the number)",
        },
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("rich_minds/multi-carrier-tracking-ai").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "trackingNumbers": [],
  "maxDiscoveryChargeUsd": 0.5,
  "sourceMode": "list",
  "itemsList": [
    {
      "tracking_number": "MSKU6104820",
      "reference": "PO-44120 sample",
      "origin": {
        "terminal": "TANJUNG PELEPAS TERMINAL",
        "city": "TANJUNG PELEPAS",
        "country_code": "MY",
        "location_code": "MYTPP"
      },
      "destination": {
        "terminal": "FELIXSTOWE TERMINAL",
        "city": "FELIXSTOWE",
        "country_code": "GB",
        "location_code": "GBFXT"
      },
      "containers": [
        {
          "container_num": "MSKU6104820",
          "container_size": "40",
          "container_type": "DRY",
          "locations": [
            {
              "city": "Tanjung Pelepas",
              "country_code": "MY",
              "location_code": "MYTPP",
              "events": [
                {
                  "activity": "GATE-IN",
                  "actfor": "EXP",
                  "event_time": "2026-10-03T08:10:00.000",
                  "stempty": false
                },
                {
                  "activity": "LOAD",
                  "actfor": "EXP",
                  "event_time": "2026-10-05T21:40:00.000",
                  "stempty": false,
                  "vessel_name": "MAERSK SAMPLE BAY",
                  "voyage_num": "442W"
                },
                {
                  "activity": "CONTAINER DEPARTURE",
                  "actfor": "EXP",
                  "event_time": "2026-10-06T04:00:00.000",
                  "stempty": false,
                  "vessel_name": "MAERSK SAMPLE BAY",
                  "voyage_num": "442W"
                }
              ]
            },
            {
              "city": "Felixstowe",
              "country_code": "GB",
              "location_code": "GBFXT",
              "events": [
                {
                  "activity": "DISCHARG",
                  "actfor": "IMP",
                  "event_time": "2026-11-02T06:00:00.000",
                  "event_time_type": "EXPECTED",
                  "stempty": false,
                  "vessel_name": "MAERSK SAMPLE BAY",
                  "voyage_num": "442W"
                }
              ]
            }
          ]
        }
      ],
      "sampleChange": {
        "changeType": "eta",
        "etaSlipDays": 3
      }
    },
    {
      "tracking_number": "MSCU7421055",
      "tracking_type": "Container",
      "reference": "PO-44107 sample",
      "bill_of_ladings": [
        {
          "BillOfLadingNumber": "MEDUSAMPLE01",
          "NumberOfContainers": 1,
          "GeneralTrackingInfo": {
            "ShippedFrom": "SANTOS, BR",
            "ShippedTo": "ROTTERDAM, NL",
            "PortOfLoad": "SANTOS, BR",
            "PortOfDischarge": "ROTTERDAM, NL",
            "Transshipments": [],
            "FinalPodEtaDate": ""
          },
          "ContainersInfo": [
            {
              "Events": [
                {
                  "Order": 1,
                  "Date": "21/09/2026",
                  "Location": "SANTOS, BR",
                  "UnLocationCode": "BRSSZ",
                  "Description": "Export received at CY",
                  "Detail": [
                    "LADEN"
                  ]
                },
                {
                  "Order": 2,
                  "Date": "24/09/2026",
                  "Location": "SANTOS, BR",
                  "UnLocationCode": "BRSSZ",
                  "Description": "Export Loaded on Vessel",
                  "Detail": [
                    "LADEN"
                  ],
                  "Vessel": {
                    "Name": "MSC SAMPLE ARIA"
                  }
                },
                {
                  "Order": 3,
                  "Date": "22/10/2026",
                  "Location": "ROTTERDAM, NL",
                  "UnLocationCode": "NLRTM",
                  "Description": "Import Discharged from Vessel",
                  "Detail": [
                    "LADEN"
                  ],
                  "Vessel": {
                    "Name": "MSC SAMPLE ARIA"
                  }
                }
              ]
            }
          ]
        }
      ],
      "sampleChange": {
        "changeType": "milestone"
      }
    },
    {
      "tracking_number": "CMAU5382066",
      "reference": "PO-44131 sample",
      "events": [
        {
          "eventType": "EQUIPMENT",
          "eventClassifierCode": "ACT",
          "eventDateTime": "2026-10-12T09:30:00Z",
          "equipmentEventTypeCode": "GTIN",
          "emptyIndicatorCode": "LADEN",
          "transportCall": {
            "UNLocationCode": "CNSHA",
            "modeOfTransport": "TRUCK"
          }
        },
        {
          "eventType": "EQUIPMENT",
          "eventClassifierCode": "ACT",
          "eventDateTime": "2026-10-15T17:05:00Z",
          "equipmentEventTypeCode": "LOAD",
          "emptyIndicatorCode": "LADEN",
          "transportCall": {
            "UNLocationCode": "CNSHA",
            "modeOfTransport": "VESSEL",
            "vessel": {
              "vesselName": "CMA CGM SAMPLE STAR"
            },
            "exportVoyageNumber": "0MX3EW1MA"
          }
        },
        {
          "eventType": "TRANSPORT",
          "eventClassifierCode": "ACT",
          "eventDateTime": "2026-10-16T02:00:00Z",
          "transportEventTypeCode": "DEPA",
          "transportCall": {
            "UNLocationCode": "CNSHA",
            "modeOfTransport": "VESSEL",
            "vessel": {
              "vesselName": "CMA CGM SAMPLE STAR"
            },
            "exportVoyageNumber": "0MX3EW1MA"
          }
        },
        {
          "eventType": "TRANSPORT",
          "eventClassifierCode": "EST",
          "eventDateTime": "2026-11-03T08:00:00Z",
          "transportEventTypeCode": "ARRI",
          "transportCall": {
            "UNLocationCode": "USLAX",
            "modeOfTransport": "VESSEL",
            "vessel": {
              "vesselName": "CMA CGM SAMPLE STAR"
            }
          }
        }
      ]
    },
    {
      "tracking_number": "CBHU7719301",
      "carrier": "COSCO",
      "reference": "PO-44139 sample",
      "pol": "Ningbo, CN",
      "pod": "Hamburg, DE",
      "events": [
        {
          "description": "Gate-In at First POL",
          "location": "Ningbo, CN",
          "unlocode": "CNNGB",
          "time": "2026-10-08 10:20",
          "isActual": true
        },
        {
          "description": "Loaded on vessel",
          "location": "Ningbo, CN",
          "unlocode": "CNNGB",
          "time": "2026-10-10 23:45",
          "isActual": true,
          "vessel": "COSCO SAMPLE PRIDE",
          "voyage": "071W"
        },
        {
          "description": "Vessel omitted the port call, cargo rolled to the next sailing",
          "location": "Singapore, SG",
          "unlocode": "SGSIN",
          "time": "2026-10-19 06:00",
          "isActual": true
        },
        {
          "description": "Vessel arrival at POD",
          "location": "Hamburg, DE",
          "unlocode": "DEHAM",
          "time": "2026-11-12 05:00",
          "isActual": false
        }
      ]
    },
    {
      "trackingNumber": "MSCU1234560",
      "reference": "PO-44150 sample (typo in the number)"
    }
  ]
}' |
apify call rich_minds/multi-carrier-tracking-ai --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,rich_minds/multi-carrier-tracking-ai"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/zlcTC82CwlCHoKwmz/builds/2C7c21KL1V1LpPjaD/openapi.json
