API v1

TMS Integration API

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.

API Key Auth

Bearer token authentication scoped to your organization.

Idempotent & Auditable

Retry-safe with idempotency keys. Every screen gets a tamper-evident evidence hash.

Webhooks

HMAC-signed event delivery for screen completions, high-risk alerts, and more.

60 req/min

Rate limited per key. Upgrade available for high-volume integrations.

Evidence Export

Litigation-grade evidence bundles with policy snapshots and SHA-256 integrity hashes.

Async Mode

Fire-and-poll pattern for TMS integrations that prefer async workflows.

Base URL

https://dotscreener.com/api/v1

Authentication

All 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.

Scopes

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.

ScopeGrants access to
screen:readGET /screen, GET /screen/:id, GET /screen/:id/evidence
screen:writePOST /screen
webhook:readGET /webhooks
webhook:writePOST /webhooks, PATCH /webhooks/:id, DELETE /webhooks/:id

Request IDs & Error Format

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.

Screen a Carrier

POST/screenScope: screen:write

Submit 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
}
FieldTypeRequiredDescription
mc_numberstringone of mc/dotMotor Carrier number
dot_numberstringone of mc/dotUSDOT number
equipment_typestringyesdry_van, reefer, flatbed, step_deck, hotshot, rgn_lowboy, auto_carrier, power_only, box_truck, other
origin_statestringno2-letter state code
destination_statestringno2-letter state code
pickup_datestringnoISO date (defaults to today)
load_idstringnoYour internal load reference
screening_typestringnocarrier (default) or broker
hazmatbooleannoHazmat load flag
oversize_overweightbooleannoOversize/overweight flag
heavy_haulbooleannoHeavy haul flag
team_expeditedbooleannoTeam/expedited flag
specialized_securementbooleannoSpecialized securement flag
operational_notesstringnoFree-text notes for audit trail
load_value_usdnumbernoLoad 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.

Risk-Adaptive Attestation

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.

ClassificationRequirementMeaning
Standard Review (low)not_requiredNo carrier attestation required under policy.
Additional Verification (moderate)recommendedRecommended; may be skipped with a documented justification.
Enhanced Review (high)requiredRequired before approval unless a supervisor waives it.
Disqualifying Condition (hard fail)blockedAttestation 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"
}

Idempotency

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.

Async Screening Mode

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"

Get a Screen

GET/screen/:idScope: screen:read

Retrieve 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"

Evidence Export

GET/screen/:id/evidenceScope: screen:read

The 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": [ ... ]
}

List Screens

GET/screenScope: screen:read

List all screens for your organization with optional filtering and pagination.

Query paramDescription
pagePage number (default: 1)
per_pageResults per page, 1-100 (default: 25)
mc_numberFilter by MC number
dot_numberFilter by DOT number
risk_levelFilter by LOW, MODERATE, or HIGH
statusFilter by pending, approved, or rejected
sinceISO timestamp — screens created on or after
untilISO 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
}

Evidence Export

GET/screen/:id/evidenceScope: screen:read

Retrieve 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.

Webhooks

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

POST/webhooksScope: webhook:write
curl -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

EventFired when
screen.completedA screen finishes with LOW risk (auto-approved)
screen.high_riskA screen scores HIGH risk
screen.pending_reviewA screen requires manual review (MODERATE or HIGH)
screen.failedA screening attempt fails (FMCSA error, etc.)
carrier.attestation.completedA carrier completes the attestation flow

Other webhook endpoints

MethodEndpointDescription
GET/webhooksList all endpoints (secrets masked)
PATCH/webhooks/:idUpdate URL, events, or enabled state
DELETE/webhooks/:idRemove 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");
}

Health Check

GET/status

Verify 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"
}

Error Codes

StatusMeaning
400Bad request — invalid or missing fields
401Unauthorized — missing, malformed, or revoked API key
403Forbidden — API key lacks the required scope
404Not found — no FMCSA record or screen with that ID
409Conflict — idempotency key reused with different body
429Rate limit exceeded — max 60 requests per minute
500Internal server error
502FMCSA lookup failed — upstream service unavailable

Ready to integrate?

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