CarrierVet

Thẩm định các hãng vận tải Hoa Kỳ theo MC hoặc USDOT: kết luận PASS/REVIEW/FAIL, tín hiệu rủi ro, lịch sử, biên nhận.

Máy chủ MCP được lưu trữ

npx add-mcp 'https://carriervet.dev/mcp'

Cài vào Claude Code, Codex, Cursor và nhiều công cụ khác

Tài liệu

API reference

Beta: CarrierVet is in active testing. Verify critical decisions against FMCSA SAFER, and report anything that looks wrong to hello@carriervet.dev.

Base URL: https://carriervet.dev. Send Authorization: Bearer cv_live_... on every /v1 route except signup, login, and the Stripe webhook.

POST/v1/signup

Create a free account. Requires accept_terms: true, meaning you accept the Terms of Service; without it the call returns 400 terms_not_accepted. Returns the API key once. The key works for 5 lookups until you click the link emailed to you; GET /v1/verify-email?token=… confirms it and POST /v1/verify-email/resend sends a new link.

curl -s -X POST https://carriervet.dev/v1/signup \
  -H 'content-type: application/json' \
  -d '{"email":"desk@example.com","password":"a-long-password","accept_terms":true}'

POST/v1/login

Mint another key from email and password. The previous key cannot be retrieved; only its hash is stored. An account holds at most 10 active keys; at the cap, login revokes the oldest key a login created.

curl -s -X POST https://carriervet.dev/v1/login \
  -H 'content-type: application/json' \
  -d '{"email":"broker@example.com","password":"local-demo-password"}'

GET/v1/account

Account, plan, webhook signing secret, and key prefixes.

curl -s https://carriervet.dev/v1/account -H "authorization: Bearer $KEY"

POST/v1/keys

Create a named key. The secret is returned once. Maximum 10 active keys.

curl -s -X POST https://carriervet.dev/v1/keys \
  -H "authorization: Bearer $KEY" -H 'content-type: application/json' \
  -d '{"name":"tms-prod"}'

POST/v1/keys/{id}/rotate

Revoke the named key and return a replacement.

curl -s -X POST https://carriervet.dev/v1/keys/key_demo_business/rotate -H "authorization: Bearer $KEY"

DELETE/v1/keys/{id}

Revoke a key. The last active key cannot be revoked.

curl -s -X DELETE https://carriervet.dev/v1/keys/key_demo_business -H "authorization: Bearer $KEY"

GET/v1/carriers/{dot}

Overall verdict, then the full profile. Counts as one lookup, including a not-found response.

curl -s https://carriervet.dev/v1/carriers/53467 -H "authorization: Bearer $KEY"

Example response shape · 200 · application/json (illustrative values from a fictional sample carrier)

{
  "verdict": "PASS",
  "verdict_reasons": [],
  "verdict_basis": ["authority", "insurance", "out_of_service", "operating_status", "safety_rating"],
  "carrier": {
    "dot_number": "9900001",
    "legal_name": "Northline Freight LLC",
    "authority": { "status": "active", "grant_date": "2014-06-01", ... },
    "insurance": { "status": "on_file", ... },
    "safety": { "out_of_service": false, ... },
    ...
  },
  "receipt_id": "rcpt_0123456789abcdef01234567",
  "usage": { "used": 3, "limit": 50, "remaining": 47, "warning": null }
}

GET/v1/carriers/mc/{mc}

Verdict and profile by MC, FF, or MX number. Authority status, and the verdict, use the matched docket's motor-carrier authority; a broker-only or forwarder-only docket FAILs as not_a_carrier.

curl -s https://carriervet.dev/v1/carriers/mc/MC138328 -H "authorization: Bearer $KEY"

GET/v1/carriers/{dot}/risk

Overall verdict, then a score from 0 to 100 plus reason codes. The verdict here also uses the contact check and the risk level. A hard failure is listed in gates and keeps the score at 50 or more, so a carrier that fails a check is never scored low. The x-data-freshness header and snapshot_date show how current the record is. Weights live in the risk config.

curl -s https://carriervet.dev/v1/carriers/53467/risk -H "authorization: Bearer $KEY"

Example response shape · 200 · application/json (illustrative values from a fictional sample carrier)

{
  "verdict": "FAIL",
  "verdict_reasons": [
    { "code": "revoked_authority", "check": "authority", "result": "FAIL",
      "message": "Operating authority is revoked or suspended." },
    { "code": "insurance_missing", "check": "insurance", "result": "FAIL",
      "message": "Required insurance is not on file with FMCSA." },
    { "code": "out_of_service", "check": "out_of_service", "result": "FAIL",
      "message": "Carrier is under an out-of-service order." },
    { "code": "risk_level_severe", "check": "risk_level", "result": "FLAG",
      "message": "Risk level is severe." }
  ],
  "verdict_basis": ["authority", "insurance", "out_of_service", "operating_status", "safety_rating", "contact", "risk_level"],
  "dot_number": "9900003",
  "score": 100,
  "level": "severe",
  "gates": ["insurance_missing", "revoked_authority", "out_of_service"],
  "reasons": [
    { "code": "revoked_authority", "points": 45,
      "message": "Operating authority is revoked or suspended.",
      "details": { "authority_status": "revoked" } },
    { "code": "out_of_service", "points": 40,
      "message": "Carrier is under an out-of-service order.",
      "details": { "oos_status": "out_of_service" } },
    { "code": "insurance_missing", "points": 25,
      "message": "Required insurance is not on file with FMCSA.",
      "details": { "insurance_status": "missing" } }
  ],
  "rules_version": "2026.10.27",
  "snapshot_date": "2026-10-27",
  "receipt_id": "rcpt_0123456789abcdef01234567",
  "meta": { "disclaimer": "Carrier data is derived from records maintained by the Federal Motor Carrier Safety Administration (FMCSA). It is provided as-is, ..." }
}

