You need to programmatically check congestion for Colombo (LKCMB) and display it reliably in your logistics app. By the end of this guide, you’ll have a production-ready PHP integration that queries FreightPulse for LKCMB, handles the current “no data” state gracefully, and renders fields you care about (congestion, ratio_vs_baseline, as_of) when data becomes available.
What the FreightPulse LKCMB endpoint returns (and what it doesn’t)
The FreightPulse Port Congestion endpoint for Colombo (LKCMB) returns an envelope of {"success":true,"data":{...}}. Data is derived from IMF PortWatch and is lagged about 6–7 days. It represents a 7‑day average versus a 90‑day baseline; it is not a live AIS wait time feed.
At the time of writing, there is no official congestion JSON published for LKCMB. The API responds successfully but with no available results for this port. You should still build your integration to read the canonical field names you’ll use when data becomes available: congestion, ratio_vs_baseline, and as_of.
- Auth: header X-API-Key: YOUR_API_KEY. Guest without key: 100 requests/day per IP.
- Envelope: {"success":true,"data":{...}}
- Port congestion is IMF PortWatch, lagged ~6–7 days, 7‑day avg vs 90‑day baseline — not live AIS.
- Port: Colombo (LKCMB)
- curl: curl -H "X-API-Key: YOUR_API_KEY" "https://freightpulsehq.com/api/v1/port-congestion?port=LKCMB"
- No official congestion JSON is published for LKCMB. Show the curl and field names (congestion, ratio_vs_baseline, as_of). Do not reuse any unrelated fixture.
Use the Documentation for the broader schema and changes over time, but keep your code tolerant to fields being missing when data is unavailable.
Authentication, headers, and quotas
Send your API key in the X-API-Key header. If you don’t have a key yet, the endpoint can be called unauthenticated, but you will be rate-limited to 100 requests per day per IP. In production, authenticate every call and add client-side rate limiting or caching to avoid unnecessary retries.
- Header: X-API-Key: YOUR_API_KEY
- Guest limit: 100 requests/day per IP (subject to change; consult the docs for updates)
- Transport: HTTPS only
Quick test with cURL (copy and run)
Run this exactly as shown to verify connectivity and see the current state for LKCMB.
Live FreightPulse congestion for LKCMB.
Source: live
curl -H "X-API-Key: YOUR_API_KEY" "https://freightpulsehq.com/api/v1/port-congestion?port=LKCMB"
{
"success": true,
"data": {
"results": [],
"available": false,
"reason": "Error querying IMF PortWatch"
},
"meta": {
"request_id": "9c327678-dfa3-4de6-9c01-af39f069e063"
}
}
Interpretation: the request succeeded but no congestion series is currently available for LKCMB. Your integration must handle available=false and an empty results array.
Schema you should code against
When available, congestion data for a port typically includes:
- congestion: a numeric level for the port’s congestion for a given date
- ratio_vs_baseline: a numeric ratio comparing the 7‑day average to a 90‑day baseline
- as_of: an ISO 8601 timestamp or date indicating the period the value reflects (lagged ~6–7 days)
The endpoint envelopes data inside success and data. Always check:
- success (boolean)
- data.available (boolean): whether a usable series is present
- data.results (array): zero or more observations; iterate defensively
- data.reason (string): optional human-readable context when available=false
- meta.request_id (string): helpful for logging and support tickets
PHP guide: end-to-end LKCMB lookup
Below is a complete PHP example using native cURL. It handles the current “no data” case, prints a developer-friendly message if data.available=false, and will automatically start rendering congestion, ratio_vs_baseline, and as_of once FreightPulse publishes an official series for LKCMB.
PHP example (cURL + robust parsing)
<?php
declare(strict_types=1);
// Configuration
$endpoint = 'https://freightpulsehq.com/api/v1/port-congestion?port=LKCMB';
$apiKey = 'YOUR_API_KEY'; // Set to your key. Calls without a key are limited.
// Basic HTTP GET with cURL
$ch = curl_init($endpoint);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_HTTPHEADER => [
'Accept: application/json',
'X-API-Key: ' . $apiKey,
],
]);
$responseBody = curl_exec($ch);
$curlErrNo = curl_errno($ch);
$curlErrMsg = curl_error($ch);
$httpStatus = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// Transport errors
if ($curlErrNo !== 0) {
http_response_code(502);
echo "Transport error calling FreightPulse: {$curlErrMsg}\n";
exit(1);
}
// Non-2xx status handling (the API uses a success: true envelope for logical outcomes)
if ($httpStatus < 200 || $httpStatus >= 300) {
http_response_code($httpStatus);
echo "Unexpected HTTP status from FreightPulse: {$httpStatus}\n";
echo $responseBody . "\n";
exit(1);
}
// Decode JSON
$payload = json_decode($responseBody, true);
if (!is_array($payload)) {
http_response_code(502);
echo "Invalid JSON from FreightPulse.\n";
exit(1);
}
// Envelope checks
$success = $payload['success'] ?? false;
$data = $payload['data'] ?? null;
$meta = $payload['meta'] ?? null;
if (!$success || !is_array($data)) {
echo "FreightPulse signaled failure or missing data envelope.\n";
if (is_array($meta) && isset($meta['request_id'])) {
echo "request_id: " . $meta['request_id'] . "\n";
}
exit(1);
}
// Current LKCMB availability
$available = (bool) ($data['available'] ?? false);
$results = $data['results'] ?? [];
$reason = $data['reason'] ?? null;
if (!$available || empty($results)) {
// Graceful, developer-friendly output
echo "Colombo (LKCMB) congestion is currently unavailable from IMF PortWatch." . "\n";
if (is_string($reason) && $reason !== '') {
echo "Reason: {$reason}\n";
}
if (is_array($meta) && isset($meta['request_id'])) {
echo "request_id: " . $meta['request_id'] . "\n";
}
// Optionally, cache a null to avoid repeated calls for a short TTL.
exit(0);
}
// When data becomes available, iterate and render the latest observation.
// Expect fields: congestion, ratio_vs_baseline, as_of
// Sort by as_of if needed; here we just take the last element.
$latest = end($results);
$congestion = $latest['congestion'] ?? null;
$ratioVsBaseline = $latest['ratio_vs_baseline'] ?? null;
$asOf = $latest['as_of'] ?? null;
// Validate and print
if ($congestion === null || $ratioVsBaseline === null || $asOf === null) {
echo "LKCMB data present but missing required fields. Check docs and payload.\n";
exit(1);
}
// Output for your UI layer
echo "Port: Colombo (LKCMB)\n";
echo "As of: {$asOf} (lagged ~6–7 days, 7d avg vs 90d baseline)\n";
echo "Congestion: {$congestion}\n";
echo "Ratio vs Baseline: {$ratioVsBaseline}\n";
Drop this into a CLI job, a queue worker, or a controller action. Cache the entire JSON for a short TTL (for example, 30–60 minutes) to conserve your daily allotment if you are still testing without an API key.
JavaScript example (Node.js fetch)
The following script hits the same endpoint, inspects data.available, and reads congestion, ratio_vs_baseline, and as_of if provided.
import fetch from 'node-fetch';
const endpoint = 'https://freightpulsehq.com/api/v1/port-congestion?port=LKCMB';
const apiKey = 'YOUR_API_KEY';
async function getLkcmb() {
const res = await fetch(endpoint, {
headers: {
'Accept': 'application/json',
'X-API-Key': apiKey
},
timeout: 15000
});
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
const payload = await res.json();
if (!payload || payload.success !== true || !payload.data) {
throw new Error('Bad envelope from FreightPulse');
}
const { available = false, results = [], reason } = payload.data;
if (!available || results.length === 0) {
const rid = payload.meta && payload.meta.request_id ? payload.meta.request_id : 'n/a';
console.log('LKCMB congestion not available. Reason:', reason || 'n/a', 'request_id:', rid);
return null;
}
const latest = results[results.length - 1];
const { congestion, ratio_vs_baseline, as_of } = latest || {};
if (congestion == null || ratio_vs_baseline == null || as_of == null) {
throw new Error('LKCMB data present but required fields missing');
}
return { congestion, ratio_vs_baseline, as_of };
}
getLkcmb()
.then(data => {
if (!data) return;
console.log('Colombo (LKCMB) latest:', data);
})
.catch(err => {
console.error('Error:', err.message);
process.exitCode = 1;
});
Operational details that will save you time
- Lag and interpretation: values are ~6–7 days behind present day and reflect a 7‑day rolling average compared to a 90‑day baseline. This is not a real-time AIS wait-time feed.
- Timestamps and timezone: use as_of as the canonical reference; treat it as ISO 8601. Display it in your user’s local timezone if desired, but keep UTC internally.
- Caching: when available=false, cache a null result with a modest TTL (e.g., 1–6 hours) to avoid redundant polling. When data is available, 30–60 minutes is typical for dashboards.
- Retries and backoff: network errors should be retried with exponential backoff and jitter. Do not retry a logical unavailable state immediately; respect your cache TTL.
- Rate limiting: if you’re calling without X-API-Key, enforce your own request budget (~100/day/IP for guests). Add server-side aggregation if multiple widgets call the same endpoint.
- Pagination: treat data.results as a finite list for the requested port; iterate defensively and prefer the latest observation when building UI.
- Units: congestion and ratio_vs_baseline are unitless numeric ratios/levels suitable for charts or thresholds; avoid converting to currency or inferring dwell time.
Handling “no data” cleanly in UI
Given LKCMB currently returns available=false with an empty results array, present a clear, non-blocking notice to users. Recommended flow:
- Show a neutral status like “Congestion data for Colombo (LKCMB) is currently unavailable.”
- Optionally surface the reason string if present (e.g., “Error querying IMF PortWatch”).
- Disable alerts that depend on congestion thresholds only for LKCMB while unavailable, or fall back to manual notes maintained by your ops team.
- Keep the chart frame rendered so the UI doesn’t reflow; add “data pending” skeletons.
Testing strategies
Because LKCMB currently has no published series, you should unit-test parsing and rendering using dependency injection or fixtures in your test environment. In production code, do not hardcode fake congestion values. Instead, test logic paths by:
- Verifying your code handles available=false and empty results without exceptions.
- Asserting logs include meta.request_id for support correlation.
- Simulating a single well-formed observation that includes congestion, ratio_vs_baseline, and as_of to ensure UI rendering paths behave as expected.
Security and reliability notes
- Do not embed your API key in client-side code. Keep calls server-side and proxy to browsers if needed.
- Log meta.request_id with warnings and errors to speed up support cases.
- Time-box outbound calls (connect timeout ~5s, total timeout ~15s) and fail fast with graceful degradation.
- Prefer read-only service accounts or keys with limited scope if supported.
Production checklist for Colombo (LKCMB)
- Authentication: X-API-Key header set to YOUR_API_KEY in all server-to-server calls.
- Error handling: transport errors, non-2xx, success=false, available=false all covered.
- Parsing: resilient to missing fields, but ready for congestion, ratio_vs_baseline, as_of.
- Caching: null-cache unavailable states; short TTL for available data.
- Observability: logs include request_id and timing metrics; alerts on persistent unavailability beyond your business threshold.
- UI: stable layout with skeleton states; no currency or fare displays tied to congestion levels.
FAQ
Q: Is this congestion live AIS data?
A: No. It is derived from IMF PortWatch, lagged ~6–7 days, reflecting a 7‑day average versus a 90‑day baseline.
Q: Can I call the endpoint without an API key?
A: Yes, but you’re limited to 100 requests per day per IP. Use X-API-Key: YOUR_API_KEY for consistent throughput.
Q: What fields should I persist in my database?
A: Store as_of (ISO date/time), congestion (number), and ratio_vs_baseline (number), plus the port code (LKCMB). Keep meta.request_id only in logs.
Q: How often should I poll?
A: Because data is lagged and updates infrequently, polling hourly or a few times per day is usually enough. Add caching to minimize duplicate requests.
Q: Why does LKCMB show no data?
A: The current response indicates an error querying IMF PortWatch for that port. Your code should treat this as a normal “temporarily unavailable” state and retry on your next scheduled poll.
Ready to implement this in your stack? Create an account to get an API key and move from guest limits to authenticated throughput. Register and then revisit the Documentation to finalize field mappings for Colombo (LKCMB).