FMCSA QCMobile + SAFER + L&I Integration
OpenInsure integrates with three upstream FMCSA surfaces — the QCMobile JSON API, the public SAFER HTML snapshot, and the L&I out-of-service orders page — to build a complete carrier profile for underwriting and monitoring. This page documents the endpoints, authentication, response field mappings, and how the client handles rate limits, SAFER/L&I scrape failures, and retries.
Data Sources At A Glance
Section titled “Data Sources At A Glance”| Source | Format | Auth | Fields Unique to This Source |
|---|---|---|---|
| QCMobile API | JSON | webKey | Legal/DBA name, authority, BIPD insurance, BASICs, OOS rates, crashes, safety rating, power units, drivers |
| SAFER snapshot | HTML scrape | None | Phone, DUNS, mailing address, HM shipper/carrier flags, MCS-150 form date, MCS-150 mileage |
| L&I OOS orders | HTML scrape | None | Active OCO (Out-of-service Carrier Order) / OSO (Out-of-service Shipper Order) decisions — hard decline signal for UW |
The server fans out all three in parallel on every DOT lookup, so the interactive UW panel and the server-side submission enrichment always see the same merged record. QCMobile is the source of truth where it has data; SAFER and L&I fill the gaps that QCMobile doesn’t cover at all.
Authentication
Section titled “Authentication”The QCMobile API uses a webKey query parameter for authentication. Keys are issued at no cost through the FMCSA portal at mobile.fmcsa.dot.gov.
GET https://mobile.fmcsa.dot.gov/qc/services/carriers/1234567?webKey=<your_key>The webKey is stored in Cloudflare Workers secrets as FMCSA_WEB_KEY and injected into every request by the API worker. It is never logged or exposed in error messages.
Endpoints Used
Section titled “Endpoints Used”GET /carriers/:dot — Carrier Authority & Insurance
Section titled “GET /carriers/:dot — Carrier Authority & Insurance”Retrieves the carrier’s operating authority, insurance on file, and contact information.
https://mobile.fmcsa.dot.gov/qc/services/carriers/:dotNumber?webKey=<key>Key response fields:
| FMCSA Field | Internal Mapping | Description |
|---|---|---|
dotNumber | dotNumber | USDOT number |
legalName | legalName | Legal entity name |
dbaName | dbaName | DBA name, if any |
carrierOperation | operationType | A (interstate), B (intrastate hazmat), C (intrastate non-hazmat) |
authorityStatus | authorityStatus | A (active), I (inactive), N (not authorized) |
bipdInsuranceOnFile | insuranceOnFile | Y / N — BI/PD insurance currently on file |
bipdInsuranceRequired | insuranceRequired | Required insurance amount (dollars) |
phyCity, phyState | address.city/state | Physical location |
totalDrivers | totalDrivers | Number of drivers on file |
totalPowerUnits | totalPowerUnits | Number of power units on file |
GET /carriers/:dot/basics — CSA BASIC Scores
Section titled “GET /carriers/:dot/basics — CSA BASIC Scores”Retrieves the carrier’s Compliance, Safety, Accountability (CSA) BASIC percentile scores and investigation history.
https://mobile.fmcsa.dot.gov/qc/services/carriers/:dotNumber/basics?webKey=<key>Seven BASIC categories:
| BASIC Name | Internal Key | Intervention Threshold |
|---|---|---|
| Unsafe Driving | UNSAFE_DRIVING | 65th percentile |
| Hours-of-Service | HOS_COMPLIANCE | 65th percentile |
| Driver Fitness | DRIVER_FITNESS | 80th percentile |
| Controlled Substances/Alcohol | CONTROLLED_SUBSTANCES | 80th percentile |
| Vehicle Maintenance | VEHICLE_MAINT | 80th percentile |
| Hazardous Materials | HAZMAT | 60th percentile |
| Crash Indicator | CRASH_INDICATOR | 65th percentile |
The fetchBasics() function (in apps/api/src/lib/fmcsa.ts) maps the raw FMCSA basics array — which is keyed by numeric BASIC category codes — to the named keys above, and attaches whether each score has crossed the intervention threshold.
// Simplified output of fetchBasics(){ basics: [ { basic: 'HOS_COMPLIANCE', percentile: 78, interventionThreshold: 65, aboveThreshold: true, measureValue: '4.12', inspections: 24, violations: 9 }, // ... ], investigations: [ { type: 'COMPLIANCE_REVIEW', date: '2024-11-15', rating: 'CONDITIONAL' } ]}GET /carriers/:dot/oos — Out-of-Service Rates
Section titled “GET /carriers/:dot/oos — Out-of-Service Rates”Retrieves the carrier’s out-of-service rates for vehicles, drivers, and hazmat, compared to national averages.
https://mobile.fmcsa.dot.gov/qc/services/carriers/:dotNumber/oos?webKey=<key>The fetchOosRates() function extracts and normalizes the three OOS rate categories:
// Simplified output of fetchOosRates(){ vehicle: { inspections: 48, outOfService: 9, rate: 18.75, // percentage nationalAverage: 21.4 }, driver: { inspections: 48, outOfService: 3, rate: 6.25, nationalAverage: 5.5 }, hazmat: { inspections: 12, outOfService: 0, rate: 0.0, nationalAverage: 4.2 }}SAFER HTML Snapshot
Section titled “SAFER HTML Snapshot”fetchSaferSnapshot(dotNumber) in apps/api/src/lib/fmcsa.ts scrapes the public SAFER page at https://safer.fmcsa.dot.gov/query.asp and extracts the fields that QCMobile does not expose:
// Simplified shape returned by fetchSaferSnapshot(){ phone: '(617) 555-0123', dunsNumber: '012345678', mailingAddress: '100 Main St\nBoston, MA 02108', hmShipper: false, hmCarrier: true, mcs150FormDate: '2025-06-14', mcs150Mileage: 4_250_000}The scraper returns null on any parse failure; the caller treats failures as non-fatal and continues with the QCMobile-only record. The multi-line mailingAddress is split into mailingStreet / mailingCity / mailingState / mailingZipcode before being merged into the final carrier record.
L&I Out-of-Service Orders
Section titled “L&I Out-of-Service Orders”fetchOosOrders(dotNumber) scrapes the FMCSA Licensing & Insurance (L&I) “Out-of-service Orders” view and returns any active OCO or OSO orders. An active order is a hard UW decline signal — FMCSAPanel renders a red banner and the underwriting rules engine sets auto_decline_oos_order.
// Return shape{ orders: [ { orderType: 'OCO' | 'OSO', orderDate: '2026-02-11', reason: 'Unsatisfactory safety rating', status: 'active', }, ];}Three response states distinguish themselves by convention:
| Response | Meaning |
|---|---|
{ orders: [] } | Checked successfully; carrier has no orders |
{ orders: [ { ... } ] } | Carrier has one or more active orders |
null / field omitted | L&I fetch failed — unknown OOS state |
The UW /v1/motor-carriers/dot/:dot response includes oosOrders when the fetch succeeded and omits the field entirely otherwise, so consumers can distinguish “no orders” from “no data”.
Client Implementation
Section titled “Client Implementation”The FMCSA client lives in apps/api/src/lib/fmcsa.ts and exports:
lookupDotNumber(dotNumber, webKey, insuredName?) // QCMobile carrier + authorityfetchBasics(dotNumber, webKey) // QCMobile BASICsfetchOosRates(dotNumber, webKey) // QCMobile OOS ratesfetchInsuranceFilings(dotNumber, webKey) // QCMobile BIPD + cargo filingsfetchSaferSnapshot(dotNumber) // SAFER HTMLfetchOosOrders(dotNumber) // L&I OOS ordersAll QCMobile functions follow the same pattern:
- Build the QCMobile URL with the DOT number and webKey.
- Fetch with a 10-second timeout.
- On HTTP 404, return
null(carrier not found in FMCSA — may be a new or deactivated carrier). - On HTTP 429 or 503, throw
FmcsaRateLimitError(see Retry Strategy below). - On any other non-2xx status, throw
FmcsaFetchErrorwith the status code and URL. - Parse and validate the response with Zod before returning.
Rate Limits and Retry Strategy
Section titled “Rate Limits and Retry Strategy”The QCMobile API does not publish a formal rate limit, but sustained traffic above approximately 5 requests per second triggers HTTP 429 responses. The cron pipeline enforces a 200ms delay between requests — meaning a portfolio of 150 carriers takes roughly 90 seconds to fully scan (150 carriers × 3 endpoints × 200ms).
The retry strategy for transient errors:
| Error | Action |
|---|---|
| HTTP 429 | Exponential backoff: 1s → 2s → 4s, then skip carrier and log warning |
| HTTP 503 | Same as 429 |
| Network timeout | Retry once immediately, then skip carrier |
| HTTP 404 | No retry — carrier not in FMCSA, mark as not_found |
| HTTP 400 | No retry — invalid DOT number, log error |
Carriers that could not be fetched are excluded from the alert diff for that run. They are not marked as having no alerts — they are simply not evaluated. The staleCarriers field in the /v1/fmcsa-history/stats response surfaces carriers with gaps in their snapshot history.
Consumer Endpoints
Section titled “Consumer Endpoints”Two API endpoints fuse QCMobile, SAFER, and L&I into a single response:
GET /v1/motor-carriers/dot/:dotNumber
Section titled “GET /v1/motor-carriers/dot/:dotNumber”Interactive DOT lookup from the UW workbench Insured Info tab. Fans out QCMobile + SAFER + L&I OOS in parallel and returns the merged carrier record. Also triggers the OCO/OSO scraper so any later carrier-alert checks see fresh data.
Response includes carrierName, dbaName, phyStreet/City/State/Zipcode, mailingStreet/City/State/Zipcode, telephone, operatingAuthority, csaScore, issScore, totalPowerUnits, totalDrivers, crash/inspection/OOS counts, safetyRating, mcs150FormDate, mcs150Mileage, HM flags, and oosOrders[] when available. See FmcsaCarrierRecord in the OpenAPI spec for the full field list.
POST /v1/submissions/:id/enrich-fmcsa
Section titled “POST /v1/submissions/:id/enrich-fmcsa”Server-side enrichment for a submission. In addition to the parallel fan-out above, this endpoint also fetches basics, oos-rates, insurance-filings, cargo-carried, operation-classification, and docket-numbers from QCMobile, merges everything into the submission’s extractedData.fmcsaEnrichment block, and writes an append-only row per endpoint to fmcsa_snapshots for trend analysis.
Returns { submission, fmcsa, safer, qcmobile }:
{ "submission": { "id": "sub_01J8...", "insuredName": "ACME Trucking LLC" /* ... */ }, "fmcsa": { /* merged carrier record — same shape as motor-carriers/dot response */ }, "safer": { "phone": "...", "dunsNumber": "...", "mailingAddress": "...", "hmCarrier": true }, "qcmobile": { "basics": { /* ... */ }, "oos": { /* ... */ }, "filings": [ /* ... */ ], "cargo": { /* ... */ }, "operation": { /* ... */ }, "docket": [ /* ... */ ], },}Sole-prop detection runs in this path: if the SAFER-sourced legal name matches an individual (e.g., “JOHN D SMITH”) the enrichment merges the name into the insured’s name fields (not the company field) and populates phone in one setInsured batch.