Screen carriers programmatically from your TMS, load board, or internal dispatch tools. One API call returns a full FMCSA risk assessment — score, tender decision, and line-item breakdown — so your dispatchers never leave their workflow.
Bearer token authentication scoped to your organization.
Retry-safe with idempotency keys. Every screen gets a tamper-evident evidence hash.
HMAC-signed event delivery for screen completions, high-risk alerts, and more.
Rate limited per key. Upgrade available for high-volume integrations.
Litigation-grade evidence bundles with policy snapshots and SHA-256 integrity hashes.
Fire-and-poll pattern for TMS integrations that prefer async workflows.
Base URL
https://dotscreener.com/api/v1All API requests require a Bearer token in the Authorization header. Keys are prefixed with sk_live_ and scoped to your organization.
curl https://dotscreener.com/api/v1/status \
-H "Authorization: Bearer sk_live_your_api_key_here"Contact mason@dotscreener.com to provision API keys for your organization.
Each API key is issued with one or more scopes that control which endpoints it can access. A request to an endpoint that requires a scope the key doesn't have returns 403 insufficient_scope.
| Scope | Grants access to |
|---|---|
| screen:read | GET /screen, GET /screen/:id, GET /screen/:id/evidence |
| screen:write | POST /screen |
| webhook:read | GET /webhooks |
| webhook:write | POST /webhooks, PATCH /webhooks/:id, DELETE /webhooks/:id |
Every API response includes an X-Request-ID header containing a unique UUID. Include this ID in any support request so we can trace the exact execution path.
All errors use a structured JSON format:
{
"error": {
"code": "carrier_not_found",
"message": "No FMCSA record found for that MC/DOT number.",
"request_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
}
}The code field is a stable, machine-readable string. The message is human-readable and may change between versions.
/screenScope: screen:writeSubmit an MC or DOT number to run a full carrier screening. Returns the risk score, tender decision, checklist results, and score breakdown in a single synchronous response.
Request body
{
"mc_number": "123456",
"equipment_type": "dry_van",
"origin_state": "TX",
"destination_state": "CA",
"load_id": "LD-2026-4421",
"hazmat": false,
"load_value_usd": 85000
}| Field | Type | Required | Description |
|---|---|---|---|
| mc_number | string | one of mc/dot | Motor Carrier number |
| dot_number | string | one of mc/dot | USDOT number |
| equipment_type | string | yes | dry_van, reefer, flatbed, step_deck, hotshot, rgn_lowboy, auto_carrier, power_only, box_truck, other |
| origin_state | string | no | 2-letter state code |
| destination_state | string | no | 2-letter state code |
| pickup_date | string | no | ISO date (defaults to today) |
| load_id | string | no | Your internal load reference |
| screening_type | string | no | carrier (default) or broker |
| hazmat | boolean | no | Hazmat load flag |
| oversize_overweight | boolean | no | Oversize/overweight flag |
| heavy_haul | boolean | no | Heavy haul flag |
| team_expedited | boolean | no | Team/expedited flag |
| specialized_securement | boolean | no | Specialized securement flag |
| operational_notes | string | no | Free-text notes for audit trail |
| load_value_usd | number | no | Load value for insurance adequacy check |
Response (201 Created)
{
"id": "a1b2c3d4-...",
"carrier": {
"legal_name": "ACME FREIGHT LLC",
"dba_name": null,
"mc_number": "123456",
"dot_number": "7890123",
"entity_type": "CARRIER",
"operating_status": "AUTHORIZED",
"authority_status": "A"
},
"risk_score": 22,
"risk_level": "LOW",
"tender_decision": "APPROVE",
"tender_rationale": "Carrier meets all configured thresholds...",
"decision": "PASS",
"score_breakdown": [ ... ],
"checklist": [ ... ],
"data_complete": true,
"data_gaps": [],
"verification_sources": [ ... ],
"attestation_requirement": { "status": "not_required", "...": "..." },
"attestation_plan": { "status": "not_required", "modules": [], "required_question_count": 0 },
"attestation_status": "not_required",
"created_at": "2026-05-21T14:30:00.000Z"
}Legacy fields (risk_score, risk_level, tender_decision) are retained for backward compatibility. New integrations should prefer tender_workflow_status, review_classification, and the attestation fields described below.
DOTScreener does not require a carrier attestation on every screen. The requirement is risk-adaptive: the level — and the specific verification questions — scale with the objective indicators observed at screen time. The response carries an attestation_requirement and a targeted attestation_plan.
| Classification | Requirement | Meaning |
|---|---|---|
| Standard Review (low) | not_required | No carrier attestation required under policy. |
| Additional Verification (moderate) | recommended | Recommended; may be skipped with a documented justification. |
| Enhanced Review (high) | required | Required before approval unless a supervisor waives it. |
| Disqualifying Condition (hard fail) | blocked | Attestation cannot cure it; the recommendation remains Do Not Tender. |
The attestation_plan assembles targeted modules from the screen's triggers. For example: elevated driver out-of-service indicators add the Driver Qualification and Hours-of-Service modules; elevated vehicle out-of-service indicators add Vehicle Maintenance; a hazmat load adds Hazmat; a conditional safety rating adds the Conditional Safety Rating module.
// Example: a high-classification screen with elevated driver OOS,
// elevated vehicle OOS, a hazmat load, and a conditional safety rating.
{
"review_classification": "Enhanced Review",
"tender_workflow_status": "POLICY_EXCEPTION_REQUIRED",
"verification_level": "enhanced",
"policy_exception_required": true,
"disqualifying_conditions": [],
"public_data_gaps": [],
"attestation_requirement": {
"status": "required",
"reason": "Carrier attestation was required because elevated risk indicators were present. ...",
"triggers": [
{ "code": "driver_oos_high", "label": "Driver out-of-service rate 2.6x national average", "severity": "critical", "source_rule": "Driver Out-of-Service Rate" },
{ "code": "vehicle_oos_high", "label": "Vehicle out-of-service rate 2.5x national average", "severity": "critical", "source_rule": "Vehicle Out-of-Service Rate" },
{ "code": "conditional_safety_rating", "label": "FMCSA Conditional safety rating", "severity": "critical" },
{ "code": "hazmat", "label": "Hazmat load", "severity": "warning" }
],
"can_skip": true,
"skip_requires_justification": true,
"skip_requires_supervisor": true
},
"attestation_plan": {
"status": "required",
"required_question_count": 26,
"modules": [
{ "id": "base", "title": "Base Carrier Representations", "questions": [ ... ] },
{ "id": "driver_qualification", "title": "Driver Qualification", "trigger_codes": ["driver_oos_high", "..."], "questions": [ ... ] },
{ "id": "hours_of_service", "title": "Hours of Service", "questions": [ ... ] },
{ "id": "vehicle_maintenance", "title": "Vehicle Maintenance", "questions": [ ... ] },
{ "id": "conditional_safety_rating", "title": "Conditional Safety Rating", "questions": [ ... ] },
{ "id": "hazmat", "title": "Hazmat", "questions": [ ... ] }
]
},
"attestation_status": "pending"
}POST /screen supports idempotent requests. Send an Idempotency-Key header with a unique value (e.g. a UUID). If the same key + org combination is seen again within 24 hours with the same request body, the original response is returned without re-executing the screen.
curl -X POST https://dotscreener.com/api/v1/screen \
-H "Authorization: Bearer sk_live_your_api_key_here" \
-H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-H "Content-Type: application/json" \
-d '{"mc_number": "123456", "equipment_type": "dry_van"}'If the same key is reused with a different request body, the API returns 409 idempotency_conflict. Keys expire automatically after 24 hours.
For TMS integrations that prefer a fire-and-poll pattern, set "async": true in the POST /screen request body. The API returns 202 Accepted immediately with a poll URL:
// Request with async: true
curl -X POST https://dotscreener.com/api/v1/screen \
-H "Authorization: Bearer sk_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"mc_number": "123456", "equipment_type": "reefer", "async": true}'
// Response (202 Accepted)
{
"id": "a1b2c3d4-...",
"status": "processing",
"poll_url": "/api/v1/screen/a1b2c3d4-..."
}
// Poll for results
curl https://dotscreener.com/api/v1/screen/a1b2c3d4-... \
-H "Authorization: Bearer sk_live_your_api_key_here"/screen/:idScope: screen:readRetrieve a previously submitted screen by its UUID. Returns the same structure as the POST response with current status.
curl https://dotscreener.com/api/v1/screen/a1b2c3d4-... \
-H "Authorization: Bearer sk_live_your_api_key_here"/screen/:id/evidenceScope: screen:readThe machine-readable companion to the Tender Defense Packet. Returns the assessment, the risk-adaptive attestation_requirement and attestation_plan, the carrier's per-question targeted responses, the documented policy exception (if any), and the tamper-evident audit chain.
curl https://dotscreener.com/api/v1/screen/a1b2c3d4-.../evidence \
-H "Authorization: Bearer sk_live_your_api_key_here"
# Response (abridged)
{
"id": "a1b2c3d4-...",
"review_classification": "Enhanced Review",
"tender_workflow_status": "POLICY_EXCEPTION_REQUIRED",
"attestation_requirement": { "status": "required", "...": "..." },
"attestation_plan": { "modules": [ ... ], "required_question_count": 26 },
"attestation_status": "completed",
"attestation_responses": [
{ "module_id": "driver_qualification", "question_id": "dq_valid_cdl", "response": true, "signer_name": "..." }
],
"policy_exception": null,
"audit_log": [ ... ]
}/screenScope: screen:readList all screens for your organization with optional filtering and pagination.
| Query param | Description |
|---|---|
| page | Page number (default: 1) |
| per_page | Results per page, 1-100 (default: 25) |
| mc_number | Filter by MC number |
| dot_number | Filter by DOT number |
| risk_level | Filter by LOW, MODERATE, or HIGH |
| status | Filter by pending, approved, or rejected |
| since | ISO timestamp — screens created on or after |
| until | ISO timestamp — screens created on or before |
curl "https://dotscreener.com/api/v1/screen?risk_level=HIGH&per_page=10" \
-H "Authorization: Bearer sk_live_your_api_key_here"
# Response
{
"screens": [ ... ],
"total": 47,
"page": 1,
"per_page": 10
}/screen/:id/evidenceScope: screen:readRetrieve a litigation-grade evidence bundle for a completed screen. Contains the full FMCSA snapshot, policy in effect at screening time, risk scoring details, and a SHA-256 integrity hash that proves the record has not been tampered with.
curl https://dotscreener.com/api/v1/screen/a1b2c3d4-.../evidence \
-H "Authorization: Bearer sk_live_your_api_key_here"
# Response
{
"screen_id": "a1b2c3d4-...",
"org_id": "...",
"api_key_id": "...",
"request_payload": { ... },
"load_context": { ... },
"fmcsa_snapshot": { ... },
"policy_snapshot": { ... },
"policy_version_hash": "sha256...",
"scoring_engine_version": "1.0.0",
"risk_assessment": {
"risk_score": 22,
"risk_level": "LOW",
"decision": "PASS",
"tender_decision": "APPROVE",
"tender_rationale": "..."
},
"checklist": [ ... ],
"data_gaps": [],
"human_approval": {
"status": "approved",
"approved_by": "API",
"approved_at": "2026-05-21T14:30:00.000Z",
"notes": "Auto-approved — low risk"
},
"evidence_hash": "c3ab8ff13720e8ad..."
}The evidence_hash is computed over the canonical (key-sorted) JSON of the entire bundle, excluding the hash field itself. Recompute the SHA-256 to verify integrity at any time.
Register HTTPS endpoints to receive real-time event notifications. All webhook payloads are signed with HMAC-SHA256 using a per-endpoint secret, following the same pattern used by Stripe.
Create an endpoint
/webhooksScope: webhook:writecurl -X POST https://dotscreener.com/api/v1/webhooks \
-H "Authorization: Bearer sk_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-tms.com/webhooks/dotscreener",
"events": ["screen.completed", "screen.high_risk"]
}'
# Response (201) — secret shown ONLY on creation
{
"id": "ep_...",
"url": "https://your-tms.com/webhooks/dotscreener",
"secret": "whsec_a1b2c3...",
"enabled": true,
"events": ["screen.completed", "screen.high_risk"],
"created_at": "..."
}Available events
| Event | Fired when |
|---|---|
| screen.completed | A screen finishes with LOW risk (auto-approved) |
| screen.high_risk | A screen scores HIGH risk |
| screen.pending_review | A screen requires manual review (MODERATE or HIGH) |
| screen.failed | A screening attempt fails (FMCSA error, etc.) |
| carrier.attestation.completed | A carrier completes the attestation flow |
Other webhook endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /webhooks | List all endpoints (secrets masked) |
| PATCH | /webhooks/:id | Update URL, events, or enabled state |
| DELETE | /webhooks/:id | Remove an endpoint (cascades deliveries) |
Verifying signatures (Node.js)
import crypto from "crypto";
function verifyWebhook(payload, secret, signatureHeader) {
const parts = Object.fromEntries(
signatureHeader.split(",").map(p => {
const [k, ...v] = p.split("=");
return [k, v.join("=")];
})
);
const timestamp = parts.t;
const expected = crypto
.createHmac("sha256", secret)
.update(timestamp + "." + JSON.stringify(payload))
.digest("hex");
return expected === parts.v1;
}
// In your webhook handler:
const sig = req.headers["dotscreener-signature"];
if (!verifyWebhook(req.body, process.env.WEBHOOK_SECRET, sig)) {
return res.status(401).send("Invalid signature");
}/statusVerify your API key is valid and check connectivity. Returns your key's active scopes.
curl https://dotscreener.com/api/v1/status \
-H "Authorization: Bearer sk_live_your_api_key_here"
# Response
{
"status": "ok",
"version": "1",
"scopes": ["screen:read", "screen:write"],
"timestamp": "2026-05-21T14:30:00.000Z"
}| Status | Meaning |
|---|---|
| 400 | Bad request — invalid or missing fields |
| 401 | Unauthorized — missing, malformed, or revoked API key |
| 403 | Forbidden — API key lacks the required scope |
| 404 | Not found — no FMCSA record or screen with that ID |
| 409 | Conflict — idempotency key reused with different body |
| 429 | Rate limit exceeded — max 60 requests per minute |
| 500 | Internal server error |
| 502 | FMCSA lookup failed — upstream service unavailable |
Contact us to provision API keys for your organization. We'll get you set up with test credentials and a dedicated integration channel.
Request API Access