Home → Blog

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.

Illustration: API to Check Congestion at Colombo

You will read three operational fields for your workflows:

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:

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:

Caching, freshness, and limits that matter in production

Error handling and edge cases

Reproducibility and deployment checklist

ORIGIN CATALOG FACTS

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.