Home → Blog

You’re planning sailings through Ningbo (CNNBO) and need a concrete, programmatic way to track port congestion so you can adjust booking windows, equipment positioning, or buffers. By the end of this guide you will be able to pull FreightPulse’s IMF PortWatch congestion signal for Ningbo, parse the key fields (congestion, ratio_vs_baseline, as_of), and wire basic alerts or dashboards into your stack.

What this dataset tells you about Ningbo congestion

FreightPulse’s port congestion feed for Ningbo (LOCODE CN NBO, often written as CNNBO in API params) is built from IMF PortWatch using UN Global Platform AIS data. It compares the recent 7-day average of port calls against the port’s own 90-day baseline. This is not a live AIS wait-time product and it does not report anchorage delays or queue lengths. Instead, it’s a stable, lagged indicator of activity that updates daily with approximately a 6–7 day delay.

Illustration: Ningbo Port Congestion

That design makes the metric reliable for planning windows, trend detection, and capacity balancing where you don’t need minute-by-minute vessel positions but do need a consistent historical benchmark against which to judge whether activity is above or below normal.

How to query FreightPulse for Ningbo (CNNBO)

The congestion endpoint is a simple GET request with one required parameter, the port code:

Envelope shape is consistent: {"success": true, "data": {...}}. Inside data, you’ll find results (an array), a global_summary, and meta that describes the source and methodology.

Copy-paste cURL for Ningbo

Authentication uses an HTTP header. Replace YOUR_API_KEY with your key if you have one; otherwise, you can test as a guest subject to the daily IP limit.

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

Official JSON response example

The following is the official sample for Ningbo. Copy it as-is when building your parser or mock tests.

{
"success": true,
"data": {
"results": [
{
"port": "Ningbo",
"locode": "CN NBO",
"country": "China",
"congestion": "moderate",
"as_of": "2026-10-09",
"metrics": {
"recent_avg_portcalls_per_day": 47.6,
"baseline_avg_portcalls_per_day": 46.8,
"ratio_vs_baseline": 1.02
}
}
],
"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": "47ad5c93-5fef-469d-a27e-2826b7961c2b"
}
}

Fields you will actually use:

What the Ningbo signal means for planning

Because this metric is derived from port calls, a ratio_vs_baseline near 1.0 suggests activity is roughly normal for Ningbo. Deviations help you decide whether to add slack time or adjust bookings. For example, a sustained ratio of 1.15+ over several days may justify pulling forward cutoffs or rebalancing equipment to avoid bunching. Conversely, a lower ratio suggests less traffic relative to the port’s recent history.

The congestion label (low, moderate, high) is an abstraction of that ratio, helpful for user-facing dashboards. Keep the numeric ratio in your automations; use the label for quick-glance UIs.

End-to-end example in Python

This script queries the same endpoint shown in the cURL and extracts the congestion, ratio_vs_baseline, and as_of fields. It also demonstrates a simple guard against missing fields.

import os
import requests

API_URL = "https://freightpulsehq.com/api/v1/port-congestion"
PORT = "CNNBO" # Ningbo
API_KEY = os.getenv("FREIGHTPULSE_API_KEY", "YOUR_API_KEY") # replace or set env var

def fetch_ningbo_congestion():
resp = requests.get(
API_URL,
params={"port": PORT},
headers={"X-API-Key": API_KEY},
timeout=15
)
resp.raise_for_status()
payload = resp.json()

# Expect the envelope: {"success": true, "data": {...}}
if not payload.get("success"):
raise RuntimeError("API reported success=false")

data = payload.get("data", {})
results = data.get("results", [])
if not results:
raise ValueError("No results for CNNBO")

row = results[0]

congestion = row.get("congestion")
as_of = row.get("as_of")
metrics = row.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")

return {
"port": row.get("port"),
"locode": row.get("locode"),
"country": row.get("country"),
"as_of": as_of,
"congestion": congestion,
"ratio_vs_baseline": ratio,
"recent_avg_portcalls_per_day": recent,
"baseline_avg_portcalls_per_day": baseline,
"source": data.get("meta", {}).get("source"),
"data_lag_days": data.get("meta", {}).get("data_lag_days"),
}

if __name__ == "__main__":
info = fetch_ningbo_congestion()
print(
f"[Ningbo | {info['as_of']}] congestion={info['congestion']}, "
f"ratio_vs_baseline={info['ratio_vs_baseline']}, "
f"recent={info['recent_avg_portcalls_per_day']}, "
f"baseline={info['baseline_avg_portcalls_per_day']}"
)

Key implementation details that save time

Practical ways to operationalize Ningbo congestion

Most teams wire the ratio_vs_baseline into simple threshold rules and time-series dashboards. Below are compact patterns that are quick to implement and straightforward to reason about.

Simple rules for interpreting Ningbo’s fields

Implementation checklist

Troubleshooting and edge cases

If your request succeeds but results is empty, retry later; data can be temporarily unavailable during upstream refresh. If success=false, treat as a transient condition and back off. If you query without a key and hit the 100 requests/day/IP threshold, you will need to add the X-API-Key header with YOUR_API_KEY to continue. Because the dataset is lagged, do not compare the as_of date to “today” to determine freshness; compare it to the prior as_of you ingested.

Also, remember that this is not a wait-time data product: increased port calls can correlate with congestion, but the metric does not measure vessel queues or time-at-anchor. Keep any SLA commitments framed in terms of activity-relative-to-baseline, not live anchorage durations.

Security and deployment notes

FAQ

Is this live AIS congestion?
No. The feed uses IMF PortWatch, updated daily with a ~6–7 day lag. It compares the last 7 days’ average port calls to a 90-day baseline. It does not report live vessel wait times or anchorage queues.

What is the exact port identifier I should pass?
Use port=CNNBO in the query. The response includes locode "CN NBO" and port "Ningbo" for reference.

How should I handle authentication?
Send the API key via the X-API-Key header. Without a key, you can make up to 100 requests per day per IP as a guest.

How often should I poll?
Once per day is typical. The dataset is lagged and updates daily, so more frequent polling does not provide fresher values.

Which fields should I store?
At minimum: as_of, congestion, ratio_vs_baseline. For diagnostics and context, also store recent_avg_portcalls_per_day and baseline_avg_portcalls_per_day.

Ready to wire Ningbo congestion into your planning flow? Create your API key and start integrating today: Register. For endpoint specifics and schema references, see the Documentation.