You need a reliable way to programmatically check port congestion at Colombo (LKCMB) for planning sailings, allocating buffers, or alerting your ops team. By the end of this guide you will be able to call a single FreightPulse endpoint in Python, parse the congestion level and ratio vs baseline for Colombo, and productionize it with sensible caching and error handling.
What you get from the Colombo congestion endpoint
The FreightPulse congestion endpoint provides a lagged congestion view for the port you request. For Colombo (LKCMB), the signal is computed from IMF PortWatch data: a 7-day average of port calls compared to a 90-day baseline for the same port. It is not a live AIS wait-time feed.
- HTTP method: GET
- Endpoint: https://freightpulsehq.com/api/v1/port-congestion?port=LKCMB
- Auth: X-API-Key header (guest access allowed; see limits below)
- Response envelope: {"success": true, "data": { ... }}
You will read three operational fields for your workflows:
- congestion (categorical: e.g., low, moderate, high)
- metrics.ratio_vs_baseline (numeric ratio, 7-day average vs 90-day baseline)
- as_of (ISO date for the latest data point, lagged ~6–7 days)
Quick start: cURL you can paste into a terminal
Use your FreightPulse API key as the X-API-Key header. If you do not have a key, you can still make up to 100 requests/day per IP in guest mode, but production setups should authenticate to avoid rate limiting.
Live FreightPulse congestion for LKCMB.
Source: live
cURL:
curl -H "X-API-Key: YOUR_API_KEY" "https://freightpulsehq.com/api/v1/port-congestion?port=LKCMB"
Python: call the API and extract congestion, ratio_vs_baseline, and as_of
The snippet below requests the same endpoint and prints the fields your ops team usually needs for dashboards and alerts. Replace the API key variable with your own value (or leave it empty to test guest mode).
import os
import sys
import time
import json
import urllib.request
API_URL = "https://freightpulsehq.com/api/v1/port-congestion?port=LKCMB"
API_KEY = os.getenv("FREIGHTPULSE_API_KEY", "YOUR_API_KEY") # set your key via env var or inline
def fetch_colombo_congestion(timeout=20):
req = urllib.request.Request(API_URL)
if API_KEY:
req.add_header("X-API-Key", API_KEY)
with urllib.request.urlopen(req, timeout=timeout) as resp:
if resp.status != 200:
raise RuntimeError(f"HTTP {resp.status}")
raw = resp.read().decode("utf-8")
payload = json.loads(raw)
# Envelope is {"success": true, "data": {...}}
if not payload.get("success"):
raise RuntimeError("API returned success=false")
data = payload.get("data", {})
results = data.get("results", [])
if not results:
raise RuntimeError("No results returned for LKCMB")
row = results[0]
congestion = row.get("congestion")
as_of = row.get("as_of")
ratio_vs_baseline = None
metrics = row.get("metrics", {})
if isinstance(metrics, dict):
ratio_vs_baseline = metrics.get("ratio_vs_baseline")
return {
"port": row.get("port"),
"locode": row.get("locode"),
"country": row.get("country"),
"congestion": congestion,
"as_of": as_of,
"ratio_vs_baseline": ratio_vs_baseline
}
if __name__ == "__main__":
try:
result = fetch_colombo_congestion()
print(json.dumps(result, indent=2))
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
Notes:
- The API returns JSON with a top-level success flag and a data object. Always validate these before reading results.
- The as_of field is an ISO date (no timezone time-of-day) reflecting the latest available lagged data (~6–7 days behind today).
- ratio_vs_baseline is a simple numeric ratio; for example, 0.91 means recent activity is 9% below the 90-day baseline.
Field-level example and how to use each value
Below is the official JSON sample for Colombo. You can rely on the same structure in production (envelope + data.results array). Values will update over time, but the schema and field names are consistent.
JSON:
{
"success": true,
"data": {
"results": [
{
"port": "Colombo",
"locode": "LK CMB",
"country": "Sri Lanka",
"congestion": "moderate",
"as_of": "2026-10-16",
"metrics": {
"recent_avg_portcalls_per_day": 9.9,
"baseline_avg_portcalls_per_day": 10.9,
"ratio_vs_baseline": 0.91
}
}
],
"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": "65f50ebe-6e51-4dcc-a7ed-916cbd3ed552"
}
}
How to use it:
- congestion: a categorical label suitable for on-screen badges or alert thresholds.
- as_of: the date of the most recent data point (ISO 8601 date, no timezone needed).
- metrics.ratio_vs_baseline: numerical ratio to drive comparisons, rolling KPIs, or trigger-based alerts (e.g., alert if below 0.85 or above 1.15).
- recent_avg_portcalls_per_day and baseline_avg_portcalls_per_day: useful for diagnostic displays but not required for basic alerts.
- meta.source and data_lag_days confirm that this is IMF PortWatch-derived and lagged ~6–7 days; do not interpret as real-time anchorage or berth waits.
Caching, freshness, and limits that matter in production
- Data lag: ~6–7 days. Although FreightPulse updates daily, the underlying series is not real-time AIS wait times. Do not represent it as live congestion or anchorage delays.
- Update cadence: daily. A once-per-day fetch is sufficient; set cache TTL between 6 and 24 hours based on your dashboard’s needs.
- Envelope: every response is wrapped in {"success": true, "data": { ... }}. Code should assert success before reading fields.
- Auth: use X-API-Key. Guest (no key) is permitted at 100 requests/day per IP; authenticated requests are recommended for steady workloads.
- Timestamps: as_of is a plain date (YYYY-MM-DD). There is no timezone component; treat it as the date for which the 7-day average is last computed.
- Pagination: the results array may contain multiple ports if you query more than one in other contexts; for a single port parameter like LKCMB, read index 0 defensively.
Error handling and edge cases
- Empty results: if data.results is empty, handle gracefully (retry later, or mark state unknown).
- HTTP errors: back off on 429/5xx. Implement exponential backoff with jitter and cap total retries to avoid thundering herds.
- Schema checks: always test the presence of keys like data, results, metrics before accessing nested values.
- Operational alerts: build alerts off both congestion and ratio_vs_baseline. For example, only notify if congestion is “high” or if ratio_vs_baseline deviates by a threshold for two consecutive update days.
Reproducibility and deployment checklist
- Pin the endpoint and port code: GET /api/v1/port-congestion?port=LKCMB
- Authenticate with X-API-Key and store keys in environment variables (not in source control).
- Normalize outputs: cast ratio_vs_baseline to float, retain as_of as a date string, and persist the raw JSON for audits.
- Cache for at least 6 hours; refresh on a daily schedule aligned with your reporting windows.
- Display a data provenance note: “IMF PortWatch-derived, ~6–7 day lag, not live AIS wait times.”
- Monitor non-200s and success=false responses, and alert instrumentation when your last successful update is older than two days.
ORIGIN CATALOG FACTS
- 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: Colombo (LKCMB)
- curl: curl -H "X-API-Key: YOUR_API_KEY" "https://freightpulsehq.com/api/v1/port-congestion?port=LKCMB"
- No official congestion JSON is published for LKCMB. Show the curl and field names (congestion, ratio_vs_baseline, as_of). Do not reuse the USLAX 0.85 fixture.
Frequently asked questions
Is this a live AIS congestion or waiting-time feed?
No. It is derived from IMF PortWatch and reflects the last 7 days’ average port calls versus a 90-day baseline. It is lagged by roughly 6–7 days and does not measure vessel waiting or anchorage times.
How often should I call the endpoint?
Daily is sufficient. Cache the result for 6–24 hours. The underlying series updates daily but remains lagged.
What are the minimum fields I need to store?
as_of (date), congestion (label), and metrics.ratio_vs_baseline (float). Optionally keep recent_avg_portcalls_per_day and baseline_avg_portcalls_per_day for troubleshooting.
Do I need an API key?
Production use should include X-API-Key. Guest requests without a key are limited to 100 per day per IP and may be rate-limited.
Can I use this to quote today’s freight rates or live berth delays?
No. Do not represent this as today’s price or real-time delays. It is an activity-based congestion proxy from IMF PortWatch with a known lag.
Ready to integrate this into your ops stack? Create an account to get your API key and start shipping with a daily, reproducible signal for Colombo: Register. For endpoint specifics and schema details, see the Documentation.