REST API · v1

COOPCHECK
API

Query carrier safety scores, chameleon cluster detection, and FMCSA crash and inspection data programmatically. One endpoint. One key. JSON.

Base URL https://coopcheck-api.quiet-wood-44ba.workers.dev

Overview

The CoopCheck API exposes a single read endpoint that returns a carrier's full safety profile — Chameleon Score grade, cluster membership, FMCSA crash and inspection summary, and out-of-service rate — in a single JSON response.

Data is sourced from FMCSA public records and refreshed monthly; operating authority is re-checked daily. The Chameleon Score ranks a carrier against peers with a comparable FMCSA record and reports its worst safety behavior as a percentile — it is not a composite of weighted components. See About & Methodology for the full method, including the grade floors.

Current version: v1. The API is in public beta. Endpoint paths and response fields are stable, but additional fields may be added in future minor versions.

Authentication

Send your API key in an X-API-Key header on every request. Authorization: Bearer {api_key} is accepted too, if that is what your client reaches for.

GET /v1/carrier?dot={dot_number}
HEADER X-API-Key: {api_key}
Keep your key private. Keys are tied to a monthly usage limit. If a key is compromised, contact us to rotate it.

The ?key= parameter is deprecated

Passing the key as a query parameter still works, and we are not going to break your integration without telling you first. But a key in a URL is recorded in server logs, sent onward in Referer headers, kept in browser history, and stored by any proxy in between — none of which you control. Requests that use it come back with a Deprecation header and a Warning saying the same thing. Move it to the header when you get a moment.

Test Key

Use test_coop_dev for sandbox testing. The test key returns real data but has a low monthly limit and is not suitable for production.

Rate Limits

Each key has a monthly request limit that resets on the first of every month (UTC). When you exceed the limit, the API returns 429 rate_limit_exceeded with the limit in the response body.

Header / FieldDescription
monthly_limitYour key's monthly cap (returned in 429 response body)
requests_this_monthNot exposed in responses — check your dashboard or contact us
Tip: Cache responses on your side. Carrier grades and cluster status change monthly — daily re-fetching of the same DOT wastes quota.

GET /v1/carrier

Returns the full safety profile for a single carrier by FMCSA DOT number.

GET https://coopcheck-api.quiet-wood-44ba.workers.dev/v1/carrier?dot={dot_number}

Parameters

ParamTypeRequiredDescription
dot string required FMCSA DOT number (numeric digits only, e.g. 335555)
key string deprecated Your API key as a query parameter. Still honoured, but it puts the key in logs and history — send the X-API-Key header instead. Use test_coop_dev for sandbox testing.

Example Request

curl
curl -H "X-API-Key: test_coop_dev" \
  "https://coopcheck-api.quiet-wood-44ba.workers.dev/v1/carrier?dot=335555"

Example Response — Grade F from the Crash Percentile

json · 200 OK
{
  "dot_number":             "335555",
  "carrier_name":           "ABILENE MOTOR EXPRESS LLC",
  "state":                  "VA",
  "grade":                  "F",          // A · B · C · D · F
  "total_score":            99,         // worst-behavior percentile; F starts at 99
  "score_version":          "2026-08-12",
  "score_calculated_at":    "2026-08-12T20:36:44+00:00",
  "base_grade":             "F",          // grade from the percentile alone
  "grade_floor": {
    "applied":              false,      // base_grade == grade, so nothing was floored
    "type":                 null,
    "reason":               null
  },
  "behaviors": {
    "crash":                { "percentile": 99, "eligible": true },
    "fatality":             { "percentile": 91, "eligible": true },
    "driver_oos":           { "percentile": 44, "eligible": true },
    "vehicle_oos":          { "percentile": 42, "eligible": true },
    "out_of_service":       { "percentile": 44, "eligible": true },  // worse of the two, for older consumers
    "violations":           { "percentile": 19, "eligible": true }
  },
  "worst_behavior":         "crash",    // the behavior that set the grade
  "insufficient_data":      false,
  "cluster_id":             null,       // phone shared by cluster, or null
  "cluster_size":           null,
  "active_cluster_members": null,
  "last_crash_date":        "25-MAR-26",
  "total_crashes":          28,
  "total_fatalities":       1,
  "total_violations":       412,
  "record_state":           "adverse",  // adverse | clean | none | unknown
  "oos_rate":               10.0,       // percent of inspections with an OOS order
  "total_inspections":      491,
  "data_as_of":             "11-MAY-26"
}
Why this is an F: this carrier sits at the 99th percentile on crash rate, which sets the grade. Its other behaviors are unremarkable — 31st percentile on out-of-service, 19th on violations. The score is the worst eligible behavior, not an average, so strong performance elsewhere never offsets it. A carrier with a 10.0% OOS rate and a clean violation record can still be an F.

