You’re trying to factor Antwerp’s port congestion into routing, ETA buffers, or procurement dashboards, but you need a reliable, automatable signal rather than anecdotal “vessel wait time” chatter. By the end of this guide you’ll be able to programmatically fetch, parse, cache, and operationalize FreightPulse’s Antwerp congestion data (BEANR) using a single endpoint and two fields you actually need: congestion and ratio_vs_baseline, plus the as_of date for traceability.
What the Antwerp congestion metric actually measures
FreightPulse’s port congestion series is sourced from IMF PortWatch and refreshed daily with a lag of roughly 6–7 days. It is not a live AIS wait-time feed. Instead, congestion is computed as the last 7 days’ average port calls compared with a 90-day baseline for the same port. That ratio is then bucketed into a readable congestion label (for example, low, moderate, high). The methodology focuses on throughput signal (port calls), not anchorage or berth waiting time.
For operational use, read this as: “How busy is Antwerp, relative to its own recent normal?” It’s an early-warning trend, not a live queue length.
Endpoint, authentication, and envelope
Use a single GET endpoint to query Antwerp (BEANR):
- HTTP method: GET
- URL: https://freightpulsehq.com/api/v1/port-congestion?port=BEANR
- Auth: header X-API-Key: YOUR_API_KEY
- Guest use: up to 100 requests/day per IP without a key (subject to change; check the docs for updates)
- Envelope structure: {"success": true, "data": {...}}
The “data” object includes a results array with the Antwerp record, plus a meta block describing source and methodology. The primary fields you’ll consume are:
- congestion: string bucket (for example, moderate)
- metrics.ratio_vs_baseline: numeric ratio of recent activity vs the 90-day average
- as_of: ISO-8601 date stamp for the observation (daily granularity)
Units and conventions:
- recent_avg_portcalls_per_day and baseline_avg_portcalls_per_day: port calls per day (float)
- ratio_vs_baseline: dimensionless (float); values above 1 imply above-baseline activity
- as_of: YYYY-MM-DD date; treat as a daily snapshot. The dataset is lagged ~6–7 days behind “today” and is not real-time AIS.
Antwerp congestion via cURL and JSON
The following request and JSON are the official sample for BEANR. You can copy this cURL as-is and swap in your API key if you have one.
Live FreightPulse congestion for BEANR.
Source: live
cURL:
curl -H "X-API-Key: YOUR_API_KEY" "https://freightpulsehq.com/api/v1/port-congestion?port=BEANR"
JSON:
{
"success": true,
"data": {
"results": [
{
"port": "Antwerp",
"locode": "BE ANR",
"country": "Belgium",
"congestion": "moderate",
"as_of": "2026-10-02",
"metrics": {
"recent_avg_portcalls_per_day": 55.9,
"baseline_avg_portcalls_per_day": 53.7,
"ratio_vs_baseline": 1.04
}
}
],
"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": "f6015aa0-a2fa-4533-9c60-fd4d0528e26b"
}
}
What to read from this payload:
- data.results[0].congestion: “moderate” is your user-facing label (categorical)
- data.results[0].metrics.ratio_vs_baseline: 1.04 indicates recent activity is ~4% above the 90-day baseline
- data.results[0].as_of: 2026-10-02 is the daily observation date (lagged relative to today)
Parse Antwerp congestion in Python
This example calls the same endpoint, checks the top-level envelope, and extracts congestion, ratio_vs_baseline, and as_of. Replace YOUR_API_KEY with your real key if you have one; otherwise omit the header and rely on the guest quota (100 requests/day per IP).
import os
import sys
import json
import urllib.request
API_URL = "https://freightpulsehq.com/api/v1/port-congestion?port=BEANR"
API_KEY = os.getenv("FREIGHTPULSE_API_KEY", "YOUR_API_KEY") # or leave header out if using guest quota
def fetch_antwerp_congestion():
req = urllib.request.Request(API_URL)
# Use API key header if provided; guest users may omit this line
if API_KEY and API_KEY != "YOUR_API_KEY":
req.add_header("X-API-Key", API_KEY)
else:
# If you're intentionally testing the guest path, comment this next line out
req.add_header("X-API-Key", API_KEY)
with urllib.request.urlopen(req, timeout=20) as resp:
body = resp.read().decode("utf-8")
return json.loads(body)
def main():
try:
payload = fetch_antwerp_congestion()
except Exception as e:
print(f"Request failed: {e}", file=sys.stderr)
sys.exit(1)
if not isinstance(payload, dict) or not payload.get("success"):
print("Unexpected response shape or success=false", file=sys.stderr)
print(json.dumps(payload, indent=2))
sys.exit(2)
data = payload.get("data", {})
results = data.get("results", [])
if not results:
print("No results for BEANR", file=sys.stderr)
sys.exit(3)
antwerp = results[0]
congestion = antwerp.get("congestion")
as_of = antwerp.get("as_of")
metrics = antwerp.get("metrics", {})
ratio = metrics.get("ratio_vs_baseline")
recent = metrics.get("recent_avg_portcalls_per_day")
baseline = metrics.get("baseline_avg_portcalls_per_day")
print(f"Antwerp congestion: {congestion}")
print(f"As of date: {as_of}")
print(f"Ratio vs 90-day baseline: {ratio}")
print(f"Recent avg port calls/day: {recent}")
print(f"Baseline avg port calls/day: {baseline}")
# Example: interpret the ratio for routing buffers
if isinstance(ratio, (int, float)):
if ratio > 1.1:
print("Signal: activity is well above baseline; consider extra dwell buffers.")
elif ratio < 0.9:
print("Signal: activity is below baseline; buffers may be relaxed (still monitor).")
else:
print("Signal: near baseline; follow normal ops with routine monitoring.")
if __name__ == "__main__":
main()
Integration checklist and time-savers
- Refresh cadence: the feed updates daily but reflects data lagged by ~6–7 days. Poll once per day per port; more frequent polling won’t yield new values within the same UTC day.
- Caching: cache each day’s response for at least 24 hours keyed by port code (BEANR) and as_of.
- Idempotence: since “as_of” is a daily tick, treat it as the primary version marker when storing history.
- Error handling: check top-level success; if false or missing, don’t parse deeper fields. Log the response for observability.
- Guest quota: without an API key, you can make up to 100 requests/day per IP (sufficient for one port with daily pulls). For multiple ports or higher frequency, use an API key.
- Timeouts and retries: set client timeouts (~10–20s) and retry on transient 5xx with jittered backoff. Don’t retry 4xx except optionally 429 after a cool-down.
- Units: port calls are counts per day (floats). The ratio is dimensionless. No currency fields are present in this endpoint.
- No pagination for a single port: you request exactly one port by code. The response includes results as an array, but for a single port you’ll typically get one element.
- Historical storage: if you persist snapshots by as_of, you can graph the ratio over time and alert on threshold crossings.
- Display guidance: label clearly as “Port calls (7-day avg vs 90-day baseline) — lagged ~6–7 days. Not a live wait-time metric.”
Applying Antwerp’s ratio and label to operations
Use the categorical congestion label for quick UI signals and basic triage (for example, moderate may display amber). The ratio_vs_baseline is where the operational nuance lives. For instance, 1.04 implies Antwerp’s recent port-call activity is about 4% above its 90-day baseline. In practice, you can bind alerting thresholds around 0.9 and 1.1 to capture meaningful deviations, with hysteresis to avoid flapping.
If you drive ETAs or booking buffers from this signal, define a simple mapping, e.g. “+1 day hold when ratio > 1.1 and congestion not low,” or adapt contract SLAs for terminal handling. Keep in mind the lag: a sharp event that occurred within the last few days might not be fully reflected yet. Pair this dataset with your own exceptions feed (missed connections, rolled bookings) to validate the thresholds you set.
Field-by-field notes for BEANR
- port and locode: Human-friendly vs UN/LOCODE. BE ANR aligns with Antwerp.
- as_of: Daily snapshot; treat this date as the canonical time for the metric. If you ingest multiple ports, ensure a date index to simplify joins and rollups.
- metrics.recent_avg_portcalls_per_day and baseline_avg_portcalls_per_day: These let you explain the ratio and back up the label in an analyst-facing view.
- metrics.ratio_vs_baseline: Store as float and consider rounding to two decimals in UI, but keep full precision in storage.
- congestion: Useful for dashboards and filters. When you trigger alerts, include both the label and the numeric ratio for clarity.
Quality guardrails and what not to do
- Do not represent this dataset as today’s live vessel wait times or anchorage durations. It measures port-call activity, not queue length.
- Do not quote a freight rate from this response; there are no rate fields here, and the endpoint is unrelated to USD spot or contract fares.
- Do not extrapolate beyond provided fields, parameters, or limits. If a detail isn’t in the payload or documented, surface it as unknown in your UI.
Operational patterns that work
- Daily job at 02:00 UTC: Fetch BEANR, validate success, store by as_of, compute alerts vs yesterday’s ratio.
- UI overlay: Show congestion label and a small sparkline of ratio over the past 90 days. Tooltips can quote recent vs baseline port calls to add context.
- Exception routing: If Antwerp crosses your “high” boundary, add pre-advice notes to drayage partners and alert planners to pre-book alternative windows where possible.
- Change control: When thresholds are modified, log both the old and new boundaries to trace why alerts fired differently after deployment.
FAQ
Is the Antwerp congestion number real-time?
No. It is lagged by roughly 6–7 days and updated daily. It measures 7-day average port calls vs a 90-day baseline, not live anchorage or berth wait times.
How often should I poll the endpoint?
Once per day is sufficient, as the data updates daily and reflects a lagged snapshot. Cache the response and key on as_of.
What happens if I call without an API key?
Guest access is available and limited to 100 requests per day per IP. For higher volume or multiple ports, set the X-API-Key: YOUR_API_KEY header.
Which fields should I use in production?
congestion (label), metrics.ratio_vs_baseline (numeric), as_of (date). Optionally show recent_avg_portcalls_per_day and baseline_avg_portcalls_per_day for analyst transparency.
Can I infer dwell time or queue length from this?
No. The methodology is throughput-based and not a measure of vessel waiting. Treat it as a relative busyness signal.
Get a key and wire this into your pipeline in minutes. Read the Documentation and grab your account here: Register.