# Fmcsa Usdot Trucking Fleet Scraper (`arezdev/fmcsa-usdot-trucking-fleet-scraper`) Actor

Enrich USDOT numbers and ranges with public FMCSA SAFER and SMS records. Extract truck and driver counts, operating status, cargo types and published registration contacts. Filter fleets and export data for logistics research and prospecting.

- **URL**: https://apify.com/arezdev/fmcsa-usdot-trucking-fleet-scraper.md
- **Developed by:** [Arezdev](https://apify.com/arezdev) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 carrier enricheds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

**Turn USDOT numbers into official fleet profiles with truck counts, registration contacts, cargo types, and source links.**

Enrich trucking companies and freight brokers using the FMCSA SAFER Company Snapshot and public SMS Carrier Registration pages. Supply a list, scan an inclusive USDOT range, or combine both. Matching fleets go directly into an exportable Apify Dataset.

Best for fleet segmentation and USDOT record enrichment. Each successful row is a carrier or broker. Set the minimum power units to zero to include brokers; emails depend on the public registration page and may be unavailable.

### How to use

1. Enter your USDOT numbers or provide both range endpoints.
2. Set the minimum fleet size. Use `minPowerUnits: 0` to include brokers and companies without reported power units.
3. Enable **Only active carriers** when you need active USDOT registrations.
4. Run the Actor and download the Dataset as JSON, CSV, Excel, or through the Apify API.

The input form includes one carrier number that returned a public record during review. These are live federal records: some may be inactive or excluded by the default fleet-size filter. Source availability and sample completion within five seconds cannot be guaranteed. Use your own known carrier numbers for a representative enrichment run.

```json
{
  "usdotNumbers": ["3000005"],
  "minPowerUnits": 0,
  "onlyActiveCarriers": false,
  "maxConcurrency": 15,
  "timeoutSecs": 15,
  "proxyConfiguration": { "useApifyProxy": false }
}
```

For discovery, use an inclusive range. It is generated incrementally without allocating an array for the range:

```json
{
  "scanRangeStart": 3000000,
  "scanRangeEnd": 3000100,
  "minPowerUnits": 5,
  "onlyActiveCarriers": true
}
```

Duplicate numbers, including overlaps between the list and range, are processed once. USDOT numbers must be positive strings of 1 to 10 digits; leading zeros are normalized. A supplied range must contain both endpoints.

### What data can you extract?

| Fields | Source and meaning |
| --- | --- |
| `usdotNumber`, `saferUrl`, `scrapedAt` | Normalized USDOT identifier, official snapshot link, UTC extraction timestamp |
| `legalName`, `dbaName`, `entityType` | Official legal name, trading name, and carrier or broker entity type |
| `mcNumber` | Reported MC, MX, or FF docket identifiers, kept together in one string |
| `operatingStatus` | USDOT registration status when present; legacy operating status or authority status otherwise |
| `powerUnitsCount`, `driversCount`, `fleetSizeTier` | Reported fleet and driver counts, plus a fleet-size category |
| `phone`, `physicalAddress`, `city`, `state`, `zipCode`, `mailingAddress` | Public SAFER contact and address fields |
| `email`, `cellPhone`, `fax`, `companyOfficers` | Public SMS registration contact information, when exposed |
| `mcs150Date`, `mcs150Mileage` | MCS-150 filing date in `YYYY-MM-DD` format and reported mileage, excluding the mileage year |
| `cargoTypes` | Cargo categories marked as carried in SAFER |
| `status`, `errorMessage` | `SUCCESS` or `ERROR`, with a safe error message for failed snapshots |

Missing scalar values are `null`; missing cargo and officer lists are empty arrays. Fax is included as an additional flat field. Fleet counts of zero remain zero. Contacts are extracted from registration fields; emails are never guessed or taken from FMCSA help links.

| Fleet size | `fleetSizeTier` |
| --- | --- |
| Not reported | `UNKNOWN` |
| 0 power units | `NO_POWER_UNITS` |
| 1 power unit | `OWNER_OPERATOR` |
| 2-10 power units | `SMALL` |
| 11-50 power units | `MEDIUM` |
| 51-500 power units | `LARGE` |
| 501 or more power units | `ENTERPRISE` |

### Input settings

| Setting | Default | Behavior |
| --- | --- | --- |
| `usdotNumbers` | One-number form prefill | Specific positive USDOT identifiers |
| `scanRangeStart` | Optional | First number, inclusive, from 1 to 9,999,999,999 |
| `scanRangeEnd` | Optional | Last number, inclusive; must be at least the start |
| `minPowerUnits` | `1` | Successful rows must meet this fleet threshold; `0` includes zero or unreported fleets |
| `onlyActiveCarriers` | `false` | Excludes successful records with inactive, out-of-service, or unknown status |
| `maxConcurrency` | `15` | Upper bound of 1-50 simultaneous carriers; reduced when needed for memory limits |
| `timeoutSecs` | `15` | Total deadline of 3-60 seconds per source, including DNS, redirects, and fallback attempts |
| `proxyConfiguration` | Automatic direct-first mode | Optional explicit direct, Apify Proxy, or custom proxy settings |

Each carrier starts two parallel source requests. At the default 256 MB allocation, concurrency is capped at 16 carriers for memory headroom; the requested default remains 15. Higher allocations can use all 50 requested workers.

Automatic proxy behavior starts with a direct connection. HTTP 403, 429, or 503 can trigger an Apify datacenter attempt using SHADER, followed by the default datacenter pool when needed. A configured final pool, including residential proxies, is attempted only when you explicitly set `useApifyProxy: true`. Selecting residential does not replace the initial direct and datacenter attempts.

`useApifyProxy: false` forces direct connections. Custom `proxyUrls` are used exclusively and must point to public HTTP or HTTPS proxies. Proxy connections use pinned destination IPs through CONNECT, preserve the source hostname for TLS and HTTP, and use HTTP/1.1. Proxy traffic can incur separate Apify or provider charges.

### Output example

This is an illustrative record with fictional company and contact values, rather than a claim about the current owner of this USDOT number.

```json
{
  "usdotNumber": "3000005",
  "status": "SUCCESS",
  "mcNumber": "MC-123456",
  "legalName": "EXAMPLE CARRIER LLC",
  "dbaName": "EXAMPLE FREIGHT",
  "entityType": "CARRIER",
  "operatingStatus": "ACTIVE",
  "powerUnitsCount": 12,
  "driversCount": 14,
  "fleetSizeTier": "MEDIUM",
  "phone": "+1 (817) 555-0100",
  "cellPhone": "+1 (817) 555-0101",
  "email": "dispatch@example.com",
  "fax": "(817) 555-0102",
  "companyOfficers": ["JANE EXAMPLE"],
  "physicalAddress": "123 EXAMPLE ROAD, FORT WORTH, TX 76101",
  "city": "FORT WORTH",
  "state": "TX",
  "zipCode": "76101",
  "mailingAddress": "PO BOX 100 FORT WORTH, TX 76101",
  "mcs150Date": "2026-09-20",
  "mcs150Mileage": 1234567,
  "cargoTypes": ["General Freight", "Refrigerated Food"],
  "saferUrl": "https://safer.fmcsa.dot.gov/query.asp?searchtype=ANY&query_type=queryCarrierSnapshot&query_param=USDOT&query_string=3000005",
  "scrapedAt": "2026-10-19T12:00:00.000Z",
  "errorMessage": null
}
```

### Use the results in Clay, n8n, Make, or your API

- **Clay:** Import the Dataset CSV, match companies by USDOT number, and map fleet size, state, phone, and registration email to enrichment columns.
- **n8n:** Start an Actor run with the Apify node, wait for completion, read the default Dataset, and route `SUCCESS` and `ERROR` records separately.
- **Make:** Use an Apify run-completed trigger, fetch Dataset items, and map matching fleets into your CRM or spreadsheet.
- **API:** Use an authenticated Apify API client to start the Actor and retrieve its default Dataset. Select successful rows before sending contacts to downstream workflows.

The [Apify Dataset API](https://docs.apify.com/api/v2/dataset-items-get) supports structured exports. Store API authentication in your integration's secure connection settings.

### Pricing and limits

The custom pay-per-event name is **`carrier-enriched`**. One saved `SUCCESS` record consumes one event when that event is configured in Apify pricing. Fleet filters run before Dataset insertion and charging.

**`ERROR` rows cost $0 in `carrier-enriched` event fees.** They remain visible in the Dataset and are saved without a custom charge event. Filtered-out successful fleets consume no event fees and are not saved. A successful SAFER snapshot can still be chargeable when SMS is unavailable and email or other registration fields are `null`.

For a concrete example, at an **illustrative** event price of **$0.002 per matching carrier**, 1,000 saved successful carriers cost **$2.00** in event fees; 100 additional `ERROR` rows add **$0.00** in event fees. This example does not establish a published price. Check the Actor's current Store pricing; platform compute, storage, and proxy charges may apply separately.

On Apify, non-paying users can save **25 total rows per run**, counting both successful and failed records. Paid plans and local runs do not have this Actor-specific row cap. The configured event charge limit still applies. When either cap is reached, new work stops, buffered rows are flushed, and the Actor exits cleanly.

The Actor uses 256 MB by default, with a default run timeout of 3,600 seconds. Repeated source errors do not crash the run. Invalid input fails validation before target requests and exits with code 1. A storage failure stops extraction safely and is reported in the final status; inspect the Dataset before retrying to avoid duplicate records.

### FAQ and troubleshooting

**Why are there no matching rows?**

The default minimum is one power unit. Brokers often report zero trucks. Set `minPowerUnits: 0` to inspect these records, or provide USDOT numbers for known motor carriers. A missing power-unit count does not satisfy a positive fleet threshold.

**Why is the email missing?**

The Actor returns public SMS registration fields only when the page exposes them. A timeout, access restriction, blank field, or unrecognized contact layout leaves the corresponding value `null`. A valid SAFER snapshot is retained. Warnings include the source domain, USDOT identifier, and safe failure code. No sign-in bypass is attempted.

**What does active mean?**

The filter primarily follows the snapshot's USDOT registration status. It does not independently verify operating authority, insurance, or safety fitness. Review the official source before making a carrier eligibility decision.

**How are inactive or missing snapshots handled?**

A SAFER inactive-record page, a missing record, an identifier mismatch, an access page, or a failed SAFER request produces an `ERROR` row without a custom event fee. Errors remain visible even when fleet filters are enabled.

**What happens after a source block or timeout?**

Direct requests are bounded by the source timeout. The proxy cascade can help with HTTP 403, 429, and 503. Lower concurrency if the source throttles requests. When the platform supplies no Apify proxy password, automatic datacenter fallback is unavailable; direct mode and custom proxies continue to work.

**Can a partial HTML response be used?**

HTML is streamed and capped at 4 MB after decompression. A larger response is truncated. A timed-out or reset 2xx response can be salvaged after at least 16 KiB has arrived. A row becomes successful only if a matching carrier identity was actually parsed. Partial responses are reported in run warnings; missing fields remain `null`.

**Are exports safe to open in a spreadsheet?**

Strings have null bytes removed and spreadsheet formula prefixes escaped. Valid phone fields keep their international `+` prefix. Counts remain numeric, and source HTML is never executed.

### Development and deployment

Requires Node.js 22 or newer. This repository uses TypeScript strict mode, the Apify SDK, got-scraping, and a streaming htmlparser2 parser. All target requests use direct HTTP clients.

```sh
npm ci
npm run build
npm test
npm run smoke
npm start
```

`npm test` compiles the tests and runs Node's built-in test runner. Tests are offline and cover SSRF, DNS pinning, redirect checks, HTTP protocol fallback, proxy selection, partial-stream salvage, parser layouts, filters, concurrent batching, event budgets, and the free-plan cap. `npm run smoke` performs live direct requests for three representative USDOT numbers, including unavailable records and reports source availability and peak process memory. Build before running the smoke check. Source blocks can make a live smoke check fail even when the offline suite passes.

For a local Actor run, place the input JSON at `storage/key_value_stores/default/INPUT.json` before `npm start`. Local output is stored under `storage/datasets/default`. The start, test, and smoke runners set `UV_THREADPOOL_SIZE=64`, `NODE_ENV=production`, and certificate verification before spawning their Node process. No environment file is required.

The Dockerfile uses a Node 22 Alpine multi-stage build, a locked production dependency tree, and the non-root `node` user. Build from this repository root:

```sh
docker build -t fmcsa-usdot-trucking-fleet-scraper .
```

Deploy with Apify CLI or connect this directory as the Actor source. The manifest includes default memory and run timeout metadata; verify those defaults in Console for your chosen deployment method. Select **Limited permissions** in the Actor settings. The Actor uses the platform's paying-user flag and run charging manager; it never retrieves the user account profile. Proxy initialization requires a platform-supplied proxy password or explicit custom proxies and skips account/status probes.

Configure the `carrier-enriched` event and its price in Apify Console before publishing a paid Actor. Keep the automatic `apify-default-dataset-item` event free: the Actor refuses a positive automatic dataset-item fee so failed rows cannot be charged implicitly. Pricing activation and platform permission settings are deployment operations, rather than effects of building the local repository.

Official source references: [SAFER](https://safer.fmcsa.dot.gov/), [FMCSA SMS](https://ai.fmcsa.dot.gov/SMS/), and [Apify charging manager](https://docs.apify.com/sdk/js/reference/class/ChargingManager).

Repository initialized by Arezgit.

# Actor input Schema

## `usdotNumbers` (type: `array`):

Positive USDOT numbers, each containing 1 to 10 digits. Leading zeros are normalized.

## `scanRangeStart` (type: `integer`):

First USDOT number to scan, inclusive. Provide both range endpoints.

## `scanRangeEnd` (type: `integer`):

Last USDOT number to scan, inclusive. Must be greater than or equal to the range start.

## `minPowerUnits` (type: `integer`):

Save only matching fleets. Use 0 to include brokers and records with no reported fleet. Error rows remain visible without carrier-enriched fees.

## `onlyActiveCarriers` (type: `boolean`):

Keep carriers with an active USDOT status. Inactive, out-of-service, and unknown statuses are excluded from successful results.

## `maxConcurrency` (type: `integer`):

Upper limit on simultaneous carrier lookups. Each carrier uses two parallel requests. Reduced automatically when required by the memory budget.

## `timeoutSecs` (type: `integer`):

Total deadline for each source, including DNS, redirects, and proxy fallbacks. SAFER and SMS are fetched in parallel.

## `proxyConfiguration` (type: `object`):

Direct connections are tried first, followed by datacenter proxies on HTTP 403, 429, or 503. Configured or residential proxies require explicit useApifyProxy: true. Custom proxy URLs are used exclusively; useApifyProxy: false forces direct connections.

## Actor input object example

```json
{
  "usdotNumbers": [
    "3000005"
  ],
  "minPowerUnits": 1,
  "onlyActiveCarriers": false,
  "maxConcurrency": 15,
  "timeoutSecs": 15
}
```

# Actor output Schema

## `results` (type: `string`):

Default dataset items. Review each record status before using extracted data downstream.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "usdotNumbers": [
        "3000005"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("arezdev/fmcsa-usdot-trucking-fleet-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "usdotNumbers": ["3000005"] }

# Run the Actor and wait for it to finish
run = client.actor("arezdev/fmcsa-usdot-trucking-fleet-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "usdotNumbers": [
    "3000005"
  ]
}' |
apify call arezdev/fmcsa-usdot-trucking-fleet-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,arezdev/fmcsa-usdot-trucking-fleet-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/iPuim77sRafsOu3dl/builds/lcH5eXZgoTTzNcj2D/openapi.json
