Home → Blog

You’re trying to quantify how congested Singapore (SGSIN) is right now, but you need a reliable, automatable signal you can plug into planning dashboards and alerts. By the end of this guide you’ll query FreightPulse’s IMF PortWatch–based congestion feed for SGSIN, parse the exact fields you need (congestion, ratio_vs_baseline, as_of), and ship a small integration you can cache and monitor.

What this dataset is (and isn’t)

FreightPulse’s port congestion endpoint is derived from IMF PortWatch and UN Global Platform AIS data. It is lagged by roughly 6–7 days and updates daily; it does not represent live AIS wait/anchorage times. The method compares the last 7 days’ average port calls to a 90-day baseline for the same port and classifies a simple congestion level.

Illustration: Singapore (SGSIN) Congestion

Key points to internalize before you integrate:

Quick start: call the SGSIN endpoint

Use the official port code SGSIN. Provide your API key via the X-API-Key header.

cURL (copy/paste):

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

Official example response and field map

Below is the official sample response for SGSIN. Use it to validate your parser and to understand where the fields live in the envelope.

JSON:

{
"success": true,
"data": {
"results": [
{
"port": "Singapore",
"locode": "SG SIN",
"country": "Singapore",
"congestion": "moderate",
"as_of": "2026-10-16",
"metrics": {
"recent_avg_portcalls_per_day": 116.7,
"baseline_avg_portcalls_per_day": 117.4,
"ratio_vs_baseline": 0.99
}
},
{
"port": "Serangoon Harbor",
"locode": "SG SIN",
"country": "Singapore",
"congestion": "moderate",
"as_of": "2026-10-16",
"metrics": {
"recent_avg_portcalls_per_day": 7,
"baseline_avg_portcalls_per_day": 7.1,
"ratio_vs_baseline": 0.98
}
}
],
"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": "85921b91-a184-4edd-85e4-f7c9a668f284"
}
}

What you’ll actually use in code:

Notes for SGSIN specifically:

Python example: fetch, select Singapore, and read the key fields

The following script shows how to query SGSIN, filter for the primary “Singapore” entry, and extract congestion, ratio_vs_baseline, and as_of. It also demonstrates minimal validation and caching hints.

import os
import sys
import time
import json
import urllib.request

API_KEY = os.getenv("FREIGHTPULSE_API_KEY", "YOUR_API_KEY")
URL = "https://freightpulsehq.com/api/v1/port-congestion?port=SGSIN"

def fetch_sgsin():
req = urllib.request.Request(URL, headers={"X-API-Key": API_KEY})
with urllib.request.urlopen(req, timeout=20) as resp:
raw = resp.read().decode("utf-8")
return json.loads(raw)

def select_primary_singapore(results):
# Prefer the "Singapore" entry when multiple share the same LOCODE
primary = [r for r in results if r.get("port") == "Singapore"]
if primary:
return primary[0]
# Fallback: first result if naming differs
return results[0] if results else None

def main():
data = fetch_sgsin()
if not data.get("success"):
print("API indicated failure", file=sys.stderr)
sys.exit(1)
results = data.get("data", {}).get("results", [])
if not results:
print("No results for SGSIN", file=sys.stderr)
sys.exit(2)

entry = select_primary_singapore(results)
if not entry:
print("Could not identify primary Singapore record", file=sys.stderr)
sys.exit(3)

congestion = entry.get("congestion")
as_of = entry.get("as_of")
ratio = (entry.get("metrics") or {}).get("ratio_vs_baseline")

# Minimal sanity checks
if congestion not in ("low", "moderate", "high"):
print(f"Unexpected congestion level: {congestion}", file=sys.stderr)
if ratio is None:
print("Missing ratio_vs_baseline in metrics", file=sys.stderr)

print(json.dumps({
"port": entry.get("port"),
"locode": entry.get("locode"),
"congestion": congestion,
"ratio_vs_baseline": ratio,
"as_of": as_of
}, ensure_ascii=False))

# Caching hint: dataset is ~6–7 days lagged and updates daily.
# Cache for a day to avoid redundant calls (guest limit is 100/day/IP).
# For example, write to a file with a timestamp.
cache_path = "/tmp/sgsin_congestion.json"
with open(cache_path, "w") as f:
f.write(json.dumps({
"fetched_at_epoch": int(time.time()),
"payload": entry
}))
print(f"Cached to {cache_path}", file=sys.stderr)

