{
  "openapi": "3.1.0",
  "info": {
    "title": "SheLLM API",
    "version": "1.20.0",
    "description": "SheLLM exposes the owner's own LLM CLI subscriptions (Claude Code, Codex)\nas one REST API that answers in both the OpenAI and the Anthropic formats.\nIt spawns the official CLI binary per request; see the repository README for\nthe fair-use position on provider terms.\n\n## Authentication\n\n**Client endpoints** (`/v1/*`) take an API key as `Authorization: Bearer <key>`\nor `x-api-key`. Keys are created by `shellm init` and managed through the Admin API.\n\n**Admin endpoints** (`/admin/*`) use HTTP Basic authentication.\nThe password is set via the `SHELLM_ADMIN_PASSWORD` environment variable.\n\n## Rate Limiting\n\nWhen rate-limited, responses include a `Retry-After` header (seconds).\n- **Global limit**: configurable via `SHELLM_GLOBAL_RPM` (default 60 RPM)\n- **Per-client limit**: configurable per API key (default 10 RPM)\n- **Admin brute-force**: 5 failed attempts per IP triggers a 5-minute lockout\n",
    "x-logo": {
      "url": "logo.svg",
      "altText": "SheLLM",
      "backgroundColor": "transparent",
      "href": "../"
    },
    "license": {
      "name": "MIT",
      "identifier": "MIT"
    }
  },
  "servers": [
    {
      "url": "http://localhost:6100",
      "description": "Local development"
    }
  ],
  "tags": [
    {
      "name": "Health",
      "description": "Liveness, diagnostics and this specification"
    },
    {
      "name": "Chat Completions",
      "description": "OpenAI-compatible chat completions"
    },
    {
      "name": "Messages",
      "description": "Anthropic-compatible Messages API"
    },
    {
      "name": "Models",
      "description": "List available models"
    },
    {
      "name": "Keys",
      "description": "API key management"
    },
    {
      "name": "Logs",
      "description": "Request log inspection"
    },
    {
      "name": "Stats",
      "description": "Aggregated usage statistics"
    },
    {
      "name": "Providers",
      "description": "Provider state and configuration"
    },
    {
      "name": "Update",
      "description": "Self-update of the host"
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "operationId": "getHealth",
        "tags": [
          "Health"
        ],
        "summary": "Liveness check",
        "description": "Answers `{\"status\":\"ok\"}` as soon as the process is listening. It never reports on\nproviders, so a monitor cannot mistake an expired CLI login for a dead service.\n`GET /health/detailed` is the one that knows.\n",
        "security": [],
        "responses": {
          "200": {
            "description": "The process is up",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LivenessResponse"
                }
              }
            }
          }
        }
      }
    },
    "/health/detailed": {
      "get": {
        "operationId": "getHealthDetailed",
        "tags": [
          "Health"
        ],
        "summary": "Full diagnostics",
        "description": "Per-provider install and login state, circuit breakers, queue depth, the running build and\nuptime. Provider state comes from a cache refreshed by the background poller, so a login\nthat expired seconds ago can still read as authenticated.\n",
        "security": [
          {
            "BasicAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Diagnostics",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "description": "The health probe itself failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/docs/openapi.json": {
      "get": {
        "operationId": "getOpenApiSpec",
        "tags": [
          "Health"
        ],
        "summary": "This specification",
        "description": "The bundled OpenAPI document the running build was shipped with, so tooling can be pointed\nat a live instance instead of at a file in the repository.\n",
        "security": [],
        "responses": {
          "200": {
            "description": "The OpenAPI document",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/v1/models": {
      "get": {
        "operationId": "listModels",
        "tags": [
          "Models"
        ],
        "summary": "List available models",
        "description": "Model names that map to a real CLI model on an enabled provider. Any other id the\nprovider's CLI accepts also routes (for example `claude-sonnet-4-5-20250929`) but is\nnot listed here.\n",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Model list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedOpenAI"
          }
        }
      }
    },
    "/v1/chat/completions": {
      "post": {
        "operationId": "createChatCompletion",
        "tags": [
          "Chat Completions"
        ],
        "summary": "Create a chat completion",
        "description": "OpenAI-compatible endpoint. Sends the prompt to the requested provider\nCLI and returns the response in OpenAI format. Set `stream` to `true`\nfor server-sent events instead of one JSON body.\n",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChatCompletionRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Chat completion",
            "headers": {
              "X-Queue-Depth": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-Queue-Active": {
                "schema": {
                  "type": "integer"
                }
              },
              "x-shellm-queue-ms": {
                "description": "Milliseconds the request waited for a free CLI slot.",
                "schema": {
                  "type": "integer"
                }
              },
              "x-shellm-queue-position": {
                "description": "Where the request stood in the queue, or 0 when a slot was free. A streamed response carries this header before any body, since the wait happens after the headers flush.\n",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatCompletionResponse"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "description": "Sent when `stream` is `true`: `data:` lines carrying\n`chat.completion.chunk` objects, terminated by `data: [DONE]`.\n"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequestOpenAI"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedOpenAI"
          },
          "403": {
            "$ref": "#/components/responses/OriginNotAllowedOpenAI"
          },
          "413": {
            "description": "The body is larger than `SHELLM_MAX_CHAT_BODY_BYTES` (20 MiB by default). Checked after the\nkey, so an unauthenticated caller gets a 401 instead.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAIError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimitedOpenAI"
          },
          "502": {
            "description": "Provider CLI failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAIError"
                }
              }
            }
          },
          "503": {
            "description": "Provider unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAIError"
                }
              }
            }
          },
          "504": {
            "description": "Provider timeout",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAIError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/messages": {
      "post": {
        "operationId": "createMessage",
        "tags": [
          "Messages"
        ],
        "summary": "Create a message",
        "description": "Anthropic-compatible endpoint. Sends the prompt to the requested provider\nCLI and returns the response in Anthropic Messages format. Set `stream` to `true`\nfor server-sent events instead of one JSON body.\n",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessagesRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Message response",
            "headers": {
              "X-Queue-Depth": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-Queue-Active": {
                "schema": {
                  "type": "integer"
                }
              },
              "x-shellm-queue-ms": {
                "description": "Milliseconds the request waited for a free CLI slot.",
                "schema": {
                  "type": "integer"
                }
              },
              "x-shellm-queue-position": {
                "description": "Where the request stood in the queue, or 0 when a slot was free. A streamed response carries this header before any body, since the wait happens after the headers flush.\n",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessagesResponse"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "description": "Sent when `stream` is `true`: `message_start`, `content_block_start`, a run of\n`content_block_delta`, `content_block_stop`, `message_delta`, `message_stop`.\n"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequestAnthropic"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedAnthropic"
          },
          "403": {
            "$ref": "#/components/responses/OriginNotAllowedAnthropic"
          },
          "429": {
            "$ref": "#/components/responses/RateLimitedAnthropic"
          },
          "502": {
            "description": "Provider CLI failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnthropicError"
                }
              }
            }
          },
          "503": {
            "description": "Provider unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnthropicError"
                }
              }
            }
          },
          "504": {
            "description": "Provider timeout",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnthropicError"
                }
              }
            }
          }
        }
      }
    },
    "/admin/keys": {
      "get": {
        "operationId": "listKeys",
        "tags": [
          "Keys"
        ],
        "summary": "List all API keys",
        "security": [
          {
            "BasicAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Key list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "keys": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/KeyResponse"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "createKey",
        "tags": [
          "Keys"
        ],
        "summary": "Create a new API key",
        "description": "Returns the full `raw_key` in the response. This is the only time\nthe full key is shown — store it securely.\n",
        "security": [
          {
            "BasicAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KeyCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Key created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KeyCreateResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/admin/keys/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/KeyId"
        }
      ],
      "patch": {
        "operationId": "updateKey",
        "tags": [
          "Keys"
        ],
        "summary": "Update an API key",
        "security": [
          {
            "BasicAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KeyUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Key updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "key": {
                      "$ref": "#/components/schemas/KeyResponse"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "deleteKey",
        "tags": [
          "Keys"
        ],
        "summary": "Delete an API key",
        "security": [
          {
            "BasicAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Key deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "type": "boolean",
                      "example": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/admin/keys/{id}/rotate": {
      "parameters": [
        {
          "$ref": "#/components/parameters/KeyId"
        }
      ],
      "post": {
        "operationId": "rotateKey",
        "tags": [
          "Keys"
        ],
        "summary": "Rotate an API key",
        "description": "Generates a new key value for an existing key. The old key stops working immediately.",
        "security": [
          {
            "BasicAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Key rotated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KeyRotateResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/admin/logs": {
      "get": {
        "operationId": "listLogs",
        "tags": [
          "Logs"
        ],
        "summary": "List request logs",
        "security": [
          {
            "BasicAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/LogProvider"
          },
          {
            "$ref": "#/components/parameters/LogClient"
          },
          {
            "$ref": "#/components/parameters/LogModel"
          },
          {
            "$ref": "#/components/parameters/LogErrorCode"
          },
          {
            "$ref": "#/components/parameters/LogStatus"
          },
          {
            "$ref": "#/components/parameters/LogFrom"
          },
          {
            "$ref": "#/components/parameters/LogTo"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated logs",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LogsResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "clearLogs",
        "tags": [
          "Logs"
        ],
        "summary": "Delete all request logs",
        "description": "Deletes every row, ignoring the filters the list endpoint takes.",
        "security": [
          {
            "BasicAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Logs deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "type": "integer",
                      "description": "Number of log entries deleted"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/admin/stats": {
      "get": {
        "operationId": "getStats",
        "tags": [
          "Stats"
        ],
        "summary": "Aggregated usage statistics",
        "security": [
          {
            "BasicAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Statistics",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StatsResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/admin/logs/export": {
      "get": {
        "operationId": "exportLogs",
        "tags": [
          "Logs"
        ],
        "summary": "Export request logs as CSV",
        "description": "The same filters as `GET /admin/logs`, with no pagination — every matching row is streamed.\nFields that start with `=`, `+`, `-` or `@` are prefixed with a quote, so a spreadsheet opens\nthe file as data rather than as formulas.\n",
        "security": [
          {
            "BasicAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/LogProvider"
          },
          {
            "$ref": "#/components/parameters/LogClient"
          },
          {
            "$ref": "#/components/parameters/LogModel"
          },
          {
            "$ref": "#/components/parameters/LogErrorCode"
          },
          {
            "$ref": "#/components/parameters/LogStatus"
          },
          {
            "$ref": "#/components/parameters/LogFrom"
          },
          {
            "$ref": "#/components/parameters/LogTo"
          }
        ],
        "responses": {
          "200": {
            "description": "CSV, one header row and one row per log entry",
            "headers": {
              "Content-Disposition": {
                "schema": {
                  "type": "string",
                  "example": "attachment; filename=\"shellm-logs-2026-09-20T03-30-12.csv\""
                }
              }
            },
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/admin/audit": {
      "get": {
        "operationId": "listAuditLog",
        "tags": [
          "Logs"
        ],
        "summary": "Admin audit log",
        "description": "Key and provider changes made through the admin API, newest first.",
        "security": [
          {
            "BasicAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 100,
              "maximum": 500
            }
          },
          {
            "name": "resource_id",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Only entries about this resource."
          }
        ],
        "responses": {
          "200": {
            "description": "Audit entries",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "logs": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AuditEntry"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/admin/providers": {
      "get": {
        "operationId": "listProviders",
        "tags": [
          "Providers"
        ],
        "summary": "List providers",
        "description": "Every configured provider with its health, its circuit breaker, when it was last used and\nwhich models it accepts.\n",
        "security": [
          {
            "BasicAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Provider list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "providers": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ProviderDetail"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/admin/providers/check": {
      "post": {
        "operationId": "runProviderChecks",
        "tags": [
          "Providers"
        ],
        "summary": "Run all provider checks now",
        "description": "Probes every provider with the same sign-in check the health poll runs and re-reads each\nCLI's version, which is otherwise read once per process. No request reaches a model, so no\nquota is spent. The results are read back from `GET /admin/providers`.\n",
        "security": [
          {
            "BasicAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The checks ran",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "checked_at"
                  ],
                  "properties": {
                    "checked_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "description": "A check is already running. `code` is `check_in_progress`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/admin/providers/{name}": {
      "patch": {
        "operationId": "updateProvider",
        "tags": [
          "Providers"
        ],
        "summary": "Enable, disable or reprioritize a provider",
        "security": [
          {
            "BasicAuth": []
          }
        ],
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "claude"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProviderUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The provider as stored",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "provider": {
                      "$ref": "#/components/schemas/ProviderRecord"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/admin/config": {
      "get": {
        "operationId": "getConfig",
        "tags": [
          "Config"
        ],
        "summary": "Every setting, the value in effect, and where it came from",
        "description": "The same view `shellm config` prints. `unseen` is the bounded list the dashboard leads with:\nsettings absent from the operator's config file that were introduced by the running release or\na later one — what an update added and a config file written before it will never mention.\n\nSecrets are masked before they leave the process.\n",
        "security": [
          {
            "BasicAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The configuration surface",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "running": {
                      "type": "string",
                      "example": "v1.10.0"
                    },
                    "config_file": {
                      "type": "string",
                      "example": "/home/shellmer/.config/shellm/env"
                    },
                    "restart_command": {
                      "type": "string",
                      "description": "What applies a config change on this host. The file is read once, at startup, so\nevery edit needs it — and it is not the same command under systemd as it is on a\nlaptop.\n",
                      "enum": [
                        "Ctrl+C, then shellm start",
                        "sudo systemctl restart shellm"
                      ]
                    },
                    "settings": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ConfigSetting"
                      }
                    },
                    "unseen": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "example": [
                        "SHELLM_CORS_ORIGINS"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/admin/update": {
      "get": {
        "operationId": "getUpdateStatus",
        "tags": [
          "Update"
        ],
        "summary": "Can this host update itself, and what happened last time",
        "security": [
          {
            "BasicAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Updater state",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpdateStatus"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "operationId": "requestUpdate",
        "tags": [
          "Update"
        ],
        "summary": "Request an update to a release tag",
        "description": "Writes the request and returns. It does not deploy — a root systemd unit picks the request up,\nor nothing does. Only upgrades are accepted here: rolling back stays on the SSH path\n(`SHELLM_REF=vX.Y.Z sudo shellm update`), so the worst outcome of a forged request is an\nupgrade rather than a downgrade to a release with a known hole.\n",
        "security": [
          {
            "BasicAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The request was written",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "requested": {
                      "type": "string"
                    },
                    "from": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "description": "The request cannot be taken right now. `code` says which case: `updater_unavailable`,\n`update_pending`, `update_running`, `not_an_upgrade` or `confirmation_required`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The request could not be written to disk",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/admin/health": {
      "get": {
        "operationId": "getAdminHealth",
        "tags": [
          "Providers"
        ],
        "summary": "Full diagnostics, for the dashboard",
        "description": "The same payload as `GET /health/detailed`. It exists so the dashboard reaches everything it\nneeds under `/admin`; new integrations should call `/health/detailed`.\n",
        "security": [
          {
            "BasicAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Diagnostics",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "description": "The health probe itself failed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Client API key (e.g. `shellm-a1b2c3…`)"
      },
      "BasicAuth": {
        "type": "http",
        "scheme": "basic",
        "description": "Admin password via `SHELLM_ADMIN_PASSWORD`"
      }
    },
    "schemas": {
      "LivenessResponse": {
        "type": "object",
        "required": [
          "status"
        ],
        "properties": {
          "status": {
            "type": "string",
            "const": "ok"
          }
        }
      },
      "HealthResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok",
              "degraded",
              "down"
            ],
            "description": "`degraded` when some enabled provider is not authenticated, `down` when none is,\n`ok` when all are — or when no provider is enabled at all.\n"
          },
          "timezone": {
            "type": "string",
            "description": "IANA zone the dashboard renders every timestamp in, from `SHELLM_TZ`. Defaults to\n`America/Mexico_City`; an unknown name falls back to it rather than failing the request.\n",
            "example": "America/Mexico_City"
          },
          "providers": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/ProviderStatus"
            }
          },
          "build": {
            "type": "object",
            "properties": {
              "version": {
                "type": "string"
              },
              "commit": {
                "type": "string"
              }
            }
          },
          "circuit_breakers": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "state": {
                  "type": "string",
                  "enum": [
                    "closed",
                    "open",
                    "half_open"
                  ]
                },
                "failures": {
                  "type": "integer"
                },
                "lastFailureAt": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "lastTransitionAt": {
                  "type": [
                    "integer",
                    "null"
                  ]
                }
              }
            }
          },
          "queue": {
            "type": "object",
            "properties": {
              "pending": {
                "type": "integer"
              },
              "active": {
                "type": "integer"
              },
              "max_concurrent": {
                "type": "integer"
              },
              "timeout_ms": {
                "type": "integer",
                "description": "TIMEOUT_MS, the age at which a running CLI process is killed."
              },
              "in_flight": {
                "type": "array",
                "description": "Every request running or waiting for a slot, running first (oldest first), then the line in order. Held in memory only: a restart clears it.\n",
                "items": {
                  "type": "object",
                  "properties": {
                    "state": {
                      "type": "string",
                      "enum": [
                        "running",
                        "queued"
                      ]
                    },
                    "age_ms": {
                      "type": "integer"
                    },
                    "position": {
                      "type": "integer",
                      "description": "Place in line; only on a queued request."
                    },
                    "request_id": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "client": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "path": {
                      "type": "string"
                    },
                    "stream": {
                      "type": "boolean"
                    },
                    "provider": {
                      "type": "string"
                    },
                    "model": {
                      "type": "string"
                    }
                  }
                }
              },
              "oldest_age_ms": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Age of the longest-running request; null when none is running."
              }
            }
          },
          "uptime_seconds": {
            "type": "integer"
          }
        }
      },
      "ProviderStatus": {
        "type": "object",
        "properties": {
          "installed": {
            "type": "boolean"
          },
          "authenticated": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "`null` when the probe could not decide. Only an unmistakable credential failure reads as\n`false`; any other probe error leaves it unknown rather than taking the provider offline.\n"
          },
          "enabled": {
            "type": "boolean"
          },
          "version": {
            "type": [
              "string",
              "null"
            ],
            "description": "What the CLI reports for `--version`, which costs no quota."
          },
          "error": {
            "type": "string",
            "description": "The probe's stderr, truncated and with anything key-shaped redacted."
          }
        }
      },
      "ProviderDetail": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "example": "claude"
          },
          "type": {
            "type": "string"
          },
          "enabled": {
            "type": "boolean"
          },
          "capabilities": {
            "type": [
              "string",
              "null"
            ]
          },
          "priority": {
            "type": "integer",
            "description": "Lower wins when a request names no provider."
          },
          "installed": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "authenticated": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "health_error": {
            "type": [
              "string",
              "null"
            ]
          },
          "version": {
            "type": [
              "string",
              "null"
            ]
          },
          "checked_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the last sign-in probe ran."
          },
          "passed_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "When a probe last found the CLI installed and signed in."
          },
          "last_used_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_status": {
            "type": [
              "integer",
              "null"
            ]
          },
          "circuit": {
            "type": "object",
            "properties": {
              "state": {
                "type": "string",
                "enum": [
                  "closed",
                  "open",
                  "half_open"
                ]
              },
              "failures": {
                "type": "integer"
              },
              "lastFailureAt": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "lastTransitionAt": {
                "type": [
                  "integer",
                  "null"
                ]
              }
            }
          },
          "models": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The model ids this provider's CLI accepts, as `/v1/models` lists them."
          }
        }
      },
      "ProviderRecord": {
        "type": "object",
        "description": "The stored row, without the health and circuit fields the list endpoint adds.",
        "properties": {
          "name": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "subprocess",
              "http"
            ]
          },
          "enabled": {
            "description": "A boolean on the enable/disable path, the stored 0/1 on a general update.",
            "oneOf": [
              {
                "type": "boolean"
              },
              {
                "type": "integer",
                "enum": [
                  0,
                  1
                ]
              }
            ]
          },
          "capabilities": {
            "type": "string"
          },
          "health_check": {
            "type": "string"
          },
          "priority": {
            "type": "integer"
          },
          "updated_at": {
            "type": "string"
          }
        }
      },
      "ProviderUpdate": {
        "type": "object",
        "minProperties": 1,
        "properties": {
          "enabled": {
            "description": "A disabled provider answers 503 without spawning its CLI.",
            "oneOf": [
              {
                "type": "boolean"
              },
              {
                "type": "integer",
                "enum": [
                  0,
                  1
                ]
              }
            ]
          },
          "capabilities": {
            "type": "string"
          },
          "priority": {
            "type": "integer"
          }
        }
      },
      "ModelList": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Model"
            }
          }
        }
      },
      "Model": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "claude"
          },
          "object": {
            "type": "string",
            "enum": [
              "model"
            ]
          },
          "created": {
            "type": "integer",
            "example": 0
          },
          "owned_by": {
            "type": "string",
            "example": "shellm"
          },
          "context_window": {
            "type": "integer",
            "description": "Input tokens the model takes. 200000 when nothing more specific is known.",
            "example": 200000
          },
          "context_window_1m": {
            "type": "integer",
            "description": "The window of the model's 1M-context variant, present only when the CLI lists one for this account. Used by default; with SHELLM_CLAUDE_LONG_CONTEXT=false, ask for it with the header `anthropic-beta: context-1m-<date>` on either endpoint, as the Anthropic API does. The model id stays the same.\n",
            "example": 1000000
          },
          "max_output_tokens": {
            "type": "integer",
            "description": "The output cap, when the manifest records one."
          },
          "reasoning_efforts": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The reasoning efforts the model accepts."
          },
          "default_reasoning_effort": {
            "type": "string"
          },
          "input_modalities": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "x_shellm": {
            "type": "object",
            "properties": {
              "sources": {
                "type": "object",
                "description": "Where each limit above came from: `cli` (the CLI reported it), `manifest` (the hand-kept config/model-limits.yaml) or `default`. Best effort and informational; the CLI still validates every request.\n",
                "additionalProperties": {
                  "type": "string",
                  "enum": [
                    "cli",
                    "manifest",
                    "default"
                  ]
                }
              }
            }
          }
        }
      },
      "ChatCompletionRequest": {
        "type": "object",
        "required": [
          "model",
          "messages"
        ],
        "properties": {
          "model": {
            "type": "string",
            "description": "Provider name, or a model id the provider's CLI accepts",
            "example": "claude-haiku"
          },
          "messages": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/ChatRequestMessage"
            },
            "description": "Must contain at least one `user` message. The first `system` message becomes the system\nprompt; the rest are flattened into one prompt, since the CLI takes a prompt, not a thread.\n"
          },
          "max_tokens": {
            "type": "integer",
            "minimum": 1,
            "maximum": 128000,
            "description": "Validated and then ignored — no CLI exposes an output cap. Bound the length in the prompt.\n"
          },
          "stream": {
            "type": "boolean",
            "default": false,
            "description": "`true` answers `text/event-stream` with OpenAI-style `data:` chunks and a `[DONE]`\nterminator. Streaming holds a slot for the whole response and is capped separately by\n`MAX_STREAM_CONCURRENT`.\n"
          },
          "stream_options": {
            "type": "object",
            "description": "Only valid with `stream: true`; sending it otherwise is a 400. With `include_usage: true` a final chunk with an empty `choices` array carries `usage` and `x_shellm`.\n",
            "properties": {
              "include_usage": {
                "type": "boolean"
              }
            }
          },
          "reasoning_effort": {
            "type": "string",
            "enum": [
              "minimal",
              "low",
              "medium",
              "high"
            ],
            "description": "How long the model reasons before answering: --effort for Claude, model_reasoning_effort for Codex. minimal runs as low, since Claude has nothing below it and Codex's default model refuses it. Omitted, Claude uses SHELLM_CLAUDE_EFFORT (medium) and Codex its own configuration. Reasoning is billed as completion tokens and nothing streams while it runs.\n"
          },
          "temperature": {
            "type": "number",
            "minimum": 0,
            "maximum": 2
          },
          "top_p": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          },
          "response_format": {
            "type": "object",
            "required": [
              "type"
            ],
            "description": "`json_schema` constrains the answer to a JSON Schema and returns it as a string in\n`choices[0].message.content`. Codex applies OpenAI's strict-mode rules whatever `strict`\nsays. Every refusal of this field is a 400 whose message names `response_format`.\n",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "json_schema",
                  "json_object",
                  "text"
                ]
              },
              "json_schema": {
                "type": "object",
                "description": "Required when `type` is `json_schema`.",
                "required": [
                  "name",
                  "schema"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "pattern": "^[a-zA-Z0-9_-]{1,64}$"
                  },
                  "schema": {
                    "type": "object",
                    "description": "A JSON Schema, at most 100 KiB serialized.",
                    "additionalProperties": true
                  },
                  "strict": {
                    "type": "boolean"
                  }
                }
              }
            }
          },
          "stop": {
            "description": "Up to 4 stop sequences.",
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "maxItems": 4,
                "items": {
                  "type": "string"
                }
              }
            ]
          }
        }
      },
      "ChatMessage": {
        "type": "object",
        "required": [
          "role",
          "content"
        ],
        "properties": {
          "role": {
            "type": "string",
            "enum": [
              "system",
              "user",
              "assistant"
            ]
          },
          "content": {
            "type": "string"
          }
        }
      },
      "ChatRequestMessage": {
        "type": "object",
        "required": [
          "role",
          "content"
        ],
        "properties": {
          "role": {
            "type": "string",
            "enum": [
              "system",
              "user",
              "assistant"
            ]
          },
          "content": {
            "description": "A string, or an array of parts. `image_url` parts are accepted in `user` messages only and\nonly as base64 `data:` URLs of JPEG, PNG, WebP or GIF — a remote URL is a 400, because\nSheLLM does not fetch on a caller's behalf. `SHELLM_MAX_IMAGES` caps the count per request\nand `SHELLM_MAX_IMAGE_BYTES` the decoded size of each; every image refusal is a 400 whose\nmessage names the image.\n",
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/ContentBlock"
                    },
                    {
                      "$ref": "#/components/schemas/ImageUrlPart"
                    }
                  ]
                }
              }
            ]
          }
        }
      },
      "ImageUrlPart": {
        "type": "object",
        "required": [
          "type",
          "image_url"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "image_url"
            ]
          },
          "image_url": {
            "type": "object",
            "required": [
              "url"
            ],
            "properties": {
              "url": {
                "type": "string",
                "pattern": "^data:image/(jpeg|png|webp|gif);base64,",
                "example": "data:image/png;base64,iVBORw0KGgo..."
              },
              "detail": {
                "type": "string",
                "description": "Accepted and ignored."
              }
            }
          }
        }
      },
      "ChatCompletionResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "shellm-abc123"
          },
          "object": {
            "type": "string",
            "enum": [
              "chat.completion"
            ]
          },
          "created": {
            "type": "integer"
          },
          "model": {
            "type": "string"
          },
          "choices": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "index": {
                  "type": "integer"
                },
                "message": {
                  "$ref": "#/components/schemas/ChatMessage"
                },
                "finish_reason": {
                  "type": "string",
                  "enum": [
                    "stop"
                  ]
                }
              }
            }
          },
          "usage": {
            "$ref": "#/components/schemas/Usage"
          },
          "x_shellm": {
            "$ref": "#/components/schemas/ShellmMeta"
          }
        }
      },
      "Usage": {
        "type": "object",
        "properties": {
          "prompt_tokens": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Every input token, the cached ones included."
          },
          "completion_tokens": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Every output token, the model's reasoning included."
          },
          "total_tokens": {
            "type": [
              "integer",
              "null"
            ]
          },
          "prompt_tokens_details": {
            "type": "object",
            "properties": {
              "cached_tokens": {
                "type": "integer",
                "description": "Input tokens read from the provider's prompt cache."
              }
            }
          },
          "completion_tokens_details": {
            "type": "object",
            "description": "Present when the CLI reports its reasoning apart.",
            "properties": {
              "reasoning_tokens": {
                "type": "integer",
                "description": "The part of completion_tokens spent reasoning, which the answer does not show."
              }
            }
          }
        }
      },
      "MessagesRequest": {
        "type": "object",
        "required": [
          "model",
          "messages",
          "max_tokens"
        ],
        "properties": {
          "model": {
            "type": "string",
            "example": "claude"
          },
          "messages": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/AnthropicMessage"
            }
          },
          "max_tokens": {
            "type": "integer",
            "minimum": 1,
            "maximum": 128000
          },
          "system": {
            "type": "string",
            "description": "Optional system prompt"
          },
          "stream": {
            "type": "boolean",
            "default": false,
            "description": "`true` answers `text/event-stream` with Anthropic's named events. `message_start` is sent\nbefore the CLI has produced anything, so wait for `content_block_delta` to call it the first\ntoken. Streaming is capped separately by `MAX_STREAM_CONCURRENT`.\n"
          }
        }
      },
      "AnthropicMessage": {
        "type": "object",
        "required": [
          "role",
          "content"
        ],
        "properties": {
          "role": {
            "type": "string",
            "enum": [
              "user",
              "assistant"
            ]
          },
          "content": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ContentBlock"
                }
              }
            ]
          }
        }
      },
      "ContentBlock": {
        "type": "object",
        "required": [
          "type",
          "text"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "text"
            ]
          },
          "text": {
            "type": "string"
          }
        }
      },
      "MessagesResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "msg_shellm-abc123"
          },
          "type": {
            "type": "string",
            "enum": [
              "message"
            ]
          },
          "role": {
            "type": "string",
            "enum": [
              "assistant"
            ]
          },
          "content": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContentBlock"
            }
          },
          "model": {
            "type": "string"
          },
          "stop_reason": {
            "type": "string",
            "enum": [
              "end_turn"
            ]
          },
          "stop_sequence": {
            "type": [
              "null"
            ]
          },
          "usage": {
            "type": "object",
            "properties": {
              "input_tokens": {
                "type": "integer",
                "description": "Input tokens not served from or written to the cache, as the Anthropic API counts them."
              },
              "cache_creation_input_tokens": {
                "type": "integer"
              },
              "cache_read_input_tokens": {
                "type": "integer"
              },
              "output_tokens": {
                "type": "integer"
              }
            }
          },
          "x_shellm": {
            "$ref": "#/components/schemas/ShellmMeta"
          }
        }
      },
      "KeyCreate": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Unique client name",
            "example": "my-app"
          },
          "rpm": {
            "type": "integer",
            "minimum": 1,
            "description": "Requests per minute (default 10)"
          },
          "models": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Restrict key to specific models"
          },
          "origins": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string",
              "format": "uri"
            },
            "description": "Browser origins allowed to use this key (scheme://host[:port], exact match). null or absent means any caller. A request carrying another Origin gets 403 origin_not_allowed; a request with no Origin is accepted, so this scopes pages rather than authenticating callers.\n",
            "example": [
              "https://you.github.io"
            ]
          }
        }
      },
      "KeyUpdate": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "description": "Renames the key. Must be unique. The key's request logs are relabelled with it, so its history follows the key.\n"
          },
          "rpm": {
            "type": "integer",
            "minimum": 1
          },
          "models": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            }
          },
          "origins": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string",
              "format": "uri"
            },
            "description": "Browser origins allowed to use this key (scheme://host[:port], exact match). null or absent means any caller. A request carrying another Origin gets 403 origin_not_allowed; a request with no Origin is accepted, so this scopes pages rather than authenticating callers.\n",
            "example": [
              "https://you.github.io"
            ]
          },
          "active": {
            "type": "integer",
            "enum": [
              0,
              1
            ]
          }
        }
      },
      "KeyResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "key_prefix": {
            "type": "string",
            "example": "shellm-a1"
          },
          "rpm": {
            "type": "integer"
          },
          "models": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            }
          },
          "origins": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string",
              "format": "uri"
            },
            "description": "Browser origins allowed to use this key (scheme://host[:port], exact match). null or absent means any caller. A request carrying another Origin gets 403 origin_not_allowed; a request with no Origin is accepted, so this scopes pages rather than authenticating callers.\n",
            "example": [
              "https://you.github.io"
            ]
          },
          "active": {
            "type": "integer",
            "enum": [
              0,
              1
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "KeyCreateResponse": {
        "type": "object",
        "properties": {
          "key": {
            "allOf": [
              {
                "$ref": "#/components/schemas/KeyResponse"
              },
              {
                "type": "object",
                "properties": {
                  "raw_key": {
                    "type": "string",
                    "description": "Full API key (shown only once)",
                    "example": "shellm-a1b2c3d4e5f6…"
                  }
                }
              }
            ]
          }
        }
      },
      "KeyRotateResponse": {
        "type": "object",
        "properties": {
          "key": {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer"
              },
              "raw_key": {
                "type": "string"
              },
              "key_prefix": {
                "type": "string"
              }
            }
          }
        }
      },
      "LogEntry": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "request_id": {
            "type": "string"
          },
          "client_name": {
            "type": "string"
          },
          "provider": {
            "type": "string"
          },
          "model": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "duration_ms": {
            "type": "integer"
          },
          "queued_ms": {
            "type": "integer"
          },
          "tokens": {
            "type": "integer"
          },
          "cost_usd": {
            "type": "number"
          },
          "cost_estimate_usd": {
            "type": [
              "number",
              "null"
            ],
            "description": "API list-price estimate for a request the CLI did not price (ADR-0012)."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "LogsResponse": {
        "type": "object",
        "properties": {
          "logs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LogEntry"
            }
          },
          "total": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        }
      },
      "StatsResponse": {
        "type": "object",
        "properties": {
          "window": {
            "type": "object",
            "description": "The single window every figure below is measured over: every request still in the database. Retention bounds it, so there is no period to choose.",
            "properties": {
              "from": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "description": "Oldest surviving request. Null when nothing has been recorded."
              },
              "to": {
                "type": "string",
                "format": "date-time"
              },
              "hours": {
                "type": "number",
                "description": "Hours actually observed, which is what cost_burn_rate divides by."
              },
              "requests": {
                "type": "integer"
              },
              "retention_days": {
                "type": "integer"
              }
            }
          },
          "total_requests": {
            "type": "integer"
          },
          "total_tokens": {
            "type": "integer"
          },
          "total_cost_usd": {
            "type": "number"
          },
          "avg_duration_ms": {
            "type": "integer"
          },
          "by_provider": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            }
          },
          "by_status": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            }
          },
          "active_clients": {
            "type": "integer"
          }
        }
      },
      "AuditEntry": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "action": {
            "type": "string",
            "example": "rotate"
          },
          "resource": {
            "type": "string",
            "example": "key"
          },
          "resource_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "details": {
            "type": [
              "string",
              "null"
            ],
            "description": "A JSON string of whatever the action recorded."
          },
          "created_at": {
            "type": "string"
          }
        }
      },
      "UpdateStatus": {
        "type": "object",
        "properties": {
          "trigger": {
            "type": "string",
            "enum": [
              "ready",
              "missing",
              "disabled"
            ],
            "description": "Whether the root unit that performs the update is installed and enabled."
          },
          "trigger_help": {
            "type": [
              "string",
              "null"
            ],
            "description": "What to run to fix a trigger that is not `ready`. `null` when it is."
          },
          "running": {
            "type": "object",
            "properties": {
              "version": {
                "type": "string"
              },
              "commit": {
                "type": "string"
              }
            }
          },
          "pending": {
            "type": "boolean",
            "description": "A request is already on disk, waiting to be picked up."
          },
          "last": {
            "type": [
              "object",
              "null"
            ],
            "description": "What the previous run reported, as the runner left it on disk."
          }
        }
      },
      "UpdateRequest": {
        "type": "object",
        "required": [
          "ref"
        ],
        "properties": {
          "ref": {
            "type": "string",
            "pattern": "^v[0-9]+\\.[0-9]+\\.[0-9]+$",
            "example": "v1.4.0"
          },
          "confirm": {
            "type": "string",
            "description": "Required, and equal to `ref`, when the target is a major version jump."
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "request_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "retry_after": {
            "type": "integer",
            "description": "Present only on 429 responses"
          }
        }
      },
      "OpenAIError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "message": {
                "type": "string"
              },
              "type": {
                "type": "string",
                "enum": [
                  "invalid_request_error",
                  "authentication_error",
                  "rate_limit_error",
                  "server_error"
                ]
              },
              "code": {
                "type": "string"
              },
              "param": {
                "type": [
                  "null"
                ]
              }
            }
          }
        }
      },
      "AnthropicError": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "error"
            ]
          },
          "error": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "invalid_request_error",
                  "authentication_error",
                  "rate_limit_error",
                  "api_error"
                ]
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "ShellmMeta": {
        "type": "object",
        "description": "SheLLM's own numbers for the request, namespaced so no SDK collides with them. Every key is always present; a value SheLLM could not measure is null rather than absent. On a streamed response the same block rides the final chunk (OpenAI) or the message_delta event (Anthropic).\n",
        "properties": {
          "cost_usd": {
            "type": [
              "number",
              "null"
            ],
            "description": "What the request would have cost on the provider's API, when the CLI reports it.",
            "example": 0.0042
          },
          "cost_estimate_usd": {
            "type": [
              "number",
              "null"
            ],
            "description": "What the request would have cost at the API's list price, estimated by SheLLM from the token counts when the CLI reports no cost (codex). Null whenever cost_usd is set, or the model has no listed price. An estimate, not a charge; never summed into cost_usd.\n",
            "example": 0.012586
          },
          "queue_ms": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Time spent waiting for a free CLI slot."
          },
          "cli_ms": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Time the CLI itself took, queueing excluded."
          },
          "ttft_ms": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Time to the first token. Streamed responses only; null otherwise."
          }
        }
      },
      "ConfigSetting": {
        "type": "object",
        "description": "One configuration setting as src/config/schema.js declares it, with the value currently in\neffect. A secret's value is always the literal mask and never the real one.\n",
        "properties": {
          "name": {
            "type": "string",
            "example": "SHELLM_CORS_ORIGINS"
          },
          "value": {
            "description": "The value in effect, coerced to the declared type. `null` when nothing sets it.",
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "integer"
              },
              {
                "type": "boolean"
              },
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              {
                "type": "null"
              }
            ]
          },
          "source": {
            "type": "string",
            "description": "Where the value came from, after PostgreSQL's pg_settings.",
            "enum": [
              "default",
              "config file",
              "environment"
            ]
          },
          "since": {
            "type": "string",
            "description": "The release whose code first read this setting.",
            "example": "v1.10.0"
          },
          "reload": {
            "type": "string",
            "description": "Whether the code re-reads the value while running (`live`) or froze it at startup\n(`restart`). Editing the config file needs a restart either way — it is read once, at boot.\n",
            "enum": [
              "live",
              "restart"
            ]
          },
          "describe": {
            "type": "string"
          },
          "example": {
            "type": [
              "string",
              "null"
            ],
            "description": "What to write after the equals sign when the default is empty. Null for a secret."
          },
          "secret": {
            "type": "boolean",
            "description": "When true, `value` is a mask and the real value never leaves the process."
          }
        }
      }
    },
    "parameters": {
      "KeyId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "integer"
        }
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 200,
          "default": 50
        }
      },
      "Offset": {
        "name": "offset",
        "in": "query",
        "schema": {
          "type": "integer",
          "default": 0
        }
      },
      "LogProvider": {
        "name": "provider",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "Filter by provider name"
      },
      "LogClient": {
        "name": "client",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "Filter by client name"
      },
      "LogModel": {
        "name": "model",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "Filter by the requested model or the one the CLI actually ran"
      },
      "LogErrorCode": {
        "name": "error_code",
        "in": "query",
        "schema": {
          "type": "string",
          "example": "cli_failed"
        },
        "description": "Filter by SheLLM error code"
      },
      "LogStatus": {
        "name": "status",
        "in": "query",
        "schema": {
          "type": "string",
          "pattern": "^\\d$|^\\d{3}$"
        },
        "description": "Status class (2–5) or exact code (e.g. 200, 429). Anything else is ignored."
      },
      "LogFrom": {
        "name": "from",
        "in": "query",
        "schema": {
          "type": "string",
          "format": "date-time"
        },
        "description": "Start date (ISO 8601), compared against created_at"
      },
      "LogTo": {
        "name": "to",
        "in": "query",
        "schema": {
          "type": "string",
          "format": "date-time"
        },
        "description": "End date (ISO 8601), compared against created_at"
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing or invalid credentials",
        "headers": {
          "X-Request-Id": {
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limit exceeded",
        "headers": {
          "X-Request-Id": {
            "schema": {
              "type": "string"
            }
          },
          "Retry-After": {
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "BadRequest": {
        "description": "Invalid request",
        "headers": {
          "X-Request-Id": {
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found",
        "headers": {
          "X-Request-Id": {
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "UnauthorizedOpenAI": {
        "description": "Missing or invalid credentials",
        "headers": {
          "X-Request-Id": {
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/OpenAIError"
            }
          }
        }
      },
      "BadRequestOpenAI": {
        "description": "Invalid request",
        "headers": {
          "X-Request-Id": {
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/OpenAIError"
            }
          }
        }
      },
      "OriginNotAllowedOpenAI": {
        "description": "The key is scoped to a set of browser origins and the request carried another one. Only a request that sends an Origin header can hit this.\n",
        "headers": {
          "X-Request-Id": {
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/OpenAIError"
            }
          }
        }
      },
      "RateLimitedOpenAI": {
        "description": "Rate limit exceeded",
        "headers": {
          "X-Request-Id": {
            "schema": {
              "type": "string"
            }
          },
          "Retry-After": {
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/OpenAIError"
            }
          }
        }
      },
      "BadRequestAnthropic": {
        "description": "Invalid request",
        "headers": {
          "X-Request-Id": {
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AnthropicError"
            }
          }
        }
      },
      "UnauthorizedAnthropic": {
        "description": "Missing or invalid credentials",
        "headers": {
          "X-Request-Id": {
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AnthropicError"
            }
          }
        }
      },
      "OriginNotAllowedAnthropic": {
        "description": "The key is scoped to a set of browser origins and the request carried another one. Only a request that sends an Origin header can hit this.\n",
        "headers": {
          "X-Request-Id": {
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AnthropicError"
            }
          }
        }
      },
      "RateLimitedAnthropic": {
        "description": "Rate limit exceeded",
        "headers": {
          "X-Request-Id": {
            "schema": {
              "type": "string"
            }
          },
          "Retry-After": {
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AnthropicError"
            }
          }
        }
      }
    }
  },
  "x-tagGroups": [
    {
      "name": "API",
      "tags": [
        "Health",
        "Chat Completions",
        "Messages",
        "Models"
      ]
    },
    {
      "name": "Admin",
      "tags": [
        "Keys",
        "Logs",
        "Stats",
        "Providers",
        "Update"
      ]
    }
  ]
}