Home → Blog

You need a transportation-grade signal for how busy Port Klang is without wiring up raw AIS feeds. By the end of this guide, you will query FreightPulse’s Port Congestion API for Port Klang (MYPKG), interpret the 7‑day average vs 90‑day baseline, and ship a production-safe integration that caches results and surfaces only the fields your operations team needs.

What this API measures (and what it does not)

FreightPulse’s congestion data for Port Klang comes from IMF PortWatch and is updated daily with a typical lag of about 6–7 days. It compares the recent 7‑day average of port calls to a 90‑day baseline for the same port. This is a throughput proxy, not a live AIS anchorage or wait-time metric.

Illustration: Port Klang Port Congestion API

Key takeaways for transportation teams:

Endpoint, method, auth, and envelope

Use a simple HTTP GET request with an API key header. Guests without a key are allowed up to 100 requests per day per IP.

The payload will include a data object with the results array and metadata that documents IMF PortWatch as the source and the lag characteristics. The fields you will actually use in transports ops are congestion (categorical), metrics.ratio_vs_baseline (numeric), and as_of (ISO date).

Copy-pasteable curl for Port Klang

You can paste the following into your terminal to retrieve congestion for MYPKG (Port Klang). Replace YOUR_API_KEY with your key; you can also call as a guest up to the guest limits.

curl -H "X-API-Key: YOUR_API_KEY" "https://freightpulsehq.com/api/v1/port-congestion?port=MYPKG"

Official live sample: reading the JSON for MYPKG

The block below is an official sample for Port Klang. It shows the response structure, including the categorical congestion level, the calculation window noted in meta, and the 7‑day vs 90‑day baseline metrics that underpin the label.

Live FreightPulse congestion for MYPKG.
Source: live

cURL:

JSON:

{
"success": true,
"data": {
"results": [
{
"port": "Port Klang",
"locode": "MY PKG",
"country": "Malaysia",
"congestion": "moderate",
"as_of": "2026-10-02",
"metrics": {
"recent_avg_portcalls_per_day": 32.4,
"baseline_avg_portcalls_per_day": 31.6,
"ratio_vs_baseline": 1.03
}
},
{
"port": "Pelabuhan Sungai Udang",
"locode": "MY PKG",
"country": "Malaysia",
"congestion": "moderate",
"as_of": "2026-10-02",
"metrics": {
"recent_avg_portcalls_per_day": 1.6,
"baseline_avg_portcalls_per_day": 1.9,
"ratio_vs_baseline": 0.83
}
}
],
"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": "5459622d-5463-4fcd-a821-a4643aee3b1d"
}
}

How to read it:

Minimal JavaScript example (fetch)

This snippet calls the same endpoint, extracts the top-line congestion label, the as_of date, and the ratio_vs_baseline you will surface on dashboards or push to your TMS.

async function getPortKlangCongestion() {
const url = "https://freightpulsehq.com/api/v1/port-congestion?port=MYPKG";
const res = await fetch(url, {
headers: { "X-API-Key": "YOUR_API_KEY" }
});

if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
const body = await res.json();

if (!body.success || !body.data || !Array.isArray(body.data.results)) {
throw new Error("Unexpected API envelope");
}

// Depending on your use case, you may select the first matching facility in results.
const first = body.data.results[0];
if (!first) {
return { available: false };
}

const congestion = first.congestion; // e.g., "moderate"
const asOf = first.as_of; // e.g., "2026-10-02"
const ratio = first.metrics && first.metrics.ratio_vs_baseline;

return {
available: true,
port: first.port,
locode: first.locode,
country: first.country,
congestion,
ratio_vs_baseline: ratio,
as_of: asOf
};
}

// Example usage:
getPortKlangCongestion()
.then(data => console.log("Port Klang congestion:", data))
.catch(err => console.error("API error:", err));

Practical integration notes for transportation teams

These details tend to save an hour during production rollout:

Python example: production-safe retrieval with caching and validation

