You need to programmatically check congestion at Tianjin Xin Gang (Tianjin) and surface a clear status, the ratio vs baseline, and the date those figures apply to. By the end of this guide you will have a PHP integration that queries the FreightPulse congestion endpoint for Tianjin, handles the response envelope, and extracts the exact fields you need for your application.
What the Tianjin endpoint returns and how to read it
FreightPulse exposes congestion for Tianjin Xin Gang (Tianjin) via a single read-only endpoint. Responses are wrapped in an envelope where success is a boolean and the data payload contains results. The congestion level is derived from IMF PortWatch and reflects a 7-day average versus a 90-day baseline, lagged roughly 6–7 days and not a live AIS wait-time feed.
- Auth: send X-API-Key: YOUR_API_KEY as an HTTP header. Guests without a key are allowed 100 requests/day per IP.
- Envelope: {"success": true, "data": {...}} at the top level.
- Key fields you will use: congestion (string), metrics.ratio_vs_baseline (number), as_of (date string).
- Port: Tianjin Xin Gang (Tianjin). Use the query parameter port=Tianjin when calling the endpoint.
- Source and lag: IMF PortWatch; lagged ~6–7 days (daily updates, not real-time).
For endpoint specifics and broader options, see the Documentation.
Quick test: cURL and the official JSON
The following cURL is copy-pasteable and targets Tianjin directly using the port filter. Replace YOUR_API_KEY with your own key when you have one; you can also test as a guest (subject to the guest quota).
Live FreightPulse congestion for Tianjin.
Source: live
cURL:
curl -H "X-API-Key: YOUR_API_KEY" "https://freightpulsehq.com/api/v1/port-congestion?port=Tianjin"
JSON:
{
"success": true,
"data": {
"results": [
{
"port": "Tianjin Xin Gang",
"locode": null,
"country": "China",
"congestion": "moderate",
"as_of": "2026-10-23",
"metrics": {
"recent_avg_portcalls_per_day": 26.4,
"baseline_avg_portcalls_per_day": 29.4,
"ratio_vs_baseline": 0.9
}
}
],
"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": "8c30b0a0-9abb-4181-b5a1-a8b90cc592ae"
}
}
What you’ll actually read for Tianjin:
- data.results[0].congestion: congestion level string (e.g., moderate).
- data.results[0].metrics.ratio_vs_baseline: numeric ratio of recent vs baseline port calls.
- data.results[0].as_of: the date string those computed values apply to.
PHP implementation: production-ready lookup for Tianjin
This section walks through a minimal, dependency-free PHP client using cURL. It queries the Tianjin port, checks the envelope, handles basic HTTP failures, and extracts congestion, ratio_vs_baseline, and as_of for display or downstream logic.
Step 1 — Basic request with cURL
<?php
declare(strict_types=1);
// Configuration
$apiUrl = 'https://freightpulsehq.com/api/v1/port-congestion?port=Tianjin';
$apiKey = 'YOUR_API_KEY'; // Replace with your key when available
// Build request
$ch = curl_init($apiUrl);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Accept: application/json',
'X-API-Key: ' . $apiKey,
],
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 20,
]);
$responseBody = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$err = curl_error($ch);
curl_close($ch);
if ($err) {
http_response_code(502);
echo 'Transport error calling FreightPulse: ' . htmlspecialchars($err);
exit;
}
if ($httpCode < 200 || $httpCode >= 300) {
http_response_code($httpCode);
echo 'FreightPulse returned HTTP ' . $httpCode . ' with body: ' . htmlspecialchars((string) $responseBody);
exit;
}
// Parse JSON
$data = json_decode((string) $responseBody, true, 512, JSON_THROW_ON_ERROR);
// Validate envelope
if (!isset($data['success']) || !$data['success'] || !isset($data['data']['results'][0])) {
http_response_code(502);
echo 'Unexpected FreightPulse envelope or missing results.';
exit;
}
$result = $data['data']['results'][0];
// Extract fields for Tianjin
$port = $result['port'] ?? 'Tianjin Xin Gang';
$congestion = $result['congestion'] ?? null;
$asOf = $result['as_of'] ?? null;
$ratio = $result['metrics']['ratio_vs_baseline'] ?? null;
// Optional additional metrics available
$recentAvg = $result['metrics']['recent_avg_portcalls_per_day'] ?? null;
$baselineAvg = $result['metrics']['baseline_avg_portcalls_per_day'] ?? null;
// Render a compact summary
header('Content-Type: application/json');
echo json_encode([
'port' => $port,
'congestion' => $congestion,
'ratio_vs_baseline' => $ratio,
'as_of' => $asOf,
'recent_avg_portcalls_per_day' => $recentAvg,
'baseline_avg_portcalls_per_day' => $baselineAvg,
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
Step 2 — Notes on error handling and quotas
- Authentication: For higher limits and stability, include X-API-Key. Guests are capped at 100 requests/day per IP.
- Transport errors: Catch cURL errors (timeouts, DNS). Return a 5xx in your edge if FreightPulse is temporarily unreachable.
- Application errors: Always verify success === true and that data.results has at least one element before dereferencing.
- Backoff: If you receive rate-limit responses, back off and cache the last good value (see caching guidance below).
Step 3 — Output shaping for your UI
Most UIs only need three fields for Tianjin:
- congestion: a string label suitable for badges.
- ratio_vs_baseline: a decimal you can format as a relative indicator (e.g., 0.9 = 90%).
- as_of: a date you can display as the effective date; it is not a live timestamp.
JavaScript example: Node.js fetch for Tianjin
If you prefer JavaScript for backend or scripts, here is a minimal Node.js example using fetch. It reads the exact same fields as the PHP sample.
import fetch from 'node-fetch';
const API_URL = 'https://freightpulsehq.com/api/v1/port-congestion?port=Tianjin';
const API_KEY = 'YOUR_API_KEY'; // Replace with your key or omit header to test as guest
async function getTianjinCongestion() {
const res = await fetch(API_URL, {
headers: {
'Accept': 'application/json',
'X-API-Key': API_KEY,
},
timeout: 20000,
});
if (!res.ok) {
const body = await res.text();
throw new Error(`FreightPulse HTTP ${res.status}: ${body}`);
}
const json = await res.json();
if (!json.success || !json.data || !Array.isArray(json.data.results) || json.data.results.length === 0) {
throw new Error('Unexpected envelope or empty results');
}
const r = json.data.results[0];
return {
port: r.port,
congestion: r.congestion,
ratio_vs_baseline: r.metrics?.ratio_vs_baseline ?? null,
as_of: r.as_of,
};
}
getTianjinCongestion()
.then(console.log)
.catch(err => {
console.error('Error fetching Tianjin congestion:', err.message);
process.exitCode = 1;
});
Field semantics, timing, and caching
FreightPulse congestion for Tianjin is derived from IMF PortWatch and intentionally lagged. This section clarifies how to present and cache values without implying live conditions.
- as_of: Date string in YYYY-MM-DD representing the most recent day used in the 7-day computation. Treat it as a date, not a timezone-aware instant.
- congestion: A qualitative label mapped from the ratio of recent vs baseline port calls. It does not represent anchorage wait time.
- ratio_vs_baseline: A numeric ratio (e.g., 0.9) comparing the recent 7-day average to the 90-day baseline. You can display this as a percentage.
- Units: Port call metrics are per day (recent_avg_portcalls_per_day and baseline_avg_portcalls_per_day).
- Lag: Expect ~6–7 day lag relative to the current date, with daily updates.
Recommended caching:
- Cache the entire parsed object keyed by port name (Tianjin Xin Gang) or the exact request URL.
- Set a TTL of roughly 12–24 hours, since upstream updates are daily and lagged. Refresh on schedule or on user demand.
- On fetch failure, continue serving the last cached value with a clear “as_of” date so users know recency.
Production integration checklist for PHP
- Use the documented endpoint for Tianjin: /api/v1/port-congestion?port=Tianjin.
- Always send X-API-Key: YOUR_API_KEY when you have a key to avoid guest quota limits.
- Validate the envelope: success must be true; data.results must contain at least one item.
- Extract fields: congestion, metrics.ratio_vs_baseline, as_of. Optionally show recent/baseline averages.
- Surface the source and lag disclaimer in your UI: not live AIS; derived from IMF PortWatch; ~6–7 day lag.
- Cache responses to minimize calls and smooth over transient failures or rate limits.
Troubleshooting and observability
Here are pragmatic steps that avoid common pitfalls when shipping to production:
- Logging: Capture request_id from the top-level meta (when present) to correlate support tickets. In the official sample, it appears at meta.request_id.
- Input drift: Keep the port query fixed as port=Tianjin for this integration; do not substitute synonyms or change casing arbitrarily.
- Validation: If results is empty, back off and retry on a schedule rather than spamming the endpoint.
- User messaging: Display the as_of date alongside congestion to set expectations about data recency.
Testing strategy
Manual and automated checks help ensure reliable deployments:
- Unit tests: Mock the envelope and verify your PHP extractor returns congestion, ratio_vs_baseline, and as_of correctly.
- Contract tests: Fail fast if the response is missing success, data, or results.
- Resilience tests: Simulate 429 or 5xx to verify your cache serves stale-but-valid data and retries with backoff.
- Latency budget: Time your calls; typical JSON decoding and rendering should remain well under a second including network.
FAQ
Is this data real-time AIS?
No. It is derived from IMF PortWatch and lagged roughly 6–7 days. It measures relative port-call activity, not anchorage wait time.
What authentication should I use?
Send X-API-Key: YOUR_API_KEY in the request header. Guests without a key are limited to 100 requests/day per IP.
Which fields should I display to summarize Tianjin?
Show congestion, metrics.ratio_vs_baseline (formatted as a percentage), and as_of. Optionally show recent_avg_portcalls_per_day and baseline_avg_portcalls_per_day.
How often should I refresh the data?
Daily is sufficient for most use cases. Cache for 12–24 hours and surface the as_of date so users understand recency.
Does this endpoint include freight rates?
No. This endpoint returns congestion metrics for Tianjin Xin Gang (Tianjin). Do not present any freight-rate USD fare as a current price when showing congestion.
Get started
Spin up your Tianjin congestion checker in minutes. Create an API key and move from guest limits to a stable integration: Register. For endpoint behaviors, response envelopes, and additional options, review the Documentation.