You need a reliable way to programmatically check port congestion for Port Klang so your transportation planning can account for berth availability and flow-through risk. By the end of this guide, you’ll be able to query the FreightPulse API for Port Klang (MYPKG), parse the lagged IMF PortWatch congestion signal in Python, and ship it to production with sensible caching and monitoring.
What this API returns and what it does not
The FreightPulse port congestion signal for Port Klang is derived from IMF PortWatch (UN Global Platform AIS data), updated daily but lagged by roughly 6–7 days. It measures the last 7 days’ average port calls compared to a 90-day baseline for the same port and classifies congestion qualitatively (e.g., low, moderate, high). It does not provide live AIS vessel wait times or anchorage delays.
Important constraints for transportation workflows:
- Signal is a 7-day average vs a 90-day baseline, not a live queue or dwell time metric.
- Data is updated daily with a lag (~6–7 days). If you need live wait times, this is not the correct source.
- Units: port calls per day (recent_avg_portcalls_per_day and baseline_avg_portcalls_per_day).
- Timestamps: dates are in YYYY-MM-DD (assume UTC where needed for scheduling).
- Envelope: {"success": true, "data": {...}} in successful responses.
Endpoint, method, authentication, and rate limits
Endpoint:
- GET /api/v1/port-congestion?port=
Port code:
- Port Klang (MYPKG)
Authentication:
- Header: X-API-Key: YOUR_API_KEY
- Guest access without a key: 100 requests per day per IP
Documentation and onboarding:
Quickstart: run the cURL for Port Klang
Copy-paste the following to fetch Port Klang congestion. It uses the documented header and port code.
curl -H "X-API-Key: YOUR_API_KEY" "https://freightpulsehq.com/api/v1/port-congestion?port=MYPKG"
Official sample JSON response for Port Klang (copy of the live example):
{
"success": true,
"data": {
"results": [
{
"port": "Port Klang",
"locode": "MY PKG",
"country": "Malaysia",
"congestion": "moderate",
"as_of": "2026-10-16",
"metrics": {
"recent_avg_portcalls_per_day": 33.3,
"baseline_avg_portcalls_per_day": 31.8,
"ratio_vs_baseline": 1.05
}
},
{
"port": "Pelabuhan Sungai Udang",
"locode": "MY PKG",
"country": "Malaysia",
"congestion": "moderate",
"as_of": "2026-10-16",
"metrics": {
"recent_avg_portcalls_per_day": 2,
"baseline_avg_portcalls_per_day": 1.9,
"ratio_vs_baseline": 1.07
}
}
],
"global_summary": {
"ports_high_congestion": 0,
"ports_moderate": 2,
"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": "a94e3560-4f62-4631-acfb-9d608229a913"
}
}
Field highlights you’ll actually use:
- data.results[].congestion (string): qualitative level you can alert on.
- data.results[].metrics.ratio_vs_baseline (number): compare to your internal thresholds.
- data.results[].as_of (YYYY-MM-DD): apply staleness and caching rules.
Python integration: fetch, parse, and normalize Port Klang
The snippet below calls the same endpoint, validates the success envelope, extracts Port Klang’s congestion, ratio_vs_baseline, and as_of, and emits a compact record suitable for your transportation ops pipeline.
import os
import requests
from datetime import datetime, timezone
API_URL = "https://freightpulsehq.com/api/v1/port-congestion"
PORT_CODE = "MYPKG"
def fetch_port_klang_congestion(api_key: str) -> dict:
headers = {"X-API-Key": api_key} if api_key else {}
params = {"port": PORT_CODE}
resp = requests.get(API_URL, headers=headers, params=params, timeout=20)
resp.raise_for_status()
payload = resp.json()
# Validate envelope
if not isinstance(payload, dict) or not payload.get("success"):
raise RuntimeError("Unexpected response envelope or success flag false")
data = payload.get("data", {})
results = data.get("results", [])
if not results:
raise RuntimeError("No results returned for Port Klang (MYPKG)")
# Prefer the record whose 'port' equals 'Port Klang'
record = None
for item in results:
if item.get("port") == "Port Klang":
record = item
break
if record is None:
# Fall back to the first record if specific port match is not present
record = results[0]
metrics = record.get("metrics", {})
congestion = record.get("congestion")
ratio = metrics.get("ratio_vs_baseline")
as_of = record.get("as_of")
if congestion is None or ratio is None or as_of is None:
raise RuntimeError("Missing required fields: congestion, ratio_vs_baseline, or as_of")
# Normalize date to ISO date (assumed UTC day)
try:
as_of_date = datetime.strptime(as_of, "%Y-%m-%d").date().isoformat()
except Exception as e:
raise RuntimeError(f"Invalid as_of date format: {as_of}") from e
# Return a compact transport-facing record
return {
"port_code": PORT_CODE,
"port_name": record.get("port"),
"country": record.get("country"),
"congestion_level": congestion,
"ratio_vs_baseline": ratio,
"recent_avg_portcalls_per_day": metrics.get("recent_avg_portcalls_per_day"),
"baseline_avg_portcalls_per_day": metrics.get("baseline_avg_portcalls_per_day"),
"as_of": as_of_date,
"source": data.get("meta", {}).get("source"),
"data_lag_days": data.get("meta", {}).get("data_lag_days")
}
if __name__ == "__main__":
api_key = os.getenv("FREIGHTPULSE_API_KEY", "")
result = fetch_port_klang_congestion(api_key)
print(result)
Notes for transportation deployments:
- Timeout: 20s is a defensive default for network conditions on the road or ship-to-shore links.
- Validation: enforce the success flag and required fields to avoid ingesting partial or stale data.
- Normalization: prefer ISO dates and capture the data lag for downstream planning logic.
Data model and field semantics for transportation planning
FreightPulse wraps responses in an envelope: success (boolean), data (object), and sometimes meta (request metadata). Your planner likely needs a single congestion signal (categorical) and a numeric confirmatory signal (ratio_vs_baseline) to drive alerting and routing heuristics.
- data.results[].congestion: use for categorical alerting. Map to your internal severity scale (“moderate” → Medium).
- data.results[].metrics.ratio_vs_baseline: numeric support; threshold examples could be > 1.1 for heightened watch (do not assume this threshold is official—tune to your ops).
- data.results[].as_of: when the 7-day window ends. Combine with data.meta.data_lag_days to determine if a refresh is warranted.
- data.results[].metrics.recent_avg_portcalls_per_day and baseline_avg_portcalls_per_day: both in port calls/day, useful for trend explanations in ops dashboards.
FreightPulse meta explains methodology and lag explicitly. Ensure dispatchers and schedulers understand it is not a live vessel-wait metric and should be combined with tactical sources if your SOP requires real-time anchorage conditions.
Caching, staleness, and operations
Because the dataset updates daily and is lagged ~6–7 days, an hourly polling cadence is unnecessary. A 12–24 hour refresh is usually sufficient for transportation planning that revises port calls daily or intra-day only when thresholds breach.
- Recommended cache TTL: 24 hours in production; 6–12 hours if you are aggregating signals across multiple dependent systems.
- Staleness check: if current_date - as_of > 10 days, mark data_stale=true and fall back to last known good value or policy defaults.
- Resilience: back off on non-200 responses; keep the last valid snapshot to avoid flapping alerts.
- Guest quota: 100 requests/day per IP without an API key—budget calls across environments and CI.
Transport calendars and weekends: since the source is daily and not tied to exchange trading days, expect updates on weekends but continue to treat as lagged. Use the as_of date rather than system time to anchor your operational decisions.
Validation tests with canonical JSON examples
The following blocks are complete JSON response examples you can paste into unit tests or contract tests. They are the official Port Klang sample repeated to verify parsing, envelope validation, and field extraction across code paths (e.g., selecting the Port Klang record among multiple results).
Example 1: Baseline parsing of Port Klang congestion
{
"success": true,
"data": {
"results": [
{
"port": "Port Klang",
"locode": "MY PKG",
"country": "Malaysia",
"congestion": "moderate",
"as_of": "2026-10-16",
"metrics": {
"recent_avg_portcalls_per_day": 33.3,
"baseline_avg_portcalls_per_day": 31.8,
"ratio_vs_baseline": 1.05
}
},
{
"port": "Pelabuhan Sungai Udang",
"locode": "MY PKG",
"country": "Malaysia",
"congestion": "moderate",
"as_of": "2026-10-16",
"metrics": {
"recent_avg_portcalls_per_day": 2,
"baseline_avg_portcalls_per_day": 1.9,
"ratio_vs_baseline": 1.07
}
}
],
"global_summary": {
"ports_high_congestion": 0,
"ports_moderate": 2,
"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": "a94e3560-4f62-4631-acfb-9d608229a913"
}
}
Example 2: Selecting the specific Port Klang record when multiple results exist
{
"success": true,
"data": {
"results": [
{
"port": "Port Klang",
"locode": "MY PKG",
"country": "Malaysia",
"congestion": "moderate",
"as_of": "2026-10-16",
"metrics": {
"recent_avg_portcalls_per_day": 33.3,
"baseline_avg_portcalls_per_day": 31.8,
"ratio_vs_baseline": 1.05
}
},
{
"port": "Pelabuhan Sungai Udang",
"locode": "MY PKG",
"country": "Malaysia",
"congestion": "moderate",
"as_of": "2026-10-16",
"metrics": {
"recent_avg_portcalls_per_day": 2,
"baseline_avg_portcalls_per_day": 1.9,
"ratio_vs_baseline": 1.07
}
}
],
"global_summary": {
"ports_high_congestion": 0,
"ports_moderate": 2,
"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": "a94e3560-4f62-4631-acfb-9d608229a913"
}
}
Example 3: Contract test for envelope and meta presence
{
"success": true,
"data": {
"results": [
{
"port": "Port Klang",
"locode": "MY PKG",
"country": "Malaysia",
"congestion": "moderate",
"as_of": "2026-10-16",
"metrics": {
"recent_avg_portcalls_per_day": 33.3,
"baseline_avg_portcalls_per_day": 31.8,
"ratio_vs_baseline": 1.05
}
},
{
"port": "Pelabuhan Sungai Udang",
"locode": "MY PKG",
"country": "Malaysia",
"congestion": "moderate",
"as_of": "2026-10-16",
"metrics": {
"recent_avg_portcalls_per_day": 2,
"baseline_avg_portcalls_per_day": 1.9,
"ratio_vs_baseline": 1.07
}
}
],
"global_summary": {
"ports_high_congestion": 0,
"ports_moderate": 2,
"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": "a94e3560-4f62-4631-acfb-9d608229a913"
}
}
Use these fixtures to:
- Confirm your parser selects the “Port Klang” record when multiple entries are present.
- Verify that congestion, ratio_vs_baseline, and as_of are captured correctly.
- Assert that data.meta fields are retained for transparency in dashboards and logs.
FAQ
Is this a live AIS wait time or queue length metric?
No. It is based on IMF PortWatch and reflects the last 7 days’ average port calls vs a 90-day baseline. It is lagged ~6–7 days and is updated daily. It does not measure vessel wait or anchorage time.
What is the authentication method?
Pass your API key in the X-API-Key header. Without a key, guests are limited to 100 requests per day per IP.
What fields should I store for Port Klang?
At minimum: congestion, metrics.ratio_vs_baseline, as_of, and the source/meta block to capture methodology and lag information. Optionally store recent_avg_portcalls_per_day and baseline_avg_portcalls_per_day for context.
How often should I poll?
Daily is typical. Because data is lagged, a 12–24 hour refresh interval with caching is recommended for transportation planning.
Can I use this to quote a freight rate or a “today” price?
Do not use this congestion signal to quote freight rates or assert today’s live conditions. It is not a fare or a real-time wait duration.
Ready to integrate Port Klang congestion into your transportation stack? Create an account to get an API key and start testing: Register. For full parameter and response details, see the Documentation.