{
  "openapi": "3.1.0",
  "info": {
    "title": "Pilot5 Deliberation API",
    "version": "1.0.0",
    "description": "Submit a question and get a deliberated answer from a 5-persona AI panel via webhook callback. This spec covers the partner-facing programmatic surface (API-key auth). Full reference: https://pilot5.ai/docs/api",
    "license": { "name": "Proprietary", "url": "https://pilot5.ai/terms" },
    "contact": {
      "name": "Pilot5 Support",
      "email": "support@pilot5.ai",
      "url": "https://pilot5.ai/docs/api"
    }
  },
  "servers": [
    { "url": "https://api.pilot5.ai", "description": "Production (pk_live_ keys)" },
    { "url": "https://staging-api.pilot5.ai", "description": "Staging (pk_test_ keys)" }
  ],
  "security": [{ "ApiKeyAuth": [] }],
  "tags": [{ "name": "Deliberations", "description": "Create and fetch deliberations." }],
  "paths": {
    "/v1/api/deliberations": {
      "post": {
        "tags": ["Deliberations"],
        "operationId": "createDeliberation",
        "summary": "Create a deliberation",
        "description": "Submits a question for deliberation. Returns 202 immediately; the result is delivered to `webhook_url` when the run reaches a terminal state, and can also be polled. Send an `Idempotency-Key` header to make retries safe.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Any string ≤255 chars. Replaying the same key + body within 24h returns the original deliberation_id instead of creating a duplicate.",
            "schema": { "type": "string", "maxLength": 255 }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/DeliberationRequest" }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted — deliberation queued.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/AcceptedResponse" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": {
            "description": "Insufficient credits.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
          },
          "403": {
            "description": "Content blocked by safety guardrails (no charge).",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
          },
          "409": {
            "description": "Idempotency-Key reused with a different body, or a request with this key is still in progress.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
          },
          "422": {
            "description": "Validation error (schema/length/enum).",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
          },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/v1/api/deliberations/{deliberation_id}": {
      "get": {
        "tags": ["Deliberations"],
        "operationId": "getDeliberation",
        "summary": "Get deliberation status",
        "description": "Status-only while running; full payload on terminal states (completed/failed). Poll at most every 10s — webhook delivery is faster and cheaper.",
        "parameters": [{ "$ref": "#/components/parameters/DeliberationId" }],
        "responses": {
          "200": {
            "description": "Current state. Shallow while running, full payload when terminal.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    { "$ref": "#/components/schemas/DeliberationStatus" },
                    { "$ref": "#/components/schemas/DeliberationResult" }
                  ]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/v1/api/deliberations/{deliberation_id}/result": {
      "get": {
        "tags": ["Deliberations"],
        "operationId": "getDeliberationResult",
        "summary": "Get deliberation result",
        "description": "Returns the full result on terminal states (200). While still running, returns 202 with a shallow status — keep polling or wait for the webhook.",
        "parameters": [{ "$ref": "#/components/parameters/DeliberationId" }],
        "responses": {
          "200": {
            "description": "Terminal — full result (completed or failed).",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/DeliberationResult" } }
            }
          },
          "202": {
            "description": "Still running — shallow status.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": { "$ref": "#/components/schemas/PublicStatus" },
                    "deliberation_id": { "type": "string", "format": "uuid" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    }
  },
  "webhooks": {
    "deliberation.completed": {
      "post": {
        "operationId": "onDeliberationCompleted",
        "summary": "Deliberation completed callback",
        "description": "Pilot5 POSTs this to the `webhook_url` you supplied on the deliberation when the run completes. **The body is a pointer — it does NOT contain the report.** To get the report, make a second call: `GET data.result_url` with `Authorization: Bearer pk_…`, then read the `synthesis` field. Acknowledge with a 2xx within 10s (do the fetch after acknowledging); non-2xx responses are retried (+1m, +5m, +30m, +2h, +12h).",
        "parameters": [
          { "name": "X-Pilot5-Signature", "in": "header", "required": true, "schema": { "type": "string" }, "description": "`t={unix_ts},v1={hex}` — HMAC-SHA256 over `{t}.{raw_request_body}` using your API key's webhook secret (`whsec_…`). Verify over the RAW bytes, and reject if `|now - t| > 300s`." },
          { "name": "X-Pilot5-Timestamp", "in": "header", "schema": { "type": "string" } },
          { "name": "X-Pilot5-Event", "in": "header", "schema": { "type": "string" } },
          { "name": "X-Pilot5-Event-Id", "in": "header", "schema": { "type": "string" } },
          { "name": "X-Pilot5-Delivery-Id", "in": "header", "schema": { "type": "string" } },
          { "name": "X-Pilot5-Attempt", "in": "header", "schema": { "type": "integer" } }
        ],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookEvent" } } }
        },
        "responses": {
          "200": { "description": "Acknowledged. Pilot5 retries on any non-2xx response." }
        }
      }
    },
    "deliberation.failed": {
      "post": {
        "operationId": "onDeliberationFailed",
        "summary": "Deliberation failed callback",
        "description": "Same payload shape as `deliberation.completed`, with `data.status = \"failed\"`. The run produced no report — do NOT treat it as missing data. `GET data.result_url` returns the partner-safe error block (`processing_timeout` / `processing_failed`); the deliberation cost is fully refunded.",
        "parameters": [
          { "name": "X-Pilot5-Signature", "in": "header", "required": true, "schema": { "type": "string" }, "description": "See deliberation.completed." }
        ],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookEvent" } } }
        },
        "responses": {
          "200": { "description": "Acknowledged. Pilot5 retries on any non-2xx response." }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key created in the dashboard (Settings → API). Send as `Authorization: Bearer pk_live_…` (production) or `pk_test_…` (staging)."
      }
    },
    "parameters": {
      "DeliberationId": {
        "name": "deliberation_id",
        "in": "path",
        "required": true,
        "schema": { "type": "string", "format": "uuid" }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing, malformed, revoked, or wrong-environment API key.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
      },
      "NotFound": {
        "description": "Unknown deliberation_id, or it belongs to another workspace.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
      },
      "RateLimited": {
        "description": "Per-key rate limit hit. See the Retry-After header.",
        "headers": {
          "Retry-After": { "schema": { "type": "integer" }, "description": "Seconds to wait before retrying." }
        },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
      }
    },
    "schemas": {
      "PublicStatus": {
        "type": "string",
        "enum": ["queued", "running", "completed", "failed"]
      },
      "DeliberationRequest": {
        "type": "object",
        "required": ["question"],
        "properties": {
          "question": {
            "type": "string",
            "minLength": 10,
            "maxLength": 2000,
            "description": "The question or topic to deliberate on. Multilingual — the panel answers in the question's language."
          },
          "mode": {
            "type": "string",
            "enum": ["deliberation1", "smartroute1"],
            "default": "deliberation1",
            "description": "deliberation1 = The A-Team (5 personas, ~4 cr, 5-8 min). smartroute1 = The Expert (single model, ~0.5 cr, ~30s). deliberation2 (HITL) is not available via the API."
          },
          "use_case": {
            "type": "string",
            "enum": ["general", "pricing", "code", "strategy", "marketing", "logistics"],
            "default": "general"
          },
          "webhook_url": {
            "type": ["string", "null"],
            "format": "uri",
            "description": "Optional. HTTPS URL that receives the signed terminal-state callback (private/loopback IPs rejected). Omit it to skip webhooks entirely and poll GET /v1/api/deliberations/{id}/result instead — the result is available there either way."
          },
          "metadata": {
            "type": ["object", "null"],
            "description": "Opaque JSON, ≤4 KB. Echoed verbatim in the webhook payload."
          },
          "difficulty": {
            "type": "string",
            "enum": ["EASY", "MEDIUM", "HARD"],
            "default": "MEDIUM",
            "description": "Sizes the credit reservation. Actual credits are finalized from real usage; excess is refunded."
          }
        }
      },
      "AcceptedResponse": {
        "type": "object",
        "properties": {
          "deliberation_id": { "type": "string", "format": "uuid" },
          "status": { "type": "string", "enum": ["queued"] },
          "result_url": { "type": "string", "format": "uri" },
          "estimated_credits": { "type": "number", "description": "Credits reserved at submit; final charge may be lower." }
        }
      },
      "DeliberationStatus": {
        "type": "object",
        "description": "Shallow status returned while the deliberation is still running.",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "status": { "$ref": "#/components/schemas/PublicStatus" },
          "mode": { "type": "string" },
          "use_case": { "type": "string" },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" }
        }
      },
      "DeliberationResult": {
        "type": "object",
        "description": "Full payload on a terminal deliberation. On failed, synthesis/summary are null and error is populated.",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "status": { "$ref": "#/components/schemas/PublicStatus" },
          "synthesis": { "type": ["string", "null"], "description": "Full arbiter synthesis markdown." },
          "summary": { "type": ["string", "null"], "description": "One-paragraph TL;DR." },
          "confidence_index": { "type": ["number", "null"] },
          "mode": { "type": "string" },
          "use_case": { "type": "string" },
          "credits_charged": { "type": ["number", "null"], "description": "Actual credits debited (0 on a full refund)." },
          "error": {
            "oneOf": [
              { "$ref": "#/components/schemas/FailureError" },
              { "type": "null" }
            ]
          },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" },
          "completed_at": { "type": ["string", "null"], "format": "date-time" }
        }
      },
      "FailureError": {
        "type": "object",
        "description": "Present only on a failed deliberation.",
        "properties": {
          "code": {
            "type": "string",
            "enum": ["processing_timeout", "processing_failed"],
            "description": "processing_timeout = exceeded processing budget / halted; usually transient, retry. processing_failed = other failure; retry or contact support."
          },
          "message": { "type": "string" }
        }
      },
      "WebhookEvent": {
        "type": "object",
        "description": "Body Pilot5 POSTs to your webhook_url. A pointer — fetch data.result_url for the report.",
        "properties": {
          "id": { "type": "string", "format": "uuid", "description": "Event id." },
          "event": { "type": "string", "enum": ["deliberation.completed", "deliberation.failed"] },
          "created_at": { "type": "string", "format": "date-time" },
          "data": { "$ref": "#/components/schemas/WebhookData" }
        }
      },
      "WebhookData": {
        "type": "object",
        "properties": {
          "deliberation_id": { "type": "string", "format": "uuid" },
          "status": { "$ref": "#/components/schemas/PublicStatus" },
          "mode": { "type": "string" },
          "use_case": { "type": "string" },
          "result_url": { "type": "string", "format": "uri", "description": "GET this with your API key to retrieve the report (synthesis). The webhook body never contains the report." },
          "completed_at": { "type": ["string", "null"], "format": "date-time" },
          "credits_charged": { "type": ["number", "null"] },
          "metadata": { "type": ["object", "null"], "description": "Echoed verbatim from the original request." }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": { "type": "string", "description": "Stable machine code, e.g. invalid_api_key, insufficient_credits, content_blocked, not_found, idempotency_conflict, validation_error, rate_limited, internal_error." },
              "message": { "type": "string" },
              "request_id": { "type": "string" }
            }
          }
        }
      }
    }
  }
}
