You need to look up a motor carrier by DOT number and wire the result into your app. By the end of this guide you will query FreightPulse for a carrier lookup, read the response safely in PHP, and return just the fields your system needs, without guessing at fields or endpoints.
What you are building
This walkthrough implements a DOT-based carrier lookup using FreightPulse. You will authenticate with an X-API-Key header, call the carriers endpoint with a query string, parse the standardized envelope, and render carrier identity and safety fields. A complete PHP implementation is provided, with a lightweight JavaScript example for front-end or Node scripts.
API facts you must know before writing code
- Auth: send your API key in the header X-API-Key: YOUR_API_KEY. Guests without a key are limited to 100 requests/day per IP.
- Envelope: all responses follow {"success":true,"data":{...}}. Your code should check success first.
- Endpoint and filter: use the carriers endpoint with the q parameter for the DOT lookup.
- Congestion context: FreightPulse port congestion uses IMF PortWatch, lagged about 6–7 days (7-day average vs 90-day baseline). It is not live AIS and should not be treated like real-time wait times.
- Pricing: do not quote a freight-rates USD fare as today’s price; this guide does not fetch or compute rates.
Quick test with cURL
Run this from your shell to verify connectivity and your key handling. Replace only YOUR_API_KEY with your key if you have one; anonymous calls still work within the guest limit.
curl -H "X-API-Key: YOUR_API_KEY" "https://freightpulsehq.com/api/v1/carriers?q=54283"
Official sample response for DOT 54283
This is the official fixture you should use to validate your parser. Notice the top-level success boolean and data object, and the results array containing one carrier match.
{
"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"
}
}
]
}
}
Fields you are likely to use:
- data.type: modality/category for the lookup, here "trucking".
- data.query: the echoed search term you sent (string).
- data.results: array of carrier objects. Each has:
- dot_number: integer DOT ID.
- legal_name: registered legal name.
- status: operational status string.
- safety_rating: single-letter rating code.
- drivers: count of drivers (integer).
- address.city, address.state, address.country: localization fields.
PHP implementation: end-to-end carrier lookup
The following PHP script calls the carriers endpoint, validates the envelope, and returns normalized output. It is designed to be dropped into a controller action or a small CLI tool. It handles missing keys, HTTP errors, JSON decoding errors, and the possibility of multiple results in the results array.
PHP: minimal client with robust checks
<?php
declare(strict_types=1);
/**
* Carrier lookup by DOT using FreightPulse.
* Requirements:
* - PHP 7.4+ (json extension enabled).
* - Network access to https://freightpulsehq.com/.
*
* Notes:
* - Guests without a key are limited to 100 requests/day per IP.
* - Set $apiKey to YOUR_API_KEY to authenticate; leave empty to operate as a guest.
*/
function freightpulse_carrier_lookup(string $dot, string $apiKey = ''): array
{
$url = 'https://freightpulsehq.com/api/v1/carriers?q=' . rawurlencode($dot);
$ch = curl_init($url);
if ($ch === false) {
throw new RuntimeException('Failed to initialize cURL');
}
$headers = [];
// Add X-API-Key header whether empty or not; the API accepts guest calls without a key.
$headers[] = 'X-API-Key: ' . ($apiKey !== '' ? $apiKey : 'YOUR_API_KEY');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 20,
CURLOPT_HTTPHEADER => $headers,
]);
$raw = curl_exec($ch);
if ($raw === false) {
$err = curl_error($ch);
curl_close($ch);
throw new RuntimeException('cURL error: ' . $err);
}
$httpStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpStatus < 200 || $httpStatus >= 300) {
throw new RuntimeException('HTTP error status: ' . $httpStatus . ' Body: ' . $raw);
}
$json = json_decode($raw, true);
if (!is_array($json)) {
throw new RuntimeException('Invalid JSON response');
}
if (!array_key_exists('success', $json) || $json['success'] !== true) {
throw new RuntimeException('API indicated failure or missing success flag');
}
if (!array_key_exists('data', $json) || !is_array($json['data'])) {
throw new RuntimeException('Malformed envelope: missing data');
}
$data = $json['data'];
$results = $data['results'] ?? [];
if (!is_array($results) || count($results) === 0) {
return [
'query' => strval($data['query'] ?? $dot),
'matches' => 0,
'carriers' => [],
];
}
// Normalize each result to a flat structure your app can render quickly.
$carriers = [];
foreach ($results as $r) {
$carriers[] = [
'dot_number' => $r['dot_number'] ?? null,
'legal_name' => $r['legal_name'] ?? null,
'status' => $r['status'] ?? null,
'safety_rating' => $r['safety_rating'] ?? null,
'drivers' => $r['drivers'] ?? null,
'city' => $r['address']['city'] ?? null,
'state' => $r['address']['state'] ?? null,
'country' => $r['address']['country'] ?? null,
];
}
return [
'query' => strval($data['query'] ?? $dot),
'matches' => count($carriers),
'type' => $data['type'] ?? null,
'carriers' => $carriers,
];
}
// Example usage: php carrier_lookup.php 54283
if (PHP_SAPI === 'cli' && basename(__FILE__) === basename($_SERVER['argv'][0] ?? '')) {
$dot = $_SERVER['argv'][1] ?? '';
if ($dot === '') {
fwrite(STDERR, "Usage: php " . basename(__FILE__) . " DOT_NUMBER\n");
exit(2);
}
// Provide YOUR_API_KEY to raise limits and access authenticated features.
$apiKey = getenv('FREIGHTPULSE_API_KEY') ?: '';
try {
$result = freightpulse_carrier_lookup($dot, $apiKey);
// Print as JSON for pipeline compatibility.
fwrite(STDOUT, json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n");
exit(0);
} catch (Throwable $e) {
fwrite(STDERR, "Error: " . $e->getMessage() . "\n");
exit(1);
}
}
JavaScript example (Node or browser fetch)
This snippet demonstrates how to fetch the same endpoint and read the key fields. In a browser, ensure CORS is allowed for your integration; otherwise call from your backend.
async function lookupCarrier(dot, apiKey) {
const url = 'https://freightpulsehq.com/api/v1/carriers?q=' + encodeURIComponent(dot);
const res = await fetch(url, {
headers: {
'X-API-Key': apiKey && apiKey.length > 0 ? apiKey : 'YOUR_API_KEY'
},
redirect: 'follow'
});
if (!res.ok) {
throw new Error('HTTP ' + res.status + ' from carriers endpoint');
}
const json = await res.json();
if (!json || json.success !== true || !json.data) {
throw new Error('Unexpected envelope or API failure');
}
const results = Array.isArray(json.data.results) ? json.data.results : [];
return results.map(r => ({
dot_number: r.dot_number,
legal_name: r.legal_name,
status: r.status,
safety_rating: r.safety_rating,
drivers: r.drivers,
city: r.address && r.address.city,
state: r.address && r.address.state,
country: r.address && r.address.country
}));
}
// Example usage:
lookupCarrier('54283', '')
.then(list => console.log(list))
.catch(err => console.error(err));
Implementation notes that save time
- Authentication and limits: use X-API-Key: YOUR_API_KEY. Without a key, you are limited to 100 requests/day per IP. For production, set and read your key from environment variables, not source code.
- Envelope handling: always check success === true before touching data. Do not assume results is present or non-empty; guard with Array checks.
- Query parameter: q is a string. When you pass a DOT number, coerce it to a string to avoid locale issues.
- Multiple matches: results is an array. Even for a DOT lookup you should iterate, not index at 0 blindly.
- Caching: cache positive lookups for short periods (e.g., hours to a day) based on your freshness requirements. The response model here contains static identity fields; still, respect your domain’s update cadence.
- Timestamps/timezones: this endpoint’s sample contains no timestamps. If you persist results, record your own fetched_at in UTC for auditability.
- Error surfaces: handle HTTP non-2xx, network failures, and JSON parse errors distinctly so you can retry intelligently.
- Security: never log full API keys. Redact to last 4 characters when debugging.
- Frontend calls: prefer server-side calls to keep your API key private. If you must call from the browser, use guest mode sparingly and enforce IP-based throttling on your edge.
- Congestion vs live waits: if you later consume FreightPulse port congestion, it is IMF PortWatch-derived and lagged about 6–7 days (7-day average vs 90-day baseline). Do not present it as live AIS wait time.
- Rates: do not compute or display a “today’s price” freight rate based on this lookup. This endpoint is for carrier data, not fares.
Testing and validation checklist
- Connectivity: run the provided curl and ensure you receive a JSON body with success true.
- Parser: compare your parser’s output to the official fixture to confirm field names and types.
- Null-safety: simulate no matches by querying an unlikely DOT; the code should return an empty array without exceptions.
- Throttling behavior: if you operate without a key, monitor 429 or quota messages after frequent calls; back off and add your API key.
- UTF-8: ensure your JSON decoder and database collation handle carrier names without loss.
Integrating into your stack
In a monolith or service-oriented architecture, isolate the FreightPulse call in a gateway module. Expose a function getCarrierByDot(dot) that returns your normalized shape. Surround the gateway with retries and circuit-breaking to prevent cascading failures if the upstream network blips.
When persisting, store both the original JSON and your normalized projection. The original blob lets you reprocess fields if your schema evolves. Tag each record with your fetched_at timestamp in UTC and the query string you used (data.query) for traceability.
Field mapping reference
- dot_number → your carrier_id (integer).
- legal_name → your legal_name (text).
- status → your status (enum/string); keep raw values unless you have a firm mapping.
- safety_rating → your safety_code (char).
- drivers → your driver_count (integer).
- address.city/state/country → your locality fields. Consider indexing city/state for search.
Common pitfalls and how to avoid them
- Assuming single result: even when you query by DOT, code for multiple results to avoid runtime errors.
- Skipping the envelope: always read success and data first; do not parse results from the root.
- Hardcoding secrets: never embed real keys in client-side code or repositories. Use environment variables and secrets managers.
- Missing error context: when throwing exceptions, include HTTP status and a snippet of the body for diagnostics, but redact secrets.
Where to go next
For parameter nuances and broader endpoints, read the official Documentation. Use a real API key to raise limits and build a stable integration in staging, then production.
FAQ
Do I need an API key to test?
Not strictly. Guests without a key can make up to 100 requests per day per IP. Add X-API-Key: YOUR_API_KEY to move beyond that and for production use.
Is the response always an array in data.results?
Yes. Treat results as an array and iterate, even if you expect a single DOT match.
Can I treat congestion metrics as live port wait times?
No. FreightPulse congestion uses IMF PortWatch, lagged about 6–7 days (7-day average vs 90-day baseline). It is not live AIS.
Can I quote freight rates from this endpoint?
No. This lookup is for carrier data. Do not quote a freight-rates USD fare as today’s price based on this response.
How should I cache lookups?
Cache by DOT for hours to a day, depending on how frequently you need updates. Always validate success and stale data before serving from cache.
Ready to integrate this lookup into your app? Create your account in minutes and get an API key here: Register. Then keep this guide open and ship the PHP integration in one sitting.