Build an Insurance Underwriting Risk Check
To assess a motor carrier's insurability you need four things: active operating authority, insurance on file, safety performance across FMCSA's seven BASICs, and any out-of-service or enforcement history. One /v2/profile call returns all of it — this guide turns that call into a tiered underwriting determination in about 60 lines of Python.
What underwriters evaluate, in order of severity
- Disqualifiers— inactive USDOT registration, an active out-of-service order, or (for interstate for-hire carriers) missing authority or deficient FMCSA insurance filings. Any one of these usually ends the evaluation. Intrastate carriers are state-regulated — don't hold them to FMCSA filing requirements they don't owe.
- Primary risk signals — BASIC alerts (especially Unsafe Driving, Hours-of-Service, and Crash Indicator, which FMCSA weights as the strongest crash predictors), the ISS inspection recommendation, and a Conditional or Unsatisfactory safety rating.
- Secondary signals — new-entrant status, insurance lapse history, fleet size relative to violation counts, and MCS-150 freshness.
- Fraud context — chameleon-carrier signals: authorities sharing addresses, phones, or equipment with other (possibly revoked) carriers.
Public sources make you assemble this from four systems (SAFER, SMS, L&I, and the Socrata historical datasets) — and two of the seven BASICs, Crash Indicator and Hazmat Compliance, aren't publicly displayed at all. CarrierOk computes those two in-house from the underlying public inspection and crash data, so the API returns all seven. See the SAFER glossary entry for how the public systems fit together.
Get the carrier profile
One call, by DOT number (or MC number, EIN, phone, email, VIN, plate — see the Profiles reference). sk_test_ keys hit the sandbox, sk_live_ keys hit production:
curl "https://api.carrierok.com/v2/profile?dot_number=1234567" \
-H "Authorization: Bearer YOUR_API_KEY"{
"items": [
{
"dot_number": "1234567",
"legal_name": "EXAMPLE TRANSPORT LLC",
"entity_type_desc": "Carrier",
"usdot_status": "Active",
"carrier_operation_desc": "interstate",
"operation_class_authorized_for_hire": true,
"indicator_authority": true,
"authority_common": "Active",
"out_of_service_flag": false,
"indicator_insurance": true,
"insurance_bipd_on_file": "1000000",
"insurance_bipd_required": "750000",
"total_power_units": "42",
"safety_rating_desc": "Satisfactory",
"risk_score": "Low",
"risk_score_probability": 0.08,
"iss_value": "34",
"iss_recommendation": "OPTIONAL",
"basic_alert_unsafe_driving": false,
"basic_alert_hours_of_service": true,
"basic_alert_vehicle_maintence": false,
"basic_measure_unsafe_driving": "0.42",
"basic_percentile_crash_indicator": 0.61,
"...": "320+ additional fields — see the data dictionary"
}
],
"total_count": 1
}Three response conventions to know
items[0]. Most numeric counters arrive as strings ("total_power_units": "42"). Fields with no value for a carrier are omitted from the response rather than returned as null — always read with .get(). The full field reference is the data dictionary and the machine-readable OpenAPI spec, which types all of them.Define the risk determination
An illustrative framework — not advice
Decline / refer immediately if any of:
| Condition | Check |
|---|---|
| USDOT registration not active | usdot_status != "Active" |
| Active out-of-service order | out_of_service_flag == true |
| No active operating authority | indicator_authority is not true |
| Authority explicitly inactive / revoked | indicator_authority == false |
| Insurance below FMCSA requirement | indicator_insurance == false |
Interstate vs. intrastate — don't decline carriers for filings they don't owe
usdot_status). The API encodes this: indicator_authority and indicator_insurance are tri-state(true / false / null), where null means "not applicable / nothing required." Treat only explicit false as a failure, and gate the authority-required check on carrier_operation_desc == "interstate" plus operation_class_authorized_for_hire.Flag as high-risk if any of:
| Condition | Check |
|---|---|
| Composite risk score is High | risk_score == "High" |
| ISS says inspect | iss_recommendation == "INSPECT" |
| Conditional / Unsatisfactory rating | safety_rating_desc in (...) |
| Alert in a crash-predictive BASIC | basic_alert_unsafe_driving | basic_alert_hours_of_service | basic_alert_crash_indicator |
| Two or more BASIC alerts of any kind | count(basic_alert_*) >= 2 |
Field names and scales — read before you code
hours_of_service (not hos_compliance), controlled_substance (singular), hazardous_materials, and vehicle_maintence (yes, that spelling). Percentiles (basic_percentile_*) are on a 0–1 scale— multiply by 100 to compare with the percentages shown on FMCSA's SMS site; the matching intervention thresholds are in intervention_threshold_*(e.g. 0.65 for Unsafe Driving). And don't escalate on indicator_carrier_safety == false: it's a composite pass-screen that is false for ~92% of carriers, mostly because they simply have too few inspections.Route to enhanced review (underwriter attention) if any of:
| Condition | Check |
|---|---|
| Composite risk score is Medium | risk_score == "Medium" |
| Any single BASIC alert | count(basic_alert_*) == 1 |
| New entrant | dot_age < 540 |
The unremarkable carrier — no adverse signals, risk_scorenot computed (it's null for intrastate, private, and non-carrier entities) — lands in standard review. Low risk requires positive evidence: risk_score == "Low" with zero alerts and clean registration — absence of data is not a clean bill of health. Chameleon signals (shared addresses, phones, equipment — the risk_factors array and network_graph_* fields) are best treated as context for manual review rather than automatic flags: large legitimate fleets share identifiers too.
The complete script
Save as risk_check.py:
"""Carrier underwriting risk check — CarrierOk API.
Illustrative starting framework — not underwriting, legal, or compliance
advice. Validate thresholds against your own book and model governance.
Usage:
export CARRIEROK_API_KEY=sk_test_your_key_here
python risk_check.py 1234567
Field-name notes (these are the API's real names — don't "fix" them):
- BASIC suffixes: unsafe_driving, hours_of_service, driver_fitness,
controlled_substance, vehicle_maintence, hazardous_materials,
crash_indicator
- iss_recommendation values are UPPERCASE: INSPECT / OPTIONAL / PASS
- indicator_authority / indicator_insurance are TRI-STATE
(true / false / null): null means "not applicable / nothing required"
— normal for intrastate and private carriers — and must not be
treated as a failure
- Numeric counters arrive as strings; fields with no value for a carrier
are omitted from the response entirely (not returned as null)
"""
import os
import sys
import requests
API_KEY = os.environ["CARRIEROK_API_KEY"] # sk_test_... or sk_live_...
BASE = "https://api.carrierok.com/v2"
# FMCSA weights these three as the strongest crash predictors and applies
# lower intervention thresholds to them.
CRASH_PREDICTIVE = ("unsafe_driving", "hours_of_service", "crash_indicator")
ALL_BASICS = CRASH_PREDICTIVE + (
"driver_fitness", "controlled_substance",
"vehicle_maintence", "hazardous_materials",
)
def get_profile(dot_number: str) -> dict:
resp = requests.get(
f"{BASE}/profile",
params={"dot_number": dot_number},
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=15,
)
if resp.status_code == 429:
raise RuntimeError(
f"Rate limited — retry after {resp.headers.get('Retry-After')}s")
resp.raise_for_status()
items = resp.json().get("items", [])
if not items:
raise LookupError(f"No carrier found for DOT {dot_number}")
return items[0]
def assess(p: dict) -> tuple[str, list[str]]:
reasons = []
# MC authority and FMCSA insurance filings are interstate for-hire
# requirements. Intrastate carriers are regulated by their state —
# for them the key registration check is an active USDOT.
interstate_for_hire = (
p.get("carrier_operation_desc") == "interstate"
and p.get("operation_class_authorized_for_hire") is True
)
# ── Tier 1: disqualifiers ──────────────────────────────────────────
if p.get("usdot_status") != "Active":
reasons.append("USDOT registration not active "
f"(usdot_status={p.get('usdot_status')})")
if p.get("out_of_service_flag"):
reasons.append("Active out-of-service order")
if interstate_for_hire and p.get("indicator_authority") is not True:
reasons.append("Interstate for-hire without active operating "
f"authority (common={p.get('authority_common')}, "
f"contract={p.get('authority_contract')})")
elif p.get("indicator_authority") is False:
reasons.append("Operating authority explicitly inactive or revoked")
if p.get("indicator_insurance") is False:
# Only ever false where FMCSA requires a filing (null otherwise),
# so this check self-gates for intrastate/private carriers.
reasons.append("Insurance on file below FMCSA requirement "
f"(BIPD on file: {p.get('insurance_bipd_on_file', '0')}, "
f"required: {p.get('insurance_bipd_required', '?')})")
if reasons:
return "DECLINE / REFER", reasons
# ── Tier 2: high-risk signals ──────────────────────────────────────
if p.get("risk_score") == "High":
reasons.append(f"CarrierOk composite risk score: High "
f"(p={p.get('risk_score_probability')})")
if p.get("iss_recommendation") == "INSPECT":
reasons.append(f"ISS recommendation: INSPECT "
f"(ISS {p.get('iss_value')})")
if p.get("safety_rating_desc") in ("Conditional", "Unsatisfactory"):
reasons.append(f"FMCSA safety rating: {p['safety_rating_desc']}")
crash_alerts = [b for b in CRASH_PREDICTIVE
if p.get(f"basic_alert_{b}")]
all_alerts = [b for b in ALL_BASICS if p.get(f"basic_alert_{b}")]
if crash_alerts:
reasons.append("Alert in crash-predictive BASIC(s): "
+ ", ".join(crash_alerts))
if len(all_alerts) >= 2:
reasons.append(f"{len(all_alerts)} BASIC alerts: "
+ ", ".join(all_alerts))
if reasons:
return "HIGH RISK", reasons
# ── Tier 3: enhanced review (above standard, below high-risk) ─────
if p.get("risk_score") == "Medium":
reasons.append("CarrierOk composite risk score: Medium")
if len(all_alerts) == 1:
reasons.append(f"Single BASIC alert: {all_alerts[0]}")
if int(p.get("dot_age") or "0") < 540:
reasons.append("New entrant (DOT registered < 540 days)")
if reasons:
return "ENHANCED REVIEW", reasons
# ── Tier 4: LOW RISK requires positive evidence; the unremarkable
# carrier (risk_score null/absent, no signals) is STANDARD REVIEW.
if p.get("risk_score") == "Low":
return "LOW RISK", ["Composite risk score: Low",
"No BASIC alerts, registration and filings clear"]
return "STANDARD REVIEW", [
f"Risk score: {p.get('risk_score', 'not computed')}",
"No adverse signals — apply your standard workflow"]
if __name__ == "__main__":
profile = get_profile(sys.argv[1])
verdict, reasons = assess(profile)
print(f"{profile.get('legal_name')} (DOT {profile.get('dot_number')})")
print(f"Verdict: {verdict}")
for r in reasons:
print(f" - {r}")Run it:
$ python risk_check.py 1234567
EXAMPLE TRANSPORT LLC (DOT 1234567)
Verdict: HIGH RISK
- Alert in crash-predictive BASIC(s): hours_of_serviceBatch screening
Retry-After on 429s (see rate limits). For full-book backfills and bulk files, contact support@carrierok.com.Keep the assessment current
A point-in-time check goes stale the day an insurance filing lapses or an authority is revoked. Underwriting teams typically run the risk check at quote and at bind, then move the policy onto the Monitoring API for the policy term — it watches your carrier list and surfaces authority, insurance, safety-rating, and BASIC-alert changes as they land. The monitoring guide builds that pipeline end to end.
FAQ
How is this different from FMCSA's free QCMobile API or SAFER?
QCMobile and SAFER return registration and basic safety data, but you'd still need SMS for BASIC measures, L&I for insurance filings, and the Socrata datasets for history — four integrations, four data models. CarrierOk reconciles all of them (plus MOTUS and 90+ other sources) into one profile updated four times daily.
Do you include the Crash Indicator and Hazmat Compliance BASICs?
Yes. FMCSA doesn't display these two percentiles publicly, but the underlying inspection and crash data is public. CarrierOk computes both in-house, so the API returns all seven BASICs.
How fresh is the data?
Core datasets update four times daily. Insurance filings, authority changes, and out-of-service orders typically appear within hours of FMCSA posting them.
Does this risk check work for intrastate carriers?
Yes, with the gating shown in Step 2. Intrastate carriers are state-regulated and typically have no MC authority or FMCSA insurance filing — the API returns null (not false) for indicator_authority and indicator_insurance in that case. The registration check that applies to every carrier is an active USDOT (usdot_status), plus the out-of-service flag. CarrierOk's composite risk_score is also null for intrastate carriers.
What is risk_score?
A composite computed by CarrierOk across 50+ factors — BASIC performance, inspection history, authority and insurance history, fleet characteristics, and fraud signals — returned as Low / Medium / High with an accompanying probability. It's designed as a triage signal, not a replacement for your underwriting model; the underlying fields are all in the response if you'd rather score it yourself.