You’re trying to quantify how congested Colombo (LKCMB) is right now so you can plan sailings, buffers, and allocations with a defensible number. By the end of this post, you’ll be able to call the FreightPulse congestion endpoint for Colombo, parse the response, and wire that signal into your planning or alerting systems.
What the congestion metric measures (and what it does not)
FreightPulse’s port congestion is derived from IMF PortWatch and updated daily with a lag of roughly 6–7 days. It compares the last 7 days’ average port calls to a 90‑day baseline for the same port. This is not live AIS wait-time data and does not report anchorage or berth delays directly.
Use this feed when you want a stable, comparable indicator of throughput vs. recent history. Do not treat it as a real-time queue length. If you need minute-by-minute vessel positions or ETAs, pair this with a live AIS source in your stack.
Quickstart: fetch Colombo congestion
The endpoint is a simple GET with an auth header. You’ll pass the UN/LOCODE for Colombo: LKCMB.
Auth: include X-API-Key. Without a key, guests are limited to 100 requests per day per IP.
Copy-paste the cURL below to get started.
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"
JSON:
{
"success": true,
"data": {
"results": [
{
"port": "Colombo",
"locode": "LK CMB",
"country": "Sri Lanka",
"congestion": "moderate",
"as_of": "2026-10-02",
"metrics": {
"recent_avg_portcalls_per_day": 10.1,
"baseline_avg_portcalls_per_day": 11,
"ratio_vs_baseline": 0.92
}
}
],
"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": "ff7f7d1f-b3bc-4feb-ba98-e8fa1c966935"
}
}
You will actually use these fields:
- data.results[0].congestion: categorical level (e.g., low, moderate, high).
- data.results[0].as_of: day-level date (YYYY-MM-DD) of the latest measurement, reflecting ~6–7 day lag.
- data.results[0].metrics.ratio_vs_baseline: recent 7‑day port-call average divided by the 90‑day baseline (unitless).
- data.results[0].metrics.recent_avg_portcalls_per_day and baseline_avg_portcalls_per_day: port-call counts per day (ships/day).
Colombo-specific notes you should build around
Colombo’s code is LKCMB (UN/LOCODE rendered as “LK CMB” in the response). The congestion label is derived from the ratio vs. baseline, so the categorical level and the numeric ratio should be stored together. If you need to present a single signal to users, prefer the ratio for calculations and keep the label for UI display and thresholds.
Do not quote or store a freight-rate USD fare alongside this signal as a “current price.” This endpoint only provides congestion metrics; rates require a separate data source.
Implement it in Python
The snippet below calls the same endpoint and extracts the congestion label, as_of date, and ratio_vs_baseline for Colombo. It includes light validation around the response envelope. Replace YOUR_API_KEY with your key when you have one; unauthenticated use is capped per IP as noted earlier.
import os
import requests
from typing import Optional, Dict, Any
API_URL = "https://freightpulsehq.com/api/v1/port-congestion"
PORT_CODE = "LKCMB"
API_KEY = os.getenv("FREIGHTPULSE_API_KEY", "YOUR_API_KEY")
def fetch_colombo_congestion(session: Optional[requests.Session] = None) -> Dict[str, Any]:
s = session or requests.Session()
headers = {"X-API-Key": API_KEY}
params = {"port": PORT_CODE}
resp = s.get(API_URL, headers=headers, params=params, timeout=20)
resp.raise_for_status()
payload = resp.json()
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")
r0 = results[0]
metrics = r0.get("metrics", {})
# Extract fields you will use downstream
out = {
"port": r0.get("port"),
"locode": r0.get("locode"),
"country": r0.get("country"),
"congestion": r0.get("congestion"),
"as_of": r0.get("as_of"),
"ratio_vs_baseline": metrics.get("ratio_vs_baseline"),
"recent_avg_portcalls_per_day": metrics.get("recent_avg_portcalls_per_day"),
"baseline_avg_portcalls_per_day": metrics.get("baseline_avg_portcalls_per_day"),
# Optional: keep the original envelope if you want to log/debug
"_request_id": (payload.get("meta") or {}).get("request_id"),
"_source": (data.get("meta") or {}).get("source"),
}
# Basic sanity checks to fail fast if data is malformed
if out["as_of"] is None or out["congestion"] is None or out["ratio_vs_baseline"] is None:
raise ValueError("Missing one of required fields: as_of, congestion, ratio_vs_baseline")
return out
if __name__ == "__main__":
try:
result = fetch_colombo_congestion()
print(f"Colombo ({result['locode']}): congestion={result['congestion']}, "
f"ratio_vs_baseline={result['ratio_vs_baseline']}, as_of={result['as_of']}")
except Exception as e:
# For production, branch on HTTP status codes (e.g., 401/403/429) and implement backoff/retry
print(f"Error: {e}")
How to interpret and operationalize the numbers
ratio_vs_baseline is unitless. A value of 1.0 means the last week’s average port calls equals the 90‑day baseline. Lower than 1.0 suggests fewer port calls relative to baseline; higher than 1.0 suggests more. The categorical congestion label maps to ranges of this ratio and is useful for dashboards, while the ratio is better for analytics and alerts.
Port calls per day are counts of vessel port-call events aggregated to a daily rate. These are not dwell times in hours. If your SLA logic expects time-based thresholds, you should calibrate them separately using historical outcomes tied to the ratio or supplement with an AIS wait-time source.
- Triggering alerts: For example, send a “monitor” alert when ratio_vs_baseline drops below a threshold you define and a “mitigate” alert when it crosses a lower bound for N consecutive updates.
- Buffering schedules: Increase planned buffer when the label is moderate or high and the ratio deteriorates week over week.
- Scenario planning: Compare current ratio to the last time you experienced allocation shortfalls to set procurement or booking actions.
Practical details that save you time
- Auth: Header X-API-Key: YOUR_API_KEY. Guest without key: 100 requests/day per IP.
- HTTP: GET https://freightpulsehq.com/api/v1/port-congestion?port=LKCMB
- Envelope: The response is wrapped with success and data. Your payloads should handle data.results as an array.
- Granularity and time: as_of is a date string (YYYY-MM-DD) with day-level granularity. The feed is updated daily with a ~6–7 day lag; it is not real-time AIS.
- Units: recent_avg_portcalls_per_day and baseline_avg_portcalls_per_day are ships/day. ratio_vs_baseline is unitless.
- Caching: Because the signal updates daily and lags by several days, caching for 24 hours client-side is generally safe. Bust cache when as_of changes.
- Pagination: The example returns a single port in data.results. If you request one port, plan for results[0]; still code defensively for multiple items.
- Error handling: Check HTTP status codes. 401/403 suggest auth issues; 429 indicates you’ve exceeded the guest/IP quota—back off and retry later.
- Idempotency: GET requests are read-only. Avoid retry storms; exponential backoff with jitter is recommended.
- Change control: Store both the categorical congestion and the numeric ratio so you can re-map labels if your UI thresholds evolve.
Test checklist for a production integration
- Validation: Assert that success is true and that data.results is a non-empty array.
- Schema drift: Guard your parser with .get() and fail fast if as_of, congestion, or ratio_vs_baseline are missing.
- Idempotent retries: On transient failures (HTTP 5xx, timeouts), retry with capped exponential backoff.
- Monitoring: Log _request_id from meta for traceability and attach it to your observability platform.
- Release hygiene: Feature-flag any auto-mitigation or alerting behavior tied to new thresholds before broad rollout.
Field guide to the response
Core path for Colombo:
- data.results[0].port: String (“Colombo”).
- data.results[0].locode: String (“LK CMB”). Use LKCMB when calling the API.
- data.results[0].country: String (“Sri Lanka”).
- data.results[0].congestion: String label (“low” | “moderate” | “high”, etc.).
- data.results[0].as_of: Date string, day-level.
- data.results[0].metrics.recent_avg_portcalls_per_day: Float, ships/day.
- data.results[0].metrics.baseline_avg_portcalls_per_day: Float, ships/day.
- data.results[0].metrics.ratio_vs_baseline: Float, unitless.
Global and meta context:
- data.global_summary: Counts of ports by congestion level across the dataset. Useful for dashboards, not required for single-port logic.
- data.meta.source, data.meta.data_lag_days, data.meta.methodology: Provenance and methodology; good to surface in an info tooltip.
- meta.request_id: Unique request identifier for support tickets and logs.
Origin Catalog Facts (copy-and-ship)
- 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.
Colombo integration playbook
1) Fetch on a daily cadence
Schedule a daily job (e.g., UTC 06:00) to call the endpoint and persist the latest as_of, congestion, and ratio. Daily fetches are sufficient given the update cadence and lag.
2) Normalize and store
Persist a normalized row keyed by as_of and port code (LKCMB). Store the categorical label and the numeric ratio, plus recent and baseline port calls so you can re-derive the ratio if needed.
3) Alerting thresholds
Tie alerts to the numeric ratio. Example thresholds: notify at ratio < 0.95, escalate at ratio < 0.90 for two consecutive updates. Keep thresholds configurable per port since local baselines differ by season.
4) UI display
Show the congestion label and the ratio with the as_of date and the “~6–7 day lag” disclosure from data.meta.data_lag_days. Add a help icon that links to methodology so operators understand what the number means.
5) Downstream consumers
Expose a simple internal API or table that joins Colombo’s congestion ratio with lanes that call at Colombo, enabling procurement and scheduling systems to adjust buffers or short-term allocations.
FAQ
Is this live AIS wait-time data?
No. It is IMF PortWatch derived, updated daily with a ~6–7 day lag, and reflects 7‑day average port calls vs a 90‑day baseline. It does not measure anchorage or berth wait times.
What identifier should I use for Colombo?
Use LKCMB as the port parameter. The response shows “LK CMB” as the locode text.
How often should I refresh?
Daily is appropriate. Cache results for 24 hours and refresh when as_of changes. The feed is not real-time.
What are the units?
Port-call metrics are ships per day. ratio_vs_baseline is unitless. The as_of field is a YYYY-MM-DD date string.
Can I use this to quote today’s freight rates?
No. Do not quote a USD rate from this endpoint. It provides congestion metrics only.
Get access and build your Colombo monitor
Start with a free account to get an API key, then wire the endpoint above into your data workflow for Colombo. Read the full API reference and methodology notes, and keep both the categorical label and numeric ratio in your downstream systems.