GET/v1/carriers/mc/{mc}/risk

The same risk response by MC, FF, or MX number. The authority status used is that docket's own, as on the MC profile lookup.

curl -s https://carriervet.dev/v1/carriers/mc/MC138328/risk -H "authorization: Bearer $KEY"

GET/v1/carriers/{dot}/history

Two lists, newest first. changes: differences between CarrierVet's stored copies. fmcsa_history: dated changes from FMCSA's daily authority, insurance, and revocation files and from census rows with a new MCS-150 date. Each row has fmcsa_date (the date FMCSA recorded it; fmcsa_date_basis names the column), observed_at (when CarrierVet saw it), and old_value and new_value. Company officer name changes show both values as withheld. Rows are only added, never edited. Recording started on 2026-10-27, so older changes are not included.

curl -s https://carriervet.dev/v1/carriers/53467/history -H "authorization: Bearer $KEY"

GET/v1/receipts/{id}

Every charged lookup returns receipt_id (also in the x-receipt-id header). This returns the saved copy of that response: time, endpoint, identifier, the body as sent, and its SHA-256. Any API key on the account that made the lookup can read it. It appears a few seconds after the lookup. It records what CarrierVet returned then; it is not a new check. Free.

curl -s https://carriervet.dev/v1/receipts/rcpt_0123456789abcdef01234567 -H "authorization: Bearer $KEY"

POST/v1/carriers/batch

Up to 500 DOT or MC numbers. Pro and Business. Duplicate ids are billed once. Carriers not stored yet are fetched from FMCSA (a few at a time, up to about 25 seconds per call). Each result has status: ok (with verdict, verdict_reasons, carrier, freshness), not_found, or not_checked (FMCSA slow or the time budget ran out; retry it). Only ok and not_found are charged; summary gives the counts.

curl -s -X POST https://carriervet.dev/v1/carriers/batch \
  -H "authorization: Bearer $KEY" -H 'content-type: application/json' \
  -d '{"identifiers":["53467","MC133655"]}'

GET/v1/search?name=

Name search over the indexed snapshot. Minimum 2 characters.

curl -s "https://carriervet.dev/v1/search?name=Werner" -H "authorization: Bearer $KEY"

POST/v1/monitors

Watch a carrier. Free cannot monitor. Starter is email only. Pro and Business can set a webhook.

curl -s -X POST https://carriervet.dev/v1/monitors \
  -H "authorization: Bearer $KEY" -H 'content-type: application/json' \
  -d '{"dot_number":"53467","alert_email":"ops@example.com","webhook_url":"https://example.com/hooks/carriervet"}'

GET/v1/monitors

List monitors for the calling account.

curl -s https://carriervet.dev/v1/monitors -H "authorization: Bearer $KEY"

DELETE/v1/monitors/{dot}

Stop monitoring a USDOT number.

curl -s -X DELETE https://carriervet.dev/v1/monitors/53467 -H "authorization: Bearer $KEY"

GET/v1/export/monitors.csv

Business plan. One row per monitored carrier, plus a final disclaimer column.

curl -s https://carriervet.dev/v1/export/monitors.csv -H "authorization: Bearer $KEY"

GET/v1/usage

Lookups used this UTC month, monitor count, and plan limits.

curl -s https://carriervet.dev/v1/usage -H "authorization: Bearer $KEY"

POST/v1/billing/checkout

Paid plans open after beta. Prices: Starter $49/mo, Pro $149/mo, Business $399/mo; yearly is 10× monthly. Until then this returns 403 paid_signups_paused. The free plan (50 lookups/month, no card) is available now through POST /v1/signup.

curl -s -X POST https://carriervet.dev/v1/billing/checkout \
  -H "authorization: Bearer $KEY" -H 'content-type: application/json' \
  -d '{"plan":"starter","interval":"month"}'

POST/v1/billing/portal

Stripe Customer Portal: switch plan or billing interval (prorated) or cancel at the end of the period. Requires a completed checkout. Checkout returns 409 already_subscribed if the account already has a plan.

curl -s -X POST https://carriervet.dev/v1/billing/portal -H "authorization: Bearer $KEY"

POST/v1/stripe/webhook

Stripe subscription webhook. Rejects requests without a valid Stripe-Signature.