Example Response — Grade D from the Lineage Floor

json · 200 OK
{
  "dot_number":             "32658",
  "carrier_name":           "INTERSTATE VAN LINES INC",
  "state":                  "VA",
  "grade":                  "D",          // floored — grade_floor says why
  "total_score":            45,         // 45th percentile — on its own this is an A
  "score_version":          "2026-08-12",
  "score_calculated_at":    "2026-08-12T20:36:44+00:00",
  "base_grade":             "A",          // what the percentile alone gives
  "grade_floor": {
    "applied":              true,
    "type":                 "confirmed_lineage",
    "reason":               "Confirmed reincarnation lineage: shared phone and exact street address, same officer, and a predecessor whose federal operating authority was involuntarily revoked before this carrier registered."
  },
  "behaviors": {
    "crash":                { "percentile": null, "eligible": false },
    "fatality":             { "percentile": null, "eligible": false },
    "driver_oos":           { "percentile": 13, "eligible": true },
    "vehicle_oos":          { "percentile": 8, "eligible": true },
    "out_of_service":       { "percentile": 13, "eligible": true },
    "violations":           { "percentile": 45, "eligible": true }
  },
  "worst_behavior":         "viol",
  "insufficient_data":      false,
  "cluster_id":             "(703) 569-2121",
  "cluster_size":           19,
  "active_cluster_members": 19,
  "last_crash_date":        null,
  "total_crashes":          0,
  "total_fatalities":       0,
  "total_violations":       3,
  "record_state":           "adverse",  // violations on file, so not clean
  "oos_rate":               6.7,
  "total_inspections":      15,
  "data_as_of":             "17-JUN-26"
}
Grade and percentile disagree here, deliberately. On its own safety record this carrier is a 45th-percentile A. The D comes from a grade floor: a confirmed reincarnation lineage — shared phone and exact street address, the same officer, and a predecessor whose authority was involuntarily revoked before this carrier registered. Whenever grade is more severe than total_score implies, a floor is the reason.

The payload says so itself: base_grade is what the percentile alone gives, grade_floor.type and grade_floor.reason name the rule that raised it, and behaviors carries every percentile with an eligible flag. Where grade_floor.applied is false, base_grade and grade always agree.

Response Fields

FieldTypeDescription
dot_number string FMCSA-assigned DOT number.
carrier_name string | null Legal name on file with FMCSA.
state string | null Two-letter state code of physical address.
grade string | null Chameleon Score letter grade, from the percentile in total_score. A <55   B 55–83   C 84–93   D 94–98   F ≥99. Floors can make a grade more severe than the percentile alone gives, so grade and total_score will not always agree:
  • any in-window fatal crash → minimum B
  • shared phone and exact street address, in a group of 2–20 carriers → minimum B
  • confirmed reincarnation lineage → minimum D
Only the last requires succession evidence: shared phone and exact address, same officer, and a predecessor involuntarily revoked before this carrier registered. A shared identifier alone never produces a D. Never null: a carrier with nothing rankable grades A by default, which is why insufficient_data must be read alongside it — that A means unmeasured, not safe.
total_score number | null Worst-behavior percentile, 0–100 — “worse than X% of carriers with a comparable FMCSA record”. Each carrier is ranked against rated peers on crash rate, fatality rate, out-of-service rate and violation rate, each normalized for fleet size and recency-weighted; the score is its worst eligible behavior. Higher = worse. Null if no behavior has enough data to rank.
score_version string | null The date the scoring rules that produced this carrier's grade took effect — not a data-refresh date. It changes only when the model changes, so a stored result that differs from a fresh one can be attributed to the carrier or to us, rather than guessed at. Read from the scored row, so it reports the model that actually ran rather than the newest one we publish.
score_calculated_at string | null When this carrier's score was last computed. Pair with score_version: same version and a later timestamp means the carrier's data moved, not the method.
base_grade string | null The grade from total_score alone, before any floor. When grade_floor.applied is false this equals grade.
grade_floor object { applied, type, reason }. type is one of fatal_crash, shared_address_cluster, confirmed_lineage, or null. This is the whole explanation for any gap between total_score and grade.
behaviors object Per-behavior percentiles: crash, fatality, driver_oos, vehicle_oos, violations, each { percentile, eligible }. Driver and vehicle out-of-service are ranked separately — different failures, and one inspection can produce both. out_of_service is still returned, carrying the worse of the two, for consumers written before the split. eligible: false means too little history to rank that behavior — it does not mean the carrier did well on it. The grade comes from the worst eligible one.
worst_behavior string | null Which behavior set the grade: crash, fatal, driver_oos, vehicle_oos or viol. null when no behavior could be ranked — such a carrier has no worst behavior, so there is none to name. Until 2026-08-19 this field returned the string clean in that case, which asserted a safety finding built out of missing data; if you match on that value, treat it as null and read record_state for the actual record. Rows scored before 2026-08-12 may still carry the retired combined value oos.
insufficient_data boolean true when no behavior could be ranked — the carrier falls under every eligibility minimum (2 crashes, 1 fatality, 8 inspections), so all five percentiles are null, total_score is 0 and grade defaults to A. Such a carrier is unmeasured, not safe — see the note below. Corrected 2026-08-19: this returned false for 363,346 carriers that had a crash or violation on record but too little history to rank, because it was computed as “no inspections and no crashes at all” rather than “nothing rankable”. If you cached grades before that date, refetch.