if __name__ == "__main__":
main()

SGSIN workflow design: from HTTP to an actionable indicator

Build a single source of truth for “today’s” congestion reading (bearing in mind it’s a lagged, daily dataset). A practical pattern:

  1. Fetch: Call GET /api/v1/port-congestion?port=SGSIN with X-API-Key.
  2. Filter: Prefer the entry where port == "Singapore" when multiple records share the LOCODE.
  3. Persist: Store congestion, ratio_vs_baseline, and as_of in your time-series store with a unique key on as_of to avoid duplicates.
  4. Display: Show congestion (categorical), ratio_vs_baseline (to two decimals), and as_of as the “data date.”
  5. Alert: Trigger notifications only when as_of advances or when the categorical congestion changes.

Why ratio_vs_baseline matters: It is the quantitative backbone behind the qualitative label. If you’re tuning thresholds for alerts, use ratio deltas (e.g., 0.04 move w/w) rather than the label alone.

Operational details that save time

JavaScript (Node) alternative: tiny fetch helper

If you ship Node services, here is a concise fetch that reads the same fields and demonstrates defensive parsing.

import fetch from "node-fetch";

const API_KEY = process.env.FREIGHTPULSE_API_KEY || "YOUR_API_KEY";
const URL = "https://freightpulsehq.com/api/v1/port-congestion?port=SGSIN";

async function fetchSGSIN() {
const res = await fetch(URL, {
headers: { "X-API-Key": API_KEY },
timeout: 20000
});
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
const body = await res.json();
if (!body.success) {
throw new Error("API indicated failure");
}
const results = (body.data && body.data.results) || [];
if (!results.length) {
throw new Error("No results for SGSIN");
}
const primary = results.find(r => r.port === "Singapore") || results[0];

const out = {
port: primary.port,
locode: primary.locode,
congestion: primary.congestion,
ratio_vs_baseline: primary.metrics?.ratio_vs_baseline,
as_of: primary.as_of
};

if (typeof out.ratio_vs_baseline !== "number") {
throw new Error("Missing ratio_vs_baseline");
}
return out;
}

fetchSGSIN()
.then(data => console.log(JSON.stringify(data)))
.catch(err => {
console.error(err.message);
process.exitCode = 1;
});

Testing and validation checklist for SGSIN

Embedding in planning and ETAs

Because the congestion signal is a 7‑day average vs a 90‑day baseline, pair it with your transit-time models carefully. A practical pattern is to apply small additive or multiplicative adjustments to ETAs when ratio_vs_baseline deviates materially from 1.0, but only for the port legs that are likely to be sensitive to port call throughput (e.g., berth availability proxy). Keep the adjustment bounded; avoid double-counting if you already consume AIS-based waiting signals elsewhere.

In UI components, keep the label and number together. Example: “Singapore: moderate (0.99, as of 2026‑10‑16)”. This prevents confusion about timing and makes audit logs clearer when operations ask “which data date was used?”.

Security, quotas, and change management

Troubleshooting SGSIN integrations

FAQ

Q: Is this real-time AIS waiting time for vessels at anchor or berth?
A: No. It’s a statistical signal (7‑day average port calls vs 90‑day baseline) from IMF PortWatch and UN Global Platform AIS, lagged ~6–7 days.

Q: Which fields should I store for alerts?
A: congestion, metrics.ratio_vs_baseline, and as_of. Use as_of as the key for idempotent writes.

Q: How often should I poll?
A: Once per day is sufficient because the dataset updates daily and is lagged. Cache responses for 24 hours per port.

Q: How do I authenticate?
A: Send X-API-Key: YOUR_API_KEY. Guests without a key are limited to 100 requests/day per IP.

Q: Can I combine this with freight rate quotes?
A: Don’t present any USD freight-rate figure as “today’s price” based on this endpoint; it’s not a rates API.

Ready to integrate SGSIN congestion into your ops stack? Start with a free account and the complete endpoint specs in the docs: Register and read the Documentation.