# ONE Line Port Schedules & Cut-offs Scraper (`arman-bd/one-line-port-schedules-scraper`) Actor

Scrape ONE (Ocean Network Express) berth schedules per port: arrival/berthing/departure times, cargo/document/VGM cut-offs and coded delay reasons. Optional vessel particulars (GT/NT, IMO, class).

- **URL**: https://apify.com/arman-bd/one-line-port-schedules-scraper.md
- **Developed by:** [Arman Hossain](https://apify.com/arman-bd) (community)
- **Categories:** Automation
- **Stats:** 2 total users, 1 monthly users, 90.9% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.80 / 1,000 port call scrapeds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## ONE Line Port Schedules & Cut-offs Scraper

![ONE Port Schedules: Berth calls by port with ETA, ETB, ETD, the four booking cut-offs and coded delay reasons](https://api.apify.com/v2/key-value-stores/ZQOcNAOHrIgTacAmy/records/one-line-port-schedules-scraper.jpg)

Pulls Ocean Network Express (ONE) berth schedules directly from the same public schedule service ONE's own website uses. For any port you name, you get every vessel call in your date window: arrival, berthing and departure with their status codes, four separate cut-off timestamps (cargo, document, VGM, inland), the vessel's voyage identifiers, and a coded reason whenever ONE has flagged the call as delayed.

No browser, no proxy, no login required to use this Actor. Point it at a UN/LOCODE or a port name and it does the rest, including resolving names to codes and, optionally, enriching each call with the vessel's own particulars (tonnage, IMO number, flag, class, build date).

**Agent skill: [SKILL.md](https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/one-line-port-schedules-scraper.md)**

```
https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/one-line-port-schedules-scraper.md
```

### What you get

| Field | What it holds |
|---|---|
| `portCode`, `portName` | The UN/LOCODE you asked for, and the port name ONE resolved it to |
| `vvdName`, `vesselName`, `vesselCode` | Voyage identifier (vessel + voyage + direction), vessel name, ONE's internal vessel code |
| `scheduleVoyageNumber`, `scheduleDirectionCode` | Voyage number and direction (E/W/N/S-style trade lane code) |
| `yardName`, `yardCode`, `terminalNameMaskFlag` | Terminal the call is berthing at, plus ONE's own flag for whether the terminal name is masked at source |
| `vesselServiceLaneCode`, `vesselServiceTypeCode` | ONE's own service/trade-lane codes |
| `arrivalDateTime`, `arrivalStatus` | ETA/ATA and whether it is estimated or actual |
| `berthingDateTime`, `berthingStatus` | ETB/ATB and its status |
| `departureDateTime`, `departureStatus` | ETD/ATD and its status |
| `cargoCutOffDateTime`, `documentCutOffDateTime`, `vgmCutOffDateTime`, `inlandCutOffDateTime` | The four booking cut-offs, each `null` when the source has none set |
| `vesselDelayReasonCode`, `vesselDelayReason` | ONE's coded delay reason and its plain-text explanation, when the call is delayed |
| `netTonnage`, `grossTonnage`, `portRegistry`, `ownerName`, `callSign`, `carrier`, `builtOn`, `lloydsNo`, `registryCode`, `classNo`, `age` | Vessel particulars, only when `includeVesselDetails` is on; otherwise `null` |
| `vesselDetailsFetched`, `vesselDetailDisplay` | `true` when the particulars above were actually fetched for this row; `vesselDetailDisplay` is ONE's own `Y`/`N` flag for whether it publishes vessel details for this call |
| `fromDate`, `toDate`, `vesselType`, `cargoNature` | The window and filters the run used |
| `scrapedAt` | Run timestamp |

`RUN_SUMMARY` in the key-value store holds per-port counts, any port or port-name lookup that failed, and the vessel-lookup tally.

### Use cases

- **Schedule slippage tracking.** `vesselDelayReasonCode` is a coded field most schedule sources don't expose at all, pull it across a trade lane over time and you have a real delay attribution dataset, not just a slipping ETA.
- **Booking cut-off alerts.** Watch `cargoCutOffDateTime`/`documentCutOffDateTime`/`vgmCutOffDateTime` for the vessels you have cargo on and alert before a cut-off passes.
- **Berth utilisation.** `yardCode` plus `berthingDateTime`/`departureDateTime` gives terminal-level occupancy for a port.
- **Vessel due diligence.** `includeVesselDetails` adds GT/NT, IMO number, flag and class for every vessel calling a port, without a separate vessel-particulars source.

### Quick start

Rotterdam's next two weeks, the default:

```json
{}
```

Two named ports, capped:

```json
{
 "portCodes": ["NLRTM", "DEHAM"],
 "fromDate": "2026-08-22",
 "toDate": "2026-09-05",
 "maxRecords": 200
}
```

Resolve a port by name instead of knowing its code, with vessel particulars merged in:

```json
{
 "portNames": ["rotterdam"],
 "includeVesselDetails": true,
 "maxRecords": 50
}
```

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `portCodes` | array | `["NLRTM"]` | UN/LOCODEs to read, e.g. `NLRTM`, `DEHAM`. Case-insensitive, deduplicated, merged with anything `portNames` resolves. |
| `portNames` | array | `[]` | Port names or partial names (3+ letters) to resolve to UN/LOCODEs before scraping. A name matching several ports resolves to all of them. A name matching nothing is recorded in `RUN_SUMMARY.nameResolutionFailures` and does not stop the run. |
| `fromDate` | string | today | Window start, `YYYY-MM-DD`. |
| `toDate` | string | `fromDate` + 14 days | Window end, `YYYY-MM-DD`. Must not be before `fromDate`. |
| `vesselType` | string | `I` | Direction filter passed through as-is. `I` (inbound) is the only confirmed value. |
| `cargoNature` | string | `GENERAL_PURPOSE` | Cargo-nature filter passed through as-is. `GENERAL_PURPOSE` is the only confirmed value. |
| `maxRecords` | integer | `200` | Stop once this many berth calls have been saved across all ports. `0` means no limit. |
| `includeVesselDetails` | boolean | `false` | Merge tonnage/IMO/flag/class/build-date into every row for each distinct vessel found. Adds one extra request per distinct vessel, capped at 40 per run. |

Nothing is required. Running with no input returns Rotterdam's next two weeks.

### Output example

A berth call with vessel particulars off (the default):

```json
{
 "portCode": "NLRTM",
 "portName": "ROTTERDAM, NETHERLANDS",
 "vvdName": "OOCL VERACRUZ 020W",
 "vesselName": "OOCL VERACRUZ",
 "vesselCode": "OVZT",
 "vesselDetailDisplay": "Y",
 "scheduleVoyageNumber": "0020",
 "scheduleDirectionCode": "W",
 "yardName": "RWG (ROTTERDAM WORLD GATEWAY)",
 "yardCode": "NLRTM15",
 "vesselServiceLaneCode": "AT3",
 "arrivalDateTime": "2026-08-18 12:39",
 "arrivalStatus": "A",
 "berthingDateTime": "2026-08-20 18:44",
 "berthingStatus": "A",
 "departureDateTime": "2026-08-22 00:02",
 "departureStatus": "A",
 "cargoCutOffDateTime": "2026-08-19 18:44",
 "documentCutOffDateTime": "2026-08-18 09:00",
 "vgmCutOffDateTime": "2026-08-19 09:00",
 "inlandCutOffDateTime": null,
 "vesselDelayReasonCode": "OPP",
 "vesselDelayReason": "Delay from previous port/voyage",
 "vesselServiceTypeCode": "J",
 "terminalNameMaskFlag": "N",
 "netTonnage": null,
 "grossTonnage": null,
 "portRegistry": null,
 "ownerName": null,
 "callSign": null,
 "carrier": null,
 "builtOn": null,
 "lloydsNo": null,
 "registryCode": null,
 "classNo": null,
 "age": null,
 "vesselDetailsFetched": false,
 "fromDate": "2026-08-22",
 "toDate": "2026-08-29",
 "vesselType": "I",
 "cargoNature": "GENERAL_PURPOSE",
 "scrapedAt": "2026-08-21T22:47:53.796Z"
}
```

With `includeVesselDetails: true`, the same row also carries the vessel's own particulars:

```json
{
 "netTonnage": "27173.000",
 "grossTonnage": "57907.000",
 "portRegistry": "MONROVIA, LIBERIA (LRMLW)",
 "ownerName": "CHARTER",
 "callSign": "5LNG2",
 "carrier": "ORIENT OVERSEAS CONTAINER LINE (OOL)",
 "builtOn": "2024-01-01",
 "lloydsNo": "9979266",
 "registryCode": "LR",
 "classNo": "ABS / ",
 "age": "3",
 "vesselDetailsFetched": true
}
```

### Naming a port

| You write in `portCodes` | Actor reads |
|---|---|
| `NLRTM` | Rotterdam, exactly as given (uppercased) |
| `nlrtm`, `NLRTM` | Same, case-folded and trimmed |

| You write in `portNames` | Actor resolves to |
|---|---|
| `rotterdam` | `NLRTM` (one match) |
| `ham` | Every port whose name contains "ham", Hamburg, Hamina, Immingham, and more. Use `portCodes` once you know the exact one. |

`portCodes` and `portNames` are additive: name every port you know the code for in `portCodes`, and use `portNames` only for discovery.

### API example

```bash
curl -X POST "https://api.apify.com/v2/acts/arman-bd~one-line-port-schedules-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
 "portCodes": ["NLRTM"],
 "fromDate": "2026-08-22",
 "toDate": "2026-09-05",
 "maxRecords": 200
 }'
```

### JavaScript example

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

const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('arman-bd/one-line-port-schedules-scraper').call({
 portCodes: ['NLRTM', 'DEHAM'],
 fromDate: '2026-08-22',
 toDate: '2026-09-05',
 maxRecords: 300,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const call of items) {
  console.log(`${call.portCode} ${call.vesselName} berths ${call.berthingDateTime} (${call.berthingStatus}), cargo cut-off ${call.cargoCutOffDateTime}`);
}
```

### Notes

- There is no pagination on the source: one request per port returns every call in the window at once, so `maxRecords` is the only depth control and it bounds the whole run, not each port.
- An unrecognised port code is a legitimate empty result (zero rows, no failure recorded), the same way it behaves on ONE's own schedule page, it is not distinguishable from "this port has no calls in this window."
- An unresolvable `portNames` entry is recorded in `RUN_SUMMARY.nameResolutionFailures` and does not stop the run as long as another port produced results.
- Requests to different ports run one at a time rather than in parallel, so a run over several ports takes roughly as long as the sum of its parts.
- `vesselDelayReasonCode`/`vesselDelayReason`, the four cut-off fields, and `arrivalStatus`/`berthingStatus`/`departureStatus` change as a call approaches and completes. Treat every run as a snapshot at `scrapedAt`, not a final record.
- Retries (3 attempts, linear backoff) cover transient failures. A malformed date, or an input with nothing usable to look up, ends the run immediately with the reason recorded in `RUN_SUMMARY.configError`.

### FAQ

**Do I need a ONE account or API key?** No. The service this Actor reads is the same unauthenticated one ONE's own public schedule page calls.

**Which ports are covered?** Any port ONE calls at, globally. Try `portNames` first if you are unsure of the UN/LOCODE.

**How far ahead can I query?** As far as ONE's own schedule page shows, schedules are typically published a few weeks out, and how far back or forward a given port has data varies port to port.

**Why is a cut-off field `null`?** ONE simply has none set for that call. `inlandCutOffDateTime` in particular is empty on most calls.

**What does `vesselDelayReasonCode` mean?** It is ONE's own operational delay code (for example `OPP` for a delay carried over from the previous port/voyage). `vesselDelayReason` is the paired plain-text explanation; use the code for grouping and the text for display.

**Can I get a vessel's IMO number without pulling a whole port's schedule?** Not from this Actor directly, vessel particulars are looked up per vessel found while scraping a port's schedule (`includeVesselDetails: true`), not standalone.

**Can I plug it into something else?** Yes. Apify API, the client libraries, webhooks, scheduled runs, dataset exports to JSON, CSV or Excel, or MCP. The output is structured JSON.

# Actor input Schema

## `portCodes` (type: `array`):

Ports to read, as UN/LOCODEs (e.g. 'NLRTM' for Rotterdam, 'DEHAM' for Hamburg). Case-insensitive, deduplicated. Combined with any codes found through 'Port name search'. Defaults to Rotterdam when both this and the name search are empty.

## `portNames` (type: `array`):

Port names or partial names (3+ letters each) to resolve to UN/LOCODEs before scraping, e.g. 'rotterdam' or 'ham'. A name matching several ports (like 'ham') resolves to all of them, so prefer 'Port codes' once you know the exact one. A name matching nothing is recorded in RUN\_SUMMARY and does not stop the run.

## `fromDate` (type: `string`):

Start of the schedule window, as YYYY-MM-DD. Leave empty to default to today (run time).

## `toDate` (type: `string`):

End of the schedule window, as YYYY-MM-DD. Leave empty to default to 14 days after 'From date'. Must not be before 'From date'.

## `vesselType` (type: `string`):

Direction filter passed to the source as-is. 'I' (inbound) is the only value the source is confirmed to accept; it is also the default used by ONE's own public schedule page.

## `cargoNature` (type: `string`):

Cargo-nature filter passed to the source as-is. 'GENERAL\_PURPOSE' is the value ONE's own schedule page uses and the only one confirmed to work.

## `maxRecords` (type: `integer`):

Stop once this many berth calls have been saved across all ports. Set 0 for no limit, bounded only by what each port's window returns (there is no pagination; a busy port over two weeks is roughly 100-150 rows).

## `includeVesselDetails` (type: `boolean`):

For each distinct vessel in the results, look up gross/net tonnage, IMO number, flag, call sign, class and build date and merge them into every one of that vessel's rows. Off by default: it adds one extra request per distinct vessel (capped at 40 per run) on top of the port schedule requests.

## Actor input object example

```json
{
  "portCodes": [
    "NLRTM",
    "DEHAM"
  ],
  "portNames": [
    "rotterdam"
  ],
  "fromDate": "2026-08-22",
  "toDate": "2026-09-05",
  "vesselType": "I",
  "cargoNature": "GENERAL_PURPOSE",
  "maxRecords": 200,
  "includeVesselDetails": false
}
```

# Actor output Schema

## `items` (type: `string`):

Every berth call the run produced.

## `runsummary` (type: `string`):

The RUN\_SUMMARY record from the run's key-value store.

# 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 = {
    "portCodes": [
        "NLRTM"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("arman-bd/one-line-port-schedules-scraper").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 = { "portCodes": ["NLRTM"] }

# Run the Actor and wait for it to finish
run = client.actor("arman-bd/one-line-port-schedules-scraper").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 '{
  "portCodes": [
    "NLRTM"
  ]
}' |
apify call arman-bd/one-line-port-schedules-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,arman-bd/one-line-port-schedules-scraper"
        }
    }
}

```

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/pxtrb0fcIQmf8dff2/builds/G4vD6LOP22frAytrA/openapi.json
