{
  "openapi": "3.1.0",
  "info": {
    "title": "SentioGate API",
    "version": "1",
    "summary": "OpenAI-compatible BYOK gateway with price-aware auto routing.",
    "description": "SentioGate forwards OpenAI-compatible chat completions to the providers whose keys you store in your vault (BYOK).\n\nSend `model: \"auto\"` to let SentioGate pick a suitable, cost-effective model from the providers you enabled.\n\nRequests on your own models run on the provider keys in your vault. Without any keys, `model: \"auto\"` is served by the free community pool (600 requests per 5 hours per user).\n\nAccount endpoints (keys, vault, cache, integrations) need a SentioGate API key or a dashboard session.\n\nErrors from /chat/completions use the OpenAI shape `{error:{message,type,code}}`; account endpoints return `{detail}`.\n\nPOST /messages speaks the Anthropic Messages API (for Claude Code and Anthropic SDKs). Errors there use the Anthropic shape `{type:\"error\",error:{type,message}}`."
  },
  "servers": [
    {
      "url": "https://api.sentiogate.dev/v1",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Inference"
    },
    {
      "name": "Anthropic"
    },
    {
      "name": "Catalog"
    },
    {
      "name": "Auth"
    },
    {
      "name": "Vault"
    },
    {
      "name": "Cache"
    },
    {
      "name": "Integrations"
    },
    {
      "name": "Discover"
    },
    {
      "name": "Batch"
    }
  ],
  "paths": {
    "/chat/completions": {
      "post": {
        "tags": [
          "Inference"
        ],
        "operationId": "createChatCompletion",
        "summary": "Create a chat completion (OpenAI-compatible)",
        "description": "With `stream: true` the upstream Server-Sent Events stream is passed through unchanged and router metadata is returned in the `X-SentioGate-Router` response header (JSON). Non-streaming responses carry the same metadata in the `router` field.",
        "parameters": [
          {
            "name": "X-Session-ID",
            "in": "header",
            "required": false,
            "description": "Conversation or agent-session id (max 256 chars). Optional: keeps the requests of one session together, so `model: \"auto\"` stays consistent across tool turns and provider prompt caching works better.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-SentioGate-Prompt-Cache",
            "in": "header",
            "required": false,
            "description": "Send `off` to stop SentioGate adding a provider prompt-cache breakpoint. Client cache_control fields are always forwarded.",
            "schema": {
              "type": "string",
              "enum": [
                "off"
              ]
            }
          },
          {
            "name": "X-SentioGate-Cascade",
            "in": "header",
            "required": false,
            "description": "Cascade (opt-in, non-streaming, `model: \"auto\"`): `on` first tries a cheaper model from your vault and, if its answer does not pass SentioGate's quality checks, retries once with the normal pick. Both attempts are billed by your provider. `off` turns it off for this request. The outcome is reported in `router.cascade`.",
            "schema": {
              "type": "string",
              "enum": [
                "on",
                "off"
              ]
            }
          },
          {
            "name": "X-SentioGate-Length-Cap",
            "in": "header",
            "required": false,
            "description": "Send `on` to enable the answer-length cap for this request (off by default), or `off` to disable it.",
            "schema": {
              "type": "string",
              "enum": [
                "on",
                "off"
              ]
            }
          },
          {
            "name": "X-SentioGate-Cache",
            "in": "header",
            "required": false,
            "description": "Send `off` (or `bypass`) to skip the response cache, or `force` to cache this request at any temperature (still never for streaming, tools or n>1). Responses carry `X-SentioGate-Cache` (`exact`, `semantic`, `miss` or `skip-<reason>`) and, on a hit, `X-SentioGate-Cache-Saved-USD`.",
            "schema": {
              "type": "string",
              "enum": [
                "off",
                "bypass",
                "force"
              ]
            }
          },
          {
            "name": "X-SentioGate-Tool-Trim",
            "in": "header",
            "required": false,
            "description": "Tool-output trimming (on by default for `model: \"auto\"` and your own keys): shortens large tool results from earlier agent steps. System, user and assistant messages and the newest tool results are never changed. Send `off` to opt out for this request, or `on` to apply it when your account setting is off. Savings are reported in `router.tool_trim`.",
            "schema": {
              "type": "string",
              "enum": [
                "on",
                "off"
              ]
            }
          },
          {
            "name": "X-SentioGate-Loop-Stop",
            "in": "header",
            "required": false,
            "description": "Runaway-loop stop (on by default). When an agent looks stuck repeating the same steps, the request is flagged in the `X-SentioGate-Loop` response header and answered with 400 `loop_detected` instead of calling a model. Send `off` to only flag it (overrides the account setting); `on` forces the stop.",
            "schema": {
              "type": "string",
              "enum": [
                "on",
                "off"
              ]
            }
          },
          {
            "name": "X-SentioGate-Tool-Guard",
            "in": "header",
            "required": false,
            "description": "Dangerous tool-call flag (on by default). When the model asks to run a tool call that looks dangerous, such as deleting the root or home directory, piping a download into a shell, sending credential files or secrets out, or force-pushing to main, the response is flagged in the `X-SentioGate-Guard` response header and in `router.guard`. On streams the flag arrives as an SSE comment line starting with `: x-sentiogate-guard` before the end of the stream. Flag only: the answer is never changed or blocked. Send `off` to skip the check for this request (overrides the account setting).",
            "schema": {
              "type": "string",
              "enum": [
                "flag",
                "off"
              ]
            }
          },
          {
            "name": "X-SentioGate-Allow-Queue",
            "in": "header",
            "required": false,
            "description": "Advanced, for background jobs only. On a non-streaming request served by the free community pool, `on` lets a busy pool answer 202 Accepted with a poll link (`Location: /v1/batch/poll/{jobId}`) instead of a busy error. Requests on your own provider keys are always answered directly.",
            "schema": {
              "type": "string",
              "enum": [
                "on",
                "off"
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChatCompletionRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Completion. `text/event-stream` when `stream: true`.",
            "headers": {
              "X-SentioGate-Router": {
                "description": "JSON-encoded RouterInfo (streaming responses).",
                "schema": {
                  "type": "string"
                }
              },
              "X-SentioGate-Loop": {
                "description": "Present when the agent looks stuck in a loop. Also in `router.loop`.",
                "schema": {
                  "type": "string"
                }
              },
              "X-SentioGate-Guard": {
                "description": "Present when a tool call in the answer looks dangerous: `flagged; rule=<id>[,<id>]`. Also in `router.guard`. Not sent on streams (see the SSE comment).",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatCompletionResponse"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "description": "OpenAI-format SSE chunks, ending with `data: [DONE]`."
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body. Also when the agent looks stuck repeating the same steps and runaway-loop stop is on, the default (`loop_detected`). Change the approach or input and send again; retrying the same request gives the same answer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid SentioGate API key (`invalid_api_key`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "No provider key in your vault for the requested provider, or no keys at all (`no_provider_key`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Model disabled by vault policy (`model_disabled_by_policy`), or auto found no capable/enabled model (`no_capable_model`, `no_enabled_models`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Vault spend/request cap reached (`policy_limit_reached`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "501": {
            "description": "Selected provider is not OpenAI-compatible and cannot be forwarded by the edge (`provider_not_supported_on_edge`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Upstream provider returned an error or was unreachable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Temporarily unavailable, for example the free community pool is busy (`community_busy`, with `Retry-After`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/messages": {
      "post": {
        "tags": [
          "Anthropic"
        ],
        "operationId": "createMessage",
        "summary": "Create a message (Anthropic Messages API)",
        "security": [
          {
            "apiKeyHeader": []
          },
          {
            "bearerAuth": []
          }
        ],
        "description": "Anthropic-compatible endpoint for Claude Code and Anthropic SDKs: set `ANTHROPIC_BASE_URL=https://api.sentiogate.dev` and `ANTHROPIC_API_KEY` to your `sg_live_...` key (sent as `x-api-key` or `Authorization: Bearer`). `?beta=true` is accepted.\n\nWith your own Anthropic key in the Vault, `claude-*` models are sent natively to Anthropic, so the request (thinking, tools, cache_control, documents) and the response or SSE stream are passed through as Anthropic sent them.\n\nAny other model (`auto`, `provider/model`, a bare id) is translated to an OpenAI-compatible chat completion on your vault keys and the answer is translated back into Anthropic's message shape and SSE event order (`message_start`, `content_block_*`, `message_delta`, `message_stop`).\n\nWithout vault keys, only `auto` (or `sentiogate-community`) is served, on the free community models; other ids return 403 `permission_error`. For Claude Code set `ANTHROPIC_MODEL=auto` and `ANTHROPIC_SMALL_FAST_MODEL=auto` (and `ANTHROPIC_DEFAULT_HAIKU_MODEL=auto`). Claude models need your own Anthropic or OpenRouter key.\n\nA 429 carries the upstream `Retry-After` when the provider sent one. Router metadata is returned in the `X-SentioGate-Router` header.",
        "parameters": [
          {
            "name": "anthropic-version",
            "in": "header",
            "required": false,
            "description": "Anthropic API version (default `2023-06-01`). Forwarded on native Anthropic calls.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "anthropic-beta",
            "in": "header",
            "required": false,
            "description": "Anthropic beta flags. Forwarded on native Anthropic calls.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-SentioGate-Tool-Trim",
            "in": "header",
            "required": false,
            "description": "Tool-output trimming (on by default for `model: \"auto\"` and your own keys): shortens large tool results from earlier agent steps. System, user and assistant messages and the newest tool results are never changed. Send `off` to opt out for this request, or `on` to apply it when your account setting is off. Savings are reported in `router.tool_trim`.",
            "schema": {
              "type": "string",
              "enum": [
                "on",
                "off"
              ]
            }
          },
          {
            "name": "X-SentioGate-Loop-Stop",
            "in": "header",
            "required": false,
            "description": "Runaway-loop stop (on by default). When an agent looks stuck repeating the same steps, the request is flagged in the `X-SentioGate-Loop` response header and answered with 400 `loop_detected` instead of calling a model. Send `off` to only flag it (overrides the account setting); `on` forces the stop.",
            "schema": {
              "type": "string",
              "enum": [
                "on",
                "off"
              ]
            }
          },
          {
            "name": "X-SentioGate-Tool-Guard",
            "in": "header",
            "required": false,
            "description": "Dangerous tool-call flag (on by default). When the model asks to run a tool call that looks dangerous, such as deleting the root or home directory, piping a download into a shell, sending credential files or secrets out, or force-pushing to main, the response is flagged in the `X-SentioGate-Guard` response header and in `router.guard`. On streams the flag arrives as an SSE comment line starting with `: x-sentiogate-guard` before the end of the stream. Flag only: the answer is never changed or blocked. Send `off` to skip the check for this request (overrides the account setting).",
            "schema": {
              "type": "string",
              "enum": [
                "flag",
                "off"
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessagesRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Message. `text/event-stream` when `stream: true`.",
            "headers": {
              "X-SentioGate-Router": {
                "description": "JSON-encoded RouterInfo.",
                "schema": {
                  "type": "string"
                }
              },
              "X-SentioGate-Loop": {
                "description": "Present when the agent looks stuck in a loop. Also in `router.loop`.",
                "schema": {
                  "type": "string"
                }
              },
              "X-SentioGate-Guard": {
                "description": "Present when a tool call in the answer looks dangerous: `flagged; rule=<id>[,<id>]`. Also in `router.guard`. Not sent on streams (see the SSE comment).",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessagesResponse"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "description": "Anthropic-format SSE events."
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (`invalid_request_error`). Also when the agent looks stuck repeating the same steps and runaway-loop stop is on, the default (`invalid_request_error`). Change the approach or input and send again; retrying the same request gives the same answer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnthropicError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid SentioGate API key (`authentication_error`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnthropicError"
                }
              }
            }
          },
          "402": {
            "description": "No provider key in your vault for the requested model (`billing_error`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnthropicError"
                }
              }
            }
          },
          "403": {
            "description": "Model not available without your own provider key, or disabled by vault policy (`permission_error`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnthropicError"
                }
              }
            }
          },
          "405": {
            "description": "Only POST (and HEAD) are supported.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnthropicError"
                }
              }
            }
          },
          "413": {
            "description": "Request too large (`request_too_large`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnthropicError"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited (`rate_limit_error`); `Retry-After` is set when known.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnthropicError"
                }
              }
            }
          },
          "501": {
            "description": "The requested provider cannot be served by the edge (`api_error`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnthropicError"
                }
              }
            }
          },
          "502": {
            "description": "Upstream provider error (`api_error`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnthropicError"
                }
              }
            }
          },
          "504": {
            "description": "Upstream provider timed out (`timeout_error`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnthropicError"
                }
              }
            }
          },
          "529": {
            "description": "Upstream provider overloaded (`overloaded_error`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnthropicError"
                }
              }
            }
          }
        }
      }
    },
    "/messages/count_tokens": {
      "post": {
        "tags": [
          "Anthropic"
        ],
        "operationId": "countMessageTokens",
        "summary": "Count input tokens (Anthropic Messages API)",
        "security": [
          {
            "apiKeyHeader": []
          },
          {
            "bearerAuth": []
          }
        ],
        "description": "For `claude-*` models with your own Anthropic key in the Vault, Anthropic counts the tokens. Otherwise SentioGate returns an estimate. The `X-SentioGate-Token-Count` response header says which (`anthropic` or `estimate`).",
        "parameters": [
          {
            "name": "anthropic-version",
            "in": "header",
            "required": false,
            "description": "Anthropic API version (default `2023-06-01`). Forwarded on native Anthropic calls.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "anthropic-beta",
            "in": "header",
            "required": false,
            "description": "Anthropic beta flags. Forwarded on native Anthropic calls.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessagesRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Token count",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "input_tokens"
                  ],
                  "properties": {
                    "input_tokens": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (`invalid_request_error`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnthropicError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid SentioGate API key (`authentication_error`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnthropicError"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited (`rate_limit_error`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnthropicError"
                }
              }
            }
          }
        }
      }
    },
    "/models": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "operationId": "listModels",
        "summary": "List routable model ids",
        "security": [],
        "responses": {
          "200": {
            "description": "Model list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelList"
                }
              }
            }
          }
        }
      }
    },
    "/providers": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "operationId": "listProviders",
        "summary": "List supported providers",
        "security": [],
        "responses": {
          "200": {
            "description": "Providers",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/pricing": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "operationId": "getPricing",
        "summary": "Model list prices used for routing and cost estimates",
        "security": [],
        "responses": {
          "200": {
            "description": "Pricing snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/pricing/models": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "operationId": "getModelPrices",
        "summary": "Per-model list prices (USD per token), context length, tool support and capability tier",
        "security": [],
        "responses": {
          "200": {
            "description": "Model price catalog",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "source": {
                      "type": "string"
                    },
                    "fetched_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "count": {
                      "type": "integer"
                    },
                    "models": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "object",
                        "properties": {
                          "prompt": {
                            "type": [
                              "number",
                              "null"
                            ]
                          },
                          "completion": {
                            "type": [
                              "number",
                              "null"
                            ]
                          },
                          "context": {
                            "type": [
                              "integer",
                              "null"
                            ]
                          },
                          "tools": {
                            "type": [
                              "boolean",
                              "null"
                            ]
                          },
                          "tier": {
                            "type": [
                              "integer",
                              "null"
                            ]
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/waitlist": {
      "post": {
        "tags": [
          "Catalog"
        ],
        "operationId": "joinWaitlist",
        "summary": "Get an email when a coming-soon paid plan launches (\"Notify me\" on /pricing)",
        "security": [],
        "description": "No account needed; when signed in, the account is linked to the sign-up. Stores only the email, the plan and the time. Signing up twice for the same plan is fine and returns the same answer. Nothing is emailed until the plan launches. Rate limited per IP.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "plan"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 254
                  },
                  "plan": {
                    "type": "string",
                    "enum": [
                      "pro",
                      "team"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "On the list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "subscribed"
                      ]
                    },
                    "plan": {
                      "type": "string",
                      "enum": [
                        "pro",
                        "team"
                      ]
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid email or plan",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          },
          "429": {
            "description": "Too many sign-ups from this address; see Retry-After",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          }
        }
      }
    },
    "/benchmarks": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "operationId": "getBenchmarks",
        "summary": "Per-model latency and success rates measured from recent routed traffic",
        "security": [],
        "responses": {
          "200": {
            "description": "Measured model stats",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BenchmarkList"
                }
              }
            }
          }
        }
      }
    },
    "/auth/login": {
      "post": {
        "tags": [
          "Auth"
        ],
        "operationId": "login",
        "summary": "Exchange email/password for a session JWT",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "password"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "password": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Session",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "token": {
                      "type": "string"
                    },
                    "role": {
                      "type": "string"
                    },
                    "user": {
                      "type": "object"
                    },
                    "workspaces": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing fields",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          },
          "401": {
            "description": "Invalid email or password",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          }
        }
      }
    },
    "/auth/me": {
      "get": {
        "tags": [
          "Auth"
        ],
        "operationId": "me",
        "summary": "Current user",
        "responses": {
          "200": {
            "description": "User and workspaces",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          }
        }
      }
    },
    "/auth/keys": {
      "get": {
        "tags": [
          "Auth"
        ],
        "operationId": "listApiKeys",
        "summary": "List SentioGate API keys (prefixes only)",
        "responses": {
          "200": {
            "description": "Keys",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Auth"
        ],
        "operationId": "createApiKey",
        "summary": "Create a SentioGate API key (`sg_live_...`, shown once)",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created key; `raw_key` is only returned here",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          }
        }
      }
    },
    "/auth/keys/{key_id}": {
      "delete": {
        "tags": [
          "Auth"
        ],
        "operationId": "revokeApiKey",
        "summary": "Revoke a SentioGate API key",
        "parameters": [
          {
            "name": "key_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Revoked",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          }
        }
      }
    },
    "/vault/keys": {
      "get": {
        "tags": [
          "Vault"
        ],
        "operationId": "listVaultKeys",
        "summary": "List stored provider keys (masked) and model policy",
        "responses": {
          "200": {
            "description": "Vault",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Vault"
        ],
        "operationId": "storeVaultKey",
        "summary": "Store or update a provider key (encrypted at rest) and model policy",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "provider"
                ],
                "properties": {
                  "provider": {
                    "type": "string"
                  },
                  "api_key": {
                    "type": "string"
                  },
                  "model_overrides": {
                    "type": "object"
                  },
                  "key_policy_defaults": {
                    "type": "object"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stored",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid key or provider",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          }
        }
      }
    },
    "/vault/keys/{provider}": {
      "delete": {
        "tags": [
          "Vault"
        ],
        "operationId": "deleteVaultKey",
        "summary": "Delete a stored provider key",
        "parameters": [
          {
            "name": "provider",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          }
        }
      }
    },
    "/cache/stats": {
      "get": {
        "tags": [
          "Cache"
        ],
        "operationId": "cacheStats",
        "summary": "Your response-cache entries, hits and recorded savings",
        "responses": {
          "200": {
            "description": "Cache stats",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          }
        }
      }
    },
    "/cache": {
      "delete": {
        "tags": [
          "Cache"
        ],
        "operationId": "clearCache",
        "summary": "Delete your cached responses (other users are never affected)",
        "responses": {
          "200": {
            "description": "Cleared",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          }
        }
      }
    },
    "/integrations/langfuse": {
      "get": {
        "tags": [
          "Integrations"
        ],
        "operationId": "getLangfuseSettings",
        "summary": "Your Langfuse export settings (the secret key is never returned)",
        "responses": {
          "200": {
            "description": "Settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LangfuseSettings"
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          },
          "503": {
            "description": "Temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "Integrations"
        ],
        "operationId": "updateLangfuseSettings",
        "summary": "Save Langfuse export settings",
        "description": "Opt-in export of a trace per chat request to your own Langfuse project. Off by default. Fields you leave out keep their saved value; `secret_key` is write-only and stored encrypted. Enabling needs both keys. Prompts and answers are only exported when `include_content` is true; otherwise traces carry metadata only (model, provider, token usage, cost, latency, status).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LangfuseSettingsUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LangfuseSettings"
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON, host or key, or enabling without both keys",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          },
          "503": {
            "description": "Temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Integrations"
        ],
        "operationId": "deleteLangfuseSettings",
        "summary": "Turn off the Langfuse export and delete the saved keys",
        "responses": {
          "200": {
            "description": "Settings after deletion (`configured: false`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LangfuseSettings"
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          },
          "503": {
            "description": "Temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          }
        }
      }
    },
    "/integrations/langfuse/test": {
      "post": {
        "tags": [
          "Integrations"
        ],
        "operationId": "testLangfuseConnection",
        "summary": "Check the saved Langfuse keys against your Langfuse host",
        "responses": {
          "200": {
            "description": "Result. `ok: false` with a `detail` when the keys are missing, rejected or the host is unreachable; `status` is the HTTP status Langfuse answered (0 when unreachable).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "status": {
                      "type": "integer"
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          },
          "429": {
            "description": "Tested too often; `Retry-After` says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "Temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          }
        }
      }
    },
    "/workload/analyze": {
      "post": {
        "tags": [
          "Discover"
        ],
        "operationId": "analyzeWorkload",
        "summary": "Estimate savings from an uploaded log export (JSON, JSONL or CSV, max 5 MB). Nothing is stored.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "raw": {
                    "type": "string",
                    "description": "File contents"
                  },
                  "records": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Estimated report",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Empty or unparseable input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          },
          "413": {
            "description": "Upload too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          }
        }
      }
    },
    "/batch/enqueue": {
      "post": {
        "tags": [
          "Batch"
        ],
        "operationId": "enqueueBatch",
        "summary": "Submit an asynchronous task to the free community pool",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "item_text": {
                    "type": "string"
                  },
                  "messages": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/Message"
                    }
                  },
                  "model": {
                    "type": "string",
                    "default": "auto"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued task with poll_url",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "job_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "poll_url": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid payload",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          },
          "401": {
            "description": "Sign-in or API key required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          },
          "403": {
            "description": "The free community pool is not available for this account (for example when you have provider keys in the Vault)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          },
          "413": {
            "description": "Item too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          },
          "429": {
            "description": "Personal allowance (600 requests per 5 hours) used up, too many pending items, or rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          }
        }
      }
    },
    "/batch/poll/{jobId}": {
      "get": {
        "tags": [
          "Batch"
        ],
        "operationId": "pollBatch",
        "summary": "Check status and retrieve results of an enqueued batch task",
        "parameters": [
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Job status and completed model output (owner only)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "job_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "response": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Sign-in or API key required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          },
          "404": {
            "description": "Job not found (or not yours)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "A SentioGate API key (`sg_live_...`) or a dashboard session JWT."
      },
      "apiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "A SentioGate API key (`sg_live_...`), as Anthropic SDKs and Claude Code send it."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "message"
            ],
            "properties": {
              "message": {
                "type": "string"
              },
              "type": {
                "type": "string"
              },
              "code": {
                "type": "string",
                "enum": [
                  "invalid_api_key",
                  "no_provider_key",
                  "model_disabled_by_policy",
                  "no_capable_model",
                  "no_enabled_models",
                  "policy_limit_reached",
                  "provider_not_supported_on_edge"
                ]
              },
              "provider": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "model": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        }
      },
      "Detail": {
        "type": "object",
        "properties": {
          "detail": {
            "type": "string"
          }
        }
      },
      "AnthropicError": {
        "type": "object",
        "required": [
          "type",
          "error"
        ],
        "properties": {
          "type": {
            "type": "string",
            "const": "error"
          },
          "error": {
            "type": "object",
            "required": [
              "type",
              "message"
            ],
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "invalid_request_error",
                  "authentication_error",
                  "billing_error",
                  "permission_error",
                  "not_found_error",
                  "request_too_large",
                  "rate_limit_error",
                  "timeout_error",
                  "api_error",
                  "overloaded_error"
                ]
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "MessagesRequest": {
        "type": "object",
        "required": [
          "model",
          "max_tokens",
          "messages"
        ],
        "properties": {
          "model": {
            "type": "string",
            "description": "`claude-*` (native with your Anthropic key; via OpenRouter as `anthropic/<id>` otherwise), `auto`, `sentiogate-community`, or `provider/model`."
          },
          "max_tokens": {
            "type": "integer",
            "minimum": 1
          },
          "messages": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "role",
                "content"
              ],
              "properties": {
                "role": {
                  "type": "string",
                  "enum": [
                    "user",
                    "assistant"
                  ]
                },
                "content": {
                  "oneOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  ]
                }
              }
            }
          },
          "system": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "object"
                }
              }
            ]
          },
          "stream": {
            "type": "boolean",
            "default": false
          },
          "temperature": {
            "type": "number"
          },
          "top_p": {
            "type": "number"
          },
          "top_k": {
            "type": "integer"
          },
          "stop_sequences": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "tools": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Custom tools are supported on every route; Anthropic server tools only on the native route."
          },
          "tool_choice": {
            "type": "object"
          },
          "thinking": {
            "type": "object"
          },
          "metadata": {
            "type": "object"
          }
        }
      },
      "MessagesResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "const": "message"
          },
          "role": {
            "type": "string",
            "const": "assistant"
          },
          "model": {
            "type": "string"
          },
          "content": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "stop_reason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "end_turn",
              "max_tokens",
              "stop_sequence",
              "tool_use",
              "pause_turn",
              "refusal",
              null
            ]
          },
          "stop_sequence": {
            "type": [
              "string",
              "null"
            ]
          },
          "usage": {
            "type": "object",
            "properties": {
              "input_tokens": {
                "type": "integer"
              },
              "output_tokens": {
                "type": "integer"
              },
              "cache_creation_input_tokens": {
                "type": "integer"
              },
              "cache_read_input_tokens": {
                "type": "integer"
              }
            }
          }
        }
      },
      "Message": {
        "type": "object",
        "required": [
          "role",
          "content"
        ],
        "properties": {
          "role": {
            "type": "string",
            "enum": [
              "system",
              "user",
              "assistant",
              "tool"
            ]
          },
          "content": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "object"
                }
              }
            ]
          },
          "name": {
            "type": "string"
          }
        }
      },
      "ChatCompletionRequest": {
        "type": "object",
        "required": [
          "messages"
        ],
        "properties": {
          "model": {
            "type": "string",
            "default": "auto",
            "description": "`auto`, `provider/model` (e.g. `openai/gpt-4o-mini`) or a bare model id."
          },
          "messages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Message"
            }
          },
          "stream": {
            "type": "boolean",
            "default": false
          },
          "temperature": {
            "type": "number",
            "description": "Sent upstream only when set; unset uses the provider default."
          },
          "max_tokens": {
            "type": "integer"
          },
          "top_p": {
            "type": "number"
          },
          "frequency_penalty": {
            "type": "number"
          },
          "presence_penalty": {
            "type": "number"
          },
          "stop": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            ]
          },
          "task_class": {
            "$ref": "#/components/schemas/TaskClass"
          },
          "route_preference": {
            "$ref": "#/components/schemas/RoutePreference"
          },
          "session_id": {
            "type": "string",
            "description": "Same as header X-Session-ID."
          },
          "cache_control": {
            "type": "object",
            "description": "Forwarded untouched to the provider. Block-level cache_control inside messages is also forwarded untouched."
          },
          "prompt_cache_key": {
            "type": "string",
            "description": "Forwarded (OpenAI-style cache routing key)."
          },
          "prompt_cache": {
            "type": "boolean",
            "description": "false stops SentioGate auto-inserting a prompt-cache breakpoint."
          },
          "cascade": {
            "type": "boolean",
            "description": "Same as header X-SentioGate-Cascade."
          },
          "length_cap": {
            "type": "boolean",
            "description": "true enables the answer-length cap for this request (off by default; same as header `X-SentioGate-Length-Cap: on`). It applies only when you set no max_tokens / max_completion_tokens and the request is not streaming; an answer cut by the cap is retried once without it."
          },
          "cache": {
            "type": "boolean",
            "description": "false skips the response cache (same as header `X-SentioGate-Cache: off`); true opts in at any temperature (same as `force`). By default only deterministic requests are cached (a low temperature, or a single-turn factual question with temperature unset)."
          },
          "tool_trim": {
            "type": "boolean",
            "description": "false turns tool-output trimming off for this request (same as header `X-SentioGate-Tool-Trim: off`)."
          },
          "compress": {
            "type": "string",
            "enum": [
              "auto",
              "lossless",
              "on",
              "off"
            ],
            "default": "auto",
            "description": "Context compression. `auto` (default): lossless clean-up only, plus summarizing older turns when a long session nears the model's context limit. System prompts, code blocks and tool-call pairs are never changed, and provider prompt caching keeps working. `lossless`: never summarize. `on`: summarize older turns more readily. `off`: send messages unchanged. Header `X-SentioGate-Compress` is equivalent. Savings are reported in `router.compression`."
          },
          "metadata": {
            "type": "object",
            "properties": {
              "task_class": {
                "$ref": "#/components/schemas/TaskClass"
              },
              "route_preference": {
                "$ref": "#/components/schemas/RoutePreference"
              }
            }
          },
          "tools": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "OpenAI-style tools; forwarded upstream."
          },
          "tool_choice": {
            "description": "Forwarded upstream with tools."
          },
          "parallel_tool_calls": {
            "type": "boolean",
            "description": "Forwarded upstream when set."
          },
          "functions": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Legacy OpenAI functions; forwarded when tools is absent."
          },
          "provider_keys": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Per-request provider key overrides (not stored)."
          }
        }
      },
      "TaskClass": {
        "type": "string",
        "enum": [
          "classification",
          "simple_qa",
          "summarization",
          "general",
          "long_context",
          "coding",
          "reasoning"
        ],
        "description": "Optional hint for `model: \"auto\"` that overrides the automatic task detection."
      },
      "RoutePreference": {
        "type": "string",
        "enum": [
          "cost",
          "quality",
          "latency"
        ],
        "default": "cost",
        "description": "`cost` (default): lowest price among suitable models. `quality`: prefer more capable models. `latency`: prefer faster models."
      },
      "RouterInfo": {
        "type": "object",
        "description": "SentioGate routing metadata. Informational; other fields may appear.",
        "properties": {
          "latency_ms": {
            "type": "integer",
            "description": "End-to-end time at the edge (non-streaming)."
          },
          "provider": {
            "type": "string"
          },
          "model": {
            "type": "string"
          },
          "policy": {
            "type": "string",
            "enum": [
              "sentiogate_auto",
              "explicit",
              "cache"
            ]
          },
          "task_class": {
            "$ref": "#/components/schemas/TaskClass"
          },
          "min_tier": {
            "type": "integer"
          },
          "model_tier": {
            "type": [
              "integer",
              "null"
            ]
          },
          "estimated_prompt_tokens": {
            "type": "integer"
          },
          "estimated_output_tokens": {
            "type": "integer"
          },
          "estimated_cost_usd": {
            "type": [
              "number",
              "null"
            ]
          },
          "pricing_source": {
            "type": "string"
          },
          "reason": {
            "type": "string"
          },
          "candidates": {
            "type": "integer"
          },
          "attempts": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "`provider/model:status` per upstream attempt."
          },
          "route_preference": {
            "$ref": "#/components/schemas/RoutePreference"
          },
          "measured_latency_ms": {
            "type": [
              "number",
              "null"
            ],
            "description": "Recently measured latency of the chosen model, if known."
          },
          "prompt_cache": {
            "type": "object",
            "description": "Provider prompt-cache usage for this response and the estimated saving.",
            "properties": {
              "cached_tokens": {
                "type": "integer"
              },
              "cache_write_tokens": {
                "type": "integer"
              },
              "saved_usd": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "saved_source": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "auto": {
                "type": [
                  "object",
                  "null"
                ]
              }
            }
          },
          "agent": {
            "type": "object",
            "description": "Present on `model: \"auto\"` requests that carry tools.",
            "properties": {
              "tools": {
                "type": "boolean"
              },
              "tool_rounds": {
                "type": "integer"
              }
            }
          },
          "cost_source": {
            "type": "string",
            "description": "How estimated_cost_usd was computed (for example from the provider's published prices, or `unknown_price`)."
          },
          "reasoning_retry": {
            "type": "object",
            "description": "Present when a reasoning model returned an empty answer and the request was retried automatically. Both attempts are billed."
          },
          "cascade": {
            "type": "object",
            "description": "Present when cascade was requested.",
            "properties": {
              "attempted": {
                "type": "boolean"
              },
              "escalated": {
                "type": "boolean"
              },
              "reason": {
                "type": "string",
                "description": "`passed`, or why the cheaper answer was not used."
              },
              "first_model": {
                "type": "string"
              },
              "first_cost_usd": {
                "type": "number"
              },
              "first_latency_ms": {
                "type": "integer"
              }
            }
          },
          "length_cap": {
            "type": "object",
            "description": "Answer-length cap: whether it applied, the token limit, and whether a truncated answer was retried uncapped.",
            "properties": {
              "cap_applied": {
                "type": "boolean"
              },
              "cap_tokens": {
                "type": "integer"
              },
              "cap_retry": {
                "type": "boolean"
              },
              "cap_skipped": {
                "type": "string",
                "enum": [
                  "not_enabled",
                  "client_limit",
                  "streaming"
                ]
              }
            }
          },
          "cache": {
            "type": "object",
            "properties": {
              "hit": {
                "type": "boolean"
              },
              "layer": {
                "type": "string",
                "enum": [
                  "exact",
                  "semantic"
                ]
              },
              "cost_usd": {
                "type": "number",
                "const": 0
              },
              "saved_usd": {
                "type": "number"
              },
              "skipped": {
                "type": "string"
              }
            }
          },
          "tool_trim": {
            "type": "object",
            "description": "Present when tool-output trimming changed this request.",
            "properties": {
              "trimmed": {
                "type": "integer"
              },
              "chars_saved": {
                "type": "integer"
              },
              "tokens_saved_est": {
                "type": "integer"
              }
            }
          },
          "loop": {
            "type": "object",
            "description": "Present when the agent looks stuck in a loop.",
            "properties": {
              "suspected": {
                "type": "boolean"
              },
              "count": {
                "type": "integer"
              }
            }
          },
          "guard": {
            "type": "object",
            "description": "Present when the answer contains tool calls and the dangerous tool-call flag is on. Flag only: the answer is not changed.",
            "properties": {
              "mode": {
                "type": "string",
                "enum": [
                  "flag"
                ]
              },
              "inspected": {
                "type": "integer",
                "description": "Tool calls checked."
              },
              "flagged": {
                "type": "integer",
                "description": "Tool calls that look dangerous."
              },
              "rules": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Ids of the matched rules, for example `rm-root-or-home`, `pipe-to-shell`, `secret-file-exfil`, `force-push-main`, `secret-in-outbound-args`."
              }
            }
          },
          "compression": {
            "type": "object",
            "properties": {
              "applied": {
                "type": "boolean"
              },
              "original_tokens": {
                "type": "integer"
              },
              "compressed_tokens": {
                "type": "integer"
              },
              "saved_tokens": {
                "type": "integer"
              },
              "saved_pct": {
                "type": "number"
              },
              "mode": {
                "type": "string"
              },
              "lossy": {
                "type": "boolean"
              },
              "summary_cost_usd": {
                "type": "number"
              }
            }
          }
        }
      },
      "ChatCompletionResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "object": {
            "type": "string",
            "const": "chat.completion"
          },
          "created": {
            "type": "integer"
          },
          "model": {
            "type": "string"
          },
          "choices": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "index": {
                  "type": "integer"
                },
                "message": {
                  "$ref": "#/components/schemas/Message"
                },
                "finish_reason": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          },
          "usage": {
            "type": "object",
            "properties": {
              "prompt_tokens": {
                "type": "integer"
              },
              "completion_tokens": {
                "type": "integer"
              },
              "total_tokens": {
                "type": "integer"
              }
            }
          },
          "cached": {
            "type": "boolean"
          },
          "router": {
            "$ref": "#/components/schemas/RouterInfo"
          }
        }
      },
      "LangfuseSettings": {
        "type": "object",
        "description": "Never contains the secret key.",
        "properties": {
          "configured": {
            "type": "boolean"
          },
          "enabled": {
            "type": "boolean"
          },
          "include_content": {
            "type": "boolean"
          },
          "host": {
            "type": "string",
            "description": "Langfuse host, for example `https://cloud.langfuse.com` or `https://us.cloud.langfuse.com`."
          },
          "public_key": {
            "type": [
              "string",
              "null"
            ],
            "description": "Masked public key."
          },
          "has_secret_key": {
            "type": "boolean"
          },
          "updated_at": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Unix time in milliseconds."
          }
        }
      },
      "LangfuseSettingsUpdate": {
        "type": "object",
        "properties": {
          "enabled": {
            "type": "boolean"
          },
          "include_content": {
            "type": "boolean",
            "description": "Also export prompts and answers. Default false (metadata only)."
          },
          "host": {
            "type": "string",
            "description": "HTTPS Langfuse host (self-hosted allowed; private addresses are rejected)."
          },
          "public_key": {
            "type": "string",
            "description": "Starts with `pk-lf-`."
          },
          "secret_key": {
            "type": "string",
            "writeOnly": true,
            "description": "Starts with `sk-lf-`. Write-only: stored encrypted and never returned."
          }
        }
      },
      "ModelList": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "const": "list"
          },
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "object": {
                  "type": "string"
                },
                "owned_by": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "BenchmarkList": {
        "type": "object",
        "properties": {
          "window": {
            "type": "string"
          },
          "measured": {
            "type": "boolean"
          },
          "benchmarks": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "provider": {
                  "type": "string"
                },
                "model": {
                  "type": "string"
                },
                "samples": {
                  "type": "integer"
                },
                "p50_latency_ms": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "avg_ttft_ms": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "error_rate_pct": {
                  "type": [
                    "number",
                    "null"
                  ]
                }
              }
            }
          }
        }
      }
    }
  }
}
