{
  "openapi": "3.1.0",
  "info": {
    "title": "AgoraRouter API",
    "version": "1.0.0",
    "summary": "OpenAI-compatible gateway that chooses the model for you.",
    "description": "AgoraRouter is a drop-in OpenAI-compatible endpoint. Point any OpenAI client or coding agent at it, send `model: \"auto\"` (or omit the model), and the Router classifies the request, picks the model per request from a contextual bandit it trained on **verified outcomes**, and — for high-stakes task classes — convenes a small ensemble (council) of diverse model families and returns each answer as a separate `choice`. The response is plain OpenAI shape plus a non-breaking `agora` object describing what it did. You may still pin a specific model if you want.\n\nAuth is a personal `agk_live_…` key with the `route.complete` scope and a budget; the key's own budget is billed (fail-closed at 429). A compliance gate scrubs cardholder data on the way in and out.",
    "contact": { "name": "WindFox · Legate", "url": "https://agorarouter.com/router/" },
    "license": { "name": "Proprietary" }
  },
  "servers": [
    { "url": "https://agora-mcp.legate.bot/v1", "description": "Production" }
  ],
  "security": [ { "bearerAuth": [] } ],
  "tags": [
    { "name": "chat", "description": "Model-agnostic chat completions" },
    { "name": "models", "description": "Routable model catalogue" }
  ],
  "paths": {
    "/chat/completions": {
      "post": {
        "tags": ["chat"],
        "summary": "Create a chat completion (model chosen by the Router)",
        "operationId": "createChatCompletion",
        "description": "OpenAI shape in, OpenAI shape out. With `model: \"auto\"` the bandit picks the model from the auto-classified task class. For the high-stakes classes (`review`, `security_audit`, `architecture_review`) a non-streamed request fans out to several diverse models and returns them as multiple `choices` (choices[0] = champion, so single-answer clients keep working).",
        "parameters": [
          {
            "name": "X-Task-Class",
            "in": "header",
            "required": false,
            "description": "Pin the task class instead of letting the Router auto-classify the last user message (e.g. `refactor`, `debug`, `review`).",
            "schema": { "type": "string" }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ChatCompletionRequest" },
              "examples": {
                "auto": {
                  "summary": "Let the Router choose",
                  "value": {
                    "model": "auto",
                    "messages": [ { "role": "user", "content": "Refactor this function to remove the duplicated branch." } ]
                  }
                },
                "pinned": {
                  "summary": "Pin a specific model",
                  "value": {
                    "model": "claude-sonnet",
                    "messages": [ { "role": "user", "content": "Explain this stack trace." } ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "An OpenAI chat completion, plus the `agora` routing record. Response headers `X-Agora-Model`, `X-Agora-Task-Class`, `X-Agora-Route-Basis` echo the decision; ensemble responses add `X-Agora-Ensemble`.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ChatCompletionResponse" } },
              "text/event-stream": { "schema": { "type": "string", "description": "SSE stream when `stream: true` (single model; ensemble is non-streaming)." } }
            }
          },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "description": "Missing or invalid key (supply `Authorization: Bearer agk_live_…`).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "402": { "description": "Key has no billing handle / budget (`no_billing_handle`).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "422": { "description": "Compliance gate blocked cardholder data in the prompt or the model echo.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "description": "Budget exhausted (fail-closed). The upstream error is passed through verbatim." }
        }
      }
    },
    "/models": {
      "get": {
        "tags": ["models"],
        "summary": "List routable models",
        "operationId": "listModels",
        "description": "Advertises `auto` (let the bandit pick) plus the routable candidate aliases, so a caller can pin one.",
        "responses": {
          "200": {
            "description": "Model list (OpenAI shape).",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ModelList" },
                "example": {
                  "object": "list",
                  "data": [
                    { "id": "auto", "object": "model", "owned_by": "agora" },
                    { "id": "gpt", "object": "model", "owned_by": "agora" },
                    { "id": "claude-sonnet", "object": "model", "owned_by": "agora" },
                    { "id": "gemini", "object": "model", "owned_by": "agora" },
                    { "id": "llama", "object": "model", "owned_by": "agora" }
                  ]
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "agk_live_…",
        "description": "Personal Agora key with the `route.complete` scope and a budget. Self-service at https://agora-keys.legate.bot."
      }
    },
    "schemas": {
      "Message": {
        "type": "object",
        "required": ["role", "content"],
        "properties": {
          "role": { "type": "string", "enum": ["system", "user", "assistant", "tool"] },
          "content": { "type": "string" }
        }
      },
      "ChatCompletionRequest": {
        "type": "object",
        "required": ["messages"],
        "properties": {
          "model": { "type": "string", "default": "auto", "description": "`auto` (or omitted / `agora`) = the Router picks. Any catalogue id (e.g. `claude-sonnet`) pins that model." },
          "messages": { "type": "array", "items": { "$ref": "#/components/schemas/Message" }, "minItems": 1 },
          "stream": { "type": "boolean", "default": false, "description": "SSE streaming. Streaming forces a single model (an ensemble cannot be streamed)." },
          "temperature": { "type": "number" },
          "max_tokens": { "type": "integer" },
          "metadata": { "type": "object", "additionalProperties": true }
        },
        "additionalProperties": true
      },
      "Choice": {
        "type": "object",
        "properties": {
          "index": { "type": "integer" },
          "finish_reason": { "type": "string" },
          "message": { "$ref": "#/components/schemas/Message" },
          "agora_model": { "type": "string", "description": "Present on ensemble choices: which model produced this choice." }
        }
      },
      "AgoraMeta": {
        "type": "object",
        "description": "Non-breaking extra field describing the routing decision (OpenAI clients ignore unknown keys).",
        "properties": {
          "routed_model": { "type": "string" },
          "task_class": { "type": "string" },
          "task_class_basis": { "type": "string", "enum": ["caller-hint", "auto-classified"] },
          "route_basis": { "type": "string", "description": "`thompson` (bandit), `caller-pinned`, etc." },
          "was_exploration": { "type": "boolean" },
          "ensemble": { "type": "boolean" },
          "ensemble_models": { "type": "array", "items": { "type": "string" } }
        }
      },
      "ChatCompletionResponse": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "object": { "type": "string", "example": "chat.completion" },
          "model": { "type": "string", "description": "The model actually used, or `agora-ensemble`." },
          "choices": { "type": "array", "items": { "$ref": "#/components/schemas/Choice" } },
          "usage": { "type": "object", "additionalProperties": true },
          "agora": { "$ref": "#/components/schemas/AgoraMeta" }
        }
      },
      "Model": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "object": { "type": "string", "example": "model" },
          "owned_by": { "type": "string", "example": "agora" }
        }
      },
      "ModelList": {
        "type": "object",
        "properties": {
          "object": { "type": "string", "example": "list" },
          "data": { "type": "array", "items": { "$ref": "#/components/schemas/Model" } }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "message": { "type": "string" },
              "type": { "type": "string" },
              "code": { "type": "string" }
            }
          }
        }
      }
    },
    "responses": {
      "Error": {
        "description": "Error (OpenAI error shape).",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    }
  }
}
