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:
- Method: GET
- URL: https://freightpulsehq.com/api/v1/port-congestion?port=NLRTM
- Header auth: X-API-Key: YOUR_API_KEY
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:
- success: boolean for transport- and auth-level success
- data: object containing the payload fields you will use
For Rotterdam, the documented payload fields are:
- congestion: a 7‑day average congestion signal from IMF PortWatch (lagged ~6–7 days)
- ratio_vs_baseline: the same signal relative to a 90‑day baseline
- as_of: the most recent date for which the lagged 7‑day average was computed
Notes you should incorporate in dashboards and jobs:
- Time semantics: as_of is a date string (ISO 8601, e.g., YYYY-MM-DD). Because the series is lagged, as_of will be several days before today.
- Units: congestion is a normalized congestion measure from IMF PortWatch; ratio_vs_baseline is a dimensionless ratio against the 90‑day baseline.
- Data cadence: new values typically land daily with a 6–7 day lag; avoid assuming real-time updates.
- Caching: if you poll more than once per day, cache responses until the as_of date changes.
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:
- Calls the documented endpoint with the Rotterdam port code
- Validates the envelope
- Extracts congestion, ratio_vs_baseline, and as_of
- Implements a short timeout and basic error handling
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:
- congestion: The lagged 7‑day average congestion level for Rotterdam. Use this as your headline metric.
- ratio_vs_baseline: A quick way to see how current congestion compares to the port’s 90‑day baseline. Values above 1 indicate above-baseline congestion.
- as_of: The date associated with the computation. Use this for caching and to label charts so users understand data recency.
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:
- Scheduler: Run your job once per day after your typical data landing window. Avoid sub-hourly polling; the signal will not change intraday.
- Fetch and validate: Call the NLRTM endpoint, require success=true, and enforce presence of congestion, ratio_vs_baseline, and as_of.
- Idempotency: If you already have a row for a given as_of, skip or update in place. Use as_of as a unique key.
- Cache: Memoize last-good payload in a small store so front ends remain stable during transient network failures.
- 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
- Time and date handling: Treat as_of as an ISO date string. Store it as a date column rather than a timestamp to avoid false “changes” from time zone conversions.
- Data gaps: Transportation datasets occasionally skip calendar days. Use forward-fill only for visual charts and always label the underlying as_of to avoid misinterpretation.
- Rate limiting: Without X-API-Key, your IP is limited to 100 requests per day. If you orchestrate across multiple workers, centralize the fetch to a single job or ensure all workers share a cache.
- Resilience: Wrap calls with a 10–15 second timeout and two to three retries on connection resets or 5xx responses. Log and continue rather than failing the entire ETL run.
- Change detection: Most dashboards need only notify users when ratio_vs_baseline meaningfully deviates. Consider an alert that triggers if the ratio is above 1 for three consecutive as_of values.
Testing and validation for transportation analytics
To verify that your Rotterdam integration is solid:
- Schema lock: Unit test that success is true and all three payload fields exist and have the expected types.
- Idempotent writes: Re-run the same response and ensure your storage layer does not duplicate the as_of row.
- Lag tolerance: Write a test that rejects any as_of more than a reasonable window in the future (should never happen) and accepts dates up to a week behind “today.”
- Visual smoke test: Plot a short rolling chart of congestion with as_of on the x-axis to confirm continuity and caching behavior.
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
- Use the documented endpoint and the NLRTM port code.
- Send X-API-Key: YOUR_API_KEY or stay under the 100 requests/day/IP guest limit.
- Implement 10s timeout, basic retries, and handle HTTP 429 by backing off.
- Parse success and data; require congestion, ratio_vs_baseline, and as_of.
- Cache by as_of and avoid intraday polling.
- Label charts and metrics with as_of to set expectations about the 6–7 day lag.
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.