Home → Blog

You need to look up a motor carrier by DOT number from PHP and ship an integration that returns reliable identification data without guessing about live rates or wait times. By the end of this guide you’ll query the FreightPulse Carrier Lookup API from PHP, parse the envelope, and safely handle auth, quotas, and caching in production.

What you’ll implement

This tutorial walks through a PHP Carrier Lookup integration that searches carriers by DOT or text query. You’ll test the endpoint with curl, then write a compact PHP client that:

Reference links for later: Register and Documentation.

API basics you must know

Carrier Lookup endpoint used below:

GET https://freightpulsehq.com/api/v1/carriers?q=54283

Quick test with curl

Verify your network path and response envelope first. The following is copy-pasteable and uses the documented header:

curl -H "X-API-Key: YOUR_API_KEY" "https://freightpulsehq.com/api/v1/carriers?q=54283"

Official fixture (copy of a real response shape you should expect; values shown are exactly as provided):

{"success":true,"data":{"type":"trucking","query":"54283","results":[{"dot_number":54283,"legal_name":"SWIFT TRANSPORTATION COMPANY OF ARIZONA LLC","status":"active","safety_rating":"S","drivers":12883,"address":{"city":"Phoenix","state":"AZ","country":"US"}}]}}

Field notes you will actually use:

PHP: minimal client with curl

The snippet below calls the same endpoint, sets the X-API-Key header, validates HTTP status, parses the JSON envelope, and extracts the first carrier. It keeps timeouts strict to avoid hanging workers and includes simple file-based caching to reduce duplicate calls.

PHP example (single-file, curl)

<?php
declare(strict_types=1);

function fp_get_carrier(string $query, ?string $apiKey = null, int $ttlSeconds = 600): array {
$endpoint = 'https://freightpulsehq.com/api/v1/carriers?q=' . rawurlencode($query);
$cacheKey = 'fp_carriers_' . sha1($endpoint . '|' . ($apiKey ?? 'guest'));
$cacheFile = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $cacheKey . '.json';

// Simple cache to ease guest quota: 100 requests/day per IP
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < $ttlSeconds)) {
$cached = file_get_contents($cacheFile);
if ($cached !== false) {
$decoded = json_decode($cached, true);
if (is_array($decoded)) return $decoded;
}
}

$ch = curl_init($endpoint);
if ($ch === false) {
throw new RuntimeException('Failed to init curl');
}

$headers = [];
if ($apiKey) {
$headers[] = 'X-API-Key: ' . $apiKey; // Auth header
}

curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_CONNECTTIMEOUT => 5, // seconds
CURLOPT_TIMEOUT => 10, // hard cap
CURLOPT_HTTPHEADER => $headers,
CURLOPT_USERAGENT => 'fp-php-client/1.0',
]);

$body = curl_exec($ch);
$errno = curl_errno($ch);
$status = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
$errstr = curl_error($ch);
curl_close($ch);

if ($errno !== 0) {
throw new RuntimeException('Network error: ' . $errstr, $errno);
}
if ($status < 200 || $status >= 300) {
throw new RuntimeException('HTTP ' . $status . ' from FreightPulse');
}

$json = json_decode((string)$body, true);
if (!is_array($json)) {
throw new RuntimeException('Invalid JSON');
}
if (!array_key_exists('success', $json) || $json['success'] !== true) {
throw new RuntimeException('API indicated failure');
}
if (!isset($json['data']) || !is_array($json['data'])) {
throw new RuntimeException('Missing data envelope');
}

// Persist to cache on success
@file_put_contents($cacheFile, json_encode($json));

return $json;
}

// Example usage:
$apiKey = getenv('FP_API_KEY') ?: null; // or hardcode YOUR_API_KEY for local testing
$response = fp_get_carrier('54283', $apiKey);

// Pull the first carrier result safely
$results = $response['data']['results'] ?? [];
$first = $results[0] ?? null;

if ($first) {
$dot = $first['dot_number'] ?? null;
$name = $first['legal_name'] ?? '';
$status = $first['status'] ?? '';
$rating = $first['safety_rating'] ?? '';
$drivers = $first['drivers'] ?? null;
$city = $first['address']['city'] ?? '';
$state = $first['address']['state'] ?? '';
$country = $first['address']['country'] ?? '';

echo 'DOT: ' . $dot . PHP_EOL;
echo 'Name: ' . $name . PHP_EOL;
echo 'Status: ' . $status . ' | Safety rating: ' . $rating . PHP_EOL;
echo 'Drivers: ' . $drivers . PHP_EOL;
echo 'Address: ' . $city . ', ' . $state . ', ' . $country . PHP_EOL;
} else {
echo "No results for query.\n";
}

