Home → Blog

You're trying to understand Rotterdam (NLRTM) congestion today and wire that signal into your workflows, but most sources mix live AIS wait times with averaged indicators. By the end of this post, you will know exactly how to query FreightPulse’s IMF PortWatch-based congestion feed for Rotterdam, interpret the fields correctly (7-day average vs 90-day baseline, lagged ~6–7 days), and integrate it safely into a production system.

What this dataset measures (and what it doesn’t)

FreightPulse’s congestion endpoint for Rotterdam is derived from IMF PortWatch using UN Global Platform AIS data. The key design choice: it reports a 7-day moving average of port calls versus a 90-day baseline for the same port. This is a throughput-oriented proxy for congestion, not a measure of anchorage or berth wait times. Updates occur daily, but the source is lagged by roughly 6–7 days. Plan your UX and alerts accordingly; do not treat it as live AIS.

Illustration: Rotterdam Congestion Today

Because the series is normalized to a local 90-day history, you can quickly tell when throughput is trending above or below typical conditions without needing to build your own baselines. However, this also means absolute live queue length or instantaneous hour-level delays are out of scope for this feed.

Key fields you will actually use

When you query Rotterdam (port=NLRTM), you should extract and persist at minimum:

The top-level response envelope is consistently structured as {"success": true, "data": {...}} to simplify error handling and feature flags across endpoints.

Authentication and request limits

Use an API key via the X-API-Key header. Guests without a key are allowed 100 requests per day per IP, suitable for prototyping and low-frequency cron jobs. For production, provision a key and send it in the header. Do not embed secrets in client-side code.

The exact Rotterdam request

Here’s the copy-paste cURL for the Rotterdam (NLRTM) endpoint. Keep the port parameter exactly as shown.

curl -H "X-API-Key: YOUR_API_KEY" "https://freightpulsehq.com/api/v1/port-congestion?port=NLRTM"

Official sample response for NLRTM

The JSON response below shows field names and structure you should code against. Integrations should not assume additional fields are present.

{
    "success": true,
    "data": {
        "results": [
            {
                "port": "Rotterdam",
                "locode": "NL RTM",
                "country": "The Netherlands",
                "congestion": "moderate",
                "as_of": "2026-10-23",
                "metrics": {
                    "recent_avg_portcalls_per_day": 80.3,
                    "baseline_avg_portcalls_per_day": 83.8,
                    "ratio_vs_baseline": 0.96
                }
            }
        ],
        "global_summary": {
            "ports_high_congestion": 0,
            "ports_moderate": 1,
            "ports_low": 0
        },
        "meta": {
            "source": "IMF PortWatch (portwatch.imf.org) - UN Global Platform AIS data",
            "data_lag_days": "~6-7 (daily updates, not real-time)",
            "methodology": "congestion level is an approximation: last 7 days' avg port calls vs 90-day baseline for the same port. Does not measure vessel wait/anchorage time."
        }
    },
    "meta": {
        "request_id": "2b33f5ae-b4ba-495e-a17d-9e67ab5bb942"
    }
}

What to read from this payload:

Quick-start in Python

The following Python snippet calls the Rotterdam endpoint, validates the envelope, extracts the fields you’ll use most, and guards against missing data. It also assigns a conservative cache TTL based on the daily update cadence.

import os
import requests
from datetime import datetime, timedelta

API_URL = "https://freightpulsehq.com/api/v1/port-congestion"
PORT = "NLRTM"
API_KEY = os.getenv("FREIGHTPULSE_API_KEY", "YOUR_API_KEY")

def fetch_rotterdam_congestion():
resp = requests.get(
API_URL,
headers={"X-API-Key": API_KEY},
params={"port": PORT},
timeout=15
)
resp.raise_for_status()
payload = resp.json()

# Validate the standard envelope
if not isinstance(payload, dict) or not payload.get("success"):
raise ValueError("API reported unsuccessful response")

data = payload.get("data", {})
results = data.get("results", [])
if not results:
raise ValueError("No results returned for NLRTM")

rtm = results[0]
congestion = rtm.get("congestion")
as_of = rtm.get("as_of")
metrics = rtm.get("metrics", {}) or {}
ratio = metrics.get("ratio_vs_baseline")
recent = metrics.get("recent_avg_portcalls_per_day")
baseline = metrics.get("baseline_avg_portcalls_per_day")

# Basic integrity checks and parsing
if not congestion or not as_of or ratio is None:
raise ValueError("Missing required fields (congestion, as_of, ratio_vs_baseline)")

