API reference

Broker Shield API

Vet a carrier from your TMS, receive a signed proof-of-diligence PDF, and attach it to the load. All endpoints are JSON over HTTPS.

Authentication

Every request requires a bearer token. Generate one from API Access in your workspace. Keys are prefixed cv_live_ and are shown only once.

Authorization: Bearer cv_live_xxxxxxxxxxxxxxxxxxxxxxxx

Base URL: https://broker-shield.com/api/public/v1

Process flow

  1. 1
    POST /vettings
    Your TMS submits dot_number + order_number. We respond 202 immediately.
  2. 2
    Background processing
    FMCSA snapshot, recorded carrier call, transcription, risk analysis, PDF generation. Typically 1–2 minutes.
  3. 3
    vetting.completed webhook
    We POST a signed payload to your configured endpoint with a 24h signed pdf_url.
  4. 4
    Attach to load
    Download the PDF and attach to the load record in your TMS.
  5. 5
    GET /vettings/:id/pdf
    Need a fresh signed URL later? Call this endpoint anytime.

Create a vetting

POST/api/public/v1/vettings

Submits a vetting job. Returns 202 Accepted immediately. The PDF arrives via webhook once processing completes.

Idempotency: pass an Idempotency-Key header (defaults to order_number). Re-submitting the same key returns the original record with "duplicate": true.

POST https://broker-shield.com/api/public/v1/vettings
Authorization: Bearer cv_live_...
Content-Type: application/json
Idempotency-Key: LD-48211

{
  "dot_number":         "1234567",
  "order_number":       "LD-48211",
  "external_client_id": "shipper-acme",
  "notes":              "Reefer, 42k lbs, ATL -> DAL"
}

202 Accepted
{
  "vetting_id":   "vt_01HF...",
  "status":       "pending",
  "dot_number":   "1234567",
  "order_number": "LD-48211",
  "created_at":   "2026-05-15T20:14:11Z",
  "message":      "Vetting accepted. Webhook will be delivered once processing completes."
}

Get a vetting

GET/api/public/v1/vettings/:id

Returns the current status and (when ready) a fresh 24h signed pdf_url. Use this if a webhook is delayed or you want to poll for status from a TMS that can't host a webhook.

GET https://broker-shield.com/api/public/v1/vettings/vt_01HF...
Authorization: Bearer cv_live_...

200 OK
{
  "vetting_id":          "vt_01HF...",
  "status":              "completed",
  "dot_number":          "1234567",
  "order_number":        "LD-48211",
  "external_client_id":  "shipper-acme",
  "carrier_name":        "ACME TRUCKING LLC",
  "pdf_status":          "ready",
  "pdf_url":             "https://...supabase.co/.../vt_01HF.pdf?token=...",
  "pdf_url_expires_at":  "2026-05-16T20:14:11Z",
  "last_error":          null,
  "created_at":          "2026-05-15T20:14:11Z",
  "signed_off_at":       "2026-05-15T20:15:48Z"
}

Re-fetch a signed PDF URL

GET/api/public/v1/vettings/:id/pdf

Returns a fresh 24h signed URL for the PDF without re-fetching the rest of the record. Useful when the URL from a webhook delivery has expired.

Add ?redirect=1 to return a 302 redirect straight to the file (handy for browser links or curl -L).

If the PDF is not yet generated, returns 409 with the current pdf_status.

GET https://broker-shield.com/api/public/v1/vettings/vt_01HF.../pdf
Authorization: Bearer cv_live_...

200 OK
{
  "vetting_id":          "vt_01HF...",
  "pdf_url":             "https://...supabase.co/.../vt_01HF.pdf?token=...",
  "pdf_url_expires_at":  "2026-05-16T20:14:11Z"
}

List vettings

GET/api/public/v1/vettings

Lists vettings for your workspace, newest first. Use for reconciliation against your TMS.

Query params: limit (max 100, default 25), status, dot_number, order_number, external_client_id, cursor (ISO timestamp; returns rows older than the cursor).

GET https://broker-shield.com/api/public/v1/vettings?status=completed&limit=50
Authorization: Bearer cv_live_...

200 OK
{
  "items": [
    { "vetting_id": "vt_01HF...", "status": "completed", ... },
    { "vetting_id": "vt_01HE...", "status": "completed", ... }
  ],
  "next_cursor": "2026-05-10T11:02:00Z"
}

Webhooks

Configure an endpoint from API Access. Each endpoint gets a unique signing secret (whsec_...). We retry failed deliveries with exponential backoff.

Event: vetting.completed

POST https://your-tms.example.com/hooks/broker-shield
content-type: application/json
x-vetting-signature: t=1715800451,v1=9a8b...                 # HMAC-SHA256

{
  "event":               "vetting.completed",
  "vetting_id":          "vt_01HF...",
  "tenant_id":           "ten_...",
  "order_number":        "LD-48211",
  "external_client_id":  "shipper-acme",
  "dot_number":          "1234567",
  "pdf_url":             "https://...supabase.co/.../vt_01HF.pdf?token=...",
  "pdf_url_expires_at":  "2026-05-16T20:14:11Z",
  "summary":             { "legal_name": "ACME TRUCKING LLC", "authority": "ACTIVE" },
  "created_at":          "2026-05-15T20:15:48Z"
}

Respond with any 2xx status to acknowledge. Non-2xx (or no response within 10s) triggers a retry.

Verifying webhook signatures

The x-vetting-signature header has the form t=<unix-ts>,v1=<hex>. Compute HMAC_SHA256(secret, "<t>." + raw_body) and compare in constant time. Reject events with a timestamp more than 5 minutes old to prevent replay attacks.

Node / TypeScript

import { createHmac, timingSafeEqual } from "node:crypto";

export function verify(rawBody: string, header: string, secret: string) {
  const parts = Object.fromEntries(header.split(",").map(p => p.split("=")));
  const t = Number(parts.t), v1 = parts.v1;
  if (!t || !v1) return false;
  if (Math.abs(Date.now() / 1000 - t) > 300) return false; // 5 min skew

  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(v1, "hex");
  return a.length === b.length && timingSafeEqual(a, b);
}

Python

import hmac, hashlib, time

def verify(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    t, v1 = int(parts["t"]), parts["v1"]
    if abs(time.time() - t) > 300:
        return False
    mac = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(mac, v1)

Ruby

require "openssl"

def verify(raw_body, header, secret)
  parts = header.split(",").map { |p| p.split("=", 2) }.to_h
  t, v1 = parts["t"].to_i, parts["v1"]
  return false if (Time.now.to_i - t).abs > 300
  mac = OpenSSL::HMAC.hexdigest("SHA256", secret, "#{t}.#{raw_body}")
  Rack::Utils.secure_compare(mac, v1)
end

Errors

StatusMeaning
400Invalid JSON or validation failed (details in body).
401Missing, malformed, or revoked API key.
404Vetting not found (or not in your workspace).
409PDF not ready yet (returned by /vettings/:id/pdf).
500Server error. Safe to retry after a short backoff.

Error bodies are always JSON: { "error": "...", "details"?: {...} }.