# NGA World Port Index Scraper: Pub 150, 2,951 Ports (`arman-bd/nga-world-port-index-scraper`) Actor

Scrape the full NGA World Port Index (Pub 150): 2,951 ports and terminals with depths, cranes, pilotage, repair facilities, ISPS security and UN/LOCODE. Filter by name, country, region, type or depth.

- **URL**: https://apify.com/arman-bd/nga-world-port-index-scraper.md
- **Developed by:** [Arman Hossain](https://apify.com/arman-bd) (community)
- **Categories:** Developer tools
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 port 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

## NGA World Port Index Scraper: Pub 150, 2,951 Ports

![World Port Index: Every port NGA tracks worldwide, over 110 fields covering depths, cranes, pilotage and repairs](https://api.apify.com/v2/key-value-stores/ZQOcNAOHrIgTacAmy/records/nga-world-port-index-scraper.jpg)

Pulls the full World Port Index (NGA Publication 150): every port and terminal the US National Geospatial-Intelligence Agency tracks worldwide, one record per port, with over 110 fields covering identity, depths, cranes, pilotage, tugs, repair facilities, ISPS security status, communications and UN/LOCODE. With no input at all you get the complete current edition, 2,951 ports.

Pub 150 is the reference data behind commercial port databases used across maritime logistics, vessel routing and marine insurance. This Actor reads the structured feed directly, so there's no browser, no proxy and no login required to use it.

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

```
https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/nga-world-port-index-scraper.md
```

### What you get

One dataset record per port, matching the current World Port Index edition. A sample of the most-used fields:

| Field | What it holds |
|---|---|
| `portNumber` | NGA's stable numeric port identifier |
| `portName` | Port name |
| `unloCode` | UN/LOCODE, where assigned (about 87% of ports) |
| `countryCode`, `countryName` | ISO country code and name |
| `regionName`, `regionNumber` | NGA's World Port Index region grouping |
| `decimalLatitude`, `decimalLongitude` | Signed decimal coordinates |
| `latitude`, `longitude` | The same position as NGA's own DMS strings (e.g. `51°54'00"N`) |
| `harborSize`, `harborSizeLabel` | Pub 150 code (`L`/`M`/`S`/`V`) and its decoded label |
| `harborType`, `harborTypeLabel` | Pub 150 code (e.g. `RB`) and its decoded label |
| `shelter`, `shelterLabel` | Shelter afforded, code and decoded label |
| `chDepth`, `anDepth`, `cpDepth`, `otDepth` | Channel, anchorage, cargo pier and oil terminal depths, in meters |
| `crFixed`, `crMobile`, `crFloating`, `cranesContainer` | Crane availability by type |
| `ptCompulsory`, `ptAvailable`, `tugsAssist`, `tugsSalvage` | Pilotage and tug service flags |
| `repairCode`, `drydock`, `railway` | Repair capability codes |
| `portSecurity`, `etaMessage` | ISPS-related fields as published by NGA |
| `globalId` | NGA's persistent GUID for the port |

The full field list runs past 110 entries; every field the source publishes is passed through. Most of the remaining fields are single-letter Pub 150 flags: `Y` (yes), `N` (no) or `U` (unknown/not reported), covering things like ice/swell exposure, cargo handling by type, medical facilities, fuel and water supply, and communications. See the **Output example** below for the complete shape; NGA's own Pub 150 front matter documents the authoritative meaning of each code.

`RUN_SUMMARY` in the key-value store holds per-run counts, the filters you used, and any failure.

### Use cases

- **Port risk pricing.** Marine insurers and P\&I clubs pulling berth depth, shelter and repair capability per port for underwriting.
- **Voyage and routing planning.** Vessel routing and husbandry systems checking max vessel length/beam/draft and pilotage requirements before nominating a port.
- **Reference data sync.** Logistics SaaS platforms keeping a local ports table current against the weekly-corrected Pub 150 edition.
- **UN/LOCODE enrichment.** Joining shipment or AIS data against `unloCode` to attach depth, crane and facility data a bare LOCODE list doesn't carry.
- **Country or region coverage checks.** Filtering to one country or NGA region to audit terminal capability across a trade lane.

### Quick start

The entire World Port Index, nothing filtered:

```json
{}
```

Every port in the Netherlands with at least a 10 m channel:

```json
{
 "countries": ["NL"],
 "minChannelDepth": 10
}
```

River-basin ports named like "port", capped at 50 rows:

```json
{
 "portNames": ["port"],
 "harborTypes": ["RB"],
 "maxPorts": 50
}
```

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `portNames` | array | `[]` | Keep ports whose name contains any of these (case-insensitive substring). Empty means every port. |
| `countries` | array | `[]` | 2-letter ISO country code (exact match) or country name (substring match). |
| `regionNames` | array | `[]` | Substring match against NGA's World Port Index region (mostly country names, sometimes a coastline like `ALASKA` or `BRAZIL NE COAST`). |
| `harborTypes` | array | `[]` | Exact Pub 150 harbor type code(s): `CN`, `CB`, `CT`, `RN`, `RB`, `RT`, `LC`, `OR`, `TH`. |
| `minChannelDepth` | integer | `0` | Drop ports with a reported channel depth below this many meters, or with no reported depth. `0` keeps everything. |
| `maxPorts` | integer | `0` | Cap on saved ports after filtering. `0` means no limit. |

Nothing is required. Running with no input returns the full 2,951-port dataset.

Filters combine with AND. `countries` and `regionNames` are not the same axis: `countries` matches the port's own country, `regionNames` matches NGA's World Port Index grouping, which for a few entries splits one country across several region names (e.g. Alaska ports are grouped separately from the rest of the US).

### Output example

```json
{
 "portNumber": 31140,
 "portName": "Rotterdam",
 "unloCode": "NL RTM",
 "countryCode": "NL",
 "countryName": "Netherlands",
 "regionNumber": 30980,
 "regionName": "NETHERLANDS",
 "navArea": "I",
 "alternateName": null,
 "anDepth": "11",
 "chDepth": "11",
 "chartNumber": "37243",
 "cht": "U",
 "cmAir": "Y",
 "cmRadio": "Y",
 "cmRadioTel": "Y",
 "cmRail": "Y",
 "cmTelegraph": "Y",
 "cmTelephone": "Y",
 "cpDepth": "6",
 "crFixed": "Y",
 "crFloating": "Y",
 "crMobile": "Y",
 "cranesContainer": "U",
 "degauss": "Y",
 "dirtyBallast": "Y",
 "dnc": "coa21a, gen21a, h2131140",
 "dodWaterBody": "North Sea; North Atlantic Ocean",
 "drydock": "M",
 "entranceWidth": null,
 "erIce": "N",
 "erOther": "Y",
 "erSwell": "N",
 "erTide": "N",
 "etaMessage": "Y",
 "firstPortOfEntry": "Y",
 "garbageDisposal": "Y",
 "globalId": "{05C7AD68-A575-481D-90C0-FD17AD560F42}",
 "goodHoldingGround": "Y",
 "harborSize": "L",
 "harborType": "RB",
 "harborUse": "UNK",
 "latitude": "51°54'00\"N",
 "lifts0": "Y",
 "lifts100": "Y",
 "lifts25": "Y",
 "lifts50": "Y",
 "lngTerminalDepth": null,
 "loAnchor": "N",
 "loBeachMoor": "N",
 "loBreakBulk": "U",
 "loContainer": "U",
 "loDangCargo": "U",
 "loIceMoor": "N",
 "loLiquidBulk": "U",
 "loLongTerm": "U",
 "loMedMoor": "N",
 "loOilTerm": "U",
 "loOther": "U",
 "loRoro": "U",
 "loSolidBulk": "U",
 "loWharves": "Y",
 "longitude": "4°29'00\"E",
 "maxVesselBeam": null,
 "maxVesselDraft": null,
 "maxVesselLength": null,
 "medFacilities": "Y",
 "offMaxVesselBeam": null,
 "offMaxVesselDraft": null,
 "offMaxVesselLength": null,
 "otDepth": "13",
 "overheadLimits": "Y",
 "portSecurity": "U",
 "ptAdvisable": "Y",
 "ptAvailable": null,
 "ptCompulsory": "Y",
 "ptLocalAssist": null,
 "publicationNumber": "Sailing Directions Pub. 192 (Enroute) - North Sea",
 "qtOther": "Y",
 "qtPratique": "Y",
 "qtSanitation": "Y",
 "railway": "S",
 "repairCode": "A",
 "s101Enc": "",
 "s121WaterBody": "",
 "s57Enc": null,
 "searchAndRescue": "U",
 "shelter": "F",
 "srDiving": "U",
 "srElectRepair": "Y",
 "srElectrical": "Y",
 "srIceBreaking": "U",
 "srLongshore": "Y",
 "srNavigEquip": "Y",
 "srSteam": "Y",
 "suAviationFuel": "U",
 "suDeck": "Y",
 "suDiesel": "Y",
 "suEngine": "Y",
 "suFuel": "Y",
 "suProvisions": "Y",
 "suWater": "Y",
 "tide": 1,
 "tss": "U",
 "tugsAssist": "Y",
 "tugsSalvage": "U",
 "turningArea": "Y",
 "ukcMgmtSystem": "U",
 "usRep": "U",
 "vts": "U",
 "xcoord": 4.483333000000073,
 "ycoord": 51.900000000000034,
 "decimalLatitude": 51.900000000000034,
 "decimalLongitude": 4.483333000000073,
 "harborSizeLabel": "Large",
 "harborTypeLabel": "River basin",
 "shelterLabel": "Fair",
 "scrapedAt": "2026-08-21T22:47:49.786Z"
}
```

### API example

```bash
curl -X POST "https://api.apify.com/v2/acts/arman-bd~nga-world-port-index-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
 "countries": ["NL"],
 "minChannelDepth": 10
 }'
```

### JavaScript example

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

const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('arman-bd/nga-world-port-index-scraper').call({
 harborTypes: ['RB', 'CN'],
 minChannelDepth: 12,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const p of items) console.log(`${p.portName} (${p.unloCode ?? p.countryCode}): channel ${p.chDepth ?? '?'} m, ${p.harborTypeLabel}`);
```

### Notes

- One run reads the entire dataset in a single request, then filters and caps in-memory, so `portNames`, `countries`, `regionNames` and `harborTypes` all combine with AND against the same 2,951-port list.
- `chDepth`, `anDepth`, `cpDepth` and `otDepth` are reported in meters as numeric strings, matching the source. About 12% of ports carry no `chDepth` at all (`null`); `minChannelDepth` drops those along with any port below the threshold.
- `decimalLatitude`/`decimalLongitude` come straight from the source's own decimal coordinates, not a DMS parse, and are exact for every one of the 2,951 ports.
- `unloCode` is present for about 87% of ports. Ports without one (mostly smaller anchorages and terminals) still carry every other field.
- Pub 150 is corrected weekly; this Actor always reads the current edition, so re-running later can pick up NGA's own corrections.
- A `maxPorts` value outside 0..2,951 (negative, non-numeric) is refused rather than treated as "unlimited": the run finishes with the failure recorded in `RUN_SUMMARY` and nothing saved, so a bad cap can never be billed as a full pull.

### FAQ

**Do I need an API key?** No. The source is a public US government dataset (17 USC 105, public domain).

**How current is the data?** It tracks NGA's live World Port Index edition, corrected weekly. There is no historical or versioned endpoint; each run reflects the current edition.

**Can I get just one port?** Yes: set `portNames` to the port's name. Matching is substring, so a distinctive fragment (e.g. `"Rotterdam"`) is enough; a common fragment (e.g. `"port"`) returns several.

**Why do some fields show `"U"` instead of yes/no?** `U` is Pub 150's own "unknown / not reported" value, distinct from `N` (reported as absent). Pass both through unchanged rather than guessing which one a missing report means.

**Does this include berth-level or terminal-level detail below the port record?** No. Pub 150 is a port-level index; it does not break a port down into individual berths or terminals.

**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

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

Keep ports whose name contains any of these (case-insensitive substring match), e.g. 'Rotterdam' or 'san'. Leave empty for every port.

## `countries` (type: `array`):

Keep ports in any of these countries. Accepts a 2-letter ISO 3166 country code (exact match, e.g. 'NL') or a country name (substring match, e.g. 'nether'). Leave empty for every country.

## `regionNames` (type: `array`):

Keep ports whose World Port Index region contains any of these (case-insensitive substring match). Regions are NGA's own groupings, mostly by country but sometimes by coastline (e.g. 'BRAZIL NE COAST', 'ALASKA'). Leave empty for every region.

## `harborTypes` (type: `array`):

Keep only these Pub 150 harbor type codes (exact match, case-insensitive): CN (coastal natural), CB (coastal breakwater), CT (coastal tide gate), RN (river natural), RB (river basin), RT (river tide gate), LC (lake or canal), OR (open roadstead), TH (typhoon harbor). Leave empty for every type.

## `minChannelDepth` (type: `integer`):

Drop ports whose reported channel depth (chDepth, in meters) is below this, or whose depth is unknown. 0 keeps every port including those with no reported depth.

## `maxPorts` (type: `integer`):

Cap on saved ports, applied after filtering. 0 means no limit (the whole matched set, up to all 2,951 ports).

## Actor input object example

```json
{
  "portNames": [
    "Rotterdam",
    "Santos"
  ],
  "countries": [
    "NL",
    "Brazil"
  ],
  "regionNames": [
    "NETHERLANDS"
  ],
  "harborTypes": [
    "RB",
    "CN"
  ],
  "minChannelDepth": 0,
  "maxPorts": 0
}
```

# Actor output Schema

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

Every port record 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("arman-bd/nga-world-port-index-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("arman-bd/nga-world-port-index-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 '{}' |
apify call arman-bd/nga-world-port-index-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,arman-bd/nga-world-port-index-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/qFGjtiF9duful7fvc/builds/GYeKL8gjvlhTRzWao/openapi.json
