openapi: 3.1.0

info:
  title: LLMHosts API
  version: 0.1.0
  description: |
    LLMHosts.com turns your hardware into real AI infrastructure accessible from anywhere.
    This API provides OpenAI and Anthropic compatible proxy endpoints, plus internal
    dashboard and metrics endpoints for monitoring and configuration.

servers:
  - url: http://localhost:4000
    description: Local proxy server

tags:
  - name: proxy
    description: OpenAI and Anthropic compatible proxy endpoints
  - name: internal
    description: Internal API for dashboard and admin operations
  - name: dashboard
    description: Static dashboard SPA serving
  - name: websocket
    description: Real-time streaming updates

security:
  - BearerAuth: []

paths:
  # ==================== PUBLIC API ====================

  /v1/chat/completions:
    post:
      tags: [proxy]
      summary: Chat completions (OpenAI format)
      description: |
        OpenAI-compatible chat completions endpoint. Use model "auto" for
        intelligent routing across local and cloud backends. Responses include
        llmhosts extension with routing, cost, cache, and latency metrics.
      operationId: createChatCompletion
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChatCompletionRequest'
      responses:
        '200':
          description: Successful completion (streaming or non-streaming)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChatCompletionResponse'
            text/event-stream:
              schema:
                type: string
                description: Server-sent events stream (when stream=true)
      security: []

  /v1/messages:
    post:
      tags: [proxy]
      summary: Messages (Anthropic format)
      description: |
        Anthropic-compatible messages endpoint. Proxies to Claude models with
        optional system prompt. Includes llmhosts extension for routing metadata.
      operationId: createMessage
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MessageRequest'
      responses:
        '200':
          description: Successful message completion
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageResponse'
      security: []

  /v1/embeddings:
    post:
      tags: [proxy]
      summary: Generate embeddings
      description: Generates embeddings for the given input text. Routes to appropriate embedding model via Conductor.
      operationId: createEmbedding
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [model, input]
              properties:
                model:
                  type: string
                  description: Embedding model name
                input:
                  oneOf:
                    - type: string
                    - type: array
                      items:
                        type: string
      responses:
        '200':
          description: Embedding response
          content:
            application/json:
              schema:
                type: object
                properties:
                  object:
                    type: string
                    enum: [list]
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        object:
                          type: string
                          enum: [embedding]
                        embedding:
                          type: array
                          items:
                            type: number
                        index:
                          type: integer

  /v1/models:
    get:
      tags: [proxy]
      summary: List available models
      description: Returns models discovered from local (Ollama) and configured cloud backends.
      operationId: listModels
      responses:
        '200':
          description: List of available models
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelsListResponse'
      security: []

  /health:
    get:
      tags: [proxy]
      summary: Health check
      description: Basic health check for load balancers and monitoring.
      operationId: getHealth
      responses:
        '200':
          description: Service health status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthResponse'
      security: []

  /metrics:
    get:
      tags: [proxy]
      summary: Prometheus metrics
      description: Prometheus-compatible metrics endpoint for scraping.
      operationId: getMetrics
      responses:
        '200':
          description: Prometheus text format
          content:
            text/plain:
              schema:
                type: string
                example: |
                  # HELP llmhosts_requests_total Total requests
                  # TYPE llmhosts_requests_total counter
                  llmhosts_requests_total 42
      security: []

  # ==================== INTERNAL API ====================

  /internal/metrics/savings:
    get:
      tags: [internal]
      summary: Savings metrics
      description: Cost savings from using local vs estimated cloud spend.
      operationId: getSavingsMetrics
      responses:
        '200':
          description: Savings metrics
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SavingsMetricsResponse'

  /internal/metrics/requests:
    get:
      tags: [internal]
      summary: Request metrics
      description: Request counts by backend and routing tier.
      operationId: getRequestMetrics
      responses:
        '200':
          description: Request metrics
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RequestMetricsResponse'

  /internal/metrics/costs:
    get:
      tags: [internal]
      summary: Cost metrics
      description: Actual vs estimated cloud costs.
      operationId: getCostMetrics
      responses:
        '200':
          description: Cost metrics
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CostMetricsResponse'

  /internal/metrics/routing:
    get:
      tags: [internal]
      summary: Routing distribution
      description: Percentage of requests routed to each tier (rule, knn, modernbert, qwen).
      operationId: getRoutingMetrics
      responses:
        '200':
          description: Routing distribution
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RoutingMetricsResponse'

  /internal/cache/stats:
    get:
      tags: [internal]
      summary: Cache statistics
      description: vCache hit rates, false positive rate, and namespace breakdown.
      operationId: getCacheStats
      responses:
        '200':
          description: Cache statistics
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CacheStatsResponse'

  /internal/cache/clear:
    post:
      tags: [internal]
      summary: Clear cache
      description: Clears all cache or specific namespace.
      operationId: clearCache
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CacheClearRequest'
      responses:
        '200':
          description: Cache cleared successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  cleared:
                    type: boolean
                    description: Whether clear succeeded
                  namespace:
                    type: string
                    nullable: true
                    description: Namespace cleared (null = all)

  /internal/keys/list:
    get:
      tags: [internal]
      summary: List API keys
      description: Lists configured provider keys (validated status, no key values).
      operationId: listKeys
      responses:
        '200':
          description: List of key metadata
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/KeysListResponse'

  /internal/keys/add:
    post:
      tags: [internal]
      summary: Add API key
      description: Validates and encrypts a new provider key.
      operationId: addKey
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/KeyAddRequest'
      responses:
        '200':
          description: Key added successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  provider:
                    type: string

  /internal/keys/remove:
    post:
      tags: [internal]
      summary: Remove API key
      description: Removes a provider key from storage.
      operationId: removeKey
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/KeyRemoveRequest'
      responses:
        '200':
          description: Key removed successfully

  /internal/doctor:
    get:
      tags: [internal]
      summary: Full health check
      description: Comprehensive health check including hardware profile and backend status.
      operationId: getDoctor
      responses:
        '200':
          description: Full diagnostic report
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DoctorResponse'

  /internal/tunnel/status:
    get:
      tags: [internal]
      summary: Tunnel status
      description: Current tunnel state, URL, provider, and uptime.
      operationId: getTunnelStatus
      responses:
        '200':
          description: Tunnel status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TunnelStatusResponse'

  /internal/tunnel/start:
    post:
      tags: [internal]
      summary: Start tunnel
      description: Starts Tailscale or Cloudflare tunnel.
      operationId: startTunnel
      responses:
        '200':
          description: Tunnel started
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TunnelStatusResponse'

  /internal/tunnel/stop:
    post:
      tags: [internal]
      summary: Stop tunnel
      description: Stops the active tunnel.
      operationId: stopTunnel
      responses:
        '200':
          description: Tunnel stopped
          content:
            application/json:
              schema:
                type: object
                properties:
                  state:
                    type: string
                    description: New tunnel state (e.g. stopped)

  # ==================== WEBSOCKET ====================

  /ws/dashboard:
    get:
      tags: [websocket]
      summary: Dashboard WebSocket
      description: |
        Streams real-time updates: new requests, cost changes, cache hits.
        Connect to receive live dashboard data.
      operationId: connectDashboardWs
      responses:
        '101':
          description: Switching to WebSocket protocol
      security: []

  # ==================== DASHBOARD ====================

  /dashboard:
    get:
      tags: [dashboard]
      summary: Dashboard SPA
      description: Serves the React single-page application.
      operationId: getDashboard
      responses:
        '200':
          description: Dashboard HTML/static assets
      security: []

  /dashboard/{path}:
    get:
      tags: [dashboard]
      summary: Dashboard SPA catch-all
      description: SPA route catch-all for client-side routing.
      operationId: getDashboardPath
      parameters:
        - name: path
          in: path
          required: true
          schema:
            type: string
          description: SPA route path
      responses:
        '200':
          description: Dashboard static files or SPA
      security: []

