You’re trying to understand how congested Rotterdam (NLRTM) is and wire that signal into a dashboard or automated workflow. By the end of this guide, you’ll query FreightPulse for Rotterdam, interpret the congestion/activity fields correctly, and ship production-safe code that polls, caches, and alerts on changes without confusing this metric for live AIS wait times.
What this dataset actually measures for Rotterdam (NLRTM)
FreightPulse sources port activity/congestion from IMF PortWatch. The series is lagged (roughly 6–7 days) and represents a 7-day average of port calls versus a 90-day baseline at the same port. It does not measure live vessel wait or anchorage time, and it is not a same-day AIS feed.
In the Rotterdam context, treat this as a standardized activity index that helps you detect whether port calls are running below, near, or above recent norms. A lower activity reading can coincide with fewer port calls because of disruptions or deferred arrivals; it does not necessarily mean berthing is fast or slow at that moment.
Rotterdam-specific facts you should copy into your implementation notes
- Auth: header X-API-Key: YOUR_API_KEY. Guest without key: 100 requests/day per IP.
- Envelope: {"success":true,"data":{...}}
- Port congestion is IMF PortWatch, lagged ~6-7 days, 7-day avg vs 90-day baseline — not live AIS.
- Do not quote a freight-rates USD fare as today’s price.
- Port: Rotterdam (NLRTM)
- curl: curl -H "X-API-Key: YOUR_API_KEY" "https://freightpulsehq.com/api/v1/port-congestion?port=NLRTM"
- No official congestion JSON is published for NLRTM. Show the curl and field names (congestion, ratio_vs_baseline, as_of). Do not reuse the USLAX 0.85 fixture.
Endpoint and authentication for NLRTM
Use a simple GET against the FreightPulse port-congestion endpoint with the UN/LOCODE for Rotterdam: NLRTM. Include your API key via the X-API-Key header. You can test without a key (guest), but the limit is 100 requests per day per IP, which is unsuitable for production polling.
Production guidance: schedule a single daily pull per port (or less) because the series is lagged and updates weekly. Cache locally and fan out to analytics, alerting, or your UI from your cache rather than re-calling the API every page view.
Official sample: cURL and JSON for Rotterdam (copy, run, and adapt)
Run this cURL command as-is (replace YOUR_API_KEY if you have one). The endpoint and field names must remain exactly as below.
curl -H "X-API-Key: YOUR_API_KEY" "https://freightpulsehq.com/api/v1/port-congestion?port=NLRTM"
Below is the official JSON sample for Rotterdam. Use it to structure your parsing and tests.
{
"success": true,
"data": {
"results": [
{
"port": "Rotterdam",
"locode": "NL RTM",
"country": "The Netherlands",
"activity": "moderate",
"congestion": "moderate",
"as_of": "2026-11-06",
"metrics": {
"recent_avg_portcalls_per_day": 81,
"baseline_avg_portcalls_per_day": 82.5,
"ratio_vs_baseline": 0.98
}
}
],
"global_summary": {
"ports_high_activity": 0,
"ports_moderate_activity": 1,
"ports_low_activity": 0,
"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": "4-11 (PortWatch republishes the daily series once a week; check as_of)",
"methodology": "activity is port-call volume, not congestion: last 7 days' avg calls vs a 90-day baseline for the same port. low means fewer calls than usual, which can be a closure or ships waiting offshore. It does not measure vessel wait or anchorage time. congestion is a deprecated alias of activity."
}
},
"meta": {
"request_id": "907190a9-e1be-4f11-81c1-59948777362e"
}
}
Field highlights you will actually use:
- data.results[0].as_of: Date stamp of the measurement (daily), in YYYY-MM-DD.
- data.results[0].congestion: Deprecated alias of activity; string bucket such as low, moderate, high.
- data.results[0].metrics.ratio_vs_baseline: Numeric ratio of recent 7-day average port calls vs 90-day baseline.
- data.meta.data_lag_days: How stale the series may be when republished; verify this alongside as_of.
Interpreting congestion and activity for Rotterdam
For Rotterdam, use the ratio_vs_baseline to quantify deviation: values near 1.0 imply activity close to normal. Values below 1.0 indicate fewer port calls than the 90-day baseline; above 1.0 indicates more. The congestion field mirrors activity and is bucketed, so it’s useful for simple status UIs but less precise for threshold alerts.
Do not conflate these values with live berth waiting time. A “low” bucket may stem from operational slowdowns, holidays, weather, or routing choices that reduce port calls; it doesn’t directly encode queue length offshore.
Production calling patterns and caching
- Polling cadence: Daily or weekly is sufficient. The time series is lagged and republished weekly; intra-day polling won’t add signal.
- Caching: Store the latest successful snapshot in your database or cache keyed by port=NLRTM and as_of. Reuse for dashboards and internal services.
- Idempotency: If as_of hasn’t changed since your last pull, skip downstream processing to avoid duplicate alerts.
- Backfill: If you need historical comparisons, retain prior snapshots or consult the docs for historical endpoints if available.
- Error handling: Treat non-200 responses or success=false as transient; retry with exponential backoff and maintain the last known good reading.
Python example: fetch, parse, and normalize Rotterdam congestion
This example calls the same endpoint, validates the envelope, and extracts congestion, ratio_vs_baseline, and as_of for NLRTM. It also demonstrates a simple cache freshness check based on as_of.
import os
import json
import time
from urllib import request, error
API_URL = "https://freightpulsehq.com/api/v1/port-congestion?port=NLRTM"
API_KEY = os.getenv("FREIGHTPULSE_API_KEY", "YOUR_API_KEY")
def fetch_nlrtm(timeout=20):
req = request.Request(API_URL, headers={"X-API-Key": API_KEY})
with request.urlopen(req, timeout=timeout) as resp:
if resp.status != 200:
raise RuntimeError("HTTP {} from FreightPulse".format(resp.status))
data = json.loads(resp.read().decode("utf-8"))
if not data.get("success"):
raise RuntimeError("API indicated success=false")
return data
def parse_rotterdam(payload):
results = payload.get("data", {}).get("results", [])
if not results:
raise ValueError("No results for NLRTM")
row = results[0]
as_of = row.get("as_of")
congestion = row.get("congestion") or row.get("activity")
ratio = None
metrics = row.get("metrics") or {}
if "ratio_vs_baseline" in metrics:
ratio = metrics.get("ratio_vs_baseline")
meta = payload.get("data", {}).get("meta", {})
lag = meta.get("data_lag_days")
return {
"port": row.get("port"),
"locode": row.get("locode"),
"as_of": as_of,
"congestion_bucket": congestion,
"ratio_vs_baseline": ratio,
"data_lag_days": lag,
}
def should_update(last_as_of, new_as_of):
return (new_as_of is not None) and (new_as_of != last_as_of)
if __name__ == "__main__":
# Example in-memory cache key
cache = {"as_of": None, "payload": None}
try:
payload = fetch_nlrtm()
parsed = parse_rotterdam(payload)
if should_update(cache["as_of"], parsed["as_of"]):
cache["as_of"] = parsed["as_of"]
cache["payload"] = parsed
print("Updated NLRTM:", json.dumps(parsed, indent=2))
else:
print("No change; current as_of={}, bucket={}".format(parsed["as_of"], parsed["congestion_bucket"]))
except error.HTTPError as e:
print("HTTPError {}: {}".format(e.code, e.read().decode("utf-8", "ignore")))
except Exception as e:
print("Error:", e)
Practical details that will save you time
- Units and scale: ratio_vs_baseline is unitless. Values hover around 1.0 when activity is typical for Rotterdam.
- Timestamps: as_of is a date string (no timezone). Treat it as local midnight UTC for comparison and scheduling; do not assume sub-day updates.
- Lag and refresh: FreightPulse republishes the PortWatch series weekly. The effective lag is roughly 6–7 days; verify data.meta.data_lag_days and the as_of date before updating user-facing indicators.
- Guest usage: Without an API key, you are limited to 100 requests per day per IP. Do not implement frontend polling from browsers that would burn this quota; proxy via your backend and cache.
- Pagination: The Rotterdam endpoint returns a single results row for the requested port; no pagination logic is required for this query.
- Error surfaces: Network failures, guest quota exhaustion, or temporary 5xx can occur. Implement retries with backoff and circuit-breaking, then display the last known state.
Alerting and dashboard hints for Rotterdam
- Thresholds: Consider alerts when ratio_vs_baseline crosses 0.90 (downside) or 1.10 (upside), or when the bucket transitions between low, moderate, and high. Tune to your sensitivity.
- Stability checks: Require two consecutive updates with the same direction before notifying users to avoid noise from minor revisions.
- Context labeling: In UI, clearly label this as “IMF PortWatch activity (7d avg vs 90d baseline), lagged data — not live AIS wait times.”
- Port identity: Always show both “Rotterdam” and “NLRTM” so analysts who work with UN/LOCODEs can pivot quickly.
cURL you can paste into CI pipelines
If you prefer shell-based health checks or ingestion steps in cron/CI, run the official command and write the JSON to disk for an ETL step:
Validate that success is true and that the data.results array is non-empty. Then parse as_of and ratio_vs_baseline for downstream use.
Rotterdam integration checklist
- Request URL and headers match the exact endpoint.
- Envelope is validated: success is true; data.results[0] exists.
- Extract as_of, congestion (or activity), and metrics.ratio_vs_baseline.
- Write to a dated cache keyed by NLRTM + as_of.
- Run alerts only when as_of advances and thresholds are crossed.
- Render a clear label that this is IMF PortWatch data with a lag.
Where to go next
If you need field-by-field reference material or historical retrieval patterns, check the documentation. When you’re ready to run production polling beyond the guest limit, register for an API key so your backend can ingest Rotterdam’s series on schedule.
FAQ
Is this live congestion or AIS-based waiting time for Rotterdam?
No. This is an IMF PortWatch-derived activity series (7-day average port calls vs a 90-day baseline). It is lagged and republished weekly. It does not measure live anchorage or berth waiting time.
What fields should I log for downstream analysis?
Log as_of, congestion (or activity), metrics.ratio_vs_baseline, and your ingestion timestamp. Also keep data.meta.data_lag_days so you can audit staleness over time.
How often should I poll Rotterdam (NLRTM)?
Daily or weekly is adequate. Because the data is lagged and refreshed weekly, frequent polling won’t add new signal.
Can I use the guest quota in production?
Not recommended. Guest is capped at 100 requests per day per IP. Use an API key in a backend service and cache results.
What should my dashboard display?
Show the bucket (low/moderate/high), ratio_vs_baseline with two decimals, and the as_of date. Include a note that this is IMF PortWatch activity and is not live AIS wait time.
Ready to integrate Rotterdam’s congestion/activity signal into your stack with proper caching and alerts? Get an API key and move your ingestion off the guest tier today: Register.