Portal Pilot Desk sign in (team leads)

API

Send portal work in, get results out. The raw spec is at /api/openapi.json. Sender contracts and sample scripts are in docs/INTEGRATIONS.md in the repo.

Authentication

Each sender gets an API key (pp_live_...), stored by Pilot only as a hash. Send it as Authorization: Bearer pp_live_....

Endpoints

MethodPathWhat it does
POST/api/tasksQueue one task or a batch
Send one task object, or {"tasks": [...]} for up to 100. Returns 201 for new tasks, 200 when the idempotency key was seen before (the first task is returned and nothing new is queued), 207 for a batch with some rows rejected, 422 for an invalid task.
GET/api/tasksList your tasks
GET/api/tasks/{id}Get one task
GET/api/playbooksPortals Pilot can work on
POST/hooks/inbound/{sender_id}Inbound webhook (HMAC signed)
Same body as POST /api/tasks. Headers: X-Pilot-Timestamp (unix seconds, within 5 minutes) and X-Pilot-Signature: sha256=hex(HMAC_SHA256(inbound_secret, timestamp + "." + raw_body)).
POST/mcpMCP endpoint (Streamable HTTP, JSON responses)
JSON-RPC 2.0. Tools: run_portal_task, get_task_status, list_playbooks. Bearer API key.

Task types

task_typeNeedsWhat Pilot does
pull_podproPull the signed POD from the TMS and return it in the evidence pack.
pod_to_disputepro, and invoice_number or dispute_idPull the POD and attach it to the open dispute on the invoice, or file a dispute with it if none is open and the POD supports one.
file_disputeinvoice_number (reason_code, amount optional)File a dispute on a short-paid invoice with the right reason and amount, POD attached. Stops if the facts do not support a dispute.
answer_claimclaim_id or invoice_numberAnswer a claim the shipper raised: accept, partly accept or reject, based on the POD.
tms_correctionpro, reason_code, changes, noteKey a freight bill correction in the TMS. Over $250 net change waits for a person's approval.
portal_actionaction and its targetRestricted actions (void, write off, cancel, mass rerate, accept all charges, withdraw, pay now, close account). Always stopped by the never-click guard and escalated.

Task fields

FieldTypeMeaning
task_typestringWhat to do. See the task types table.
instructionstringThe request in plain English. Kept with the task and shown to approvers.
paramsobjectInputs for the task type: pro, invoice_number, dispute_id, claim_id, chargeback_id, reason_code, amount, note, comment, action, changes
client_refstringYour reference. Echoed in results.
idempotency_keystringSend the same key again and you get the first task back; the work is never done twice. If you leave it out, Pilot derives one from the task content.
callback_urlstringhttps URL for signed result webhooks. Defaults to the sender's registered URL.
prioritystring
requested_byobject

Example

curl -X POST https://pilot.ankitu.com/api/tasks \
  -H "Authorization: Bearer $PILOT_API_KEY" -H "Content-Type: application/json" \
  -d '{
  "task_type": "tms_correction",
  "client_ref": "AUD-2026-00412",
  "idempotency_key": "AUD-2026-00412",
  "instruction": "Billed weight was keyed as 1,600 lbs; the BOL says 600. Please correct.",
  "params": {
    "pro": "4710024532",
    "reason_code": "WC",
    "note": "BILLED WT KEYED IN ERROR - BOL 600 LBS",
    "changes": {
      "billed_weight": 600
    }
  },
  "callback_url": "https://your-system.example/pilot/results"
}'

Results out

Pilot POSTs JSON to your callback_url on task.completed, task.escalated, task.rejected, task.failed and task.approval_needed. Headers: X-Pilot-Event, X-Pilot-Delivery (stable across retries), X-Pilot-Client, X-Pilot-Timestamp, X-Pilot-Signature = sha256=hex(HMAC_SHA256(outbound_secret, timestamp + "." + raw_body)). Answer 2xx; anything else is retried with backoff for about an hour.

Every task also has a result page at /tasks/<id> and an evidence pack at /evidence/<id>/manifest.json: the confirmation number, the POD file, the page text of every step, the guard and checker verdicts, and a plain-English summary.