# as_of is a date-only string (YYYY-MM-DD)
as_of_date = datetime.strptime(as_of, "%Y-%m-%d").date()

return {
"port": rtm.get("port"),
"locode": rtm.get("locode"),
"country": rtm.get("country"),
"congestion": congestion,
"as_of": as_of_date.isoformat(),
"ratio_vs_baseline": ratio,
"recent_avg_portcalls_per_day": recent,
"baseline_avg_portcalls_per_day": baseline,
# Recommend a 24h cache TTL since source updates daily and is lagged ~6-7 days.
"cache_ttl_seconds": 24 * 3600
}

if __name__ == "__main__":
snapshot = fetch_rotterdam_congestion()
print(f"[Rotterdam NLRTM] as_of={snapshot['as_of']} congestion={snapshot['congestion']} "
f"ratio_vs_baseline={snapshot['ratio_vs_baseline']}")

Interpreting Rotterdam’s congestion safely

Because the indicator is a 7-day average versus a 90-day baseline, it smooths short spikes or dips. This is helpful for planning, but it also means the metric won’t reflect an overnight disruption or a single-day labor action immediately. Use it as a directional signal for throughput conditions at Rotterdam, not as a same-day operations alert.

Practical interpretation patterns you can implement:

Caching, retries, and change detection

Given daily updates and a known lag, a 24-hour cache TTL is appropriate. Write-through cache on successful fetches; backoff and serve stale for transient 5xx responses. Because the same as_of date may be returned for multiple hours until the next daily update lands, use a compound fingerprint such as (as_of, ratio_vs_baseline) to detect material changes for alerts.

Working with units, timestamps, and provenance

All congestion fields are unitless ratios or categorical labels. Port call counts are daily averages (numeric, per day). The as_of field is a date string (YYYY-MM-DD) rather than a timestamp; you can handle it as UTC-implied day granularity. Since the underlying source is IMF PortWatch, include attribution in your downstream metadata if you expose it in a data catalog or analytics notebook.

Operational guardrails for Rotterdam consumers

To avoid misinterpretations inside incident channels or planning meetings:

Error handling and envelope validation

The top-level structure always begins with success and data. Your client should:

Embedding the request in backend jobs and dashboards

The endpoint is simple enough to run hourly from a backend job, even though daily polling is sufficient. If you have multiple regions in a single job, isolate Rotterdam (NLRTM) as its own task so failures elsewhere do not mask this port’s updates. When rendering in dashboards, pair the categorical congestion with the numeric ratio to avoid excessive color toggling on small deviations.

Sample end-to-end flow for Rotterdam

1) Fetch

Use the cURL above or your backend client to request port=NLRTM with your API key header. Expect a JSON envelope with success=true and a data.results array.

2) Validate and parse

Stop if congestion, as_of, or metrics.ratio_vs_baseline are missing. Log and alert. Otherwise, proceed to caching.

3) Cache and diff

Cache by port with a 24-hour TTL. Compare ratio_vs_baseline to the previously stored value. If absolute delta exceeds your threshold (e.g., 0.03), generate a “trend shift” event and stash the as_of date with it.

4) Display

In UI, render “Rotterdam (NLRTM) • moderate • as of 2026-10-23 • 0.96 vs 90d baseline” as a concise line. Add a tooltip clarifying “IMF PortWatch, lagged ~6–7 days; 7-day avg vs 90-day baseline.”

Troubleshooting Rotterdam queries

API reference pointers and next steps

For broader context on envelopes, error semantics, and related datasets, see the official docs. If you are moving to production, register for an API key and set up environment variables in your deployment platform rather than embedding secrets in code.

Documentation

FAQ

Is this “live” congestion for Rotterdam?
It is updated daily but lagged by ~6–7 days from IMF PortWatch. It reflects a 7-day average vs a 90-day baseline, not live AIS wait times.

Which port code should I use?
Use port=NLRTM for Rotterdam. The response also includes locode "NL RTM" for display.

What time zone does as_of use?
as_of is a date-only ISO string (YYYY-MM-DD). Treat it as day-level granularity suitable for UTC-based storage; no time-of-day component is included.

How often should I poll?
Daily is sufficient. If you poll more frequently, cache results for 24 hours and diff against the last stored ratio to avoid noisy updates.

Can I combine this with freight rates and call it a “today price”?
No. This endpoint provides congestion context; it does not quote a freight-rate fare or any USD price.

Get access and ship your integration

Set up your key, drop the Rotterdam request into your backend job, and expose congestion with the as_of date and ratio_vs_baseline to keep stakeholders aligned on what “today” means in a lagged dataset. Create your account here: Register.