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.
- Data source: IMF PortWatch (portwatch.imf.org) built on UN Global Platform AIS data.
- Update cadence: daily; data_lag_days ≈ 6–7.
- Signal: 7-day average port calls vs 90‑day baseline (ratio_vs_baseline).
- Interpretation: This approximates congestion pressure; it does not measure vessel waiting/anchorage time.
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.
- Method: GET
- URL: https://freightpulsehq.com/api/v1/port-congestion?port=SGSIN
- Header: X-API-Key: YOUR_API_KEY
- Port: Singapore (SGSIN)
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:
- data.results[].congestion: categorical level (e.g., "moderate").
- data.results[].as_of: date string YYYY-MM-DD for the metric.
- data.results[].metrics.ratio_vs_baseline: numeric ratio (7-day vs 90-day).
- data.results[].metrics.recent_avg_portcalls_per_day and baseline_avg_portcalls_per_day: floats used to compute the ratio.
Notes:
- Multiple entries can exist in results for related facilities within the SGSIN area (e.g., “Singapore”, “Serangoon Harbor”). Don’t assume a single record.
- The top-level success boolean should be checked before parsing results.
- Timestamps are delivered as dates (no time-of-day) and reflect the dataset’s daily grain with a 6–7 day lag.
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.
- Badge: show data.results[].congestion for each SGSIN entry.
- Index: visualize ratio_vs_baseline as a daily dot/line; annotate the as_of date and data lag.
- Aggregation: if your UI expects one number for “Singapore”, aggregate across results by weighted average on recent_avg_portcalls_per_day, and display both the combined badge (e.g., the “worst” categorical level) and the combined ratio. If you aggregate, label it “Composite (SGSIN)”.
Production notes that save time
- Auth: use the header X-API-Key. Guests without a key are capped at 100 requests/day per IP. Provision a key in CI and production.
- Envelope: always check success before using data. The payload shape is {"success": true, "data": {...}, "meta": {...}}.
- Lag and currency: the data is lagged ~6–7 days; there is no currency or prices in this endpoint. Do not quote freight rates as “today’s price.”
- Caching: because updates are daily and lagged, a 12–24h cache is usually appropriate. If you cache longer, show the as_of date prominently.
- Idempotence: it’s a GET endpoint; safe to retry on 5xx with exponential backoff.
- Pagination: the sample shows a compact results array for the requested port. No cursor fields are documented here; handle the array as-is.
- Localization: as_of is a date string (YYYY-MM-DD). Treat it as a date-only value in your UI; don’t add a time zone conversion.
- Field stability: congestion and ratio_vs_baseline are the main consumer-stable fields. Metrics subfields are present for transparency; avoid recomputing ratio unless all ingredients exist.
- Error handling: if results is empty, surface “No data for SGSIN” rather than showing stale numbers. Log meta.request_id for support.
End-to-end checklist for SGSIN integration
- Create a FreightPulse account and generate an API key.
- Implement the GET request to /api/v1/port-congestion?port=SGSIN with the X-API-Key header.
- Validate success, then iterate data.results.
- Read congestion, as_of, and metrics.ratio_vs_baseline for each entry.
- Render both the categorical badge and the numeric ratio (optionally a trend chart).
- Show the source and lag: “IMF PortWatch; ~6–7 day lag; daily updates.”
- Cache for 12–24 hours and include the as_of date in tooltips or footnotes.
- Set up alerts on ratio_vs_baseline and/or changes in congestion category.
Common mistakes to avoid
- Treating the signal as live AIS: it is not. It’s a seven-day average vs a ninety-day baseline from IMF PortWatch, updated daily with lag.
- Assuming a single result: SGSIN may return multiple related facilities. Aggregate or pick the relevant record deliberately.
- Hiding the lag: always display as_of and mention the 6–7 day lag in tooltips or docs.
- Mixing “price” with congestion: do not quote or imply a freight-rate USD fare as “today’s price” from this endpoint—it doesn’t return rates.
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.
- Health checks: Hit the endpoint once per deploy and verify success and a non-empty results array.
- Alert noise control: Use a minimum-change threshold for ratio_vs_baseline (e.g., ignore delta < 0.02) to avoid chattiness.
- Dashboards: Pin data.meta.source and data.meta.data_lag_days somewhere visible to set expectations for end users.
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.