TDPortCarrier Vetting
For developers

Agent API

Pair an AI agent to vet and seal under your account.

Agent API

If you have an AI agent (Claude, ChatGPT, a custom script, anything that can make HTTP calls), you can pair it with your TDPort account so it can vet carriers and seal cryptographic evidence records on your behalf. The output is a URL you click to view the sealed cert in your browser.

There are three ways in, ordered from simplest to most flexible:

  1. One-shot API key (recommended). Generate a tdpk_… key on /account, hand it to any AI, the AI does one HTTP POST and gets back a sealed URL. No client install. The server holds the agent's signing key.
  2. MCP server. Same end result, but with streaming and client-side key custody. Best for Claude Desktop / Cursor / Codex if you already use MCP for other services.
  3. Raw HTTP API (advanced). The three-call protocol the MCP wraps. Use this if you're writing a custom script that wants client-held signing keys and tight control over each step.

Option 1 — One-shot API key (recommended)

Sign in at tdport.io, open Account → Connect an AI agent, give it a name, click Generate API key. Copy the tdpk_… value. That's it — no other setup.

Hand the key to any AI agent (Claude Desktop, Claude Code, ChatGPT, Cursor, Codex, a curl script — anything that can make HTTP calls):

Vet MC-1061559 on tdport.io using this API key: tdpk_…

The agent posts to /api/agent/vet:

POST https://tdport.io/api/agent/vet?mc=1061559
Authorization: Bearer tdpk_…

…and receives:

{
  "ok": true,
  "url": "https://tdport.io/v/c-…#k=…",
  "record_id": "c-…",
  "carrier": {
    "mc_number": "1061559",
    "dot_number": 3331119,
    "legal_name": "LAWLEYS TRUCKING LLC"
  },
  "row_count": 10,
  "vetted_at": "2026-05-19T23:40:26.896Z"
}

The url is the deliverable — it opens a page that renders the full sealed vetting in the user's browser. Server-side: TDPort holds the agent's Ed25519 keypair (per agent, scoped to your account) and signs the seal on the agent's behalf. Revoking the key from /account immediately cuts off that agent.

A bare-curl example:

curl -X POST \
  -H "Authorization: Bearer tdpk_…" \
  "https://tdport.io/api/agent/vet?mc=1061559"

