You need to programmatically fetch “Supply Chain Disruptions” so your systems can flag ports at risk and route around delays. By the end of this guide, you’ll call the FreightPulse disruptions endpoint from PHP, interpret the response envelope, and ship a production-safe workflow with caching, retries, and field parsing—without guessing live prices or inventing events.
What you’re building: a small, reliable Supply Chain Disruptions client
This post focuses strictly on the FreightPulse “Supply Chain Disruptions” lookup and how to integrate it in PHP. You’ll also see a concise Python example for parity and a copy-pasteable curl to verify connectivity. Keep in mind: FreightPulse prioritizes natural hazards (GDACS/USGS/NOAA) and lagged IMF PortWatch congestion data—this is not live AIS wait times and you should not quote it as a real-time predictor.
Facts you must implement exactly as given
- 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.
- Do not quote a freight-rates USD fare as today’s price.
- Lookup: Supply Chain Disruptions
- curl: curl -H "X-API-Key: YOUR_API_KEY" "https://freightpulsehq.com/api/v1/disruptions?port=USLAX"
- Natural hazards only (GDACS/USGS/NOAA). Do not invent an event or cover strikes/canals.
Quick connectivity check with curl
Run this first to confirm your network and header formatting. Replace nothing except your API key if you have one; guests are rate-limited by IP.
curl -H "X-API-Key: YOUR_API_KEY" "https://freightpulsehq.com/api/v1/disruptions?port=USLAX"
OFFICIAL SAMPLE JSON (copy as-is for testing and parsing)
{
"success": true,
"data": {
"timestamp": "2026-10-09T00:00:25+00:00",
"source": "EIA (U.S. Energy Information Administration)",
"currency": "USD",
"unit": "gallon",
"data": {
"diesel": {
"national_average": 6.199,
"change_week": -0.183,
"change_percent": -2.87,
"updated_at": "2026-10-05",
"regions": {
"east_coast": 5.951,
"midwest": 6.286,
"gulf_coast": 5.819,
"rocky_mountain": 6.267,
"west_coast": 7.229,
"california": 8.082
}
},
"gasoline": {
"national_average": 4.354,
"change_week": -0.111,
"change_percent": -2.49,
"updated_at": "2026-10-05",
"grades": {
"regular": 4.354,
"midgrade": 4.99,
"premium": 5.4
}
},
"bunker_fuel": {
"available": false,
"note": "No known free source (Rotterdam/Singapore/Houston) — requires a paid provider (e.g. Ship & Bunker, Argus)."
}
},
"historical": {
"diesel_30d_avg": 6.349,
"diesel_90d_avg": 5.686,
"diesel_yoy_change": 67,
"gasoline_30d_avg": 4.404,
"gasoline_90d_avg": 4.155,
"gasoline_yoy_change": 39.4
}
},
"meta": {
"request_id": "cbc16380-e0cb-40d9-b1ba-e5215487b1eb"
}
}
Fields you’ll actually use operationally:
- success: Boolean wrapper for fast error gating.
- data.timestamp: ISO 8601 with timezone offset. Treat all timestamps as UTC with explicit offsets.
- data.source: Provenance string. Use for audit logs and dashboards.
- data.currency and data.unit: Always carry forward in any downstream calculation or UI labels to avoid unit confusion.
- meta.request_id: Store for troubleshooting and vendor support.
PHP: build a resilient disruptions client
This example uses ext-curl and json_decode. It demonstrates required headers, a timeout, simple retries, envelope parsing, and safe defaults. Keep your integration focused on the “Supply Chain Disruptions” lookup and avoid inventing strikes/canal events; FreightPulse emphasizes natural hazards (GDACS/USGS/NOAA) and lagged IMF congestion context.
PHP example (disruptions for USLAX)
<?php
declare(strict_types=1);
final class FreightPulseClient
{
private string $baseUrl = 'https://freightpulsehq.com/api/v1';
private string $apiKey;
private int $timeout = 10; // seconds
private int $retries = 2;
public function __construct(string $apiKey = 'YOUR_API_KEY')
{
$this->apiKey = $apiKey;
}
public function getDisruptions(string $portCode = 'USLAX'): array
{
$endpoint = sprintf('%s/disruptions?port=%s', $this->baseUrl, rawurlencode($portCode));
$attempt = 0;
$backoffMs = 200;
do {
$attempt++;
$ch = curl_init($endpoint);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Accept: application/json',
'X-API-Key: ' . $this->apiKey,
],
CURLOPT_CONNECTTIMEOUT => $this->timeout,
CURLOPT_TIMEOUT => $this->timeout,
]);
$raw = curl_exec($ch);
$err = curl_error($ch);
$code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($err !== '') {
if ($attempt <= $this->retries) {
usleep($backoffMs * 1000);
$backoffMs *= 2;
continue;
}
throw new RuntimeException('Network error: ' . $err);
}
if ($code < 200 || $code >= 300) {
if ($attempt <= $this->retries && ($code === 429 || ($code >= 500 && $code < 600))) {
usleep($backoffMs * 1000);
$backoffMs *= 2;
continue;
}
throw new RuntimeException('HTTP error: ' . $code . ' Body: ' . substr($raw, 0, 400));
}
$json = json_decode($raw, true);
if (!is_array($json)) {
throw new RuntimeException('Invalid JSON');
}
// Envelope: {"success":true,"data":{...}}
if (!isset($json['success']) || $json['success'] !== true) {
$msg = isset($json['error']) ? json_encode($json['error']) : 'Unknown API error';
throw new RuntimeException('API signaled failure: ' . $msg);
}
if (!isset($json['data']) || !is_array($json['data'])) {
throw new RuntimeException('Missing data envelope');
}
// Return the inner data payload and meta for logging
return [
'data' => $json['data'],
'meta' => $json['meta'] ?? null,
];
} while ($attempt <= $this->retries);
throw new RuntimeException('Exhausted retries without success');
}
}
// Usage:
$client = new FreightPulseClient(); // uses YOUR_API_KEY or env-injected
$result = $client->getDisruptions('USLAX');
// Persist request_id for observability
$requestId = $result['meta']['request_id'] ?? null;
// Important operational notes:
// - Natural hazards only (GDACS/USGS/NOAA). Do not surface strikes/canal closures as "natural hazards".
// - Port congestion context is IMF PortWatch (lagged ~6-7 days), not live AIS wait times.
// - Never present freight-rates USD fares as today’s price in UI.
// - Treat timestamps as ISO 8601 with timezone offset and store in UTC.
//
// $result['data'] will hold disruption metrics and/or hazard listings for the specified port.
// Integrate with your alerting pipeline here.
Python: simple parity script for dev/test
If you prefer a quick sanity check in Python, this mirrors the PHP logic at a basic level and reads the same envelope fields.
import os
import time
import requests
API_KEY = os.getenv("FREIGHTPULSE_API_KEY", "YOUR_API_KEY")
PORT = "USLAX"
URL = f"https://freightpulsehq.com/api/v1/disruptions?port={PORT}"
def fetch_disruptions(retries=2):
backoff = 0.2
for attempt in range(retries + 1):
resp = requests.get(URL, headers={"X-API-Key": API_KEY, "Accept": "application/json"}, timeout=10)
if resp.status_code == 429 or 500 <= resp.status_code < 600:
if attempt < retries:
time.sleep(backoff)
backoff *= 2
continue
resp.raise_for_status()
payload = resp.json()
if not payload.get("success"):
raise RuntimeError(f"API signaled failure: {payload}")
return payload["data"], payload.get("meta")
raise RuntimeError("Exhausted retries")
data, meta = fetch_disruptions()
print("timestamp=", data.get("timestamp"))
print("source=", data.get("source"))
print("request_id=", (meta or {}).get("request_id"))
Data handling: timestamps, units, caching and quotas
- Timestamps and timezone: Treat all returned datetime fields as ISO 8601 with explicit offsets (e.g., 2026-10-09T00:00:25+00:00). Normalize to UTC internally and display with local offsets in UIs as needed.
- Units and currency: Preserve the unit and currency fields in your models. Do not assume implicit units. Label charts and alerts with the provided unit/currency when applicable.
- Caching: Because port congestion derives from IMF PortWatch and is lagged ~6-7 days (7-day average vs 90-day baseline), you can cache congestion slices for at least several hours without losing fidelity. Natural hazard alerts (GDACS/USGS/NOAA) may warrant a shorter cache (e.g., 5–15 minutes) depending on your alerting SLA.
- Guest quota: Without an API key, you are limited to 100 requests/day per IP. Implement local caching and exponential backoff to stay within limits.
- Pagination: If you later query broader scopes (e.g., multiple ports), check the Documentation for pagination details before you roll out batch jobs.
Choosing a port and integrating safely
The disruptions endpoint supports a port selection, shown here with USLAX. Keep your scope narrow during initial rollout, and validate the envelope fields before parsing nested objects. Attach the source field and request_id to every stored record for traceability.
- Port selection: Start with a single high-volume port (e.g., USLAX) to validate ingestion and alert logic.
- Alerting: When a hazard is present, notify operations with severity and timestamp. Because FreightPulse focuses on natural hazards, do not add human-driven disruptions (e.g., strikes) unless you obtain them from another vetted feed.
- Congestion context: If you show a congestion trend, denote it as lagged IMF PortWatch 7-day average vs 90-day baseline, not live AIS wait times.
- No fare quoting: Do not present any freight-rates USD figure as a “today” price. Keep rates out of disruptions alerts entirely.
Validation checklist before shipping
- Header: X-API-Key is present and non-empty (or you accept guest quota behavior).
- Envelope: success is true; if false or missing, short-circuit parsing and log meta.request_id when available.
- Timestamps: Stored as UTC, displayed with timezone.
- Backoff: On 429 or 5xx, back off exponentially before retrying.
- Caching: Cache congestion slices longer than hazard alerts due to their lagged nature.
- Provenance: Surface the source string in dashboards for audit trails.
Common pitfalls and how to avoid them
- Confusing congestion recency: IMF PortWatch is lagged ~6–7 days. Label it and avoid phrases implying live vessel queues.
- Over-alerting on non-natural events: The disruptions feed is natural hazards focused (GDACS/USGS/NOAA). Do not inject strikes or canal issues as hazards.
- Dropping units/currency: Keep unit and currency attached to any numeric display.
- Skipping meta.request_id: Store it so you can correlate logs during incident reviews.
- Ignoring guest limits: Without a key, 100 requests/day per IP is easy to burn. Cache aggressively and batch wisely.
End-to-end workflow example
- Fetch with PHP client every 10 minutes for targeted ports (e.g., USLAX).
- On success, record timestamp, source, meta.request_id, and the inner data payload.
- If natural hazards exceed your severity threshold, raise an internal alert, and annotate any impacted ETAs.
- Refresh congestion visuals daily with the latest IMF PortWatch lagged data; clearly label “7-day avg vs 90-day baseline (lagged ~6–7 days).”
- Cache hazard results at 5–15 minutes; congestion at several hours, respecting guest or key-based quotas.
FAQ
Q: Can I call the disruptions endpoint without an API key?
A: Yes, but guests are limited to 100 requests/day per IP. Use caching and backoff.
Q: Are congestion metrics live AIS wait times?
A: No. They come from IMF PortWatch, lagged ~6–7 days, and represent a 7-day average vs a 90-day baseline.
Q: What types of disruptions are included?
A: Natural hazards sourced from GDACS/USGS/NOAA. Do not treat strikes or canal closures as part of this feed.
Q: How should I store timestamps?
A: As ISO 8601 in UTC. Preserve timezone offsets on ingest and normalize in storage.
Q: Can I display current freight-rate USD fares based on this data?
A: No. Do not quote a freight-rates USD fare as today’s price.
Ready to put this in production? Create an account to get a dedicated API key and review field-by-field details in the docs: Register and read the Documentation.