curl -s -X POST https://carriervet.dev/v1/stripe/webhook \
  -H 'content-type: application/json' \
  -d '{"id":"evt_demo","type":"checkout.session.completed"}'

Webhook signatures

Alert posts include X-CarrierVet-Signature: t=<unix>,v1=<hex>. The hex is HMAC-SHA256 of t + "." + rawBody using the account webhook secret from GET /v1/account.

Alert payloads (carrier.changed) include a disclaimer field, alert emails end with the same notice, and CSV exports carry it in a final disclaimer column: informational only, not a guarantee, derived from FMCSA records and provided as-is.

Carrier corrections

Carriers who think a record or flag is wrong can request a review by email. See /corrections.

Verdict

Carrier profile, risk, and batch responses start with verdict: PASS, REVIEW, or FAIL. Existing fields are unchanged.

  • FAIL if any check is FAIL: motor-carrier authority revoked, inactive, or none; a broker-only, freight-forwarder-only, or intrastate-only entity (not_a_carrier, intrastate_only); insurance missing or lapsed; an out-of-service order; an inactive USDOT number or not allowed to operate; or an Unsatisfactory safety rating.
  • REVIEW if any check is FLAG: authority pending or under 90 days old (counted from the start of the current authority period; administrative record corrections do not reset it), insurance status unknown, a Conditional safety rating, or incomplete or old data (data_incomplete: an FMCSA source did not answer for this lookup; data_stale: an older stored copy was served because FMCSA did not answer a refresh in time). On /v1/carriers/{dot}/risk, also a recent or shared phone, email, or address, or a risk level of elevated, high, or severe.
  • PASS otherwise.

verdict_reasons lists {"code","check","result","message"}, FAIL reasons first, and is empty on PASS. Codes match the risk reason codes where one exists, plus no_authority, not_a_carrier, intrastate_only, insurance_unknown, data_incomplete, data_stale, and risk_level_elevated / _high / _severe. verdict_basis lists the checks used: profile and batch lookups use authority, insurance, out_of_service, operating_status, safety_rating (plus data_quality when a source did not answer); the risk endpoint adds contact and risk_level. Profile responses also include checks (PASS/FLAG/FAIL per check). The verdict is informational and not a guarantee.

Entity type and data quality

carrier.entity_type is motor_carrier, broker_only, freight_forwarder_only, intrastate_only, no_carrier_authority, or unknown. authority.status is motor-carrier authority only; broker and freight-forwarder authority are in authority.broker_status and authority.freight_forwarder_status. authority.grant_date starts the current authority period and authority.original_grant_date is the first grant on record.

carrier.data_quality has completeness (complete or partial), missing (checks a failed source would have backed), and the status of each FMCSA source for this copy. A partial copy is rechecked after 10 minutes and is never labeled fresh or live.

Risk reason codes

authority_under_90_days, recent_address_change, recent_phone_change, recent_email_change, multiple_contact_changes, shared_phone, shared_email, shared_address, insurance_lapsed, insurance_missing, revoked_authority, inactive_authority, pending_authority, no_authority, out_of_service, usdot_inactive, not_allowed_to_operate, unsatisfactory_rating, conditional_rating, prior_revocation, undeliverable_mail.

Hard failures (revoked_authority, inactive_authority, no_authority, out_of_service, insurance_lapsed, insurance_missing, usdot_inactive, not_allowed_to_operate, unsatisfactory_rating) are listed in gates and keep the score at 50 or more. Placeholder phone numbers such as 000-000-0000 are ignored for shared-phone checks.

Set RISK_CONFIG_JSON to override points, windows, and shared-contact thresholds. Unknown JSON is ignored and the built-in config is used. With an override, rules_version ends in +custom.<hash>, and shared-contact thresholds are at least 1.

Errors and rate limits

Every error is JSON: {"error":{"code","message","docs_url","retry_after"}}. retry_after (seconds) and the Retry-After header are set on 429, 202, and 503. Wait that long before retrying.

400 invalid_request · 400 terms_not_accepted · 401 unauthorized · 402 quota_exceeded · 403 plan_forbidden · 403 turnstile_failed · 404 not_found · 403 email_unverified (confirm your email to go past 5 lookups) · 409 email_taken · 409 key_limit · 409 already_subscribed (use the billing portal to switch plans) · 429 rate_limited · 202 fetching_from_fmcsa (new carrier being fetched; retry, not charged) · 503 fmcsa_unavailable / fmcsa_throttled (FMCSA down and carrier not cached; not charged) · 403 paid_signups_paused (paid plans open after beta) · 500 internal.

Limits: per-account requests per minute by plan, per-IP limits on signup, login, and public pages, and 3 tokenless API signups per IP per hour. Cached carriers keep working when FMCSA is down; the x-data-freshness header says whether a profile is fresh (stored, under a day old, complete), live (just fetched, complete), live-partial or cached-partial (an FMCSA source did not answer), stale-revalidating, or stale-upstream-down.

AI agents and MCP

Plain-language guides: /llms.txt, /llms-full.txt. OpenAPI: /openapi.json. MCP server (Streamable HTTP): https://carriervet.dev/mcp with Authorization: Bearer <key>.