# Panama Canal Advisories to Shipping Scraper (`arman-bd/panama-canal-advisories-scraper`) Actor

Scrape every ACP Advisory to Shipping: draft restrictions, transit slot and booking rule changes, and monthly canal operations summaries, filterable by year, advisory number and keyword.

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

## Pricing

from $1.20 / 1,000 advisory 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

## Panama Canal Advisories to Shipping Scraper

![Panama Canal Advisories: The full numbered advisory archive, with draft restrictions, tolls and booking notices](https://api.apify.com/v2/key-value-stores/ZQOcNAOHrIgTacAmy/records/panama-canal-advisories-scraper.jpg)

Pulls the Panama Canal Authority's (ACP) public **Advisory to Shipping** archive: draft
restriction changes, transit slot and booking rule updates, scheduled lock outages, and
the monthly canal operations summary. Every advisory is numbered `ADV-NN-YYYY`, and this
Actor returns one dataset record per advisory with the number, year, title, category and
a link to the source PDF, filterable by year, advisory number and keyword.

Draft and booking-system advisories move Panamax freight rates and can trigger a
Cape-of-Good-Hope reroute decision within a day of publication, so this is a source
chartering desks, dry-bulk brokers and insurers watch closely.

No browser, no proxy, no login required to use this Actor.

**Agent skill: [SKILL.md](https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/panama-canal-advisories-scraper.md)**

```
https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/panama-canal-advisories-scraper.md
```

### What you get

| Field | What it holds |
|---|---|
| `advisoryNumber` | The `NN` in `ADV-NN-YYYY`, parsed from the filename. Numbering restarts each year. |
| `year` | The `YYYY` in the same number. |
| `title` | The advisory's title, taken from the filename slug (e.g. `Draft-adjustment-in-the-Neopanamax-Locks`). `null` for the handful of advisories whose filename carries no extra text. |
| `category` | `Advisory`, `Monthly Operations Summary` or `Notice to Shipping`, classified from the title. |
| `pdfUrl` | Direct link to the source PDF. |
| `uploadYearMonth` | The `YYYY-MM` the file was uploaded, read from its storage path. Usually matches `year` but not always: an advisory can be uploaded the month after the year it is numbered for. |
| `language` | Always `en`. The archive is published in English only. |
| `pdfVerified`, `pdfBytes`, `pdfContentType` | Set only when `downloadPdf` is on: whether the PDF still resolves, its size and its content type. `null` otherwise. |
| `sourceUrl` | The archive page this record was read from. |
| `scrapedAt` | Run timestamp. |

`RUN_SUMMARY` in the key-value store holds per-run counts: how many PDF links were on
the page, how many matched the numbered-advisory pattern, how many matched your filters,
and any PDF that failed its liveness check when `downloadPdf` is on.

### Use cases

- **Freight-rate signal.** A maximum-authorized-draft change or a transit slot cut is a
  leading indicator for Panamax rates; alert on new `Advisory` rows matching `draft` or
  `booking`.
- **Chartering and routing desks.** Watch for scheduled lock outages and speed
  restrictions that affect transit windows before they hit the news wire.
- **Operations monitoring.** Pull the `Monthly Operations Summary` rows on a schedule to
  track transit counts and tonnage trends over time.
- **Compliance archive.** Keep a running, de-duplicable record of every advisory number
  issued, so a gap in the sequence is visible immediately.
- **Newsletter and briefing sourcing.** `title` and `pdfUrl` are enough to link out to
  the source document without re-hosting it.

### Quick start

The full archive, every advisory ACP has published:

```json
{}
```

Recent draft and booking advisories only:

```json
{
 "yearFrom": 2025,
 "keywords": ["draft", "booking", "transit"]
}
```

The last 20 advisories, with each PDF verified as still live:

```json
{
 "maxAdvisories": 20,
 "downloadPdf": true
}
```

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `yearFrom` | integer | `0` | Keep advisories from this year on, by their own `ADV-NN-YYYY` year, not upload date. `0` means no lower bound. |
| `yearTo` | integer | `0` | Keep advisories up to and including this year. `0` means no upper bound. |
| `advisoryNumberFrom` | integer | `0` | Drop advisories numbered below this. Numbering restarts each year, so this filters within a year. `0` means no filter. |
| `keywords` | array | `[]` | Keep only advisories whose title contains at least one of these words or phrases (`draft`, `booking`, `transit`, `maximum authorized draft`). Case-insensitive; hyphens in the title are treated as spaces. Empty keeps everything. |
| `downloadPdf` | boolean | `false` | HEAD-request each matching PDF to confirm it still resolves and record its size and content type. One extra request per advisory. |
| `maxAdvisories` | integer | `0` | Stop after this many advisories, newest (year, then advisory number) first. `0` returns every match. |

Nothing is required. Running with no input returns the entire archive, currently a few
hundred advisories.

### Output example

```json
{
 "advisoryNumber": 25,
 "year": 2026,
 "title": "Adjustment-to-the-Maximum-Allowable-Draft-in-the-Neopanamax-Locks",
 "category": "Advisory",
 "pdfUrl": "https://pancanal.com/wp-content/uploads/2026/08/ADV-25-2026-Adjustment-to-the-Maximum-Allowable-Draft-in-the-Neopanamax-Locks.pdf",
 "uploadYearMonth": "2026-08",
 "language": "en",
 "pdfVerified": null,
 "pdfBytes": null,
 "pdfContentType": null,
 "sourceUrl": "https://pancanal.com/en/maritime-services/advisory-to-shipping/",
 "scrapedAt": "2026-08-21T12:00:00.000Z"
}
```

A monthly summary record:

```json
{
 "advisoryNumber": 26,
 "year": 2026,
 "title": "Monthly-Canal-Operations-Summary-July-2026",
 "category": "Monthly Operations Summary",
 "pdfUrl": "https://pancanal.com/wp-content/uploads/2026/08/ADV-26-2026-Monthly-Canal-Operations-Summary-July-2026.pdf",
 "uploadYearMonth": "2026-08",
 "language": "en"
}
```

### API example

```bash
curl -X POST "https://api.apify.com/v2/acts/arman-bd~panama-canal-advisories-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
 "yearFrom": 2025,
 "keywords": ["draft", "booking"]
 }'
```

### JavaScript example

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

const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('arman-bd/panama-canal-advisories-scraper').call({
 yearFrom: 2025,
 keywords: ['draft', 'booking', 'transit'],
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const a of items) console.log(`ADV-${a.advisoryNumber}-${a.year} [${a.category}] ${a.title}`);
```

### Notes

- The archive page lists advisories in ascending order; this Actor returns them newest
  first (year, then advisory number, both descending) so a capped run gets recent
  advisories rather than 2020's.
- Advisory numbers restart each year, so `advisoryNumberFrom` only makes sense combined
  with a `yearFrom`/`yearTo` range unless you want it applied globally across years.
- A small number of PDFs on the source site (roughly 1 in 100) don't follow the
  `ADV-NN-YYYY` naming convention, older tolls schedules, forms, and a few
  inconsistently-named advisories. Those are intentionally excluded; this Actor covers
  the numbered advisory series.
- `title` is derived from the filename, not the PDF body. It is a close read of the
  advisory's subject but occasional source-side typos (e.g. "Montly" for "Monthly")
  carry straight through, and `category` classification is typo-tolerant for the ones
  that are known to recur.
- Occasionally a revised advisory is republished as a second PDF for the same number
  (e.g. a filename ending `Rev1`). Both are returned as separate records with the same
  `advisoryNumber`/`year`; the more recent one is the correction.

### FAQ

**Do I need an account with the Panama Canal Authority?** No. You supply no credentials.

**Can I get the PDF text itself, not just the filename-derived fields?** No, this Actor
returns the index (number, year, title, category, link) and optionally verifies the PDF
is live. Fetch `pdfUrl` yourself for the document body.

**Is there a Spanish-language version?** Not for this archive. The Panama Canal
Authority publishes the Advisory to Shipping series in English only; `language` is
therefore a constant field.

**How current is this?** The archive updates within a day of a new advisory being
issued, several times a month on average. Run this Actor on a schedule and diff on
`advisoryNumber`/`year` to catch new ones.

**Does `maxAdvisories` fetch only the newest, or filter after fetching everything?** The
whole archive is read every run (it is one page), then filtered and sorted, then capped.
`maxAdvisories` bounds what is saved and charged, not what is read.

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

## `yearFrom` (type: `integer`):

Keep only advisories published in this year or later, taken from the advisory's own ADV-NN-YYYY number, not the upload date. 0 means no lower bound.

## `yearTo` (type: `integer`):

Keep only advisories published in this year or earlier. 0 means no upper bound, so this year's advisories are always included.

## `advisoryNumberFrom` (type: `integer`):

Drop advisories numbered below this. Numbering restarts each year (ADV-01 is the first advisory of its year), so this filters within a year rather than across the whole archive. 0 means no filter.

## `keywords` (type: `array`):

Keep only advisories whose title contains at least one of these words or phrases (e.g. 'draft', 'booking', 'transit', 'maximum authorized draft'). Case-insensitive, matched against the title with hyphens treated as spaces. Leave empty to keep every advisory.

## `downloadPdf` (type: `boolean`):

HEAD-request each matching PDF to confirm it still resolves and to record its size and content type. Roughly doubles run time since it is one extra request per advisory. Leave off for a fast index-only pull.

## `maxAdvisories` (type: `integer`):

Stop after this many advisories, newest (by year, then advisory number) first. 0 means return every advisory that matches the other filters. The whole archive is a few hundred records at most.

## Actor input object example

```json
{
  "yearFrom": 0,
  "yearTo": 0,
  "advisoryNumberFrom": 0,
  "keywords": [
    "draft",
    "booking",
    "transit"
  ],
  "downloadPdf": false,
  "maxAdvisories": 0
}
```

# Actor output Schema

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

Every advisory 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/panama-canal-advisories-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/panama-canal-advisories-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/panama-canal-advisories-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,arman-bd/panama-canal-advisories-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/AL5YpfKEfKS6CygQi/builds/L9K8sL8u3c3Q3zGDD/openapi.json
