{
  "openapi": "3.1.0",
  "info": {
    "title": "Portal Pilot API",
    "version": "0.1.0",
    "description": "Send portal work to Portal Pilot and get results back. Every channel (this API, CSV, signed webhook, email, MCP) creates the same task."
  },
  "servers": [
    {
      "url": "https://pilot.ankitu.com"
    }
  ],
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Your client API key, pp_live_..."
      }
    },
    "schemas": {
      "TaskInput": {
        "type": "object",
        "required": [
          "task_type",
          "params"
        ],
        "properties": {
          "task_type": {
            "type": "string",
            "enum": [
              "pull_pod",
              "pod_to_dispute",
              "file_dispute",
              "answer_claim",
              "tms_correction",
              "portal_action"
            ],
            "description": "What to do. See the task types table."
          },
          "instruction": {
            "type": "string",
            "description": "The request in plain English. Kept with the task and shown to approvers."
          },
          "params": {
            "type": "object",
            "properties": {
              "pro": {
                "type": "string",
                "pattern": "^\\d{10}$",
                "description": "Carrier pro (freight bill) number"
              },
              "invoice_number": {
                "type": "string",
                "pattern": "^\\d{10}$",
                "description": "Invoice number on the shipper's audit portal"
              },
              "dispute_id": {
                "type": "string",
                "example": "DSP-26-04101"
              },
              "claim_id": {
                "type": "string",
                "example": "SD-2026-0201"
              },
              "chargeback_id": {
                "type": "string",
                "example": "CB-2026-031"
              },
              "reason_code": {
                "type": "string",
                "description": "TMS correction reason (WC, CC, AC, DA, CN, RR) or dispute reason (ACC, SHT, DMG, CLS, WGT, RATE, DUP, OTH)"
              },
              "amount": {
                "type": "string",
                "example": "92.00"
              },
              "note": {
                "type": "string",
                "description": "Approval note keyed with a TMS correction"
              },
              "comment": {
                "type": "string"
              },
              "action": {
                "type": "string",
                "enum": [
                  "void_bill",
                  "cancel_shipment",
                  "write_off_balance",
                  "mass_rerate",
                  "accept_all_charges",
                  "withdraw_dispute",
                  "pay_chargeback",
                  "close_account"
                ],
                "description": "For portal_action. Every one of these is on a never-click list, so the task is escalated to a person."
              },
              "changes": {
                "type": "object",
                "properties": {
                  "billed_weight": {
                    "type": "integer"
                  },
                  "billed_class": {
                    "type": "string"
                  },
                  "remove_accessorials": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "add_accessorials": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "consignee_name": {
                    "type": "string"
                  },
                  "consignee_address": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "client_ref": {
            "type": "string",
            "description": "Your reference. Echoed in results."
          },
          "idempotency_key": {
            "type": "string",
            "description": "Send 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_url": {
            "type": "string",
            "format": "uri",
            "description": "https URL for signed result webhooks. Defaults to the sender's registered URL."
          },
          "priority": {
            "type": "string",
            "enum": [
              "low",
              "normal",
              "high"
            ]
          },
          "requested_by": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "system": {
                "type": "string"
              },
              "email": {
                "type": "string"
              }
            }
          }
        }
      },
      "TaskBatch": {
        "type": "object",
        "required": [
          "tasks"
        ],
        "properties": {
          "tasks": {
            "type": "array",
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/TaskInput"
            }
          }
        }
      }
    }
  },
  "security": [
    {
      "apiKey": []
    }
  ],
  "paths": {
    "/api/tasks": {
      "post": {
        "summary": "Queue one task or a batch",
        "description": "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.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/TaskInput"
                  },
                  {
                    "$ref": "#/components/schemas/TaskBatch"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Duplicate of an earlier task"
          },
          "201": {
            "description": "Queued"
          },
          "207": {
            "description": "Batch with some rows rejected"
          },
          "401": {
            "description": "No or bad API key"
          },
          "422": {
            "description": "Invalid task"
          }
        }
      },
      "get": {
        "summary": "List your tasks",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "maximum": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tasks, newest first"
          }
        }
      }
    },
    "/api/tasks/{id}": {
      "get": {
        "summary": "Get one task",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Status, outcome, summary, approvals and evidence links"
          },
          "404": {
            "description": "Not yours or not found"
          }
        }
      }
    },
    "/api/playbooks": {
      "get": {
        "summary": "Portals Pilot can work on",
        "security": [],
        "responses": {
          "200": {
            "description": "Playbook summaries: capabilities, never-click list, approval limits"
          }
        }
      }
    },
    "/hooks/inbound/{sender_id}": {
      "post": {
        "summary": "Inbound webhook (HMAC signed)",
        "security": [],
        "description": "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)).",
        "parameters": [
          {
            "name": "sender_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Queued"
          },
          "401": {
            "description": "Bad or stale signature"
          }
        }
      }
    },
    "/mcp": {
      "post": {
        "summary": "MCP endpoint (Streamable HTTP, JSON responses)",
        "description": "JSON-RPC 2.0. Tools: run_portal_task, get_task_status, list_playbooks. Bearer API key.",
        "responses": {
          "200": {
            "description": "JSON-RPC response"
          },
          "202": {
            "description": "Notification accepted"
          }
        }
      }
    }
  },
  "x-webhooks-out": {
    "description": "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."
  }
}