The following Python code demonstrates a defensive pattern: calling the endpoint, validating the envelope, extracting congestion, and stashing a one-day cache using the as_of date.

import os
import json
import time
import hashlib
import requests

API_URL = "https://freightpulsehq.com/api/v1/port-congestion?port=MYPKG"
API_KEY = os.environ.get("FREIGHTPULSE_API_KEY", "YOUR_API_KEY")
CACHE_DIR = ".fp_cache"
os.makedirs(CACHE_DIR, exist_ok=True)

def _cache_path(key: str) -> str:
h = hashlib.sha256(key.encode("utf-8")).hexdigest()
return os.path.join(CACHE_DIR, f"{h}.json")

def _read_cache(path: str):
try:
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
except Exception:
return None

def _write_cache(path: str, data):
tmp = path + ".tmp"
with open(tmp, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False)
os.replace(tmp, path)

def get_port_klang_congestion(force_refresh=False):
headers = {"X-API-Key": API_KEY}
# Use a stable cache key for the daily fetch.
daily_key = f"{API_URL}"
path = _cache_path(daily_key)

if not force_refresh:
cached = _read_cache(path)
if cached and isinstance(cached, dict) and cached.get("success") is True:
return cached

resp = requests.get(API_URL, headers=headers, timeout=20)
resp.raise_for_status()
body = resp.json()

# Validate envelope and expected fields
if body.get("success") is not True:
raise RuntimeError("API returned success=false")

data = body.get("data", {})
results = data.get("results", [])
if not isinstance(results, list) or len(results) == 0:
raise RuntimeError("No results for MYPKG")

# Extract the first result for Port Klang
first = results[0]
congestion = first.get("congestion")
as_of = first.get("as_of")
metrics = first.get("metrics", {}) or {}
ratio = metrics.get("ratio_vs_baseline")

# You can enrich the body with a compact summary for downstream systems
summary = {
"port": first.get("port"),
"locode": first.get("locode"),
"country": first.get("country"),
"as_of": as_of,
"congestion": congestion,
"ratio_vs_baseline": ratio
}
body["_summary"] = summary

_write_cache(path, body)
return body

if __name__ == "__main__":
data = get_port_klang_congestion()
print(json.dumps(data.get("_summary", {}), indent=2))

Interpreting congestion for dispatch and routing

Because the level is derived from a 7‑day average versus a 90‑day baseline, it is best used as context rather than a binary “go/no-go” on sailings or drayage timing. Suggestions for transportation workflows:

Quality and methodology references

The response includes a meta block documenting IMF PortWatch as the underlying source and describing the 7‑day vs 90‑day approach. Since the dataset is lagged by about 6–7 days, you should align internal SLAs on data freshness accordingly and make that clear in any UI labels.

If you need more detail on the envelope and fields shown here, see the product Documentation. Use the same HTTP method, endpoint, and header pattern shown above for MYPKG.

Common pitfalls and how to avoid them

FAQ

Is this real-time congestion for Port Klang?

No. It is based on IMF PortWatch with a typical 6–7 day lag. It compares the last 7 days of port calls to a 90‑day baseline and is updated daily.

Which fields should I store for operations dashboards?

Store congestion, as_of, and metrics.ratio_vs_baseline. Optionally persist recent_avg_portcalls_per_day and baseline_avg_portcalls_per_day if you plot context bands.

How often should I refresh the data?

Once per day is appropriate. Cache results and key them by as_of to keep a historical trail and avoid reprocessing.

Can I use this to price shipments or quote surcharges?

Do not quote a freight-rates USD fare as today’s price from this dataset. It is not a pricing feed and does not represent live wait times.

What happens if I call without an API key?

Guests without a key may call the endpoint with a limit of 100 requests per day per IP. For higher volumes, add your key in the X-API-Key header.

Get access and ship your integration

Start with a key, copy the curl above, and wire up your fetch or requests client to read congestion, ratio_vs_baseline, and as_of for MYPKG. You can create an account here: Register. For field-by-field details and any transport-specific clarifications, visit the Documentation.