COOPCHECK
API
Query carrier safety scores, chameleon cluster detection, and FMCSA crash and inspection data programmatically. One endpoint. One key. JSON.
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.
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.
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 / Field | Description |
|---|---|
| monthly_limit | Your key's monthly cap (returned in 429 response body) |
| requests_this_month | Not exposed in responses — check your dashboard or contact us |
GET /v1/carrier
Returns the full safety profile for a single carrier by FMCSA DOT number.
Parameters
| Param | Type | Required | Description |
|---|---|---|---|
| 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 -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
{
"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"
}
Example Response — Grade D from the Lineage Floor
{
"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 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
| Field | Type | Description |
|---|---|---|
| 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:
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. |
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.
| HTTP | error | Cause |
|---|---|---|
| 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. |
{ "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.
Plans
- Full response schema
- Real data, shared key
- No SLA
- Dedicated key
- Priority support
- Monthly usage report
- 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