Notes:

JavaScript example (Node.js fetch)

If you need to test cross-language parity or wire up a small CLI, this shows the exact same call with the same header and envelope parsing. Values are illustrative except for field names and the endpoint.

import fetch from 'node-fetch';

async function lookupCarrier(query, apiKey) {
const url = 'https://freightpulsehq.com/api/v1/carriers?q=' + encodeURIComponent(query);
const headers = {};
if (apiKey) headers['X-API-Key'] = apiKey;

const res = await fetch(url, { headers, redirect: 'follow' });
if (!res.ok) {
throw new Error('HTTP ' + res.status + ' from FreightPulse');
}
const json = await res.json();
if (!json.success || !json.data) {
throw new Error('API indicated failure or missing data');
}
const first = (json.data.results || [])[0];
if (!first) return null;

return {
dot_number: first.dot_number,
legal_name: first.legal_name,
status: first.status,
safety_rating: first.safety_rating,
drivers: first.drivers,
address: first.address
};
}

lookupCarrier('54283', process.env.FP_API_KEY)
.then(c => console.log(c))
.catch(err => console.error(err));

Interpreting and storing results

The carrier record is compact and stable enough to store directly in your database for deduplication and quick subsequent reads. Suggested approach:

When joining into a TMS or onboarding workflow:

Error handling, quotas, and caching

Guest usage is capped at 100 requests/day per IP. Implement durable caching and backoffs from day one:

To run above guest limits, send your key with X-API-Key: YOUR_API_KEY and consolidate queries via caching to avoid waste.

Production considerations specific to FreightPulse

End-to-end PHP script you can drop in

This variant wraps everything into a single callable and prints a normalized row you can copy into a CSV or log. It uses headers and fields exactly as documented.

<?php
declare(strict_types=1);

function carrier_lookup(string $q, ?string $apiKey = null): array {
$url = 'https://freightpulsehq.com/api/v1/carriers?q=' . rawurlencode($q);
$h = [];
if ($apiKey) $h[] = 'X-API-Key: ' . $apiKey;

$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_HTTPHEADER => $h,
]);
$body = curl_exec($ch);
$status = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
$err = curl_error($ch);
curl_close($ch);

if ($body === false) throw new RuntimeException('Network error: ' . $err);
if ($status < 200 || $status >= 300) throw new RuntimeException('HTTP ' . $status);

$json = json_decode($body, true);
if (!is_array($json) || ($json['success'] ?? null) !== true) {
throw new RuntimeException('Unexpected envelope');
}
$results = $json['data']['results'] ?? [];
if (!$results) return [];

$out = [];
foreach ($results as $r) {
$out[] = [
'dot_number' => $r['dot_number'] ?? null,
'legal_name' => $r['legal_name'] ?? '',
'status' => $r['status'] ?? '',
'safety_rating'=> $r['safety_rating'] ?? '',
'drivers' => $r['drivers'] ?? null,
'city' => $r['address']['city'] ?? '',
'state' => $r['address']['state'] ?? '',
'country' => $r['address']['country'] ?? '',
];
}
return $out;
}

// Run:
$apiKey = getenv('FP_API_KEY') ?: null;
$rows = carrier_lookup('54283', $apiKey);
foreach ($rows as $r) {
echo implode(',', [
$r['dot_number'],
$r['legal_name'],
$r['status'],
$r['safety_rating'],
$r['drivers'],
$r['city'],
$r['state'],
$r['country'],
]) . PHP_EOL;
}

Testing checklist

FAQ

Do I need an API key to start?
Not strictly. You can call the endpoint as a guest, limited to 100 requests/day per IP. Add X-API-Key: YOUR_API_KEY to raise limits for production.

How should I handle multiple results for a text query?
Treat results as an array and apply your own matching logic (e.g., exact DOT match first, then strict legal_name comparison). Do not assume a single element.

Is port congestion live?
No. FreightPulse port congestion uses IMF PortWatch and is lagged about 6–7 days (7-day average vs 90-day baseline). It is not live AIS wait time.

Can I cache responses safely?
Yes. For DOT lookups, cache for minutes to hours depending on your update policy. Always store the raw envelope and a retrieved_at timestamp in UTC for audits.

Are there rate or price quotes I can display?
Do not quote a freight-rates USD fare as today’s price. If you display rates from elsewhere, label them clearly and keep them separate from identity data.

Ready to move beyond the guest quota and wire this into your TMS? Create an account and get your key: Register. For more implementation details and endpoint variations, see the Documentation.