# ==================== COMPONENTS ====================

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API Key
      description: Optional API key for internal endpoints (when configured)

  schemas:
    # --- Chat Completions ---
    ChatCompletionRequest:
      type: object
      required: [messages]
      properties:
        model:
          type: string
          description: Model ID or "auto" for intelligent routing
          example: "auto"
        messages:
          type: array
          items:
            $ref: '#/components/schemas/Message'
          description: Conversation messages
        stream:
          type: boolean
          default: false
          description: Enable streaming response
        temperature:
          type: number
          format: float
          minimum: 0
          maximum: 2
          description: Sampling temperature
        max_tokens:
          type: integer
          minimum: 1
          description: Maximum tokens to generate

    Message:
      type: object
      required: [role, content]
      properties:
        role:
          type: string
          enum: [system, user, assistant]
          description: Message role
        content:
          oneOf:
            - type: string
            - type: array
              items:
                type: object
          description: Message content (string or multimodal content blocks)

    ChatCompletionResponse:
      type: object
      description: OpenAI format with llmhosts extension
      properties:
        id:
          type: string
          description: Completion ID
        object:
          type: string
          example: "chat.completion"
        created:
          type: integer
          format: int64
          description: Unix timestamp
        model:
          type: string
          description: Model used
        choices:
          type: array
          items:
            type: object
            properties:
              index:
                type: integer
              message:
                $ref: '#/components/schemas/AssistantMessage'
              finish_reason:
                type: string
        usage:
          type: object
          properties:
            prompt_tokens:
              type: integer
            completion_tokens:
              type: integer
            total_tokens:
              type: integer
        llmhosts:
          $ref: '#/components/schemas/LLMHostsExtension'

    AssistantMessage:
      type: object
      properties:
        role:
          type: string
          example: "assistant"
        content:
          type: string

    LLMHostsExtension:
      type: object
      description: LLMHosts-specific response metadata
      properties:
        routing:
          $ref: '#/components/schemas/RoutingInfo'
        cost:
          $ref: '#/components/schemas/CostInfo'
        cache:
          $ref: '#/components/schemas/CacheInfo'
        latency:
          $ref: '#/components/schemas/LatencyInfo'

    RoutingInfo:
      type: object
      properties:
        tier:
          type: string
          enum: [rule, knn, modernbert, qwen]
          description: Routing tier that selected backend
        confidence:
          type: number
          format: float
          description: Confidence score 0-1
        reason:
          type: string
          description: Human-readable routing reason
        backend:
          type: string
          enum: [ollama, openai, anthropic]
          description: Backend that served the request
        model_used:
          type: string
          description: Actual model used

    CostInfo:
      type: object
      properties:
        actual:
          type: number
          format: float
          description: Actual cost in USD
        estimated_cloud:
          type: number
          format: float
          description: Estimated cost if using cloud
        saved:
          type: number
          format: float
          description: Amount saved vs cloud

    CacheInfo:
      type: object
      properties:
        hit:
          type: boolean
          description: Whether response was cached
        tier:
          type: string
          enum: [exact, entity, semantic]
          description: Cache tier that matched (if hit)
        confidence:
          type: number
          format: float
          description: Semantic match confidence (if applicable)

    LatencyInfo:
      type: object
      properties:
        routing_ms:
          type: number
          format: float
          description: Routing decision time in milliseconds
        inference_ms:
          type: number
          format: float
          description: Backend inference time
        total_ms:
          type: number
          format: float
          description: Total request latency

    # --- Anthropic Messages ---
    MessageRequest:
      type: object
      required: [model, messages]
      properties:
        model:
          type: string
          description: Claude model ID
        messages:
          type: array
          items:
            $ref: '#/components/schemas/Message'
        system:
          type: string
          description: System prompt
        max_tokens:
          type: integer
          minimum: 1
        stream:
          type: boolean
          default: false

    MessageResponse:
      type: object
      description: Anthropic format with llmhosts extension
      properties:
        id:
          type: string
        type:
          type: string
          example: "message"
        role:
          type: string
          example: "assistant"
        content:
          type: array
          items:
            type: object
            properties:
              type:
                type: string
              text:
                type: string
        model:
          type: string
        stop_reason:
          type: string
        usage:
          type: object
          properties:
            input_tokens:
              type: integer
            output_tokens:
              type: integer
        llmhosts:
          $ref: '#/components/schemas/LLMHostsExtension'

    # --- Models ---
    ModelsListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/ModelInfo'

    ModelInfo:
      type: object
      properties:
        id:
          type: string
          description: Model identifier
        object:
          type: string
          example: "model"
        owned_by:
          type: string
          description: Backend/provider owning the model
        capabilities:
          type: object
          description: Model capabilities
          additionalProperties: true

    # --- Health ---
    HealthResponse:
      type: object
      properties:
        status:
          type: string
          enum: [healthy, degraded, unhealthy]
        version:
          type: string
          description: LLMHosts version
        uptime_seconds:
          type: number
          format: float
          description: Process uptime in seconds
        backends:
          type: object
          properties:
            ollama:
              type: boolean
              description: Ollama local backend available
            openai:
              type: boolean
              description: OpenAI configured and reachable
            anthropic:
              type: boolean
              description: Anthropic configured and reachable

    # --- Internal Metrics ---
    SavingsMetricsResponse:
      type: object
      properties:
        total_saved:
          type: number
          format: float
          description: Total USD saved vs cloud
        period_saved:
          type: number
          format: float
          description: Saved in current period
        percent_reduction:
          type: number
          format: float
          description: Percentage cost reduction vs cloud baseline
        baseline_monthly:
          type: number
          format: float
          description: Estimated monthly cloud spend baseline

    RequestMetricsResponse:
      type: object
      properties:
        total:
          type: integer
          description: Total requests all-time
        today:
          type: integer
          description: Requests today
        by_backend:
          type: object
          additionalProperties:
            type: integer
          description: Count per backend (ollama, openai, anthropic)
        by_tier:
          type: object
          additionalProperties:
            type: integer
          description: Count per routing tier (rule, knn, modernbert, qwen)

    CostMetricsResponse:
      type: object
      properties:
        total_actual:
          type: number
          format: float
          description: Total actual cost (local + cloud)
        total_estimated_cloud:
          type: number
          format: float
          description: Total estimated if all cloud
        today_actual:
          type: number
          format: float
        today_estimated:
          type: number
          format: float

    RoutingMetricsResponse:
      type: object
      properties:
        rule_pct:
          type: number
          format: float
          description: Percentage routed by rule tier
        knn_pct:
          type: number
          format: float
          description: Percentage routed by kNN tier
        modernbert_pct:
          type: number
          format: float
          description: Percentage routed by ModernBERT tier
        qwen_pct:
          type: number
          format: float
          description: Percentage routed by Qwen tier

    # --- Cache ---
    CacheStatsResponse:
      type: object
      properties:
        hit_rate:
          type: number
          format: float
          description: Overall cache hit rate 0-1
        exact_hits:
          type: integer
          description: Exact hash matches
        entity_hits:
          type: integer
          description: Entity namespace matches
        semantic_hits:
          type: integer
          description: Semantic similarity matches
        fp_rate:
          type: number
          format: float
          description: False positive rate
        fp_count:
          type: integer
          description: False positive count
        total_entries:
          type: integer
          description: Total cache entries
        size_mb:
          type: number
          format: float
          description: Cache size in megabytes
        namespaces:
          type: array
          items:
            type: object
            properties:
              key:
                type: string
                description: Namespace identifier
              count:
                type: integer
                description: Entry count in namespace

    CacheClearRequest:
      type: object
      properties:
        namespace:
          type: string
          nullable: true
          description: Optional namespace to clear; omit or null for all

    # --- Keys ---
    KeysListResponse:
      type: array
      items:
        type: object
        properties:
          provider:
            type: string
            description: Provider name (openai, anthropic)
          validated:
            type: boolean
            description: Whether key was validated
          added_at:
            type: string
            format: date-time
            description: When key was added

    KeyAddRequest:
      type: object
      required: [provider, key]
      properties:
        provider:
          type: string
          enum: [openai, anthropic]
          description: API provider
        key:
          type: string
          description: API key (will be encrypted)

    KeyRemoveRequest:
      type: object
      required: [provider]
      properties:
        provider:
          type: string
          enum: [openai, anthropic]
          description: Provider to remove key for

    # --- Doctor ---
    DoctorResponse:
      type: object
      description: Full diagnostic including hardware profile
      properties:
        status:
          type: string
        version:
          type: string
        uptime_seconds:
          type: number
        hardware:
          type: object
          description: Hardware profile (GPU, RAM, etc.)
          additionalProperties: true
        backends:
          type: object
          description: Per-backend health
          additionalProperties:
            type: object

    # --- Tunnel ---
    TunnelStatusResponse:
      type: object
      properties:
        state:
          type: string
          enum: [stopped, starting, active, error]
          description: Tunnel state
        url:
          type: string
          nullable: true
          description: Public URL when active
        provider:
          type: string
          enum: [tailscale, cloudflare]
          description: Tunnel provider
        uptime:
          type: number
          format: float
          nullable: true
          description: Seconds since tunnel started (when active)
