Home → Blog

You're building a transportation dashboard and need to monitor current congestion conditions at the Port of Rotterdam for routing and schedule risk. By the end of this guide, you will query the Rotterdam (NLRTM) congestion feed from FreightPulse in Python, parse the exact fields you need, and ship code you can run in production.

What this congestion feed is (and is not)

The FreightPulse port congestion endpoint for Rotterdam is derived from IMF PortWatch data and is lagged by roughly 6–7 days. It is a smoothed signal: a 7‑day average compared against a 90‑day baseline. It is not live AIS vessel wait times, and it should not be used as a minute-by-minute operations feed.

The API returns a small, consistent envelope designed for easy integration into transportation analytics pipelines. You will read three documented data fields from Rotterdam (NLRTM): congestion, ratio_vs_baseline, and as_of.

If you need to alert operations teams, design your thresholds with the 6–7 day lag and smoothing in mind. This dataset is excellent for trend tracking, planning buffers, and scorecards, rather than live berthing decisions.

Endpoint, method, and auth

The Rotterdam congestion data is available at a single HTTP GET endpoint:

Guest access without a key is allowed, with a daily limit of 100 requests per IP. For automated jobs, use an API key to avoid hitting that ceiling. You can obtain an API key from the Register page and review parameter semantics in the Documentation.

Request shape and response envelope

The API wraps data in a simple envelope to make parsing predictable:

For Rotterdam, the documented payload fields are:

Notes you should incorporate in dashboards and jobs:

Quick test with curl

Run this from any terminal to confirm you can reach Rotterdam’s congestion feed. This uses the documented header and port code (NLRTM):

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

Guest access without the header is limited to 100 requests per IP per day; add the header before you wire this into scheduled jobs.

Python: minimal client for NLRTM congestion

The example below shows a compact Python requester that:

Code

import os
import sys
import json
import time
from typing import Dict, Any, Optional

import requests

FREIGHTPULSE_URL = "https://freightpulsehq.com/api/v1/port-congestion"
PORT_CODE = "NLRTM" # Rotterdam
API_KEY = os.environ.get("FREIGHTPULSE_API_KEY", "YOUR_API_KEY")

class FreightPulseError(Exception):
pass

def fetch_rotterdam_congestion(session: Optional[requests.Session] = None) -> Dict[str, Any]:
s = session or requests.Session()
params = {"port": PORT_CODE}
headers = {"X-API-Key": API_KEY}
try:
resp = s.get(FREIGHTPULSE_URL, params=params, headers=headers, timeout=10)
except requests.exceptions.Timeout as e:
raise FreightPulseError(f"Request timed out: {e}") from e
except requests.exceptions.RequestException as e:
raise FreightPulseError(f"Transport error: {e}") from e

if resp.status_code == 429:
# Consider backing off if you are near the guest limit (100/day/IP) or plan limits for keys.
raise FreightPulseError("Rate limit hit (HTTP 429). Back off and retry later.")
if resp.status_code >= 500:
raise FreightPulseError(f"Server error (HTTP {resp.status_code}). Retry with backoff.")
if resp.status_code != 200:
raise FreightPulseError(f"Unexpected status {resp.status_code}: {resp.text}")

try:
payload = resp.json()
except json.JSONDecodeError as e:
raise FreightPulseError(f"Invalid JSON: {e}") from e

if not isinstance(payload, dict) or "success" not in payload or "data" not in payload:
raise FreightPulseError("Malformed envelope: expected 'success' and 'data' fields.")

if not payload.get("success", False):
# If provided, some APIs include error info; here we only assert success is true.
raise FreightPulseError(f"API returned success=false: {json.dumps(payload)}")

data = payload.get("data", {})
required_fields = ["congestion", "ratio_vs_baseline", "as_of"]
missing = [f for f in required_fields if f not in data]
if missing:
raise FreightPulseError(f"Missing expected fields in data: {', '.join(missing)}")

# Return the minimal subset your app needs.
return {
"as_of": data["as_of"],
"congestion": data["congestion"],
"ratio_vs_baseline": data["ratio_vs_baseline"],
}

def main() -> int:
try:
result = fetch_rotterdam_congestion()
except FreightPulseError as e:
print(f"Error: {e}", file=sys.stderr)
return 1

# Example: print concise line suitable for scraping into a metrics collector.
print(f"rotterdam_as_of={result['as_of']} congestion={result['congestion']} ratio_vs_baseline={result['ratio_vs_baseline']}")
return 0

if __name__ == "__main__":
sys.exit(main())

For production, consider a retry loop with exponential backoff for 5xx or transient network errors, and cache results keyed by as_of to avoid duplicate downstream writes.

Example JSON for Rotterdam

No official congestion JSON is published for NLRTM. The example below is illustrative and shows only the documented fields inside the standard envelope:

{
"success": true,
"data": {
"congestion": 1.12,
"ratio_vs_baseline": 1.08,
"as_of": "2026-09-20"
}
}

Field usage in your code:

End-to-end integration pattern

Because the Rotterdam series is lagged, the most robust production flows are daily batch pulls. Here’s a practical pattern you can deploy quickly:

  1. Scheduler: Run your job once per day after your typical data landing window. Avoid sub-hourly polling; the signal will not change intraday.
  2. Fetch and validate: Call the NLRTM endpoint, require success=true, and enforce presence of congestion, ratio_vs_baseline, and as_of.
  3. Idempotency: If you already have a row for a given as_of, skip or update in place. Use as_of as a unique key.
  4. Cache: Memoize last-good payload in a small store so front ends remain stable during transient network failures.
  5. Alerting: If ratio_vs_baseline crosses a threshold you define (e.g., rising above baseline for multiple days), open a non-urgent ticket or annotate dashboards. Avoid paging on this lagged series.

Handling Rotterdam (NLRTM) specifics

Port code mapping matters. For Rotterdam, use NLRTM to retrieve the correct series. The endpoint supports this via the port query parameter and returns only the fields documented above for this post.

Remember that this feed is not live AIS. If you are validating against on-the-ground or AIS-based signals, expect differences because this API intentionally smooths congestion over a 7‑day window and references a 90‑day baseline. Near real-time spikes in anchorage or berthing queues may not be reflected until several days later.

Operational tips that save time

Testing and validation for transportation analytics

To verify that your Rotterdam integration is solid:

Security and secrets management

Store YOUR_API_KEY in a secret manager or environment variable. Pass it via the X-API-Key header exactly as shown. Avoid writing keys to logs or embedding them in client-side code. If you must run from ephemeral environments (e.g., CI), scope the key to read-only access where supported and rotate periodically.

Deployment checklist

Frequently asked questions

Is this Rotterdam feed live AIS?
No. It is IMF PortWatch-derived, lagged ~6–7 days, and smoothed as a 7‑day average relative to a 90‑day baseline.

How often should I poll the NLRTM endpoint?
Once per day is sufficient. The series will not materially change intraday due to its lag and smoothing.

What happens if I query without an API key?
You can make up to 100 requests per day per IP as a guest. For automation, add X-API-Key: YOUR_API_KEY to avoid rate limiting.

What fields should I persist?
Store as_of, congestion, and ratio_vs_baseline. Use as_of as a unique key to prevent duplicates and to track data recency.

Can I reuse this Python code for other ports?
The flow is portable, but this post demonstrates NLRTM specifically. Review the port parameter details in the Documentation before generalizing.

Ready to integrate Rotterdam congestion into your transportation analytics? Create an API key on the Register page, then ship the Python client above and pin a daily scheduler. If you need parameter references or payload semantics, keep the Documentation open as you wire it into your pipeline.