This is the cleanest path for "any AI in the wild." If you want client-side key custody (the user's machine signs, TDPort never holds the private key), use Option 2 or Option 3.


Option 2 — MCP server

The server tarball lives at https://tdport.io/mcp/tdport-mcp-latest.tgz. npx will fetch and run it on demand — no global install. In your MCP client config:

{
  "mcpServers": {
    "tdport": {
      "command": "npx",
      "args": ["-y", "--package=https://tdport.io/mcp/tdport-mcp-latest.tgz", "tdport-mcp"]
    }
  }
}

For Claude Desktop on macOS this file lives at ~/Library/Application Support/Claude/claude_desktop_config.json. For Claude Code, it's the project or user .mcp.json. Restart the MCP client after editing.

The server exposes three tools:

  • tdport_pair — one-time setup. The user signs in at tdport.io, opens Account → Agent APIs → Pair via token, copies the tdpp_… token, and gives it to the agent. The agent calls tdport_pair({ token }) once, and credentials are written to ~/.tdport-agent/default.json.
  • tdport_lookup_carrier — resolves a name or MC/USDOT to a specific carrier. Used to disambiguate before vetting.
  • tdport_vet_and_seal — runs the full FMCSA + USPS vetting, seals the bundle on the TDPort chain, and returns a https://tdport.io/v/<id>#k=<key> URL. The tool's description tells the agent to return that URL as the final answer.

To pin a specific version, swap latest for the version (e.g. tdport-mcp-0.1.0.tgz). Published tarballs are immutable; latest follows the most recent release. The MCP server's source is in the TDPort repo at mcp-server/ and is AGPL-3.0 licensed.

If you want to use a different credential slot (e.g., one identity per project), pass slot: "<name>" in tool calls, or set TDPORT_AGENT_SLOT=<name> in the MCP server's env block.


Option 3 — Raw HTTP API (advanced)

If you are not using MCP, talk to the HTTP API directly. The protocol below is what the MCP server wraps; the two paths are wire-compatible.

Getting credentials

In your account, click Agent APIs, name the agent, hit Generate credentials. The dashboard shows you:

  • producerCode — public handle on the ledger (e.g. TDP-A1B2C3D4)
  • producerId — full Ed25519 pubkey (ed25519:<64-hex>)
  • privateKeyHex — 64 hex chars; the only copy is the one you take
  • apiKey — opaque string starting with tdpk_
  • baseUrlhttps://tdport.io

Hit Copy it all or Download <agent>.json. The credentials are shown exactly once; TDPort never sees the private key.

Authentication

Every API call MUST include:

Authorization: Bearer <apiKey>

A bearer that doesn't resolve to an active agent returns 401. Calls without a bearer fall through to the anonymous tier (no sealing).

Endpoints

GET /api/quick-check?docket=<query>

Resolve a carrier by MC#, USDOT#, 10-digit phone, or name prefix.

GET https://tdport.io/api/quick-check?docket=KMR%20Trucking
Authorization: Bearer tdpk_...

Returns { ok: true, matches: [...] }. If a name resolves to multiple carriers, the agent should pick one (or hand the choice back to the user).

GET /api/vet?mc=<mc> (NDJSON stream)

Stream the full FMCSA + USPS vetting. One JSON event per line:

{"event":"row","row":"identity","status":"ok","data":{...},"source":{...}}
{"event":"row","row":"authority","status":"alert","data":{...},"source":{...}}
...
{"event":"done","mcNumber":"...","vettedAt":"...","dotNumber":...}

Read all row events into an array; the done event marks end-of-stream.

POST /api/v2/seal

Seal a vetting bundle onto the chain. This is the cryptographic step.

POST https://tdport.io/api/v2/seal
Authorization: Bearer tdpk_...
Content-Type: application/json
X-App-Db-Meta: {"type":"carrier-vetting","carrierMc":"<MC>"}

{
  "submission": {
    "record_id": "c-<base36(now)>-<8 hex chars>",
    "record_type": "carrier_vetting",
    "package_hash": "sha256:<hex>",
    "producer_id": "ed25519:<your pubkey hex>",
    "title": "<carrier name and MC>",
    "application_metadata": { "type": "carrier-vetting" }
  },
  "producer_signature": "ed25519:<hex sig of canonicalized submission>",
  "encrypted_blob": "<base64 of (iv || ciphertext)>"
}

The server validates the bearer's producer matches producer_id, forwards to the chain, and returns { record_id, verify_url, ... }.

The seal recipe, step by step

  1. Build a VettingBundle plaintext (JSON):

    {
      "version": 1,
      "vettingId": "v-<16 hex chars>",
      "vettedAt": "<ISO timestamp from /api/vet 'done' event>",
      "broker": { "producerId": "<your producerId>", "displayName": "<your agent name>" },
      "carrier": { "mcNumber": "...", "dotNumber": 12345, "legalName": "..." },
      "load": null,
      "rows": [ /* every row event from /api/vet */ ],
      "tests": [],
      "generatedBy": "tdport-agent/<your agent name>"
    }
    
  2. Canonicalize that bundle using RFC 8785 JSON Canonicalization Scheme (npm: canonicalize). UTF-8 encode the canonical string to get the plaintext bytes.

  3. Generate a random 256-bit AES-GCM key and a random 12-byte IV. Encrypt the plaintext bytes. Concatenate blob = iv || ciphertext.

  4. package_hash = "sha256:" + sha256_hex(blob).

  5. record_id = "c-" + base36(Date.now()) + "-" + 8 random hex chars.

  6. Build the submission object (see endpoint above). Canonicalize it.

  7. Sign the canonical submission bytes with Ed25519 using your privateKeyHex. producer_signature = "ed25519:" + hex(signature).

  8. POST { submission, producer_signature, encrypted_blob: base64(blob) } to /api/v2/seal with the bearer header.

  9. The server returns record_id. The final output the agent should hand back to the user:

    https://tdport.io/v/<record_id>#k=<base64url(aes_key)>
    

    The #k= fragment is the AES decryption key. URL fragments never leave the user's browser — clicking the link opens the verifier, which decrypts client-side and shows the cert. Never log or post the decryption key anywhere except this URL.

Reference implementations

Two live in the TDPort source tree, both wire-compatible with the protocol above:

  • mcp-server/ — the npm-published MCP server (Option 1). Most agents should use this rather than reimplement the protocol. Sources are the spec for tool shape and the contract the agent sees.

  • scripts/agent-example.mts — a single-file CLI that does the same recipe end-to-end:

    pnpm tsx scripts/agent-example.mts pair --token=<one-time>
    pnpm tsx scripts/agent-example.mts vet MC-123456 --name=<slot>
    

Both use @noble/curves for Ed25519, Node's webcrypto for AES-GCM, and the canonicalize package for RFC 8785. Read either as the spec if anything on this page is ambiguous.

What the agent should NOT do

  • Don't summarize the cert as your final output. The user wants the URL so they can view the sealed cert in their browser. The summary is optional context; the URL is the deliverable.
  • Don't skip the seal. The whole point of paired-agent access is the cryptographic evidence record. Bare data from /api/vet is the anonymous tier — your user can already get that without you.
  • Don't make a value judgment. TDPort surfaces FMCSA's own labels. Don't say "looks risky" or "I'd recommend hire." Surface the flags; let the user decide.
  • Don't store the decryption key server-side or in logs. The key lives in the URL fragment only. If you persist anywhere, you've defeated the design.

Errors

Status Meaning
401 API key missing, malformed, or revoked. Stop and report.
403 The body's producer_id doesn't match your API key. Bug in your code.
404 Carrier not found (/api/quick-check) or record not found.
409 Producer key already registered (pairing collision; extremely rare).
429 Rate-limited — back off.
502 ProofChain unreachable. Retry with backoff.

Revoking an agent

The account owner can revoke any paired agent from the Agents panel on /account. Revocation is immediate; the next API call returns 401. Past sealed records remain valid (they were valid at the time signed).