Home → Blog

You’re trying to gauge and integrate a reliable signal for congestion at Singapore’s port (SGSIN), but you don’t want misleading “live AIS wait time” widgets. By the end of this guide you’ll query FreightPulse’s IMF PortWatch feed for SGSIN, parse the 7‑day average vs 90‑day baseline signal, and ship a production‑ready integration with caching and alerting.

What the SGSIN congestion signal measures (and what it does not)

FreightPulse exposes IMF PortWatch congestion as a daily time series that’s lagged roughly 6–7 days. It is not a live AIS anchorage or berth wait-time feed. The core metric compares the last 7 days of port calls to a rolling 90‑day baseline for the same port, producing a congestion classification and a ratio_vs_baseline.

Illustration: Singapore Port Congestion

Endpoint and authentication

Use a simple GET with the SGSIN port code. Authentication is via HTTP header X-API-Key. Guests without a key are limited to 100 requests/day per IP. The response is wrapped in an envelope like {"success":true,"data":{...}} so always check success before reading fields.

You can create an API key from the dashboard after you register. See: Register and refer to the Documentation.

Quick test with cURL

Run the following one-liner. It targets SGSIN and includes the required header.

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

Response format and field semantics

The API returns a JSON envelope containing data.results for the requested port. Below is the official sample for Singapore (SGSIN). Copy these exactly for testing and for understanding the schema and field names.

{
"success": true,
"data": {
"results": [
{
"port": "Singapore",
"locode": "SG SIN",
"country": "Singapore",
"congestion": "moderate",
"as_of": "2026-10-02",
"metrics": {
"recent_avg_portcalls_per_day": 115.9,
"baseline_avg_portcalls_per_day": 118.1,
"ratio_vs_baseline": 0.98
}
},
{
"port": "Serangoon Harbor",
"locode": "SG SIN",
"country": "Singapore",
"congestion": "moderate",
"as_of": "2026-10-02",
"metrics": {
"recent_avg_portcalls_per_day": 6.6,
"baseline_avg_portcalls_per_day": 7.2,
"ratio_vs_baseline": 0.91
}
}
],
"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": "dfa64c8d-3626-4ccd-b6fa-e34d7472d4ba"
}
}

What you’ll actually use in most apps:

Notes:

Python integration for Singapore (SGSIN)

The snippet below calls the same endpoint, validates the envelope, and extracts congestion, as_of, and ratio_vs_baseline for each SGSIN record. It also shows a basic pattern for treating missing fields defensively.

import os
import time
import requests

API_URL = "https://freightpulsehq.com/api/v1/port-congestion"
API_KEY = os.getenv("FREIGHTPULSE_API_KEY", "YOUR_API_KEY") # set your real key in production

def fetch_sgsin_congestion(session=None, timeout=10):
sess = session or requests.Session()
headers = {"X-API-Key": API_KEY}
params = {"port": "SGSIN"}
resp = sess.get(API_URL, headers=headers, params=params, timeout=timeout)
resp.raise_for_status()
payload = resp.json()

if not payload.get("success"):
raise RuntimeError("API reported success=false")

data = payload.get("data", {})
results = data.get("results", [])
out = []

for r in results:
# Required fields with defensive defaults
congestion = r.get("congestion")
as_of = r.get("as_of")
metrics = r.get("metrics", {}) or {}
ratio = metrics.get("ratio_vs_baseline")
recent = metrics.get("recent_avg_portcalls_per_day")
baseline = metrics.get("baseline_avg_portcalls_per_day")

out.append({
"label": r.get("port"),
"locode": r.get("locode"),
"country": r.get("country"),
"congestion": congestion,
"as_of": as_of,
"ratio_vs_baseline": ratio,
"recent_avg_portcalls_per_day": recent,
"baseline_avg_portcalls_per_day": baseline,
})
return out, data.get("meta", {})

if __name__ == "__main__":
# Simple fetch and print
rows, meta = fetch_sgsin_congestion()
print("Data meta:", meta)
for row in rows:
print(f"{row['label']} ({row['locode']}): {row['congestion']} on {row['as_of']} "
f"ratio={row['ratio_vs_baseline']} recent={row['recent_avg_portcalls_per_day']} "
f"baseline={row['baseline_avg_portcalls_per_day']}")

# Example: Basic caching TTL (24h) concept
# Since data updates daily and is lagged ~6–7 days, you can cache safely for a day.
CACHE_TTL_SECS = 24 * 3600
# On a server, you would persist results and reuse until TTL expires.

Designing alerts and dashboards for SGSIN

Developers typically expose two derived signals for operations: a categorical congestion badge and a numeric index to trigger thresholds. For SGSIN, the congestion field is a ready-made badge, while metrics.ratio_vs_baseline lets you drive thresholds or trend visuals.

Suggested thresholds for internal use

The API already gives congestion categories. If you create custom logic on ratio_vs_baseline, document it clearly and keep it distinct from FreightPulse’s categorical field. Do not reinterpret ratio as wait time.

Production notes that save time

End-to-end checklist for SGSIN integration

Common mistakes to avoid

Troubleshooting and observability

When debugging, log the top-level meta.request_id and the HTTP status. If your code sees success=false or a missing data field, capture the full JSON for support. For transient network errors, retry with jittered backoff.

FAQ

Is this a live measure of vessel waiting time at Singapore?
No. It’s IMF PortWatch: a 7-day average of port calls versus a 90-day baseline, updated daily with ~6–7 days of lag. It does not measure anchorage or berth wait time.

How often should I poll the endpoint?
Daily is sufficient. The dataset updates daily and is lagged. Cache for 12–24 hours to reduce calls and stabilize dashboards.

What fields should I store for alerts?
congestion (categorical), as_of (date), and metrics.ratio_vs_baseline (numeric). Optionally keep recent_avg_portcalls_per_day and baseline_avg_portcalls_per_day for auditing.

Can I combine multiple SGSIN results into one number?
Yes, if your UI needs a single index. Use a weighted average on ratio_vs_baseline with weights = recent_avg_portcalls_per_day, and document it as a composite. Keep the original entries accessible for drill-down.

What happens if I don’t have an API key?
You can call the endpoint as a guest, but you’re limited to 100 requests/day per IP. For production workloads, create a key and use the X-API-Key header.

Start integrating the SGSIN congestion feed now. Create your API key and follow the docs for deployment-ready patterns and field references: Register and Documentation.