grade can still be non-A while this is true: a lineage, fatal-crash or shared-identifier floor is evidence about the operator, not a percentile, and does not require a rankable behavior. 81,744 carriers are currently in that state. total_score stays 0 and worst_behavior stays null for them, because neither came from a ranking.
cluster_id string | null The shared phone number that identifies the chameleon cluster this carrier belongs to. Null if no cluster detected.
cluster_size number | null Total carriers in the cluster (including inactive). Null if no cluster.
active_cluster_members number | null Active carriers currently in the cluster. Null if no cluster.
last_crash_date string | null Date of most recent FMCSA-reported crash. Format: DD-MON-YY.
total_crashes number | null Total crash incidents on record in FMCSA data. May be null when the score row could not be retrieved — record_state is unknown in that case. A null here means “not retrieved”, never zero: do not treat it as a pass.
total_fatalities number | null Total fatalities across all recorded crashes. May be null when the score row could not be retrieved — record_state is unknown in that case. A null here means “not retrieved”, never zero: do not treat it as a pass.
total_violations integer | null Violations recorded across the carrier's inspections. May be null when the score row could not be retrieved — record_state is unknown in that case. A null here means “not retrieved”, never zero: do not treat it as a pass. Added 2026-08-17. Without it a clean record could not be established from this payload: 130,483 carriers returned zero crashes, zero fatalities and a zero out-of-service rate while holding 365,995 violations between them.
record_state string The derived conclusion, so you do not have to re-derive it: adverse (at least one crash, fatality, violation or out-of-service order), clean (every adverse measure verified zero across real inspections), none (no crash or inspection record at all), or unknown (no score row, so nothing can be concluded). Absent counts give unknown, never clean — missing evidence is not evidence of absence. Note none is not clean: a carrier with no record has not been measured.
oos_rate number | null Out-of-service rate as a percentage (0–100). Null when total_inspections is 0.
total_inspections number | null Total FMCSA roadside inspections on record. May be null when the score row could not be retrieved — record_state is unknown in that case. A null here means “not retrieved”, never zero: do not treat it as a pass.
data_as_of string | null Date of the most recent FMCSA record used. Format: DD-MON-YY.
Insufficient data: Carriers with no crash or inspection history return grade: null and total_score: null. These carriers are not necessarily safe — they may be newly registered or have not yet been inspected.

Error Codes

All errors return JSON with an error field and the appropriate HTTP status code.

HTTPerrorCause
400 missing_dot No ?dot= parameter provided.
400 invalid_dot DOT parameter contains non-numeric characters.
401 missing_key No key provided, in either the X-API-Key header or the deprecated ?key= parameter.
401 invalid_key Key not found in the database.
401 key_inactive Key has been deactivated. Contact us to reactivate.
404 not_found DOT number not found in the CoopCheck database (2M+ carriers indexed).
429 rate_limit_exceeded Monthly request limit reached. Response includes monthly_limit. Resets 1st of next month.
500 auth_error Internal error validating the key. Retry once; if it persists, contact support.
json · 429 Rate Limit Exceeded
{ "error": "rate_limit_exceeded", "monthly_limit": 500 }

Live Sandbox

Query the live API directly from your browser. Uses test_coop_dev by default — swap in your own key to test rate-limit behavior.

Try It Live API
DOT Number
API Key

Plans

Dev
$0 · test key
500
requests / month
  • Full response schema
  • Real data, shared key
  • No SLA
Enterprise
Contact for pricing
Unlimited
requests / month
  • Bulk DOT screening
  • Webhook alerts
  • Custom data exports

Get a Key

Keys are issued instantly. Freight brokers, insurers, safety software vendors, and researchers welcome. Questions? contact@coopcheck.org