Home → Blog

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.

Illustration: How to Get Supply Chain Disruptions

Facts you must implement exactly as given

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:

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

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.

Validation checklist before shipping

Common pitfalls and how to avoid them

End-to-end workflow example

  1. Fetch with PHP client every 10 minutes for targeted ports (e.g., USLAX).
  2. On success, record timestamp, source, meta.request_id, and the inner data payload.
  3. If natural hazards exceed your severity threshold, raise an internal alert, and annotate any impacted ETAs.
  4. Refresh congestion visuals daily with the latest IMF PortWatch lagged data; clearly label “7-day avg vs 90-day baseline (lagged ~6–7 days).”
  5. 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.