openapi: 3.1.0
info:
  title: OmniRoute API
  version: 3.8.49
  description: |
    OmniRoute is a local-first AI API proxy router. It provides an OpenAI-compatible
    endpoint that routes requests to multiple AI providers with load balancing,
    failover, and usage tracking.

    ## Base URLs
    - **Local**: `http://localhost:20128`

    ## Authentication
    All proxy endpoints require a Bearer token (API key managed via the dashboard).
    Management endpoints are protected when `requireLogin` is enabled.
  contact:
    name: OmniRoute
  license:
    name: MIT

servers:
  - url: http://localhost:20128
    description: Local development

tags:
  - name: Playground
    description: Playground Studio — preset management and prompt improvement
  - name: Memory
    description: Conversational memory management — CRUD, engine status, playground preview, summarization, reindex, and Qdrant settings (plan 21 — v3.8.6). All routes require management auth.
  - name: Chat
    description: OpenAI-compatible chat completions
  - name: Messages
    description: Anthropic-compatible messages
  - name: Responses
    description: OpenAI Responses API
  - name: Embeddings
    description: Text embedding generation
  - name: Images
    description: Image generation
  - name: Audio
    description: Audio speech and transcription
  - name: Moderations
    description: Content moderation
  - name: Rerank
    description: Document reranking
  - name: Models
    description: Available model listing
  - name: Providers
    description: Provider connection management
  - name: Provider Nodes
    description: Provider node configuration
  - name: Proxy Subscriptions
    description: >-
      Operator-supplied proxy subscription links (Karing-style). Nodes are fetched,
      parsed, and synced into the provider/account pool on create, refresh, or the
      background scheduler tick.
  - name: API Keys
    description: API key management
  - name: Combos
    description: Routing combo management
  - name: Settings
    description: Application settings
  - name: Compression
    description: Prompt compression, RTK filters, Caveman rules, and compression combos
  - name: Usage
    description: Usage analytics and logs
  - name: Translator
    description: Format translation debug & testing
  - name: CLI Tools
    description: CLI tool configuration management
  - name: Embedded Services
    description: >-
      Install, start, stop, and monitor locally-running embedded services (9Router, CLIProxyAPI).
      All routes are LOCAL_ONLY — accessible from loopback only (hard rule #17).
  - name: OAuth
    description: OAuth flows for provider authentication
  - name: System
    description: System management (restart, shutdown, backup)
  - name: Pricing
    description: Model pricing configuration
  - name: Cloud
    description: Cloud worker authentication and sync
  - name: Fallback
    description: Fallback chain management
  - name: Telemetry
    description: Telemetry and token health monitoring
  - name: Agent Skills
    description: >-
      Agent Skills catalog — 42 SKILL.md files (22 REST API + 20 CLI) for external agents,
      MCP clients, and A2A orchestrators to discover OmniRoute capabilities.
  - name: AgentBridge
    description: >-
      MITM proxy manager for 9 IDE agents (Antigravity, Kiro, Copilot, Codex, Cursor, Zed,
      Claude Code, Open Code, Trae). Controls server lifecycle, DNS/model mappings, bypass list,
      and cert management. All routes are LOCAL_ONLY + SPAWN_CAPABLE (hard rules #15, #17).
      See docs/frameworks/AGENTBRIDGE.md.
  - name: Traffic Inspector
    description: >-
      LLM-aware HTTPS traffic debugger with 4 capture modes (AgentBridge, Custom Hosts,
      HTTP_PROXY :8080, System-wide). Provides real-time WebSocket stream, session recording,
      HAR export, SSE merge, and conversation normalization.
      All routes are LOCAL_ONLY + SPAWN_CAPABLE (hard rules #15, #17).
      See docs/frameworks/TRAFFIC_INSPECTOR.md.

paths:
  # --- Playground + Search Tools (plans 17+18) ---
  /api/playground/improve-prompt:
    post:
      tags:
        - Playground
      summary: Improve prompt via LLM
      description: |
        Rewrites the supplied system prompt and/or user prompt using a meta-prompt
        (inspired by Anthropic Console Prompt Improver). Internally calls
        `/v1/chat/completions` with the model specified in the request body.
        Quota is consumed from the caller's account.
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - model
              properties:
                system:
                  type: string
                  maxLength: 50000
                  description: System prompt to improve (at least one of system/prompt required)
                prompt:
                  type: string
                  maxLength: 50000
                  description: User prompt to improve
                model:
                  type: string
                  description: Model to use for the improvement call (e.g. openai/gpt-4o)
                tone:
                  type: string
                  enum:
                    - concise
                    - detailed
                  default: concise
      responses:
        "200":
          description: Improved prompt(s)
          content:
            application/json:
              schema:
                type: object
                properties:
                  improvedSystem:
                    type: string
                  improvedPrompt:
                    type: string
                  tokensIn:
                    type: integer
                  tokensOut:
                    type: integer
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /api/playground/presets:
    get:
      tags:
        - Playground
      summary: List playground presets
      description: Returns all saved playground presets ordered by creation date (newest first).
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Preset list
          content:
            application/json:
              schema:
                type: object
                properties:
                  presets:
                    type: array
                    items:
                      $ref: "#/components/schemas/PlaygroundPreset"
        "401":
          $ref: "#/components/responses/Unauthorized"
    post:
      tags:
        - Playground
      summary: Create playground preset
      description: Saves the current playground configuration as a named preset in the database.
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PlaygroundPresetCreate"
      responses:
        "201":
          description: Created preset
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlaygroundPreset"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /api/playground/presets/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    get:
      tags:
        - Playground
      summary: Get playground preset
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Preset found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlaygroundPreset"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          description: Preset not found
    put:
      tags:
        - Playground
      summary: Update playground preset
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PlaygroundPresetCreate"
      responses:
        "200":
          description: Updated preset
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlaygroundPreset"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          description: Preset not found
    delete:
      tags:
        - Playground
      summary: Delete playground preset
      security:
        - BearerAuth: []
      responses:
        "204":
          description: Deleted
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          description: Preset not found
  # --- Memory Engine (plan 21) ---
  /api/memory:
    get:
      tags:
        - Memory
      summary: List memory entries
      security:
        - ManagementSessionAuth: []
      parameters:
        - name: apiKeyId
          in: query
          schema:
            type: string
        - name: type
          in: query
          schema:
            type: string
            enum:
              - factual
              - episodic
              - procedural
              - semantic
        - name: sessionId
          in: query
          schema:
            type: string
        - name: q
          in: query
          schema:
            type: string
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
        - name: page
          in: query
          schema:
            type: integer
            minimum: 1
            default: 1
        - name: offset
          in: query
          schema:
            type: integer
            minimum: 0
      responses:
        "200":
          description: Paginated list of memories with stats
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/MemoryEntry"
                  total:
                    type: integer
                  totalPages:
                    type: integer
                  stats:
                    type: object
                    properties:
                      total:
                        type: integer
                      tokensUsed:
                        type: integer
                      hitRate:
                        type: number
                      cacheStats:
                        type: object
                        properties:
                          hits:
                            type: integer
                          misses:
                            type: integer
        "401":
          $ref: "#/components/responses/Unauthorized"
    post:
      tags:
        - Memory
      summary: Create a memory entry
      security:
        - ManagementSessionAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - content
                - key
              properties:
                content:
                  type: string
                  minLength: 1
                key:
                  type: string
                  minLength: 1
                type:
                  type: string
                  enum:
                    - factual
                    - episodic
                    - procedural
                    - semantic
                  default: factual
                sessionId:
                  type: string
                  nullable: true
                apiKeyId:
                  type: string
                metadata:
                  type: object
                  additionalProperties: true
                expiresAt:
                  type: string
                  format: date-time
                  nullable: true
      responses:
        "201":
          description: Created memory entry
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MemoryEntry"
        "400":
          description: Validation error
        "401":
          $ref: "#/components/responses/Unauthorized"
  /api/memory/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
        description: Memory UUID
    get:
      tags:
        - Memory
      summary: Get a single memory entry
      security:
        - ManagementSessionAuth: []
      responses:
        "200":
          description: Memory entry
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MemoryEntry"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          description: Memory not found
    put:
      tags:
        - Memory
      summary: Update a memory entry
      description: Update `type`, `key`, `content`, and/or `metadata` of an existing memory. If an embedding source is available, the vector in `vec_memories` is also regenerated. Corresponds to `MemoryUpdatePutSchema`.
      security:
        - ManagementSessionAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                type:
                  type: string
                  enum:
                    - factual
                    - episodic
                    - procedural
                    - semantic
                key:
                  type: string
                  minLength: 1
                content:
                  type: string
                  minLength: 1
                metadata:
                  type: object
                  additionalProperties: true
              additionalProperties: false
      responses:
        "200":
          description: Updated memory entry
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MemoryEntry"
        "400":
          description: Validation error
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          description: Memory not found
    delete:
      tags:
        - Memory
      summary: Delete a memory entry
      description: Deletes the SQLite row, removes the vector from `vec_memories`, and best-effort deletes the point from Qdrant.
      security:
        - ManagementSessionAuth: []
      responses:
        "200":
          description: Deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          description: Memory not found
  /api/memory/health:
    get:
      tags:
        - Memory
      summary: Memory store health check
      description: Round-trip create→list→delete to verify the store is alive.
      security:
        - ManagementSessionAuth: []
      responses:
        "200":
          description: Health result
          content:
            application/json:
              schema:
                type: object
                properties:
                  working:
                    type: boolean
                  latencyMs:
                    type: number
                  error:
                    type: string
                    nullable: true
        "401":
          $ref: "#/components/responses/Unauthorized"
  /api/memory/retrieve-preview:
    post:
      tags:
        - Memory
      summary: Dry-run memory retrieval (Playground)
      description: Simulates `retrieveMemories()` for a given query and returns the ranked results with score, tier, and token count. Does NOT modify any memory. Corresponds to `RetrievePreviewSchema`.
      security:
        - ManagementSessionAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - query
              properties:
                query:
                  type: string
                  minLength: 1
                strategy:
                  type: string
                  enum:
                    - exact
                    - semantic
                    - hybrid
                  default: hybrid
                maxTokens:
                  type: integer
                  minimum: 1
                  maximum: 16000
                  default: 2000
                apiKeyId:
                  type: string
                  description: Optional — tests global pool when omitted
                limit:
                  type: integer
                  minimum: 1
                  maximum: 100
                  default: 20
              additionalProperties: false
      responses:
        "200":
          description: Preview results
          content:
            application/json:
              schema:
                type: object
                properties:
                  memories:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        type:
                          type: string
                          enum:
                            - factual
                            - episodic
                            - procedural
                            - semantic
                        key:
                          type: string
                        content:
                          type: string
                        score:
                          type: number
                        tokens:
                          type: integer
                        tier:
                          type: string
                          enum:
                            - fts5
                            - vector
                            - hybrid-rrf
                            - qdrant
                        vecScore:
                          type: number
                          nullable: true
                        ftsScore:
                          type: number
                          nullable: true
                  resolution:
                    type: object
                    properties:
                      embeddingSource:
                        type: string
                        enum:
                          - remote
                          - static
                          - transformers
                        nullable: true
                      embeddingModel:
                        type: string
                        nullable: true
                      vectorStore:
                        type: string
                        enum:
                          - sqlite-vec
                          - qdrant
                          - none
                      strategyUsed:
                        type: string
                        enum:
                          - exact
                          - semantic
                          - hybrid
                      rerankApplied:
                        type: boolean
                      fallbackReason:
                        type: string
                        nullable: true
                  totalTokensUsed:
                    type: integer
                  budgetMaxTokens:
                    type: integer
        "400":
          description: Validation error
        "401":
          $ref: "#/components/responses/Unauthorized"
  /api/memory/embedding-providers:
    get:
      tags:
        - Memory
      summary: List embedding providers
      description: Returns all providers that have embedding-capable models, indicating which have an active API key configured.
      security:
        - ManagementSessionAuth: []
      responses:
        "200":
          description: Provider list
          content:
            application/json:
              schema:
                type: object
                properties:
                  providers:
                    type: array
                    items:
                      type: object
                      properties:
                        provider:
                          type: string
                        hasKey:
                          type: boolean
                        models:
                          type: array
                          items:
                            type: object
                            properties:
                              id:
                                type: string
                                description: "Format: provider/model"
                              name:
                                type: string
                              dimensions:
                                type: integer
                                nullable: true
        "401":
          $ref: "#/components/responses/Unauthorized"
  /api/memory/engine-status:
    get:
      tags:
        - Memory
      summary: Memory engine status
      description: Returns the full engine status including keyword tier availability, embedding resolution, vector store statistics (sqlite-vec), Qdrant health, and rerank configuration. Corresponds to `MemoryEngineStatusSchema`.
      security:
        - ManagementSessionAuth: []
      responses:
        "200":
          description: Engine status
          content:
            application/json:
              schema:
                type: object
                properties:
                  keyword:
                    type: object
                    properties:
                      available:
                        type: boolean
                      backend:
                        type: string
                        enum:
                          - FTS5
                  embedding:
                    type: object
                    properties:
                      source:
                        type: string
                        enum:
                          - remote
                          - static
                          - transformers
                        nullable: true
                      model:
                        type: string
                        nullable: true
                      dimensions:
                        type: integer
                        nullable: true
                      available:
                        type: boolean
                      reason:
                        type: string
                      cacheStats:
                        type: object
                        properties:
                          hits:
                            type: integer
                          misses:
                            type: integer
                          size:
                            type: integer
                  vectorStore:
                    type: object
                    properties:
                      backend:
                        type: string
                        enum:
                          - sqlite-vec
                          - qdrant
                          - none
                      available:
                        type: boolean
                      rowCount:
                        type: integer
                      needsReindex:
                        type: integer
                      reason:
                        type: string
                  qdrant:
                    type: object
                    properties:
                      enabled:
                        type: boolean
                      healthy:
                        type: boolean
                        nullable: true
                      latencyMs:
                        type: number
                        nullable: true
                      error:
                        type: string
                        nullable: true
                  rerank:
                    type: object
                    properties:
                      enabled:
                        type: boolean
                      provider:
                        type: string
                        nullable: true
                      model:
                        type: string
                        nullable: true
                      available:
                        type: boolean
                      reason:
                        type: string
        "401":
          $ref: "#/components/responses/Unauthorized"
  /api/memory/summarize:
    post:
      tags:
        - Memory
      summary: Compact old memories
      description: "Manually triggers memory compaction for memories older than `olderThanDays`. Use `dryRun: true` to preview candidates. Corresponds to `MemorySummarizeSchema`."
      security:
        - ManagementSessionAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                olderThanDays:
                  type: integer
                  minimum: 1
                  maximum: 365
                  default: 30
                apiKeyId:
                  type: string
                  description: Optional — compacts all keys when omitted
                dryRun:
                  type: boolean
                  default: false
              additionalProperties: false
      responses:
        "200":
          description: Summarization result
          content:
            application/json:
              schema:
                type: object
                properties:
                  candidates:
                    type: integer
                  tokensSaved:
                    type: integer
                  dryRun:
                    type: boolean
        "400":
          description: Validation error
        "401":
          $ref: "#/components/responses/Unauthorized"
  /api/memory/reindex:
    post:
      tags:
        - Memory
      summary: Trigger vector reindex
      description: "Starts background reindexing of memories with `needs_reindex = 1`. Use `force: true` to regenerate ALL vectors regardless of index status. Corresponds to `MemoryReindexSchema`."
      security:
        - ManagementSessionAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                force:
                  type: boolean
                  default: false
                  description: When true, marks all memories needs_reindex=1 before running.
              additionalProperties: false
      responses:
        "200":
          description: Reindex started
          content:
            application/json:
              schema:
                type: object
                properties:
                  started:
                    type: boolean
                  pending:
                    type: integer
                    description: Memories still pending after this batch
        "400":
          description: Validation error
        "401":
          $ref: "#/components/responses/Unauthorized"
  /api/settings/memory:
    get:
      tags:
        - Memory
        - Settings
      summary: Get memory settings
      description: Returns the extended memory settings including 7 new fields added in plan 21 (embeddingSource, embeddingProviderModel, transformersEnabled, staticEnabled, rerankEnabled, rerankProviderModel, vectorStore).
      security:
        - ManagementSessionAuth: []
      responses:
        "200":
          description: Extended memory settings
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MemorySettingsExtended"
        "401":
          $ref: "#/components/responses/Unauthorized"
    put:
      tags:
        - Memory
        - Settings
      summary: Update memory settings
      description: "Update any subset of the extended memory settings. All fields are optional; only provided fields are updated. Schema: `MemorySettingsExtendedSchema`."
      security:
        - ManagementSessionAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MemorySettingsExtended"
      responses:
        "200":
          description: Updated memory settings
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MemorySettingsExtended"
        "400":
          description: Validation error
        "401":
          $ref: "#/components/responses/Unauthorized"
  /api/settings/qdrant:
    get:
      tags:
        - Memory
        - Settings
      summary: Get Qdrant settings
      description: Returns current Qdrant configuration. The `apiKey` field is never returned raw — use `hasApiKey` / `apiKeyMasked` instead.
      security:
        - ManagementSessionAuth: []
      responses:
        "200":
          description: Qdrant settings
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/QdrantSettings"
        "401":
          $ref: "#/components/responses/Unauthorized"
    put:
      tags:
        - Memory
        - Settings
      summary: Update Qdrant settings
      description: 'Update Qdrant configuration. Pass `apiKey: ""` to remove the stored key. Schema: `QdrantSettingsUpdateSchema`.'
      security:
        - ManagementSessionAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                enabled:
                  type: boolean
                host:
                  type: string
                port:
                  type: integer
                  minimum: 1
                  maximum: 65535
                collection:
                  type: string
                  minLength: 1
                embeddingModel:
                  type: string
                  minLength: 1
                apiKey:
                  type: string
                  description: Empty string removes the key
              additionalProperties: false
      responses:
        "200":
          description: Updated Qdrant settings
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/QdrantSettings"
        "400":
          description: Validation error
        "401":
          $ref: "#/components/responses/Unauthorized"
  /api/settings/qdrant/health:
    get:
      tags:
        - Memory
      summary: Qdrant health probe
      description: Performs a liveness check against the configured Qdrant instance. Returns latency and any connection error (sanitized — no stack traces).
      security:
        - ManagementSessionAuth: []
      responses:
        "200":
          description: Health result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/QdrantHealthResult"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /api/settings/qdrant/search:
    post:
      tags:
        - Memory
      summary: Qdrant semantic search test
      description: "Performs a test semantic search against the Qdrant collection. Useful for validating that the integration works end-to-end. Schema: `QdrantSearchSchema`."
      security:
        - ManagementSessionAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - query
              properties:
                query:
                  type: string
                  minLength: 1
                topK:
                  type: integer
                  minimum: 1
                  maximum: 50
                  default: 5
              additionalProperties: false
      responses:
        "200":
          description: Search results
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      type: object
        "400":
          description: Validation error
        "401":
          $ref: "#/components/responses/Unauthorized"
        "503":
          description: Qdrant unavailable (structured error, no stack trace)
  /api/settings/qdrant/cleanup:
    post:
      tags:
        - Memory
      summary: Clean up expired Qdrant points
      description: Removes Qdrant points for memories that have expired or exceeded the configured retention window.
      security:
        - ManagementSessionAuth: []
      responses:
        "200":
          description: Cleanup result
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted:
                    type: integer
                  checked:
                    type: integer
        "401":
          $ref: "#/components/responses/Unauthorized"
        "503":
          description: Qdrant unavailable (structured error, no stack trace)
  /api/settings/qdrant/embedding-models:
    get:
      tags:
        - Memory
      summary: List Qdrant embedding models
      description: Returns the list of embedding models available for use with Qdrant.
      security:
        - ManagementSessionAuth: []
      responses:
        "200":
          description: Embedding models list
          content:
            application/json:
              schema:
                type: object
                properties:
                  models:
                    type: array
                    items:
                      type: string
        "401":
          $ref: "#/components/responses/Unauthorized"
  # ─── Proxy Endpoints ──────────────────────────────────────────

  /api/v1/chat/completions:
    post:
      tags: [Chat]
      summary: Create chat completion
      description: OpenAI-compatible chat completions endpoint. Routes to configured providers.
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatCompletionRequest"
      responses:
        "200":
          description: Chat completion response (or SSE stream)
          headers:
            X-OmniRoute-Response-Cost:
              schema:
                type: string
              description: Request cost in USD, fixed 10 decimals (e.g. `0.0001234500`; `0.0000000000` for free/unpriced).
            X-OmniRoute-Tokens-In:
              schema:
                type: string
              description: Input (prompt) token count.
            X-OmniRoute-Tokens-Out:
              schema:
                type: string
              description: Output (completion) token count.
            X-OmniRoute-Model:
              schema:
                type: string
              description: Resolved model.
            X-OmniRoute-Provider:
              schema:
                type: string
              description: Resolved provider alias.
            X-OmniRoute-Latency-Ms:
              schema:
                type: string
              description: Handler latency in milliseconds.
            X-OmniRoute-Cache-Hit:
              schema:
                type: string
                enum: ["true", "false"]
              description: Whether the response was served from cache.
            X-OmniRoute-Fallback-Attempts:
              schema:
                type: string
              description: Number of fallback attempts (only present when > 0).
            X-OmniRoute-Decision:
              schema:
                type: string
              description: >-
                Routing decision trace: `strategy=<name>; provider=<alias>; latency_ms=<n>`.
                `<name>` is the combo strategy (e.g. `priority`, `weighted`, `fusion`) or
                `single` for a non-combo request. Emitted on every completion response.
            X-OmniRoute-Request-Id:
              schema:
                type: string
              description: Request correlation id (present when known).
            X-OmniRoute-Version:
              schema:
                type: string
              description: OmniRoute build version (always present).
            X-OmniRoute-Cost-Saved:
              schema:
                type: string
              description: >-
                On a semantic-cache HIT, the original (would-have-been) cost in USD that
                the cache avoided (fixed 10 decimals). Present only on cache hits;
                X-OmniRoute-Response-Cost is 0 for the same response (incremental cost).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatCompletionResponse"
            text/event-stream:
              schema:
                type: string
        "401":
          $ref: "#/components/responses/Unauthorized"
        "502":
          description: All upstream providers failed

  /api/v1/ws:
    get:
      tags: [Chat]
      summary: Chat completion over WebSocket (handshake + upgrade)
      description: >-
        OpenAI-compatible chat over a WebSocket connection. `GET` with
        `?handshake=1` returns the connection descriptor (auth path, message
        protocol and live-event channels) as JSON; a plain `GET` without an
        Upgrade returns `426 Upgrade Required`. After upgrading, the client
        exchanges JSON frames — `{type:"request", id, payload:{model, messages}}`
        to start a completion and `{type:"cancel", id}` to abort it. A separate
        live channel (default port `LIVE_WS_PORT=20129`, path `/live`) streams
        dashboard events on the `requests`, `combo` and `credentials` topics with
        a 15s heartbeat. Requires an API key.
      security:
        - BearerAuth: []
      parameters:
        - name: handshake
          in: query
          description: Set to `1` to receive the JSON connection descriptor instead of upgrading.
          required: false
          schema:
            type: string
            enum: ["1"]
      responses:
        "101":
          description: WebSocket upgrade successful
        "200":
          description: Handshake descriptor (auth path, message protocol, live channels)
        "401":
          description: WebSocket auth required (no credential supplied)
        "403":
          description: Invalid WebSocket credential
        "426":
          description: Upgrade Required — connect via WebSocket or use `?handshake=1`

  /api/v1/providers/{provider}/chat/completions:
    post:
      tags: [Chat]
      summary: Create chat completion (provider-specific)
      description: Routes to a specific provider by name.
      security:
        - BearerAuth: []
      parameters:
        - name: provider
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatCompletionRequest"
      responses:
        "200":
          description: Chat completion response
        "401":
          $ref: "#/components/responses/Unauthorized"

  /api/v1/api/chat:
    post:
      tags: [Chat]
      summary: Ollama-compatible chat endpoint
      description: Provides compatibility with Ollama's /api/chat format.
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Chat response (JSON or streaming)

  /api/v1/messages:
    post:
      tags: [Messages]
      summary: Create message (Anthropic-compatible)
      description: Anthropic Messages API endpoint. Routes to Claude providers.
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MessagesRequest"
      responses:
        "200":
          description: >-
            Message response (or SSE stream). Non-streaming success responses
            carry the `X-OmniRoute-*` cost-telemetry headers (see
            `POST /api/v1/chat/completions`), including `X-OmniRoute-Request-Id`
            and `X-OmniRoute-Version`.
        "401":
          $ref: "#/components/responses/Unauthorized"

  /api/v1/messages/count_tokens:
    post:
      tags: [Messages]
      summary: Count tokens for a message
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Token count

  /api/v1/responses:
    post:
      tags: [Responses]
      summary: Create response (OpenAI Responses API)
      description: OpenAI Responses API endpoint.
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: >-
            Response object or SSE stream. Non-streaming success responses carry
            the `X-OmniRoute-*` cost-telemetry headers (see
            `POST /api/v1/chat/completions`), including `X-OmniRoute-Request-Id`
            and `X-OmniRoute-Version`.
        "401":
          $ref: "#/components/responses/Unauthorized"

  /api/v1/embeddings:
    post:
      tags: [Embeddings]
      summary: Create embeddings
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [input, model]
              properties:
                input:
                  description: >-
                    Text, token IDs, or up to 32 canonical multimodal items. Structured media
                    input is accepted only when the resolved registry model advertises the
                    requested modalities. URL sources must be public HTTPS and are fetched
                    server-side into bounded inline media (redirect revalidation, timeout,
                    size limits, public DNS checks, and connection pinning) before provider
                    submission. Inline media is limited to 8 MiB decoded per item and 16 MiB
                    decoded per request. Canonical items are translated to provider-native
                    request shapes (Jina modality-keyed objects / Gemini embedContent parts)
                    rather than forwarded unchanged.
                  oneOf:
                    - type: string
                    - type: array
                      minItems: 1
                      items:
                        type: string
                    - type: array
                      minItems: 1
                      items:
                        type: integer
                        minimum: 0
                    - type: array
                      minItems: 1
                      items:
                        type: array
                        minItems: 1
                        items:
                          type: integer
                          minimum: 0
                    - type: array
                      minItems: 1
                      maxItems: 32
                      items:
                        $ref: "#/components/schemas/EmbeddingMultimodalItem"
                model:
                  type: string
                dimensions:
                  type: integer
                  minimum: 1
                encoding_format:
                  type: string
                  enum: [float, base64]
              additionalProperties: true
      responses:
        "200":
          description: >-
            Embedding vectors. Success responses carry the `X-OmniRoute-*`
            cost-telemetry headers (see `POST /api/v1/chat/completions`); media
            cost is computed per modality when pricing is available, otherwise
            `0` (fail-open).

  /api/v1/providers/{provider}/embeddings:
    post:
      tags: [Embeddings]
      summary: Create embeddings (provider-specific)
      security:
        - BearerAuth: []
      parameters:
        - name: provider
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Embedding vectors

  /api/v1/images/generations:
    post:
      tags: [Images]
      summary: Generate images
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [prompt]
              properties:
                prompt:
                  type: string
                model:
                  type: string
                n:
                  type: integer
                  default: 1
                size:
                  type: string
                  default: 1024x1024
      responses:
        "200":
          description: >-
            Generated images. Success responses carry the `X-OmniRoute-*`
            cost-telemetry headers (see `POST /api/v1/chat/completions`); image
            cost is computed per image when pricing is available, otherwise `0`
            (fail-open).

  /api/v1/providers/{provider}/images/generations:
    post:
      tags: [Images]
      summary: Generate images (provider-specific)
      security:
        - BearerAuth: []
      parameters:
        - name: provider
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Generated images

  /api/v1/audio/speech:
    post:
      tags: [Audio]
      summary: Generate speech audio
      description: Text-to-speech endpoint. Routes to configured TTS providers.
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [input]
              properties:
                input:
                  type: string
                model:
                  type: string
                voice:
                  type: string
      responses:
        "200":
          description: >-
            Audio data. Success responses carry the `X-OmniRoute-*`
            cost-telemetry headers (see `POST /api/v1/chat/completions`); speech
            cost is computed per character when pricing is available, otherwise
            `0` (fail-open).

  /api/v1/audio/transcriptions:
    post:
      tags: [Audio]
      summary: Transcribe audio
      description: Audio-to-text transcription endpoint.
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
                model:
                  type: string
      responses:
        "200":
          description: >-
            Transcription result. Success responses carry the `X-OmniRoute-*`
            cost-telemetry headers (see `POST /api/v1/chat/completions`);
            transcription cost is computed per second when pricing is available,
            otherwise `0` (fail-open).

  /api/v1/moderations:
    post:
      tags: [Moderations]
      summary: Create moderation
      description: Content moderation endpoint. Routes to configured moderation providers.
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [input]
              properties:
                input:
                  oneOf:
                    - type: string
                    - type: array
                      items:
                        type: string
      responses:
        "200":
          description: >-
            Moderation result. Success responses carry the `X-OmniRoute-*`
            cost-telemetry headers (see `POST /api/v1/chat/completions`);
            moderations are always cost `0` (free).

  /api/v1/rerank:
    post:
      tags: [Rerank]
      summary: Rerank documents
      description: Document reranking endpoint.
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [query, documents]
              properties:
                query:
                  type: string
                documents:
                  type: array
                  items:
                    type: string
                model:
                  type: string
      responses:
        "200":
          description: >-
            Reranked documents. Success responses carry the `X-OmniRoute-*`
            cost-telemetry headers (see `POST /api/v1/chat/completions`); rerank
            cost is computed per search-unit when pricing is available,
            otherwise `0` (fail-open).

  /api/v1:
    get:
      tags: [System]
      summary: API v1 root endpoint
      description: Returns basic API info and status.
      security:
        - BearerAuth: []
      responses:
        "200":
          description: API info

  /api/v1/models:
    get:
      tags: [Models]
      summary: List available models
      description: Returns all models available across configured providers.
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Model list
          content:
            application/json:
              schema:
                type: object
                properties:
                  object:
                    type: string
                    example: list
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Model"

  /api/v1/providers/{provider}/models:
    get:
      tags: [Models]
      summary: List models for a specific provider
      description: Returns only models for the selected provider with provider prefix removed from each model id.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: provider
          required: true
          schema:
            type: string
          description: Provider id or alias (for example `openai`, `claude`, `cc`).
      responses:
        "200":
          description: Provider-scoped model list
          content:
            application/json:
              schema:
                type: object
                properties:
                  object:
                    type: string
                    example: list
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Model"
        "400":
          description: Unknown provider

  /api/models:
    get:
      tags: [Models]
      summary: List models (management)
      responses:
        "200":
          description: Internal model list with aliases

  /api/models/alias:
    post:
      tags: [Models]
      summary: Create or update a model alias
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Alias created/updated

  /api/models/catalog:
    get:
      tags: [Models]
      summary: Get full model catalog
      responses:
        "200":
          description: Complete catalog with all providers

  # ─── Management Endpoints ──────────────────────────────────────

  /api/providers:
    get:
      tags: [Providers]
      summary: List provider connections
      responses:
        "200":
          description: Provider connection list
          content:
            application/json:
              schema:
                type: object
                properties:
                  connections:
                    type: array
                    items:
                      $ref: "#/components/schemas/ProviderConnection"
    post:
      tags: [Providers]
      summary: Create provider connection
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProviderConnectionCreate"
      responses:
        "201":
          description: Created provider connection

  /api/providers/{id}:
    get:
      tags: [Providers]
      summary: Get provider connection
      parameters:
        - $ref: "#/components/parameters/ResourceId"
      responses:
        "200":
          description: Provider connection details
        "404":
          description: Provider not found
    patch:
      tags: [Providers]
      summary: Update provider connection
      parameters:
        - $ref: "#/components/parameters/ResourceId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProviderConnectionCreate"
      responses:
        "200":
          description: Updated provider
    delete:
      tags: [Providers]
      summary: Delete provider connection
      parameters:
        - $ref: "#/components/parameters/ResourceId"
      responses:
        "200":
          description: Provider deleted

  /api/providers/{id}/test:
    post:
      tags: [Providers]
      summary: Test provider connection
      parameters:
        - $ref: "#/components/parameters/ResourceId"
      responses:
        "200":
          description: Test result

  /api/providers/{id}/models:
    get:
      tags: [Providers]
      summary: List models for a provider
      parameters:
        - $ref: "#/components/parameters/ResourceId"
      responses:
        "200":
          description: Provider model list

  /api/providers/test-batch:
    post:
      tags: [Providers]
      summary: Test multiple providers at once
      responses:
        "200":
          description: Batch test results

  /api/providers/validate:
    post:
      tags: [Providers]
      summary: Validate provider credentials
      responses:
        "200":
          description: Validation result

  /api/providers/client:
    get:
      tags: [Providers]
      summary: Get client-side provider info
      responses:
        "200":
          description: Provider info for frontend

  /api/providers/agy-auth/import:
    post:
      tags: [Providers]
      summary: Import an Antigravity CLI (agy) token file as an `agy` connection
      responses:
        "200":
          description: Created or updated provider connection

  /api/providers/agy-auth/import-bulk:
    post:
      tags: [Providers]
      summary: Bulk-import multiple Antigravity CLI (agy) token files (up to 50)
      responses:
        "200":
          description: Per-entry import results (success/failed counts)

  /api/providers/agy-auth/zip-extract:
    post:
      tags: [Providers]
      summary: Extract `.json` token files from an uploaded ZIP for agy bulk import
      responses:
        "200":
          description: Extracted token-file entries

  /api/providers/agy-auth/apply-local:
    post:
      tags: [Providers]
      summary: Auto-detect and import the local Antigravity CLI (agy) login from disk
      responses:
        "200":
          description: Created or updated provider connection
        "404":
          description: No local agy login found

  /api/provider-nodes:
    get:
      tags: [Provider Nodes]
      summary: List provider nodes
      responses:
        "200":
          description: Provider node list
    post:
      tags: [Provider Nodes]
      summary: Create provider node
      responses:
        "201":
          description: Created node

  /api/provider-nodes/{id}:
    patch:
      tags: [Provider Nodes]
      summary: Update provider node
      parameters:
        - $ref: "#/components/parameters/ResourceId"
      responses:
        "200":
          description: Updated node
    delete:
      tags: [Provider Nodes]
      summary: Delete provider node
      parameters:
        - $ref: "#/components/parameters/ResourceId"
      responses:
        "200":
          description: Node deleted

  /api/provider-nodes/validate:
    post:
      tags: [Provider Nodes]
      summary: Validate a provider node
      responses:
        "200":
          description: Validation result

  /api/provider-models:
    get:
      tags: [Provider Nodes]
      summary: List provider models
      responses:
        "200":
          description: Provider model list

  /api/keys:
    get:
      tags: [API Keys]
      summary: List API keys
      responses:
        "200":
          description: API key list
          content:
            application/json:
              schema:
                type: object
                properties:
                  keys:
                    type: array
                    items:
                      $ref: "#/components/schemas/ApiKey"
        "401":
          description: Authentication required
    post:
      tags: [API Keys]
      summary: Create API key
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [label]
              properties:
                label:
                  type: string
      responses:
        "201":
          description: Created API key (includes full key value)
        "401":
          description: Authentication required

  /api/keys/{id}:
    get:
      tags: [API Keys]
      summary: Get API key
      parameters:
        - $ref: "#/components/parameters/ResourceId"
      responses:
        "200":
          description: API key metadata
        "401":
          description: Authentication required
        "404":
          description: Key not found
    patch:
      tags: [API Keys]
      summary: Update API key
      parameters:
        - $ref: "#/components/parameters/ResourceId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
      responses:
        "200":
          description: API key settings updated
        "400":
          description: Invalid update request
        "401":
          description: Authentication required
        "404":
          description: Key not found
    delete:
      tags: [API Keys]
      summary: Delete API key
      parameters:
        - $ref: "#/components/parameters/ResourceId"
      responses:
        "200":
          description: Key deleted
        "401":
          description: Authentication required
        "404":
          description: Key not found

  /api/combos:
    get:
      tags: [Combos]
      summary: List routing combos
      responses:
        "200":
          description: Combo list
    post:
      tags: [Combos]
      summary: Create routing combo
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ComboCreate"
      responses:
        "201":
          description: Created combo

  /api/combos/{id}:
    patch:
      tags: [Combos]
      summary: Update combo
      parameters:
        - $ref: "#/components/parameters/ResourceId"
      responses:
        "200":
          description: Updated combo
    delete:
      tags: [Combos]
      summary: Delete combo
      parameters:
        - $ref: "#/components/parameters/ResourceId"
      responses:
        "200":
          description: Combo deleted

  /api/combos/metrics:
    get:
      tags: [Combos]
      summary: Get combo metrics
      responses:
        "200":
          description: Metrics for combos

  /api/combos/test:
    post:
      tags: [Combos]
      summary: Test a combo configuration
      responses:
        "200":
          description: Test result

  /api/settings:
    get:
      tags: [Settings]
      summary: Get application settings
      responses:
        "200":
          description: Current settings
    patch:
      tags: [Settings]
      summary: Update settings
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Updated settings

  /api/settings/purge-request-history:
    post:
      tags: [Settings]
      summary: Clear request log history
      description: Deletes `call_logs`, legacy `request_detail_logs`, and local request artifact files under `DATA_DIR/call_logs`.
      security:
        - ManagementSessionAuth: []
      responses:
        "200":
          description: Request history cleared
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted:
                    type: integer
                  deletedArtifacts:
                    type: integer
                  deletedDetailedLogs:
                    type: integer
                  errors:
                    type: integer
        "401":
          description: Unauthorized
        "500":
          description: Cleanup failed or reported errors
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted:
                    type: integer
                  deletedArtifacts:
                    type: integer
                  deletedDetailedLogs:
                    type: integer
                  errors:
                    type: integer
                  error:
                    type: object

  /api/settings/compression:
    get:
      tags: [Compression]
      summary: Get global compression settings
      security:
        - ManagementSessionAuth: []
      responses:
        "200":
          description: Current compression settings
    put:
      tags: [Compression]
      summary: Update global compression settings
      security:
        - ManagementSessionAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                enabled:
                  type: boolean
                defaultMode:
                  type: string
                  enum: [off, lite, standard, aggressive, ultra, rtk, stacked]
                autoTriggerMode:
                  type: string
                  enum: [off, lite, standard, aggressive, ultra, rtk, stacked]
                autoTriggerTokens:
                  type: integer
                  minimum: 0
                rtkConfig:
                  type: object
                  additionalProperties: true
                stackedPipeline:
                  type: array
                  items:
                    type: object
      responses:
        "200":
          description: Updated compression settings

  /api/settings/compression/mcp-accessibility:
    get:
      tags: [Compression]
      summary: Get the MCP tool-output accessibility (trimming) config
      security:
        - ManagementSessionAuth: []
      responses:
        "200":
          description: Current mcpAccessibility config
    put:
      tags: [Compression]
      summary: Update the MCP tool-output accessibility (trimming) config
      description: >-
        Partial-merge update. Numeric floors (e.g. a maxTextChars below the truncation-tail
        reserve) are folded back to the safe defaults server-side, so the response reflects the
        effective config.
      security:
        - ManagementSessionAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                enabled:
                  type: boolean
                maxTextChars:
                  type: integer
                  minimum: 1
                collapseThreshold:
                  type: integer
                  minimum: 1
                collapseKeepHead:
                  type: integer
                  minimum: 0
                collapseKeepTail:
                  type: integer
                  minimum: 0
                minLengthToProcess:
                  type: integer
                  minimum: 1
      responses:
        "200":
          description: Updated mcpAccessibility config (numeric floors applied)

  /api/compression/preview:
    post:
      tags: [Compression]
      summary: Preview compression for a message payload
      security:
        - BearerAuth: []
        - ManagementSessionAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [messages, mode]
              properties:
                mode:
                  type: string
                  enum: [off, lite, standard, aggressive, ultra, rtk, stacked]
                messages:
                  type: array
                  items:
                    type: object
                    required: [role, content]
                    properties:
                      role:
                        type: string
                      content:
                        oneOf:
                          - type: string
                          - type: array
                            items: {}
                config:
                  type: object
                  additionalProperties: true
      responses:
        "200":
          description: Compression preview with diff, validation, and stats

  /api/compression/language-packs:
    get:
      tags: [Compression]
      summary: List Caveman compression language packs
      security:
        - BearerAuth: []
        - ManagementSessionAuth: []
      responses:
        "200":
          description: Available languages and rule-pack metadata

  /api/compression/rules:
    get:
      tags: [Compression]
      summary: List Caveman compression rule metadata
      security:
        - BearerAuth: []
        - ManagementSessionAuth: []
      responses:
        "200":
          description: Caveman rule metadata

  /api/context/rtk/config:
    get:
      tags: [Compression]
      summary: Get RTK compression settings
      security:
        - ManagementSessionAuth: []
      responses:
        "200":
          description: Current RTK config
    put:
      tags: [Compression]
      summary: Update RTK compression settings
      security:
        - ManagementSessionAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                enabled:
                  type: boolean
                intensity:
                  type: string
                  enum: [minimal, standard, aggressive]
                customFiltersEnabled:
                  type: boolean
                trustProjectFilters:
                  type: boolean
                rawOutputRetention:
                  type: string
                  enum: [never, failures, always]
                rawOutputMaxBytes:
                  type: integer
      responses:
        "200":
          description: Updated RTK config

  /api/context/rtk/filters:
    get:
      tags: [Compression]
      summary: List RTK filters and load diagnostics
      security:
        - ManagementSessionAuth: []
      responses:
        "200":
          description: RTK filter catalog and diagnostics

  /api/context/rtk/import:
    post:
      tags: [Compression]
      summary: Validate or install an RTK TOML schema v1 filter file
      security:
        - ManagementSessionAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [action, content]
              additionalProperties: false
              properties:
                action:
                  type: string
                  enum: [validate, install]
                content:
                  type: string
                  maxLength: 1048576
                overwrite:
                  type: boolean
                  description: Replace an existing global file and create a backup
      responses:
        "200":
          description: Filter metadata, inline-test outcomes, warnings, and installation status
        "400":
          description: Invalid TOML, schema, regular expression, inline test, or install request

  /api/context/rtk/test:
    post:
      tags: [Compression]
      summary: Run RTK compression preview for text
      security:
        - ManagementSessionAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [text]
              properties:
                text:
                  type: string
                command:
                  type: string
                config:
                  type: object
                  additionalProperties: true
      responses:
        "200":
          description: Detection and RTK compression result

  /api/context/rtk/raw-output/{id}:
    get:
      tags: [Compression]
      summary: Read retained redacted RTK raw output
      security:
        - ManagementSessionAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            pattern: "^[a-f0-9]{24}$"
      responses:
        "200":
          description: Raw output text
        "404":
          description: Raw output not found

  /api/settings/payload-rules:
    get:
      tags: [Settings]
      summary: Get payload rules configuration
      description: |
        Returns the current payload rules used to mutate outgoing request payloads before they
        are sent upstream.

        Requires a dashboard management session cookie when management auth is enabled.
      security:
        - ManagementSessionAuth: []
      responses:
        "200":
          description: Current payload rules configuration
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PayloadRulesConfig"
        "401":
          $ref: "#/components/responses/ManagementAuthenticationRequired"
        "403":
          $ref: "#/components/responses/ManagementInvalidToken"
        "500":
          description: Failed to read payload rules configuration
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
    put:
      tags: [Settings]
      summary: Update payload rules configuration
      description: |
        Persists and hot reloads payload rules. The legacy input field `default-raw` is accepted
        on writes and normalized to `defaultRaw` in responses/runtime state.

        Requires a dashboard management session cookie when management auth is enabled.
      security:
        - ManagementSessionAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdatePayloadRulesRequest"
      responses:
        "200":
          description: Updated payload rules configuration
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PayloadRulesConfig"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/ManagementAuthenticationRequired"
        "403":
          $ref: "#/components/responses/ManagementInvalidToken"
        "500":
          description: Failed to update payload rules configuration
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"

  /api/settings/combo-defaults:
    get:
      tags: [Settings]
      summary: Get combo default settings
      responses:
        "200":
          description: Default combo settings

  /api/settings/proxy:
    get:
      tags: [Settings]
      summary: Get proxy settings
      responses:
        "200":
          description: Current proxy settings
    patch:
      tags: [Settings]
      summary: Update proxy settings
      responses:
        "200":
          description: Updated proxy settings

  /api/settings/proxy/test:
    post:
      tags: [Settings]
      summary: Test proxy connection
      responses:
        "200":
          description: Test result

  /api/settings/require-login:
    post:
      tags: [Settings]
      summary: Toggle login requirement
      responses:
        "200":
          description: Updated

  /api/settings/ip-filter:
    get:
      tags: [Settings]
      summary: Get IP filter configuration
      description: Returns the current IP filter settings including blacklist, whitelist, and temp bans.
      responses:
        "200":
          description: IP filter configuration
    put:
      tags: [Settings]
      summary: Update IP filter configuration
      description: |
        Configure IP filtering with blacklist/whitelist modes, add/remove individual IPs, and manage temp bans.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                enabled:
                  type: boolean
                mode:
                  type: string
                  enum: [blacklist, whitelist]
                blacklist:
                  type: array
                  items:
                    type: string
                whitelist:
                  type: array
                  items:
                    type: string
                addBlacklist:
                  type: string
                removeBlacklist:
                  type: string
                addWhitelist:
                  type: string
                removeWhitelist:
                  type: string
                tempBan:
                  type: object
                  properties:
                    ip:
                      type: string
                    durationMs:
                      type: integer
                    reason:
                      type: string
                removeBan:
                  type: string
      responses:
        "200":
          description: Updated IP filter configuration

  /api/settings/system-prompt:
    get:
      tags: [Settings]
      summary: Get system prompt configuration
      description: Returns the current system prompt injection settings.
      responses:
        "200":
          description: System prompt configuration
    put:
      tags: [Settings]
      summary: Update system prompt configuration
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                prompt:
                  type: string
                enabled:
                  type: boolean
      responses:
        "200":
          description: Updated system prompt configuration

  /api/settings/thinking-budget:
    get:
      tags: [Settings]
      summary: Get thinking budget configuration
      description: Returns the current thinking/reasoning budget settings for AI models.
      responses:
        "200":
          description: Thinking budget configuration
    put:
      tags: [Settings]
      summary: Update thinking budget configuration
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                mode:
                  type: string
                  description: Thinking mode (e.g., auto, manual, disabled)
                customBudget:
                  type: integer
                  minimum: 0
                  maximum: 131072
                effortLevel:
                  type: string
                  enum: [none, low, medium, high]
      responses:
        "200":
          description: Updated thinking budget configuration

  /api/rate-limit:
    get:
      tags: [Settings]
      summary: Get rate limit configuration
      responses:
        "200":
          description: Rate limit settings
    post:
      tags: [Settings]
      summary: Update rate limit configuration
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Updated rate limit settings

  /api/tags:
    get:
      tags: [System]
      summary: List Ollama-compatible model tags
      description: Returns models in Ollama /api/tags format for Ollama client compatibility
      responses:
        "200":
          description: Ollama model tags

  # ─── Proxy Subscriptions ───────────────────────────────────────

  /api/v1/management/proxy-subscriptions:
    get:
      tags: [Proxy Subscriptions]
      summary: List proxy subscriptions
      description: >-
        Lists all operator-supplied proxy subscription links. Also starts the
        background auto-refresh scheduler (idempotent) so enabled subscriptions
        stay in sync. Credentials embedded in `url` are redacted in the response.
      security:
        - ManagementSessionAuth: []
      responses:
        "200":
          description: Subscription list
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: "#/components/schemas/ProxySubscription"
        "401":
          $ref: "#/components/responses/ManagementAuthenticationRequired"
        "403":
          $ref: "#/components/responses/ManagementInvalidToken"
    post:
      tags: [Proxy Subscriptions]
      summary: Create a proxy subscription
      description: >-
        Creates a subscription record. If `mode` is `rule`, at least one entry in
        `ruleProviders` is required. `updateIntervalMinutes` defaults to 60 and
        `enabled` defaults to `false` when omitted or not exactly `true`.
      security:
        - ManagementSessionAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, url]
              properties:
                name:
                  type: string
                  minLength: 1
                url:
                  type: string
                  minLength: 1
                mode:
                  type: string
                  enum: [global, rule]
                  default: global
                ruleProviders:
                  type: array
                  items:
                    type: string
                  description: Required (non-empty) when mode is "rule".
                localCoreEndpoint:
                  type: string
                  nullable: true
                updateIntervalMinutes:
                  type: integer
                  default: 60
                enabled:
                  type: boolean
                  default: false
      responses:
        "201":
          description: Created subscription (url redacted)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProxySubscription"
        "400":
          description: Missing name/url, or ruleProviders empty while mode is "rule"
        "401":
          $ref: "#/components/responses/ManagementAuthenticationRequired"
        "403":
          $ref: "#/components/responses/ManagementInvalidToken"

  /api/v1/management/proxy-subscriptions/{id}:
    get:
      tags: [Proxy Subscriptions]
      summary: Get a proxy subscription
      security:
        - ManagementSessionAuth: []
      parameters:
        - $ref: "#/components/parameters/ResourceId"
      responses:
        "200":
          description: Subscription detail (url redacted)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProxySubscription"
        "401":
          $ref: "#/components/responses/ManagementAuthenticationRequired"
        "403":
          $ref: "#/components/responses/ManagementInvalidToken"
        "404":
          description: Subscription not found
    patch:
      tags: [Proxy Subscriptions]
      summary: Update a proxy subscription
      description: >-
        Partial update — only fields present in the body are changed
        (name/url/mode/ruleProviders/localCoreEndpoint/updateIntervalMinutes/enabled).
      security:
        - ManagementSessionAuth: []
      parameters:
        - $ref: "#/components/parameters/ResourceId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                url:
                  type: string
                mode:
                  type: string
                  enum: [global, rule]
                ruleProviders:
                  type: array
                  items:
                    type: string
                localCoreEndpoint:
                  type: string
                  nullable: true
                updateIntervalMinutes:
                  type: integer
                enabled:
                  type: boolean
      responses:
        "200":
          description: Updated subscription (url redacted)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProxySubscription"
        "401":
          $ref: "#/components/responses/ManagementAuthenticationRequired"
        "403":
          $ref: "#/components/responses/ManagementInvalidToken"
        "404":
          description: Subscription not found
    delete:
      tags: [Proxy Subscriptions]
      summary: Delete a proxy subscription
      description: Removes the subscription record and unbinds/drops its synced proxy_registry rows.
      security:
        - ManagementSessionAuth: []
      parameters:
        - $ref: "#/components/parameters/ResourceId"
      responses:
        "200":
          description: Deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted:
                    type: boolean
        "401":
          $ref: "#/components/responses/ManagementAuthenticationRequired"
        "403":
          $ref: "#/components/responses/ManagementInvalidToken"
        "404":
          description: Subscription not found

  /api/v1/management/proxy-subscriptions/{id}/nodes:
    get:
      tags: [Proxy Subscriptions]
      summary: Get a subscription's last-parsed node summary
      description: >-
        Returns the last-parsed node list without re-fetching the (possibly slow)
        subscription URL.
      security:
        - ManagementSessionAuth: []
      parameters:
        - $ref: "#/components/parameters/ResourceId"
      responses:
        "200":
          description: Last-parsed node summary
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  name:
                    type: string
                  mode:
                    type: string
                    enum: [global, rule]
                  enabled:
                    type: boolean
                  status:
                    type: string
                    enum: [ok, error, empty]
                  error:
                    type: string
                    nullable: true
                  lastFetchedAt:
                    type: string
                    format: date-time
                    nullable: true
                  nodes:
                    type: array
                    items: {}
        "401":
          $ref: "#/components/responses/ManagementAuthenticationRequired"
        "403":
          $ref: "#/components/responses/ManagementInvalidToken"
        "404":
          description: Subscription not found

  /api/v1/management/proxy-subscriptions/{id}/refresh:
    post:
      tags: [Proxy Subscriptions]
      summary: Refresh a proxy subscription
      description: >-
        Re-fetches and re-parses the subscription URL, syncs its nodes into
        `proxy_registry`, and (re)binds the pool.
      security:
        - ManagementSessionAuth: []
      parameters:
        - $ref: "#/components/parameters/ResourceId"
      responses:
        "200":
          description: Sync result
          content:
            application/json:
              schema:
                type: object
                properties:
                  subscriptionId:
                    type: string
                  nodes:
                    type: integer
                  needsCore:
                    type: integer
                  boundProxies:
                    type: integer
                  status:
                    type: string
                    enum: [ok, error, empty]
                  error:
                    type: string
                    nullable: true
                  applied:
                    type: boolean
        "401":
          $ref: "#/components/responses/ManagementAuthenticationRequired"
        "403":
          $ref: "#/components/responses/ManagementInvalidToken"
        "404":
          description: Subscription not found

  # ─── Usage & Analytics ─────────────────────────────────────────

  /api/usage/analytics:
    get:
      tags: [Usage]
      summary: Get usage analytics
      parameters:
        - name: period
          in: query
          schema:
            type: string
            enum: [day, week, month]
            default: day
      responses:
        "200":
          description: Usage analytics data

  /api/usage/call-logs:
    get:
      tags: [Usage]
      summary: Get call logs
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
      responses:
        "200":
          description: Paginated call logs

  /api/usage/call-logs/{id}:
    get:
      tags: [Usage]
      summary: Get a specific call log
      parameters:
        - $ref: "#/components/parameters/ResourceId"
      responses:
        "200":
          description: Call log detail

  /api/usage/{connectionId}:
    get:
      tags: [Usage]
      summary: Get usage for a specific connection
      parameters:
        - name: connectionId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Connection usage data

  /api/usage/history:
    get:
      tags: [Usage]
      summary: Get usage history
      responses:
        "200":
          description: Historical usage data

  /api/usage/logs:
    get:
      tags: [Usage]
      summary: Get usage logs
      responses:
        "200":
          description: Usage log entries

  /api/usage/proxy-logs:
    get:
      tags: [Usage]
      summary: Get proxy logs
      responses:
        "200":
          description: Proxy log entries

  /api/usage/request-logs:
    get:
      tags: [Usage]
      summary: Get request logs
      responses:
        "200":
          description: Request log entries

  /api/usage/budget:
    get:
      tags: [Usage]
      summary: Get usage budget status
      description: Returns current budget limits and consumption.
      responses:
        "200":
          description: Budget status
    post:
      tags: [Usage]
      summary: Configure usage budget
      description: Set or update budget limits for usage tracking.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Updated budget configuration

  /api/usage/cache-health:
    get:
      tags: [Usage]
      summary: Get prompt-cache health summary
      description: |
        Summarizes the `write/read` cache ratio from `call_logs` for a time window:
        distribution (p50/p90/p99/max of cache-write tokens), warm/cold/rewrite/uncached
        call counts, the "heavy write" outlier share (10x the window median, floored at
        1024 tokens — Anthropic's cache-creation minimum), and a per-model breakdown.
        Only successful (`status = 200`) calls with a non-null cache column are counted.
      parameters:
        - name: range
          in: query
          schema:
            type: string
            enum: [1h, 24h, 7d, 30d]
            default: 24h
        - name: model
          in: query
          description: Filter to one model or requested-model alias (1-200 chars).
          schema:
            type: string
            minLength: 1
            maxLength: 200
      responses:
        "200":
          description: Cache health summary for the requested window
          content:
            application/json:
              schema:
                type: object
                properties:
                  totalCalls:
                    type: integer
                  cacheReadTotal:
                    type: integer
                  cacheWriteTotal:
                    type: integer
                  writeReadRatio:
                    type: number
                  warmCalls:
                    type: integer
                  coldCalls:
                    type: integer
                  rewriteCalls:
                    type: integer
                  uncachedCalls:
                    type: integer
                  writeP50:
                    type: integer
                  writeP90:
                    type: integer
                  writeP99:
                    type: integer
                  writeMax:
                    type: integer
                  heavyWriteCalls:
                    type: integer
                  heavyWriteCallShare:
                    type: number
                  heavyWriteTokenShare:
                    type: number
                  heavyWriteThreshold:
                    type: integer
                  verdict:
                    type: string
                    enum: [healthy, degraded, thrash, no-data]
                  byModel:
                    type: array
                    items:
                      type: object
                      properties:
                        model:
                          type: string
                        calls:
                          type: integer
                        cacheReadTotal:
                          type: integer
                        cacheWriteTotal:
                          type: integer
                        writeReadRatio:
                          type: number
                        heavyWriteCalls:
                          type: integer
                  timeRange:
                    type: string
                  since:
                    type: string
                    format: date-time
                  truncated:
                    type: boolean
                    description: True when the window held more than 5000 rows and only the newest were summarized.
        "400":
          description: Invalid query parameters

  /api/usage/model-latency-stats:
    get:
      tags: [Usage]
      summary: Get per-model/provider latency statistics
      description: >-
        Aggregates `usage_history` rows into per-(provider, model) latency stats
        (avg/p50/p95/p99, std-dev, TTFT, tokens/sec) over a rolling window. Falls
        back from successful-only to all-sample rows when the successful count is
        below `minSamples`.
      security:
        - ManagementSessionAuth: []
      parameters:
        - name: windowHours
          in: query
          schema:
            type: number
            minimum: 0
            exclusiveMinimum: true
            maximum: 720
            default: 24
        - name: minSamples
          in: query
          schema:
            type: integer
            minimum: 1
            default: 1
        - name: maxRows
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 50000
            default: 10000
        - name: provider
          in: query
          schema:
            type: string
            minLength: 1
            maxLength: 64
        - name: model
          in: query
          schema:
            type: string
            minLength: 1
            maxLength: 256
      responses:
        "200":
          description: Latency stats keyed by provider/model, plus the window used
          content:
            application/json:
              schema:
                type: object
                properties:
                  entries:
                    type: array
                    items:
                      type: object
                      properties:
                        provider:
                          type: string
                        model:
                          type: string
                        key:
                          type: string
                        totalRequests:
                          type: integer
                        successfulRequests:
                          type: integer
                        successRate:
                          type: number
                        avgLatencyMs:
                          type: number
                        p50LatencyMs:
                          type: number
                        p95LatencyMs:
                          type: number
                        p99LatencyMs:
                          type: number
                        latencyStdDev:
                          type: number
                        windowHours:
                          type: number
                        avgTtftMs:
                          type: number
                        avgE2ELatencyMs:
                          type: number
                        avgTokensPerSecond:
                          type: number
                  windowHours:
                    type: number
                  generatedAt:
                    type: string
                    format: date-time
        "400":
          description: Invalid query parameters
        "401":
          $ref: "#/components/responses/ManagementAuthenticationRequired"
        "403":
          $ref: "#/components/responses/ManagementInvalidToken"

  # ─── Pricing ───────────────────────────────────────────────────

  /api/pricing:
    get:
      tags: [Pricing]
      summary: Get model pricing
      responses:
        "200":
          description: Current pricing configuration
    post:
      tags: [Pricing]
      summary: Set model pricing
      responses:
        "200":
          description: Updated pricing

  /api/pricing/defaults:
    get:
      tags: [Pricing]
      summary: Get default pricing
      responses:
        "200":
          description: Default pricing data

  /api/pricing/models:
    get:
      tags: [Pricing]
      summary: Get pricing per model
      description: Returns pricing information organized by model.
      responses:
        "200":
          description: Per-model pricing data

  # ─── Translator ────────────────────────────────────────────────

  /api/translator/detect:
    post:
      tags: [Translator]
      summary: Detect request format
      description: Detects the API format of a request body (OpenAI, Claude, Gemini, etc.)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [body]
              properties:
                body:
                  type: object
      responses:
        "200":
          description: Detected format

  /api/translator/translate:
    post:
      tags: [Translator]
      summary: Translate between formats
      description: Converts a request between API formats (e.g. Claude → OpenAI)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [sourceFormat, targetFormat, body]
              properties:
                step:
                  type: string
                sourceFormat:
                  type: string
                targetFormat:
                  type: string
                provider:
                  type: string
                body:
                  type: object
      responses:
        "200":
          description: Translated request

  /api/translator/send:
    post:
      tags: [Translator]
      summary: Send translated request to provider
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [provider, body]
              properties:
                provider:
                  type: string
                body:
                  type: object
      responses:
        "200":
          description: Provider response (may be SSE stream)

  /api/translator/history:
    get:
      tags: [Translator]
      summary: Get translation history
      description: Returns recent translation events for the Live Monitor
      responses:
        "200":
          description: Translation history entries

  # ─── CLI Remote Mode ───────────────────────────────────────────

  /api/cli/connect:
    post:
      tags: [CLI Remote Mode]
      summary: Exchange the management password for a scoped CLI access token
      description: >
        Remote-mode bootstrap. Public (password-gated) route: verifies the
        management password with brute-force lockout, then mints an `oma_`
        access token. The plaintext token is returned once.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [password]
              properties:
                password: { type: string }
                name: { type: string }
                scope: { type: string, enum: [read, write, admin] }
                expiresInDays: { type: integer, minimum: 1, maximum: 3650 }
      responses:
        "200":
          description: Token minted (token returned once)
        "401":
          description: Invalid password
        "429":
          description: Too many failed attempts

  /api/cli/whoami:
    get:
      tags: [CLI Remote Mode]
      summary: Report the current credential (scope, name, expiry)
      responses:
        "200":
          description: Authenticated; access-token details when applicable
        "401":
          description: Authentication required

  /api/cli/tokens:
    get:
      tags: [CLI Remote Mode]
      summary: List access tokens (masked) — admin scope
      responses:
        "200":
          description: Masked token list
        "403":
          description: Insufficient scope
    post:
      tags: [CLI Remote Mode]
      summary: Create a scoped access token — admin scope
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string }
                scope: { type: string, enum: [read, write, admin] }
                expiresInDays: { type: integer, minimum: 1, maximum: 3650 }
      responses:
        "200":
          description: Token created (token returned once)
        "403":
          description: Insufficient scope

  /api/cli/tokens/{id}:
    delete:
      tags: [CLI Remote Mode]
      summary: Revoke an access token by id or display prefix — admin scope
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Token revoked
        "403":
          description: Insufficient scope
        "404":
          description: Token not found or already revoked

  # ─── CLI Tools ─────────────────────────────────────────────────

  /api/cli-tools/backups:
    get:
      tags: [CLI Tools]
      summary: List CLI tool backups
      responses:
        "200":
          description: Backup list
    post:
      tags: [CLI Tools]
      summary: Create CLI tool backup
      responses:
        "200":
          description: Backup created

  /api/cli-tools/runtime/{toolId}:
    get:
      tags: [CLI Tools]
      summary: Get runtime status for a CLI tool
      parameters:
        - name: toolId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Runtime status

  /api/cli-tools/guide-settings/{toolId}:
    get:
      tags: [CLI Tools]
      summary: Get guide settings for a tool
      parameters:
        - name: toolId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Guide settings

  /api/cli-tools/antigravity-mitm:
    get:
      tags: [CLI Tools]
      summary: Get Antigravity MITM proxy settings
      responses:
        "200":
          description: MITM proxy configuration
    post:
      tags: [CLI Tools]
      summary: Update Antigravity MITM proxy settings
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Updated MITM proxy configuration
    delete:
      tags: [CLI Tools]
      summary: Reset Antigravity MITM proxy settings
      responses:
        "200":
          description: MITM proxy settings reset

  /api/cli-tools/antigravity-mitm/alias:
    get:
      tags: [CLI Tools]
      summary: Get Antigravity MITM alias configuration
      responses:
        "200":
          description: Alias configuration
    put:
      tags: [CLI Tools]
      summary: Update Antigravity MITM alias configuration
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Updated alias configuration

  /api/cli-tools/claude-settings:
    get:
      tags: [CLI Tools]
      summary: Get Claude CLI settings
      responses:
        "200":
          description: Claude CLI configuration
    post:
      tags: [CLI Tools]
      summary: Apply Claude CLI settings
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Claude CLI settings applied
    delete:
      tags: [CLI Tools]
      summary: Reset Claude CLI settings
      responses:
        "200":
          description: Claude CLI settings reset

  /api/cli-tools/cline-settings:
    get:
      tags: [CLI Tools]
      summary: Get Cline CLI settings
      responses:
        "200":
          description: Cline CLI configuration
    post:
      tags: [CLI Tools]
      summary: Apply Cline CLI settings
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Cline CLI settings applied
    delete:
      tags: [CLI Tools]
      summary: Reset Cline CLI settings
      responses:
        "200":
          description: Cline CLI settings reset

  /api/cli-tools/codex-profiles:
    get:
      tags: [CLI Tools]
      summary: Get Codex profiles
      responses:
        "200":
          description: Codex profile list
    post:
      tags: [CLI Tools]
      summary: Create Codex profile
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Profile created
    put:
      tags: [CLI Tools]
      summary: Update Codex profile
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Profile updated
    delete:
      tags: [CLI Tools]
      summary: Delete Codex profile
      responses:
        "200":
          description: Profile deleted

  /api/cli-tools/codex-settings:
    get:
      tags: [CLI Tools]
      summary: Get Codex CLI settings
      responses:
        "200":
          description: Codex CLI configuration
    post:
      tags: [CLI Tools]
      summary: Apply Codex CLI settings
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Codex CLI settings applied
    delete:
      tags: [CLI Tools]
      summary: Reset Codex CLI settings
      responses:
        "200":
          description: Codex CLI settings reset

  /api/cli-tools/droid-settings:
    get:
      tags: [CLI Tools]
      summary: Get Droid CLI settings
      responses:
        "200":
          description: Droid CLI configuration
    post:
      tags: [CLI Tools]
      summary: Apply Droid CLI settings
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Droid CLI settings applied
    delete:
      tags: [CLI Tools]
      summary: Reset Droid CLI settings
      responses:
        "200":
          description: Droid CLI settings reset

  /api/cli-tools/kilo-settings:
    get:
      tags: [CLI Tools]
      summary: Get Kilo CLI settings
      responses:
        "200":
          description: Kilo CLI configuration
    post:
      tags: [CLI Tools]
      summary: Apply Kilo CLI settings
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Kilo CLI settings applied
    delete:
      tags: [CLI Tools]
      summary: Reset Kilo CLI settings
      responses:
        "200":
          description: Kilo CLI settings reset

  /api/cli-tools/openclaw-settings:
    get:
      tags: [CLI Tools]
      summary: Get OpenClaw CLI settings
      responses:
        "200":
          description: OpenClaw CLI configuration
    post:
      tags: [CLI Tools]
      summary: Apply OpenClaw CLI settings
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: OpenClaw CLI settings applied
    delete:
      tags: [CLI Tools]
      summary: Reset OpenClaw CLI settings
      responses:
        "200":
          description: OpenClaw CLI settings reset

  # ─── Embedded Services ─────────────────────────────────────────
  # All routes LOCAL_ONLY (loopback only) — hard rule #17.
  # See docs/frameworks/EMBEDDED-SERVICES.md for full reference.

  /api/services/9router/install:
    post:
      tags: [Embedded Services]
      summary: Install 9Router from npm
      description: >-
        Installs the `9router` npm package under DATA_DIR/services/9router/.
        Uses execFile (no shell interpolation — hard rule #13).
        **LOCAL_ONLY** — loopback only.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                version:
                  type: string
                  default: latest
                  description: npm version tag or semver to install
      responses:
        "200":
          description: Install succeeded
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                  installedVersion:
                    type: string
                  path:
                    type: string
        "400":
          description: Invalid request body
        "500":
          description: npm install failed

  /api/services/9router/start:
    post:
      tags: [Embedded Services]
      summary: Start 9Router
      description: >-
        Spawns the 9Router process. Idempotent if already running.
        **LOCAL_ONLY** — loopback only.
      responses:
        "200":
          description: Service started (or already running)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ServiceStatus"
        "409":
          description: 9Router is not installed
        "503":
          description: Start failed

  /api/services/9router/stop:
    post:
      tags: [Embedded Services]
      summary: Stop 9Router
      description: >-
        Gracefully stops 9Router (SIGTERM → 15 s → SIGKILL). Idempotent.
        **LOCAL_ONLY** — loopback only.
      responses:
        "200":
          description: Service stopped
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ServiceStatus"
        "503":
          description: Stop failed

  /api/services/9router/restart:
    post:
      tags: [Embedded Services]
      summary: Restart 9Router
      description: >-
        Equivalent to stop() then start() under the operation lock.
        **LOCAL_ONLY** — loopback only.
      responses:
        "200":
          description: Service restarted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ServiceStatus"

  /api/services/9router/update:
    post:
      tags: [Embedded Services]
      summary: Update 9Router to a newer npm version
      description: >-
        Stops the service (if running), installs the newer npm version, then restarts.
        **LOCAL_ONLY** — loopback only.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                version:
                  type: string
                  default: latest
      responses:
        "200":
          description: Update succeeded
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                  previousVersion:
                    type: string
                  installedVersion:
                    type: string
        "400":
          description: Invalid request body
        "500":
          description: Update failed

  /api/services/9router/rotate-key:
    post:
      tags: [Embedded Services]
      summary: Rotate the 9Router API key
      description: >-
        Generates a new API key, encrypts it at-rest, and restarts the service to
        apply it. The plaintext key is never returned.
        **LOCAL_ONLY** — loopback only.
      responses:
        "200":
          description: Key rotated
          content:
            application/json:
              schema:
                type: object
                properties:
                  keyRotated:
                    type: boolean
                  restarted:
                    type: boolean
        "500":
          description: Rotation failed

  /api/services/9router/status:
    get:
      tags: [Embedded Services]
      summary: Get 9Router status
      description: >-
        Returns combined live supervisor state and DB metadata.
        **LOCAL_ONLY** — loopback only.
      responses:
        "200":
          description: Status response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ServiceStatusExtended"
        "500":
          description: Status read failed

  /api/services/9router/auto-start:
    post:
      tags: [Embedded Services]
      summary: Toggle 9Router auto-start
      description: >-
        When enabled, 9Router starts automatically on the next OmniRoute boot.
        **LOCAL_ONLY** — loopback only.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [enabled]
              properties:
                enabled:
                  type: boolean
      responses:
        "200":
          description: Auto-start flag updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  autoStart:
                    type: boolean
        "400":
          description: Invalid request body

  /api/services/cliproxy/install:
    post:
      tags: [Embedded Services]
      summary: Install CLIProxyAPI from npm
      description: >-
        Installs the CLIProxyAPI package under DATA_DIR/services/cliproxy/.
        **LOCAL_ONLY** — loopback only.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                version:
                  type: string
                  default: latest
      responses:
        "200":
          description: Install succeeded
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                  installedVersion:
                    type: string
        "400":
          description: Invalid request body
        "500":
          description: npm install failed

  /api/services/cliproxy/start:
    post:
      tags: [Embedded Services]
      summary: Start CLIProxyAPI
      description: >-
        Spawns the CLIProxyAPI process. Idempotent if already running.
        **LOCAL_ONLY** — loopback only.
      responses:
        "200":
          description: Service started
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ServiceStatus"
        "409":
          description: CLIProxyAPI is not installed
        "503":
          description: Start failed

  /api/services/cliproxy/stop:
    post:
      tags: [Embedded Services]
      summary: Stop CLIProxyAPI
      description: >-
        Gracefully stops CLIProxyAPI. Idempotent.
        **LOCAL_ONLY** — loopback only.
      responses:
        "200":
          description: Service stopped
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ServiceStatus"

  /api/services/cliproxy/restart:
    post:
      tags: [Embedded Services]
      summary: Restart CLIProxyAPI
      description: >-
        stop() then start() under the operation lock.
        **LOCAL_ONLY** — loopback only.
      responses:
        "200":
          description: Service restarted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ServiceStatus"

  /api/services/cliproxy/update:
    post:
      tags: [Embedded Services]
      summary: Update CLIProxyAPI to a newer npm version
      description: >-
        Stops, installs newer version, restarts.
        **LOCAL_ONLY** — loopback only.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                version:
                  type: string
                  default: latest
      responses:
        "200":
          description: Update succeeded
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                  installedVersion:
                    type: string
        "500":
          description: Update failed

  /api/services/cliproxy/status:
    get:
      tags: [Embedded Services]
      summary: Get CLIProxyAPI status
      description: >-
        Returns live supervisor state and DB metadata (no apiKeyMasked — CLIProxyAPI
        does not use an injected API key).
        **LOCAL_ONLY** — loopback only.
      responses:
        "200":
          description: Status response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ServiceStatus"

  /api/services/cliproxy/auto-start:
    post:
      tags: [Embedded Services]
      summary: Toggle CLIProxyAPI auto-start
      description: >-
        When enabled, CLIProxyAPI starts automatically on the next OmniRoute boot.
        **LOCAL_ONLY** — loopback only.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [enabled]
              properties:
                enabled:
                  type: boolean
      responses:
        "200":
          description: Auto-start flag updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  autoStart:
                    type: boolean
        "400":
          description: Invalid request body

  /api/services/mux/install:
    post:
      tags: [Embedded Services]
      summary: Install Mux from npm
      description: >-
        Installs the `mux` npm package (coder/mux — local agent-orchestration
        daemon) under DATA_DIR/services/mux/. **LOCAL_ONLY** — loopback only.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                version:
                  type: string
                  default: latest
      responses:
        "200":
          description: Install succeeded
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                  installedVersion:
                    type: string
        "400":
          description: Invalid request body
        "500":
          description: npm install failed

  /api/services/mux/start:
    post:
      tags: [Embedded Services]
      summary: Start Mux
      description: >-
        Spawns `mux server --host 127.0.0.1 --port <port>`. Idempotent if
        already running. **LOCAL_ONLY** — loopback only.
      responses:
        "200":
          description: Service started
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ServiceStatus"
        "409":
          description: Mux is not installed
        "503":
          description: Start failed

  /api/services/mux/stop:
    post:
      tags: [Embedded Services]
      summary: Stop Mux
      description: >-
        Gracefully stops Mux. Idempotent.
        **LOCAL_ONLY** — loopback only.
      responses:
        "200":
          description: Service stopped
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ServiceStatus"

  /api/services/mux/restart:
    post:
      tags: [Embedded Services]
      summary: Restart Mux
      description: >-
        stop() then start() under the operation lock.
        **LOCAL_ONLY** — loopback only.
      responses:
        "200":
          description: Service restarted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ServiceStatus"

  /api/services/mux/update:
    post:
      tags: [Embedded Services]
      summary: Update Mux to a newer npm version
      description: >-
        Stops, installs newer version, restarts.
        **LOCAL_ONLY** — loopback only.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                version:
                  type: string
                  default: latest
      responses:
        "200":
          description: Update succeeded
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                  installedVersion:
                    type: string
        "500":
          description: Update failed

  /api/services/mux/status:
    get:
      tags: [Embedded Services]
      summary: Get Mux status
      description: >-
        Returns live supervisor state and DB metadata.
        **LOCAL_ONLY** — loopback only.
      responses:
        "200":
          description: Status response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ServiceStatus"

  /api/services/mux/auto-start:
    post:
      tags: [Embedded Services]
      summary: Toggle Mux auto-start
      description: >-
        When enabled, Mux starts automatically on the next OmniRoute boot.
        **LOCAL_ONLY** — loopback only.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [enabled]
              properties:
                enabled:
                  type: boolean
      responses:
        "200":
          description: Auto-start flag updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  autoStart:
                    type: boolean
        "400":
          description: Invalid request body

  /api/services/bifrost/install:
    post:
      tags: [Embedded Services]
      summary: Install Bifrost
      description: >-
        Installs the `@maximhq/bifrost` npm package under DATA_DIR/services/bifrost/.
        The package downloads the Go binary on first run. Accepts an optional `version`
        field (semver or `latest`). **LOCAL_ONLY** — loopback only.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                version:
                  type: string
                  default: latest
      responses:
        "200":
          description: Installation result
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                  installedVersion:
                    type: string
                  installPath:
                    type: string
                  durationMs:
                    type: number

  /api/services/bifrost/start:
    post:
      tags: [Embedded Services]
      summary: Start Bifrost
      description: Starts the supervised Bifrost process. **LOCAL_ONLY** — loopback only.
      responses:
        "200":
          description: Service status after start
        "409":
          description: Bifrost is not installed

  /api/services/bifrost/stop:
    post:
      tags: [Embedded Services]
      summary: Stop Bifrost
      description: Stops the supervised Bifrost process. **LOCAL_ONLY** — loopback only.
      responses:
        "200":
          description: Service status after stop

  /api/services/bifrost/restart:
    post:
      tags: [Embedded Services]
      summary: Restart Bifrost
      description: Restarts the supervised Bifrost process. **LOCAL_ONLY** — loopback only.
      responses:
        "200":
          description: Service status after restart
        "409":
          description: Bifrost is not installed

  /api/services/bifrost/update:
    post:
      tags: [Embedded Services]
      summary: Update Bifrost
      description: >-
        Updates Bifrost to the latest npm version. Stops the running process,
        installs the new version, and restarts if it was previously running.
        **LOCAL_ONLY** — loopback only.
      responses:
        "200":
          description: Update result

  /api/services/bifrost/status:
    get:
      tags: [Embedded Services]
      summary: Get Bifrost status
      description: Returns live and DB status for the supervised Bifrost service. **LOCAL_ONLY** — loopback only.
      responses:
        "200":
          description: Bifrost service status

  /api/services/bifrost/auto-start:
    post:
      tags: [Embedded Services]
      summary: Toggle Bifrost auto-start
      description: >-
        When enabled, Bifrost starts automatically on the next OmniRoute boot.
        **LOCAL_ONLY** — loopback only.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [enabled]
              properties:
                enabled:
                  type: boolean
      responses:
        "204":
          description: Auto-start flag updated
        "400":
          description: Invalid request body

  /api/services/{name}/logs:
    get:
      tags: [Embedded Services]
      summary: Stream service logs via SSE
      description: >-
        Returns a Server-Sent Events stream from the service's in-memory ring buffer
        (5 MB, circular). Sends a `snapshot` event with historical lines first, then
        live `log` events, plus a `heartbeat` every 15 s.
        **LOCAL_ONLY** — loopback only.
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
            enum: [9router, cliproxy]
        - name: tail
          in: query
          schema:
            type: integer
            default: 200
            maximum: 1000
          description: Number of historical lines to include in the initial snapshot
        - name: filter
          in: query
          schema:
            type: string
            maxLength: 200
          description: >-
            Case-insensitive substring filter applied to log lines.
            No regex — ReDoS-safe by design.
      responses:
        "200":
          description: SSE log stream
          content:
            text/event-stream:
              schema:
                type: string
                description: >-
                  Events: `snapshot` (LogLine[]), `log` (LogLine), `heartbeat` ({})
        "400":
          description: filter parameter exceeds maximum length
        "404":
          description: Service not found

  # ─── OAuth ─────────────────────────────────────────────────────

  /api/oauth/{provider}/{action}:
    get:
      tags: [OAuth]
      summary: OAuth flow handler
      description: Handles OAuth authorization and callback for providers
      parameters:
        - name: provider
          in: path
          required: true
          schema:
            type: string
        - name: action
          in: path
          required: true
          schema:
            type: string
            enum: [authorize, callback, refresh, status]
      responses:
        "200":
          description: OAuth flow response
        "302":
          description: Redirect to provider auth page

  /api/oauth/cursor/auto-import:
    get:
      tags: [OAuth]
      summary: Auto-import Cursor OAuth credentials
      description: Automatically detects and imports Cursor credentials from local config.
      responses:
        "200":
          description: Import result

  /api/oauth/cursor/import:
    get:
      tags: [OAuth]
      summary: Get Cursor import status
      responses:
        "200":
          description: Current import status
    post:
      tags: [OAuth]
      summary: Import Cursor OAuth credentials
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Credentials imported

  /api/oauth/kiro/auto-import:
    get:
      tags: [OAuth]
      summary: Auto-import Kiro OAuth credentials
      description: Automatically detects and imports Kiro credentials from local config.
      responses:
        "200":
          description: Import result

  /api/oauth/kiro/import:
    get:
      tags: [OAuth]
      summary: Get Kiro import status
      responses:
        "200":
          description: Current import status
    post:
      tags: [OAuth]
      summary: Import Kiro OAuth credentials
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Credentials imported

  /api/oauth/kiro/social-authorize:
    get:
      tags: [OAuth]
      summary: Initiate Kiro social OAuth authorization
      description: Starts the social OAuth flow for Kiro.
      responses:
        "302":
          description: Redirect to OAuth provider

  /api/oauth/kiro/social-exchange:
    post:
      tags: [OAuth]
      summary: Exchange Kiro social OAuth token
      description: Exchanges the authorization code for access tokens.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Token exchange result

  # ─── Cloud ─────────────────────────────────────────────────────

  /api/cloud/auth:
    post:
      tags: [Cloud]
      summary: Authenticate with cloud worker
      description: Authenticates with the OmniRoute cloud worker for remote access.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Authentication result

  /api/cloud/credentials/update:
    put:
      tags: [Cloud]
      summary: Update cloud worker credentials
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Credentials updated

  /api/cloud/model/resolve:
    post:
      tags: [Cloud]
      summary: Resolve model via cloud
      description: Resolves a model request through the cloud worker.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Resolved model info

  /api/cloud/models/alias:
    get:
      tags: [Cloud]
      summary: Get cloud model aliases
      responses:
        "200":
          description: Cloud model alias list
    put:
      tags: [Cloud]
      summary: Update cloud model alias
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Alias updated

  # ─── Fallback ──────────────────────────────────────────────────

  /api/fallback/chains:
    get:
      tags: [Fallback]
      summary: List fallback chains
      description: Returns all registered fallback chains for model routing.
      responses:
        "200":
          description: Fallback chain list
    post:
      tags: [Fallback]
      summary: Create fallback chain
      description: Registers a fallback routing chain for a model.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [model, chain]
              properties:
                model:
                  type: string
                chain:
                  type: array
                  items:
                    type: object
                    properties:
                      provider:
                        type: string
                      priority:
                        type: integer
                      enabled:
                        type: boolean
      responses:
        "200":
          description: Fallback chain created
    delete:
      tags: [Fallback]
      summary: Delete fallback chain
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [model]
              properties:
                model:
                  type: string
      responses:
        "200":
          description: Fallback chain deleted

  # ─── System ────────────────────────────────────────────────────

  /api/auth/login:
    post:
      tags: [System]
      summary: Authenticate user
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [password]
              properties:
                password:
                  type: string
                  minLength: 1
      responses:
        "200":
          description: JWT token returned
        "400":
          description: Invalid login request
        "401":
          description: Invalid password
        "403":
          description: Password setup required
        "429":
          description: Too many failed attempts

  /api/auth/logout:
    post:
      tags: [System]
      summary: Log out
      responses:
        "200":
          description: Session cleared

  /api/auth/oidc/login:
    get:
      tags: [System]
      summary: Start OIDC login for the dashboard admin gate
      description: |
        Builds an authorization URL from the configured OIDC issuer/client (discovered
        via `{issuer}/.well-known/openid-configuration`, falling back to `{issuer}/authorize`),
        sets a short-lived `oidc_state` cookie, and redirects the browser. Password login
        remains available as a fallback while OIDC is enabled.
      responses:
        "302":
          description: Redirect to the identity provider's authorization endpoint
        "400":
          description: OIDC is not configured (missing issuer, client id/secret, or disabled)

  /api/auth/oidc/callback:
    get:
      tags: [System]
      summary: Complete OIDC login for the dashboard admin gate
      description: |
        Validates the `state` cookie, exchanges the authorization `code` for tokens,
        verifies the ID token against the issuer's JWKS (audience = client id), and —
        if `oidcAllowedSubjects` is configured — checks the token's `sub`/`email` against
        that allowlist. On success it mints the same 30-day `auth_token` dashboard-session
        JWT used by password login and redirects to `/dashboard`.
      parameters:
        - name: code
          in: query
          required: true
          schema:
            type: string
        - name: state
          in: query
          required: true
          schema:
            type: string
      responses:
        "302":
          description: >-
            Redirect to `/dashboard` on success, or to
            `/login?oidc_error={missing_code|invalid_state|not_configured|token_exchange|token_response|no_id_token|subject_not_allowed|id_token_invalid|server_misconfigured}`
            on failure.

  /api/init:
    get:
      tags: [System]
      summary: Initialize application
      responses:
        "200":
          description: Init status

  /api/restart:
    post:
      tags: [System]
      summary: Restart the application
      responses:
        "200":
          description: Restart initiated

  /api/shutdown:
    post:
      tags: [System]
      summary: Shutdown the application
      x-always-protected: true
      responses:
        "200":
          description: Shutdown initiated

  /api/db-backups:
    get:
      tags: [System]
      summary: List database backups
      responses:
        "200":
          description: Backup list
    post:
      tags: [System]
      summary: Create database backup
      responses:
        "200":
          description: Backup created
    patch:
      tags: [System]
      summary: Save database backup retention settings
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                keepLatest:
                  type: integer
                  minimum: 1
                  maximum: 200
                retentionDays:
                  type: integer
                  minimum: 0
                  maximum: 3650
      responses:
        "200":
          description: Backup retention settings saved

  /api/storage/health:
    get:
      tags: [System]
      summary: Check storage health
      responses:
        "200":
          description: Storage health status

  /api/sync/cloud:
    post:
      tags: [System]
      summary: Sync with cloud
      responses:
        "200":
          description: Sync result

  /api/sync/initialize:
    post:
      tags: [System]
      summary: Initialize cloud sync
      responses:
        "200":
          description: Sync initialized

  # ─── Resilience & Monitoring ────────────────────────────────────

  /api/resilience:
    get:
      tags: [System]
      summary: Get resilience configuration
      responses:
        "200":
          description: Request queue, connection cooldown, provider breaker, and wait settings
    patch:
      tags: [System]
      summary: Update resilience configuration
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Updated resilience configuration

  /api/resilience/reset:
    post:
      tags: [System]
      summary: Reset circuit breakers
      responses:
        "200":
          description: Circuit breakers reset

  /api/monitoring/health:
    get:
      tags: [System]
      summary: System health check
      description: Returns system health including uptime, memory, circuit breakers, rate limits
      responses:
        "200":
          description: Health status

  /api/rate-limits:
    get:
      tags: [System]
      summary: Get per-account rate limit status
      responses:
        "200":
          description: Rate limit status by account

  /api/sessions:
    get:
      tags: [System]
      summary: Get active sessions
      responses:
        "200":
          description: Active session list

  /api/cache:
    get:
      tags: [System]
      summary: Get cache statistics
      responses:
        "200":
          description: Semantic cache and idempotency stats
    delete:
      tags: [System]
      summary: Clear all caches
      responses:
        "200":
          description: Caches cleared

  /api/cache/stats:
    get:
      tags: [System]
      summary: Get detailed cache statistics
      description: Returns detailed statistics for all cache layers.
      responses:
        "200":
          description: Detailed cache stats
    delete:
      tags: [System]
      summary: Clear cache statistics
      responses:
        "200":
          description: Cache stats cleared

  # ─── Telemetry & Token Health ───────────────────────────────────

  /api/telemetry/summary:
    get:
      tags: [Telemetry]
      summary: Get telemetry summary
      description: Returns aggregated telemetry data including request metrics and performance stats.
      responses:
        "200":
          description: Telemetry summary data

  /api/token-health:
    get:
      tags: [Telemetry]
      summary: Get token health status
      description: Returns health status of OAuth tokens across all providers.
      responses:
        "200":
          description: Token health status

  # ─── Evals & Policies ──────────────────────────────────────────

  /api/evals:
    get:
      tags: [System]
      summary: List eval suites
      responses:
        "200":
          description: Eval suite list
    post:
      tags: [System]
      summary: Run evaluation
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Eval results

  /api/evals/{suiteId}:
    get:
      tags: [System]
      summary: Get eval suite details
      parameters:
        - name: suiteId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Eval suite details

  /api/policies:
    get:
      tags: [System]
      summary: List routing policies
      responses:
        "200":
          description: Policy list
    post:
      tags: [System]
      summary: Create routing policy
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "201":
          description: Created policy
    delete:
      tags: [System]
      summary: Delete routing policy
      responses:
        "200":
          description: Policy deleted

  /api/compliance/audit-log:
    get:
      tags: [System]
      summary: Get compliance audit log
      description: >
        Returns paginated audit log entries. Use `level=high` to filter to
        high-level actions only (powers the Activity feed). Use `level=all`
        (default) for full compliance table.
      security:
        - bearerAuth: []
      parameters:
        - name: level
          in: query
          schema:
            type: string
            enum: [high, all]
            default: all
          description: "high = Activity feed events only; all = all audit events"
        - name: action
          in: query
          schema:
            type: string
          description: Filter by exact action string (e.g. "provider.added")
        - name: actor
          in: query
          schema:
            type: string
          description: Filter by actor identifier
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            maximum: 500
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
      responses:
        "200":
          description: Audit log entries
        "401":
          description: Unauthorized
        "500":
          description: Internal server error

  # ─── Quota Sharing (Group B, plan 22) ────────────────────────────

  /api/quota/pools:
    get:
      tags: [Quota]
      summary: List quota pools
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Array of QuotaPool objects
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/QuotaPool"
        "401":
          description: Unauthorized
        "500":
          description: Internal server error
    post:
      tags: [Quota]
      summary: Create quota pool
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PoolCreate"
      responses:
        "201":
          description: Pool created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/QuotaPool"
        "400":
          description: Validation error (Zod)
        "401":
          description: Unauthorized
        "500":
          description: Internal server error

  /api/quota/pools/{id}:
    get:
      tags: [Quota]
      summary: Get quota pool by ID
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: QuotaPool object
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/QuotaPool"
        "401":
          description: Unauthorized
        "404":
          description: Pool not found
        "500":
          description: Internal server error
    patch:
      tags: [Quota]
      summary: Update quota pool (name or allocations)
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PoolUpdate"
      responses:
        "200":
          description: Updated pool
        "400":
          description: Validation error
        "401":
          description: Unauthorized
        "404":
          description: Pool not found
        "500":
          description: Internal server error
    delete:
      tags: [Quota]
      summary: Delete quota pool
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Deleted
        "401":
          description: Unauthorized
        "404":
          description: Pool not found
        "500":
          description: Internal server error

  /api/quota/pools/{id}/usage:
    get:
      tags: [Quota]
      summary: Get pool usage snapshot (per-key consumption + burn rate)
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: PoolUsageSnapshot
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PoolUsageSnapshot"
        "401":
          description: Unauthorized
        "404":
          description: Pool not found
        "500":
          description: Internal server error

  /api/quota/plans:
    get:
      tags: [Quota]
      summary: List resolved provider plans (catalog + manual overrides)
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Array of ProviderPlan
        "401":
          description: Unauthorized
        "500":
          description: Internal server error

  /api/quota/plans/{connectionId}:
    get:
      tags: [Quota]
      summary: Get resolved plan for a connection
      security:
        - bearerAuth: []
      parameters:
        - name: connectionId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: ProviderPlan (source = auto | manual)
        "401":
          description: Unauthorized
        "404":
          description: Connection not found
        "500":
          description: Internal server error
    put:
      tags: [Quota]
      summary: Upsert manual plan override for a connection
      security:
        - bearerAuth: []
      parameters:
        - name: connectionId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PlanUpsert"
      responses:
        "200":
          description: Updated plan
        "400":
          description: Validation error (Zod)
        "401":
          description: Unauthorized
        "500":
          description: Internal server error
    delete:
      tags: [Quota]
      summary: Delete manual plan override (reverts to catalog/auto)
      security:
        - bearerAuth: []
      parameters:
        - name: connectionId
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Override deleted
        "401":
          description: Unauthorized
        "404":
          description: Override not found
        "500":
          description: Internal server error

  /api/quota/preview:
    get:
      tags: [Quota]
      summary: Dry-run quota enforcement check (preview only, no consumption recorded)
      security:
        - bearerAuth: []
      parameters:
        - name: apiKeyId
          in: query
          required: true
          schema:
            type: string
        - name: poolId
          in: query
          required: true
          schema:
            type: string
        - name: estimatedTokens
          in: query
          schema:
            type: number
        - name: estimatedUsd
          in: query
          schema:
            type: number
        - name: estimatedRequests
          in: query
          schema:
            type: integer
      responses:
        "200":
          description: EnforceDecision (allow/block + reason)
        "400":
          description: Validation error (Zod)
        "401":
          description: Unauthorized
        "500":
          description: Internal server error

  /api/settings/quota-store:
    get:
      tags: [Settings]
      summary: Get current quota store driver settings
      description: Redis URL is masked in the response (shows only scheme+host).
      security:
        - bearerAuth: []
      responses:
        "200":
          description: QuotaStoreSettings (driver + masked redisUrl)
        "401":
          description: Unauthorized
        "500":
          description: Internal server error
    put:
      tags: [Settings]
      summary: Update quota store driver settings
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/QuotaStoreSettings"
      responses:
        "200":
          description: Settings updated
        "400":
          description: Validation error (Zod) — e.g. driver=redis without valid URL
        "401":
          description: Unauthorized
        "500":
          description: Internal server error

  # ─── v1beta (Gemini-Compatible) ─────────────────────────────────

  /api/v1beta/models:
    get:
      tags: [Models]
      summary: List models (Gemini format)
      description: Returns models in Gemini v1beta format for native SDK compatibility
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Model list in Gemini format

  /api/v1beta/models/{path}:
    post:
      tags: [Models]
      summary: Gemini generateContent
      description: Gemini-compatible generateContent endpoint
      security:
        - BearerAuth: []
      parameters:
        - name: path
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Generated content

  # ─── AgentBridge ──────────────────────────────────────────────

  /api/tools/agent-bridge/agents:
    get:
      tags: [AgentBridge]
      summary: List all 9 IDE agents with current state
      description: >-
        Returns the state (dns_enabled, cert_trusted, setup_completed, last_started_at,
        last_error) for all 9 configured IDE agents. LOCAL_ONLY.
      responses:
        "200":
          description: Array of agent state rows
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/AgentBridgeAgentState"
        "403":
          description: Loopback-only — request came from a non-loopback address

  /api/tools/agent-bridge/state:
    get:
      tags: [AgentBridge]
      summary: Get global AgentBridge server state
      description: Returns running status, port, cert info, and intercepted request count.
      responses:
        "200":
          description: Server state
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentBridgeServerState"

  /api/tools/agent-bridge/server:
    post:
      tags: [AgentBridge]
      summary: Control AgentBridge MITM server
      description: Start, stop, restart, trust-cert, or regenerate-cert. SPAWN_CAPABLE.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AgentBridgeServerAction"
      responses:
        "200":
          description: Action executed
        "400":
          description: Invalid action
        "409":
          description: Port 443 conflict

  /api/tools/agent-bridge/agents/{agentId}/dns:
    post:
      tags: [AgentBridge]
      summary: Enable or disable DNS for one agent
      description: Adds or removes /etc/hosts entries for the agent's host list. SPAWN_CAPABLE.
      parameters:
        - name: agentId
          in: path
          required: true
          schema:
            $ref: "#/components/schemas/AgentId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AgentBridgeDnsAction"
      responses:
        "200":
          description: DNS updated
        "400":
          description: Validation error

  /api/tools/agent-bridge/agents/{agentId}/mappings:
    get:
      tags: [AgentBridge]
      summary: Get model mappings for one agent
      parameters:
        - name: agentId
          in: path
          required: true
          schema:
            $ref: "#/components/schemas/AgentId"
      responses:
        "200":
          description: Array of source→target model mappings
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/AgentBridgeMappingRow"
    put:
      tags: [AgentBridge]
      summary: Update model mappings for one agent
      parameters:
        - name: agentId
          in: path
          required: true
          schema:
            $ref: "#/components/schemas/AgentId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AgentBridgeMappingPut"
      responses:
        "200":
          description: Mappings updated

  /api/tools/agent-bridge/bypass:
    get:
      tags: [AgentBridge]
      summary: List bypass patterns (hosts never decrypted)
      responses:
        "200":
          description: Bypass patterns
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/AgentBridgeBypassRow"
    put:
      tags: [AgentBridge]
      summary: Update user bypass patterns
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AgentBridgeBypassUpsert"
      responses:
        "200":
          description: Patterns updated

  /api/tools/agent-bridge/cert:
    post:
      tags: [AgentBridge]
      summary: Download or regenerate the AgentBridge CA certificate
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [action]
              properties:
                action:
                  type: string
                  enum: [download, regenerate]
      responses:
        "200":
          description: CA certificate PEM (download) or regeneration confirmation

  /api/tools/agent-bridge/upstream-ca:
    get:
      tags: [AgentBridge]
      summary: Get configured upstream CA cert path
      responses:
        "200":
          description: Upstream CA configuration
          content:
            application/json:
              schema:
                type: object
                properties:
                  path:
                    type: string
                    nullable: true
    post:
      tags: [AgentBridge]
      summary: Set upstream CA cert path for corporate TLS environments
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AgentBridgeUpstreamCaPost"
      responses:
        "200":
          description: Upstream CA configured
        "400":
          description: Path does not exist or is not readable

  # ─── Traffic Inspector ─────────────────────────────────────────

  /api/tools/traffic-inspector/requests:
    get:
      tags: [Traffic Inspector]
      summary: List intercepted requests (filterable)
      parameters:
        - name: profile
          in: query
          schema:
            type: string
            enum: [llm, custom, all]
        - name: host
          in: query
          schema:
            type: string
        - name: agent
          in: query
          schema:
            $ref: "#/components/schemas/AgentId"
        - name: status
          in: query
          schema:
            type: string
            enum: ["2xx", "3xx", "4xx", "5xx", error]
        - name: source
          in: query
          schema:
            $ref: "#/components/schemas/CaptureSource"
        - name: sessionId
          in: query
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Array of intercepted requests
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/InterceptedRequest"
    delete:
      tags: [Traffic Inspector]
      summary: Clear the in-memory traffic buffer
      responses:
        "204":
          description: Buffer cleared

  /api/tools/traffic-inspector/requests/{id}:
    get:
      tags: [Traffic Inspector]
      summary: Get a single intercepted request by ID
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Intercepted request details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InterceptedRequest"
        "404":
          description: Request not found in buffer

  /api/tools/traffic-inspector/requests/{id}/replay:
    post:
      tags: [Traffic Inspector]
      summary: Replay a captured request through OmniRoute router
      description: Re-executes the original request body against /v1/chat/completions. Consumes quota.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Replay response (streaming or JSON)
        "404":
          description: Request not found

  /api/tools/traffic-inspector/requests/{id}/annotation:
    put:
      tags: [Traffic Inspector]
      summary: Save or update annotation on a request
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InspectorAnnotationPut"
      responses:
        "200":
          description: Annotation saved

  /api/tools/traffic-inspector/ws:
    get:
      tags: [Traffic Inspector]
      summary: Live WebSocket stream of intercepted requests
      description: >-
        Upgrade to WebSocket. On connect, server sends `{type:"snapshot", data:[...]}`.
        Subsequent events: `{type:"new", data:{...}}`, `{type:"update", data:{...}}`,
        `{type:"clear"}`. LOCAL_ONLY.
      responses:
        "101":
          description: WebSocket upgrade successful
        "403":
          description: Non-loopback origin rejected

  /api/tools/traffic-inspector/export.har:
    get:
      tags: [Traffic Inspector]
      summary: Export current filtered request list as HAR 1.2
      parameters:
        - name: profile
          in: query
          schema:
            type: string
            enum: [llm, custom, all]
        - name: sessionId
          in: query
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: HAR file (JSON)
          content:
            application/json:
              schema:
                type: object
                description: HAR 1.2 format

  /api/tools/traffic-inspector/hosts:
    get:
      tags: [Traffic Inspector]
      summary: List custom capture hosts
      responses:
        "200":
          description: Custom hosts list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/InspectorCustomHost"
    post:
      tags: [Traffic Inspector]
      summary: Add a custom capture host (edits /etc/hosts)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InspectorCustomHostCreate"
      responses:
        "201":
          description: Host added
        "409":
          description: Host already exists

  /api/tools/traffic-inspector/hosts/{host}:
    delete:
      tags: [Traffic Inspector]
      summary: Remove a custom capture host
      parameters:
        - name: host
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Host removed
    patch:
      tags: [Traffic Inspector]
      summary: Toggle enabled state of a custom host
      parameters:
        - name: host
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [enabled]
              properties:
                enabled:
                  type: boolean
      responses:
        "200":
          description: Host updated

  /api/tools/traffic-inspector/capture-modes:
    get:
      tags: [Traffic Inspector]
      summary: Get state of all 4 capture modes
      responses:
        "200":
          description: Capture modes state
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InspectorCaptureModesState"

  /api/tools/traffic-inspector/capture-modes/http-proxy:
    post:
      tags: [Traffic Inspector]
      summary: Start or stop the HTTP_PROXY listener (port 8080)
      description: SPAWN_CAPABLE — spawns a net.Server listener.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InspectorCaptureModeAction"
      responses:
        "200":
          description: Action executed
        "409":
          description: Port conflict (EADDRINUSE) when starting

  /api/tools/traffic-inspector/capture-modes/system-proxy:
    post:
      tags: [Traffic Inspector]
      summary: Apply or revert system-wide proxy settings
      description: SPAWN_CAPABLE — executes networksetup/gsettings/netsh. Requires admin.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InspectorSystemProxyAction"
      responses:
        "200":
          description: System proxy updated
        "500":
          description: OS command failed (permission error)

  /api/tools/traffic-inspector/capture-modes/tls-intercept:
    post:
      tags: [Traffic Inspector]
      summary: Toggle TLS body decryption in HTTP_PROXY mode
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InspectorTlsInterceptToggle"
      responses:
        "200":
          description: TLS intercept mode updated

  /api/tools/traffic-inspector/sessions:
    get:
      tags: [Traffic Inspector]
      summary: List all saved recording sessions
      responses:
        "200":
          description: Sessions list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/InspectorSession"
    post:
      tags: [Traffic Inspector]
      summary: Start a new recording session
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InspectorSessionStart"
      responses:
        "201":
          description: Session started
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InspectorSession"

  /api/tools/traffic-inspector/sessions/{id}:
    get:
      tags: [Traffic Inspector]
      summary: Get session snapshot (all captured requests)
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Session with embedded requests
        "404":
          description: Session not found
    patch:
      tags: [Traffic Inspector]
      summary: Stop or rename a recording session
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InspectorSessionPatch"
      responses:
        "200":
          description: Session updated
    delete:
      tags: [Traffic Inspector]
      summary: Delete a recording session
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "204":
          description: Session deleted

  /api/tools/traffic-inspector/sessions/{id}/export.har:
    get:
      tags: [Traffic Inspector]
      summary: Export a recorded session as HAR 1.2
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: HAR file for this session
          content:
            application/json:
              schema:
                type: object
                description: HAR 1.2 format
        "404":
          description: Session not found

  /api/tools/traffic-inspector/internal/ingest:
    post:
      tags: [Traffic Inspector]
      summary: Internal ingest endpoint for server.cjs passthrough path
      description: >-
        Accepts a serialized InterceptedRequest from the CJS MITM server for requests
        that do not go through TypeScript handlers (e.g., passthrough hosts). Requires
        INSPECTOR_INTERNAL_INGEST_TOKEN header. LOCAL_ONLY.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InterceptedRequest"
      responses:
        "204":
          description: Ingested
        "401":
          description: Invalid or missing ingest token

  # ─── OpenAPI Spec ──────────────────────────────────────────────

  /api/openapi/spec:
    get:
      tags: [System]
      summary: Get OpenAPI specification catalog
      description: >-
        Returns a structured JSON catalog parsed from this `openapi.yaml`,
        including info, servers, tags, schemas, and a flat list of endpoints
        (method, path, tags, summary, security, parameters, responses).
        Used by the in-app API explorer.
      responses:
        "200":
          description: Parsed OpenAPI catalog
          content:
            application/json:
              schema:
                type: object
                properties:
                  info:
                    type: object
                  servers:
                    type: array
                    items:
                      type: object
                  tags:
                    type: array
                    items:
                      type: object
                  endpoints:
                    type: array
                    items:
                      type: object
                      properties:
                        method:
                          type: string
                        path:
                          type: string
                        tags:
                          type: array
                          items:
                            type: string
                        summary:
                          type: string
                        description:
                          type: string
                        security:
                          type: boolean
                        parameters:
                          type: array
                          items:
                            type: object
                        requestBody:
                          type: boolean
                        responses:
                          type: array
                          items:
                            type: string
                  schemas:
                    type: array
                    items:
                      type: string
        "404":
          description: openapi.yaml file not found on disk
        "500":
          description: Failed to parse OpenAPI spec

  # ─── Agent Skills Catalog ────────────────────────────────────────────────────

  /api/agent-skills:
    get:
      tags: [Agent Skills]
      summary: List agent skills catalog
      description: |
        Returns the full 42-entry Agent Skills catalog with optional filtering.
        Skills describe how to use OmniRoute's REST API and CLI — they are structured
        SKILL.md documentation files discoverable by external agents, MCP clients, and
        A2A orchestrators. No authentication required.
      parameters:
        - name: category
          in: query
          required: false
          schema:
            type: string
            enum: [api, cli]
          description: Filter by category (api = REST API skills, cli = CLI skills)
        - name: area
          in: query
          required: false
          schema:
            type: string
          description: Filter by area slug (e.g. "providers", "models", "cli-serve")
      responses:
        "200":
          description: Catalog list
          content:
            application/json:
              schema:
                type: object
                required: [skills, count, coverage]
                properties:
                  skills:
                    type: array
                    items:
                      $ref: "#/components/schemas/AgentSkill"
                  count:
                    type: integer
                  coverage:
                    $ref: "#/components/schemas/SkillCoverage"
        "400":
          $ref: "#/components/responses/BadRequest"
        "500":
          $ref: "#/components/responses/InternalError"

  /api/agent-skills/{id}:
    get:
      tags: [Agent Skills]
      summary: Get a single agent skill
      description: |
        Returns metadata for a single agent skill by its canonical ID
        (e.g. `omni-providers`, `cli-serve`). No authentication required.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            pattern: "^[a-z][a-z0-9-]*$"
          description: Canonical skill ID
          example: omni-providers
      responses:
        "200":
          description: Agent skill metadata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentSkill"
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /api/agent-skills/{id}/raw:
    get:
      tags: [Agent Skills]
      summary: Get raw SKILL.md content
      description: |
        Returns the SKILL.md content for a skill as `text/markdown`.
        Resolution order: local filesystem `skills/{id}/SKILL.md` → GitHub raw URL (1-hour cache).
        No authentication required.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            pattern: "^[a-z][a-z0-9-]*$"
          description: Canonical skill ID
          example: omni-providers
      responses:
        "200":
          description: SKILL.md content as Markdown
          headers:
            X-Skill-Source:
              schema:
                type: string
                enum: [filesystem, github, generated]
              description: Where the content was loaded from
            X-Skill-Fetched-At:
              schema:
                type: string
                format: date-time
              description: ISO timestamp of when the content was fetched
            Cache-Control:
              schema:
                type: string
              description: "public, max-age=3600"
          content:
            text/markdown:
              schema:
                type: string
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          $ref: "#/components/responses/NotFound"
        "502":
          description: Upstream GitHub fetch failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          $ref: "#/components/responses/InternalError"

  /api/agent-skills/coverage:
    get:
      tags: [Agent Skills]
      summary: Get SKILL.md coverage stats
      description: |
        Returns how many of the 22 API skills and 20 CLI skills have SKILL.md
        files on the local filesystem vs the catalog totals. No authentication required.
      responses:
        "200":
          description: Coverage stats
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SkillCoverage"
        "500":
          $ref: "#/components/responses/InternalError"

  /api/agent-skills/generate:
    post:
      tags: [Agent Skills]
      summary: Trigger SKILL.md generator
      description: |
        Runs the Agent Skills generator which writes `skills/{id}/SKILL.md` for
        all 42 catalog entries (or a subset via `onlyIds`). Preserves
        `<!-- skill:custom-start --> ... <!-- skill:custom-end -->` blocks.
        **Requires management authentication.**
      security:
        - BearerAuth: []
        - ManagementSessionAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                dryRun:
                  type: boolean
                  default: true
                  description: "If true, reports what would be generated without writing files"
                prune:
                  type: boolean
                  default: false
                  description: "If true, deletes skill directories not in the catalog"
                onlyIds:
                  type: array
                  items:
                    type: string
                  description: "If provided, only regenerate these skill IDs"
      responses:
        "200":
          description: Generator report
          content:
            application/json:
              schema:
                type: object
                required: [generated, unchanged, pruned, orphansDetected, errors]
                properties:
                  generated:
                    type: array
                    items:
                      type: string
                    description: IDs that got new/updated SKILL.md
                  unchanged:
                    type: array
                    items:
                      type: string
                    description: IDs whose content was already up to date
                  pruned:
                    type: array
                    items:
                      type: string
                    description: IDs whose directories were deleted (prune mode)
                  orphansDetected:
                    type: array
                    items:
                      type: string
                    description: Directories found in skills/ not in the catalog
                  errors:
                    type: array
                    items:
                      type: object
                      required: [id, error]
                      properties:
                        id:
                          type: string
                        error:
                          type: string
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"
        "503":
          description: Generator module not available
  /api/v1/ocr:
    post:
      tags:
        - Images
      summary: Document OCR
      description: >-
        Mistral OCR–compatible document OCR endpoint. Accepts a JSON body
        referencing a document/image and returns extracted text. Success
        responses carry the `X-OmniRoute-*` cost-telemetry headers.
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                model:
                  type: string
                document:
                  type: object
      responses:
        "200":
          description: OCR result with extracted text.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"
  /api/v1/audio/translations:
    post:
      tags:
        - Audio
      summary: Translate audio to English
      description: >-
        OpenAI Whisper–compatible audio translation (multipart/form-data).
        Unlike `/api/v1/audio/transcriptions`, output is always English
        regardless of the source language. Success responses carry the
        `X-OmniRoute-*` cost-telemetry headers.
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - file
              properties:
                file:
                  type: string
                  format: binary
                model:
                  type: string
      responses:
        "200":
          description: English translation of the audio.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"
  /api/v1/providers/suggested-models:
    get:
      tags:
        - Providers
      summary: Suggested media models
      description: >-
        Read-only server-side proxy to the public HuggingFace Hub models search
        API, used by the dashboard to suggest models for a media provider kind
        without exposing an HF token client-side. Never accepts or returns
        credentials.
      parameters:
        - name: type
          in: query
          schema:
            type: string
          description: Media kind to search for (e.g. `image`, `audio`, `video`).
      responses:
        "200":
          description: List of suggested HuggingFace Hub models.
        "500":
          $ref: "#/components/responses/InternalError"
  /api/v1/provider-plugin-manifest:
    get:
      tags:
        - Providers
      summary: Provider plugin manifest
      description: Returns the manifest describing installed provider plugins.
      responses:
        "200":
          description: Provider plugin manifest.
        "500":
          $ref: "#/components/responses/InternalError"
  /api/keys/{id}/devices:
    get:
      tags:
        - API Keys
      summary: List devices for an API key
      description: >-
        Lists the distinct devices (masked IP + User-Agent fingerprints)
        tracked for an API key by the in-memory device tracker. IPs are masked
        before storage; the route never sees the raw client IP.
      x-internal: true
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Distinct devices seen for the API key.
        "401":
          $ref: "#/components/responses/ManagementAuthenticationRequired"
        "404":
          $ref: "#/components/responses/NotFound"
  /api/settings/purge-usage-history:
    post:
      tags:
        - Settings
      summary: Purge usage history
      description: Dashboard-only. Purges stored usage-history records.
      x-internal: true
      responses:
        "200":
          description: Usage history purged.
        "401":
          $ref: "#/components/responses/Unauthorized"
  /api/oauth/codex/import-token:
    post:
      tags:
        - OAuth
      summary: Import a Codex connection from a bare access token
      description: >-
        Dashboard-only. Creates a Codex (ChatGPT/OpenAI) connection from a raw
        access token with no refresh token (authType `access_token`).
      x-internal: true
      responses:
        "200":
          description: Connection imported.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /api/cli-tools/crush-settings:
    get:
      tags:
        - CLI Tools
      summary: Read Crush CLI OmniRoute config
      description: Local-only. Reads the OmniRoute provider block in Crush's config.
      x-internal: true
      responses:
        "200":
          description: Current Crush config state.
    post:
      tags:
        - CLI Tools
      summary: Write Crush CLI OmniRoute config
      description: Local-only. Registers OmniRoute as an `openai-compat` provider in Crush's config.
      x-internal: true
      responses:
        "200":
          description: Crush config updated.
    delete:
      tags:
        - CLI Tools
      summary: Remove OmniRoute from Crush CLI config
      description: Local-only. Removes the OmniRoute provider block from Crush's config.
      x-internal: true
      responses:
        "200":
          description: Crush config entry removed.
  /api/cli-tools/codewhale-settings:
    get:
      tags:
        - CLI Tools
      summary: Read CodeWhale CLI OmniRoute config
      description: >-
        Local-only. Reads the OmniRoute config block from
        `~/.codewhale/config.toml` (with `~/.deepseek/config.toml` legacy
        fallback).
      x-internal: true
      responses:
        "200":
          description: Current CodeWhale config state.
    post:
      tags:
        - CLI Tools
      summary: Write CodeWhale CLI OmniRoute config
      description: Local-only. Writes the OmniRoute config block in CodeWhale TOML format.
      x-internal: true
      responses:
        "200":
          description: CodeWhale config updated.
    delete:
      tags:
        - CLI Tools
      summary: Remove OmniRoute from CodeWhale CLI config
      description: Local-only. Removes the OmniRoute config block from CodeWhale's config.
      x-internal: true
      responses:
        "200":
          description: CodeWhale config entry removed.

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: API key obtained from the OmniRoute dashboard
    ManagementSessionAuth:
      type: apiKey
      in: cookie
      name: auth_token
      description: Dashboard management session cookie for protected management routes

  parameters:
    ResourceId:
      name: id
      in: path
      required: true
      schema:
        type: string

  responses:
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: string
                example: Unauthorized
    ManagementAuthenticationRequired:
      description: Authentication required for management routes
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiErrorResponse"
          example:
            error:
              message: Authentication required
              type: invalid_request
            requestId: 3f9f6f5a-509a-4b35-b0a7-2d2d99d73a01
    ManagementInvalidToken:
      description: Bearer tokens are not accepted for management routes
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiErrorResponse"
          example:
            error:
              message: Invalid management token
              type: invalid_request
            requestId: 1b6a6ff8-d60c-4900-8d0a-25f81749f0a3
    ValidationError:
      description: Request body failed validation
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ValidationErrorResponse"
    BadRequest:
      description: The request was malformed or failed validation
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiErrorResponse"
          example:
            error:
              message: Invalid request
              type: invalid_request_error
            requestId: 8c2b1d44-7a3e-4c91-9b0f-1e2d3c4b5a60
    NotFound:
      description: The requested resource was not found
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiErrorResponse"
          example:
            error:
              message: Resource not found
              type: not_found_error
            requestId: 4d5e6f70-1a2b-3c4d-5e6f-7a8b9c0d1e2f
    InternalError:
      description: An unexpected server error occurred
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiErrorResponse"
          example:
            error:
              message: Internal server error
              type: api_error
            requestId: 0a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d

  schemas:
    EmbeddingMultimodalItem:
      oneOf:
        - type: object
          required: [type, text]
          properties:
            type:
              type: string
              const: text
            text:
              type: string
              minLength: 1
        - type: object
          required: [type, source]
          properties:
            type:
              type: string
              enum: [image, audio, video, document]
            source:
              oneOf:
                - type: object
                  required: [type, url]
                  properties:
                    type:
                      type: string
                      const: url
                    url:
                      type: string
                      format: uri
                      description: Public HTTPS URL.
                - type: object
                  required: [type, data, media_type]
                  properties:
                    type:
                      type: string
                      const: base64
                    data:
                      type: string
                      format: byte
                    media_type:
                      type: string
      description: Provider-neutral structured embedding input item.

    PlaygroundPreset:
      type: object
      required:
        - id
        - name
        - endpoint
        - model
        - params
        - created_at
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
          maxLength: 100
        endpoint:
          type: string
          description: Playground endpoint key (e.g. "chat.completions")
        model:
          type: string
        system:
          type: string
          nullable: true
        params:
          type: object
          additionalProperties: true
          description: Serialized parameter values (temperature, max_tokens, etc.)
        created_at:
          type: string
          format: date-time
    PlaygroundPresetCreate:
      type: object
      required:
        - name
        - endpoint
        - model
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 100
        endpoint:
          type: string
          minLength: 1
        model:
          type: string
          minLength: 1
        system:
          type: string
          nullable: true
        params:
          type: object
          additionalProperties: true
          default: {}
    MemoryEntry:
      type: object
      description: A single persisted memory entry
      properties:
        id:
          type: string
          description: UUID
        apiKeyId:
          type: string
        sessionId:
          type: string
          nullable: true
        type:
          type: string
          enum:
            - factual
            - episodic
            - procedural
            - semantic
        key:
          type: string
          description: Stable upsert key (e.g. preference:i_prefer_python)
        content:
          type: string
        metadata:
          type: object
          additionalProperties: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        expiresAt:
          type: string
          format: date-time
          nullable: true
        needsReindex:
          type: integer
          description: 1 if the vector for this memory is stale or missing
    MemorySettingsExtended:
      type: object
      description: Extended memory settings including 7 new fields from plan 21. All fields are optional for PUT (patch semantics).
      properties:
        enabled:
          type: boolean
        maxTokens:
          type: integer
          minimum: 0
          maximum: 16000
        retentionDays:
          type: integer
          minimum: 1
          maximum: 365
        strategy:
          type: string
          enum:
            - recent
            - semantic
            - hybrid
        skillsEnabled:
          type: boolean
        embeddingSource:
          type: string
          enum:
            - remote
            - static
            - transformers
            - auto
          description: Which embedding source to use. "auto" = remote > static > transformers.
        embeddingProviderModel:
          type: string
          nullable: true
          description: Embedding provider/model in "provider/model" format (e.g. openai/text-embedding-3-small).
        transformersEnabled:
          type: boolean
          description: Opt-in for Transformers.js local MiniLM model (~400MB RAM)
        staticEnabled:
          type: boolean
          description: Opt-in for static potion-base-8M local model
        rerankEnabled:
          type: boolean
          description: Enable reranking step (+200-500ms/req)
        rerankProviderModel:
          type: string
          nullable: true
          description: Rerank provider/model in "provider/model" format
        vectorStore:
          type: string
          enum:
            - sqlite-vec
            - qdrant
            - auto
          description: Which vector backend to use
    QdrantSettings:
      type: object
      description: Qdrant vector database configuration (read shape — no raw apiKey)
      properties:
        enabled:
          type: boolean
        host:
          type: string
        port:
          type: integer
          minimum: 1
          maximum: 65535
        collection:
          type: string
        embeddingModel:
          type: string
        hasApiKey:
          type: boolean
        apiKeyMasked:
          type: string
          nullable: true
          description: First 4 chars of the configured API key, or null
    QdrantHealthResult:
      type: object
      description: Result of a Qdrant liveness probe
      properties:
        ok:
          type: boolean
        latencyMs:
          type: number
        error:
          type: string
          nullable: true
          description: Sanitized error message (no stack traces)
    AgentSkill:
      type: object
      description: >-
        Single entry in the Agent Skills catalog. Describes one OmniRoute REST API surface
        (category: api) or CLI subcommand group (category: cli) with a canonical ID and a
        link to its SKILL.md documentation file.
      required: [id, name, description, category, area, rawUrl, githubUrl]
      properties:
        id:
          type: string
          pattern: "^[a-z][a-z0-9-]*$"
          description: Canonical skill ID (e.g. "omni-providers", "cli-serve")
          example: omni-providers
        name:
          type: string
          minLength: 1
          maxLength: 100
          description: Human-readable skill name
          example: Provider Management
        description:
          type: string
          minLength: 1
          maxLength: 2000
          description: One-paragraph description of what the skill covers
        category:
          type: string
          enum: [api, cli]
          description: "api = REST API skill; cli = CLI subcommand skill"
        area:
          type: string
          minLength: 1
          maxLength: 50
          description: Functional area slug (e.g. "providers", "combos-routing", "cli-serve")
          example: providers
        endpoints:
          type: array
          items:
            type: string
          description: REST API endpoints (present for api-category skills only)
          example: ["POST /api/providers", "GET /api/providers/:id"]
        cliCommands:
          type: array
          items:
            type: string
          description: CLI subcommand names (present for cli-category skills only)
          example: ["providers list", "providers test", "providers rotate"]
        icon:
          type: string
          description: Material symbol icon name for dashboard display
        isEntry:
          type: boolean
          description: Whether this is a recommended starting point
        isNew:
          type: boolean
          description: Whether this skill was added in a recent release
        rawUrl:
          type: string
          format: uri
          description: GitHub raw URL of the SKILL.md file
          example: "https://raw.githubusercontent.com/diegosouzapw/OmniRoute/refs/heads/main/skills/omni-providers/SKILL.md"
        githubUrl:
          type: string
          format: uri
          description: GitHub blob URL for viewing the SKILL.md in the browser
          example: "https://github.com/diegosouzapw/OmniRoute/blob/main/skills/omni-providers/SKILL.md"

    SkillCoverage:
      type: object
      description: >-
        Coverage statistics for the Agent Skills catalog: how many of the 22 REST API
        skills and 20 CLI skills have generated SKILL.md files on the local filesystem.
      required: [api, cli, totalSkills, generatedAt]
      properties:
        api:
          type: object
          required: [have, total]
          properties:
            have:
              type: integer
              minimum: 0
              maximum: 22
              description: Number of API skills with SKILL.md on disk
            total:
              type: integer
              enum: [22]
              description: Canonical API skill count (always 22)
        cli:
          type: object
          required: [have, total]
          properties:
            have:
              type: integer
              minimum: 0
              maximum: 20
              description: Number of CLI skills with SKILL.md on disk
            total:
              type: integer
              enum: [20]
              description: Canonical CLI skill count (always 20)
        totalSkills:
          type: integer
          minimum: 0
          maximum: 42
          description: Sum of api.have + cli.have
        generatedAt:
          type: string
          format: date-time
          description: ISO datetime when coverage was last computed

    ErrorResponse:
      type: object
      description: Standard error response body
      required: [error]
      properties:
        error:
          type: object
          required: [message]
          properties:
            message:
              type: string
              description: Human-readable error message (never includes stack traces)
            code:
              type: string
              description: Machine-readable error code

    # ─── AgentBridge Schemas ────────────────────────────────────────

    AgentId:
      type: string
      enum:
        - antigravity
        - kiro
        - copilot
        - codex
        - cursor
        - zed
        - claude-code
        - open-code
        - trae
      description: One of the 9 supported IDE agents

    AgentBridgeAgentState:
      type: object
      description: Per-agent MITM state
      properties:
        agent_id:
          $ref: "#/components/schemas/AgentId"
        dns_enabled:
          type: boolean
        cert_trusted:
          type: boolean
        setup_completed:
          type: boolean
        last_started_at:
          type: string
          format: date-time
          nullable: true
        last_error:
          type: string
          nullable: true

    AgentBridgeServerState:
      type: object
      description: Global AgentBridge MITM server state
      properties:
        running:
          type: boolean
        port:
          type: integer
          example: 443
        certReady:
          type: boolean
        interceptedCount:
          type: integer
        activeConnections:
          type: integer
        lastStartedAt:
          type: string
          format: date-time
          nullable: true

    AgentBridgeServerAction:
      type: object
      required: [action]
      properties:
        action:
          type: string
          enum: [start, stop, restart, trust-cert, regenerate-cert]

    AgentBridgeDnsAction:
      type: object
      required: [enabled]
      properties:
        enabled:
          type: boolean

    AgentBridgeMappingRow:
      type: object
      properties:
        agent_id:
          $ref: "#/components/schemas/AgentId"
        source_model:
          type: string
          example: gpt-4o
        target_model:
          type: string
          example: claude-sonnet-4.7
        updated_at:
          type: string
          format: date-time

    AgentBridgeMappingPut:
      type: object
      required: [mappings]
      properties:
        mappings:
          type: array
          items:
            type: object
            required: [source, target]
            properties:
              source:
                type: string
                example: gpt-4o
              target:
                type: string
                example: claude-sonnet-4.7

    AgentBridgeBypassRow:
      type: object
      properties:
        pattern:
          type: string
          example: "*.bank.*"
        source:
          type: string
          enum: [default, user]
        created_at:
          type: string
          format: date-time

    AgentBridgeBypassUpsert:
      type: object
      required: [patterns]
      properties:
        patterns:
          type: array
          items:
            type: string
          example: ["*.bank.*", "*.gov.*"]

    AgentBridgeUpstreamCaPost:
      type: object
      required: [path]
      properties:
        path:
          type: string
          description: Absolute path to a PEM file for corporate upstream CA
          example: "/etc/ssl/certs/corporate-ca.pem"

    # ─── Traffic Inspector Schemas ──────────────────────────────────

    CaptureSource:
      type: string
      enum: [agent-bridge, custom-host, http-proxy, system-proxy]

    DetectedKind:
      type: string
      enum: [llm, app, unknown]

    InterceptedRequest:
      type: object
      description: A single intercepted HTTP request captured by the Traffic Inspector
      required:
        [
          id,
          source,
          timestamp,
          method,
          host,
          path,
          requestHeaders,
          requestSize,
          responseHeaders,
          responseSize,
          status,
        ]
      properties:
        id:
          type: string
          format: uuid
        source:
          $ref: "#/components/schemas/CaptureSource"
        agent:
          $ref: "#/components/schemas/AgentId"
        timestamp:
          type: string
          format: date-time
        method:
          type: string
          example: POST
        host:
          type: string
          example: api.githubcopilot.com
        path:
          type: string
          example: /v1/chat/completions
        requestHeaders:
          type: object
          additionalProperties:
            type: string
        requestBody:
          type: string
          nullable: true
          description: Masked (secrets replaced with ***)
        requestSize:
          type: integer
        responseHeaders:
          type: object
          additionalProperties:
            type: string
        responseBody:
          type: string
          nullable: true
        responseSize:
          type: integer
        status:
          oneOf:
            - type: integer
            - type: string
              enum: [in-flight, error]
        proxyLatencyMs:
          type: number
          nullable: true
        upstreamLatencyMs:
          type: number
          nullable: true
        totalLatencyMs:
          type: number
          nullable: true
        error:
          type: string
          nullable: true
          description: Sanitized error message (no stack traces)
        sourceModel:
          type: string
          nullable: true
        mappedModel:
          type: string
          nullable: true
        detectedKind:
          $ref: "#/components/schemas/DetectedKind"
        contextKey:
          type: string
          nullable: true
          description: 12-char SHA-256 hex of the system prompt (for conversation grouping)
          example: a3f9c2b1d5e4
        annotation:
          type: string
          nullable: true
        sessionId:
          type: string
          format: uuid
          nullable: true
        note:
          type: string
          nullable: true
          description: Informational note (e.g. TLS tunnel metadata)

    InspectorCustomHost:
      type: object
      properties:
        host:
          type: string
          example: api.openai.com
        enabled:
          type: boolean
        label:
          type: string
          nullable: true
        kind:
          type: string
          enum: [llm, app, custom]
        added_at:
          type: string
          format: date-time
        last_seen_at:
          type: string
          format: date-time
          nullable: true

    InspectorCustomHostCreate:
      type: object
      required: [host]
      properties:
        host:
          type: string
          minLength: 1
          example: my-internal-llm.company.com
        enabled:
          type: boolean
          default: true
        label:
          type: string
          nullable: true
        kind:
          type: string
          enum: [llm, app, custom]
          default: custom

    InspectorCaptureModesState:
      type: object
      properties:
        agentBridge:
          type: object
          properties:
            active:
              type: boolean
        customHosts:
          type: object
          properties:
            active:
              type: boolean
            count:
              type: integer
        httpProxy:
          type: object
          properties:
            active:
              type: boolean
            port:
              type: integer
              example: 8080
        systemProxy:
          type: object
          properties:
            active:
              type: boolean
            guardMinutes:
              type: integer

    InspectorCaptureModeAction:
      type: object
      required: [action]
      properties:
        action:
          type: string
          enum: [start, stop]

    InspectorSystemProxyAction:
      type: object
      required: [action]
      properties:
        action:
          type: string
          enum: [apply, revert]
        port:
          type: integer
          minimum: 1
          maximum: 65535
          example: 8080
        guardMinutes:
          type: integer
          minimum: 1
          example: 30

    InspectorTlsInterceptToggle:
      type: object
      required: [enabled]
      properties:
        enabled:
          type: boolean

    InspectorAnnotationPut:
      type: object
      required: [annotation]
      properties:
        annotation:
          type: string
          maxLength: 10000

    InspectorSession:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
          nullable: true
        started_at:
          type: string
          format: date-time
        ended_at:
          type: string
          format: date-time
          nullable: true
        request_count:
          type: integer
        profile:
          type: string
          enum: [llm, custom, all]
          nullable: true

    InspectorSessionStart:
      type: object
      properties:
        name:
          type: string
          example: "Antigravity test run #1"

    InspectorSessionPatch:
      type: object
      required: [action]
      properties:
        action:
          type: string
          enum: [stop, rename]
        name:
          type: string
    QuotaPool:
      type: object
      description: A quota sharing pool — binds a provider connection to allocation rules.
      required: [id, connectionId, name, createdAt, allocations]
      properties:
        id:
          type: string
        connectionId:
          type: string
        name:
          type: string
        createdAt:
          type: string
          format: date-time
        allocations:
          type: array
          items:
            $ref: "#/components/schemas/PoolAllocation"

    PoolAllocation:
      type: object
      required: [apiKeyId, weight, policy]
      properties:
        apiKeyId:
          type: string
        weight:
          type: number
          minimum: 0
          maximum: 100
          description: Share percentage (0–100)
        capValue:
          type: number
          nullable: true
          description: Absolute cap value (optional)
        capUnit:
          type: string
          enum: [percent, requests, tokens, usd]
          nullable: true
        policy:
          type: string
          enum: [hard, soft, burst]

    PoolCreate:
      type: object
      required: [connectionId, name]
      properties:
        connectionId:
          type: string
        name:
          type: string
          maxLength: 120
        allocations:
          type: array
          items:
            $ref: "#/components/schemas/PoolAllocation"
          default: []

    PoolUpdate:
      type: object
      properties:
        name:
          type: string
          maxLength: 120
        allocations:
          type: array
          items:
            $ref: "#/components/schemas/PoolAllocation"

    PoolUsageSnapshot:
      type: object
      required: [poolId, generatedAt, dimensions]
      properties:
        poolId:
          type: string
        generatedAt:
          type: string
          format: date-time
        dimensions:
          type: array
          items:
            type: object
            properties:
              unit:
                type: string
                enum: [percent, requests, tokens, usd]
              window:
                type: string
                enum: ["5h", hourly, daily, weekly, monthly]
              limit:
                type: number
              consumedTotal:
                type: number
              perKey:
                type: array
                items:
                  type: object
                  properties:
                    apiKeyId:
                      type: string
                    consumed:
                      type: number
                    fairShare:
                      type: number
                    deficit:
                      type: number
                      description: "Negative = surplus; positive = over-allocation"
                    borrowing:
                      type: boolean
        burnRate:
          type: object
          nullable: true
          properties:
            tokensPerSecond:
              type: number
            timeToExhaustionMs:
              type: number
              nullable: true

    QuotaDimension:
      type: object
      required: [unit, window, limit]
      properties:
        unit:
          type: string
          enum: [percent, requests, tokens, usd]
        window:
          type: string
          enum: ["5h", hourly, daily, weekly, monthly]
        limit:
          type: number
          minimum: 0

    PlanUpsert:
      type: object
      required: [dimensions]
      properties:
        dimensions:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/QuotaDimension"

    QuotaStoreSettings:
      type: object
      required: [driver]
      properties:
        driver:
          type: string
          enum: [sqlite, redis]
        redisUrl:
          type: string
          format: uri
          nullable: true
          description: Redis connection URL (write-only; masked in GET responses)

    ServiceStatus:
      type: object
      description: Live supervisor state for an embedded service
      properties:
        tool:
          type: string
          example: 9router
        state:
          type: string
          enum: [not_installed, stopped, starting, running, stopping, error]
        pid:
          type: integer
          nullable: true
        port:
          type: integer
          example: 20130
        health:
          type: string
          enum: [unknown, healthy, degraded]
        startedAt:
          type: string
          format: date-time
          nullable: true
        lastError:
          type: string
          nullable: true

    ServiceStatusExtended:
      allOf:
        - $ref: "#/components/schemas/ServiceStatus"
        - type: object
          description: >-
            Extended status including version metadata and (for 9Router) API key preview.
          properties:
            installedVersion:
              type: string
              nullable: true
            latestVersion:
              type: string
              nullable: true
            updateAvailable:
              type: boolean
            apiKeyMasked:
              type: string
              nullable: true
              description: >-
                Masked API key preview (e.g. "nr_****abcd").
                Present only for services that use an injected API key (9Router).
            autoStart:
              type: boolean
            providerExpose:
              type: boolean
              description: >-
                Whether models from this service are exposed as a routing provider.
                9Router only.

    ApiErrorResponse:
      type: object
      properties:
        error:
          type: object
          properties:
            message:
              type: string
            type:
              type: string
            details:
              description: Optional additional error details
        requestId:
          type: string
          format: uuid

    ValidationErrorResponse:
      type: object
      properties:
        error:
          type: object
          required: [message, details]
          properties:
            message:
              type: string
              example: Invalid request
            details:
              type: array
              items:
                type: object
                required: [field, message]
                properties:
                  field:
                    type: string
                  message:
                    type: string

    PayloadRuleModelSpec:
      type: object
      additionalProperties: false
      required: [name]
      properties:
        name:
          type: string
          minLength: 1
        protocol:
          type: string
          minLength: 1

    PayloadMutationRule:
      type: object
      additionalProperties: false
      required: [models, params]
      properties:
        models:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/PayloadRuleModelSpec"
        params:
          type: object
          minProperties: 1
          additionalProperties: true

    PayloadFilterRule:
      type: object
      additionalProperties: false
      required: [models, params]
      properties:
        models:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/PayloadRuleModelSpec"
        params:
          type: array
          minItems: 1
          items:
            type: string
            minLength: 1

    PayloadRulesConfig:
      type: object
      additionalProperties: false
      required: [default, override, filter, defaultRaw]
      properties:
        default:
          type: array
          items:
            $ref: "#/components/schemas/PayloadMutationRule"
        override:
          type: array
          items:
            $ref: "#/components/schemas/PayloadMutationRule"
        filter:
          type: array
          items:
            $ref: "#/components/schemas/PayloadFilterRule"
        defaultRaw:
          type: array
          items:
            $ref: "#/components/schemas/PayloadMutationRule"

    UpdatePayloadRulesRequest:
      type: object
      additionalProperties: false
      description: At least one payload-rules section must be present in the request body.
      properties:
        default:
          type: array
          items:
            $ref: "#/components/schemas/PayloadMutationRule"
        override:
          type: array
          items:
            $ref: "#/components/schemas/PayloadMutationRule"
        filter:
          type: array
          items:
            $ref: "#/components/schemas/PayloadFilterRule"
        defaultRaw:
          type: array
          items:
            $ref: "#/components/schemas/PayloadMutationRule"
        default-raw:
          type: array
          items:
            $ref: "#/components/schemas/PayloadMutationRule"
      anyOf:
        - required: [default]
        - required: [override]
        - required: [filter]
        - required: [defaultRaw]
        - required: [default-raw]

    ChatCompletionRequest:
      type: object
      required: [model, messages]
      properties:
        model:
          type: string
          example: gpt-4o
        messages:
          type: array
          items:
            type: object
            required: [role]
            properties:
              role:
                type: string
                description: >-
                  Message role. The proxy accepts any non-empty string; common values
                  include system, user, assistant, tool, function, and developer.
                example: user
              content:
                description: >-
                  Message content. May be a plain string, an array of content parts
                  for multimodal inputs (text, image, audio, etc.), or null when the
                  message only carries tool/function calls.
                oneOf:
                  - type: string
                  - type: array
                    items:
                      type: object
                  - type: "null"
              name:
                type: string
              tool_call_id:
                type: string
              tool_calls:
                type: array
                items:
                  type: object
              function_call:
                type: object
        stream:
          type: boolean
          default: false
        temperature:
          type: number
          minimum: 0
          maximum: 2
        max_tokens:
          type: integer
        top_p:
          type: number
          minimum: 0
          maximum: 1
        n:
          type: integer
          minimum: 1
          default: 1
        stop:
          description: Up to 4 stop sequences (string or array of strings).
          oneOf:
            - type: string
            - type: array
              items:
                type: string
              maxItems: 4
        frequency_penalty:
          type: number
          minimum: -2
          maximum: 2
        presence_penalty:
          type: number
          minimum: -2
          maximum: 2
        seed:
          type: integer
        logprobs:
          type: boolean
        top_logprobs:
          type: integer
          minimum: 0
          maximum: 20
        response_format:
          type: object
          description: Output format constraint (e.g. JSON mode or JSON Schema).
          properties:
            type:
              type: string
              example: json_object
        tools:
          type: array
          description: Tool definitions available to the model.
          items:
            type: object
        tool_choice:
          description: Controls which tool (if any) is invoked by the model.
          oneOf:
            - type: string
              example: auto
            - type: object
        parallel_tool_calls:
          type: boolean
          default: true
        service_tier:
          type: string
          example: auto
        user:
          type: string
          description: Stable end-user identifier for abuse monitoring.

    ChatCompletionResponse:
      type: object
      properties:
        id:
          type: string
        object:
          type: string
          example: chat.completion
        choices:
          type: array
          items:
            type: object
            properties:
              index:
                type: integer
              message:
                type: object
                properties:
                  role:
                    type: string
                  content:
                    type: string
              finish_reason:
                type: string
        usage:
          type: object
          properties:
            prompt_tokens:
              type: integer
            completion_tokens:
              type: integer
            total_tokens:
              type: integer

    MessagesRequest:
      type: object
      required: [model, messages, max_tokens]
      properties:
        model:
          type: string
          example: claude-sonnet-4-5-20250514
        messages:
          type: array
          items:
            type: object
            required: [role, content]
            properties:
              role:
                type: string
                enum: [user, assistant]
              content:
                type: string
        max_tokens:
          type: integer
        stream:
          type: boolean
          default: false
        system:
          type: string

    Model:
      type: object
      properties:
        id:
          type: string
        object:
          type: string
          example: model
        owned_by:
          type: string

    ProxySubscription:
      type: object
      description: >-
        An operator-supplied proxy subscription link (Karing-style). `url` is
        redacted (credentials stripped) whenever the record is returned by the API.
      properties:
        id:
          type: string
        name:
          type: string
        url:
          type: string
          description: Redacted subscription URL.
        enabled:
          type: boolean
        mode:
          type: string
          enum: [global, rule]
        ruleProviders:
          type: array
          items:
            type: string
          nullable: true
        localCoreEndpoint:
          type: string
          nullable: true
        updateIntervalMinutes:
          type: integer
        lastFetchedAt:
          type: string
          format: date-time
          nullable: true
        status:
          type: string
          enum: [ok, error, empty]
        error:
          type: string
          nullable: true
        lastNodes:
          type: array
          items: {}
          nullable: true
        lastErrorAt:
          type: string
          format: date-time
          nullable: true
        consecutiveFailures:
          type: integer
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    ProviderConnection:
      type: object
      properties:
        id:
          type: string
        provider:
          type: string
        name:
          type: string
        url:
          type: string
        isActive:
          type: boolean
        maxConcurrent:
          type: integer
          nullable: true
          minimum: 0
        priority:
          type: integer
        testStatus:
          type: string
          enum: [active, error, untested]
        createdAt:
          type: string
          format: date-time

    ProviderConnectionCreate:
      type: object
      required: [provider, url]
      properties:
        provider:
          type: string
          example: openai
        name:
          type: string
        url:
          type: string
        apiKey:
          type: string
        isActive:
          type: boolean
          default: true
        maxConcurrent:
          type: integer
          nullable: true
          minimum: 0

    ApiKey:
      type: object
      properties:
        id:
          type: string
        label:
          type: string
        keyPreview:
          type: string
          description: Last 4 characters of the key
        isActive:
          type: boolean
        createdAt:
          type: string
          format: date-time

    ComboCreate:
      type: object
      required: [name, model]
      properties:
        name:
          type: string
        model:
          type: string
        strategy:
          type: string
          enum:
            - priority
            - weighted
            - round-robin
            - context-relay
            - fill-first
            - p2c
            - random
            - least-used
            - cost-optimized
            - reset-aware
            - reset-window
            - headroom
            - strict-random
            - auto
            - lkgp
            - context-optimized
            - fusion
          default: priority
        nodes:
          type: array
          items:
            type: object
            properties:
              connectionId:
                type: string
              weight:
                type: integer
              priority:
                type: integer
