openapi: 3.1.0
info:
  title: TechVera Gateway Service API
  version: 1.0.0
  description: |
    The TechVera Gateway is a European sovereign LLM control plane proxy.
    It provides an OpenAI-compatible API surface that routes requests to
    upstream LLM providers based on policy, jurisdiction, and routing tier
    configuration.

    ## Authentication

    All client-facing endpoints require a TechVera API key passed via the
    `Authorization` header using the Bearer scheme:

    ```
    Authorization: Bearer tvr-...
    ```

    API keys prefixed with `test-` are scoped to the `test` jurisdiction
    and route exclusively to the Quality Service pseudo-LLM.

    ## Model Parameter Format (Routing Tiers)

    The `model` field in request bodies uses a tiered format:

    | Tier | Format | Availability |
    |------|--------|--------------|
    | 1 — Explicit | `{provider}/{model}` | Initial release |
    | 2 — Jurisdiction-Aware | `{jurisdiction}/{model}` | Future |
    | 2+ — Model-Pinned Optimised | `{jurisdiction}-{strategy}/{model}` | Future |
    | 3 — Fully Optimised | `{jurisdiction}/{strategy}` | Future |

    Examples: `mistral-ai/mistral-large-latest`, `openai/gpt-4o`, `azure-eu/gpt-4o`

    ## Streaming

    `POST /v1/chat/completions` supports SSE streaming when `stream: true`.
    Token usage and cost data are included in the final SSE chunk's `usage`
    object (following the OpenAI `stream_options.include_usage` pattern)
    rather than in response headers.

    ## Error Format

    All errors use an OpenAI-compatible JSON envelope:
    ```json
    {
      "error": {
        "code": "invalid_api_key",
        "message": "The provided API key is not valid.",
        "request_id": "tvr-req-a1b2c3d4"
      }
    }
    ```
  contact:
    name: TechVera Engineering
    email: engineering@techvera.ai
    url: https://techvera.ai
  license:
    name: Proprietary
    url: https://techvera.ai/terms

servers:
  - url: https://api.techvera.ai
    description: Production (OVHcloud Paris, EU-sovereign)
  - url: http://localhost:8000
    description: Local development

tags:
  - name: Chat
    description: OpenAI-compatible chat completion endpoints
  - name: Embeddings
    description: OpenAI-compatible embedding endpoints
  - name: Models
    description: Model catalog listing
  - name: Health
    description: Kubernetes health and readiness probes

paths:
  /v1/chat/completions:
    post:
      operationId: createChatCompletion
      summary: Create a chat completion
      description: |
        Sends a chat completion request to the resolved LLM provider and
        returns the response. Supports both streaming (SSE) and non-streaming
        modes. The request and response formats are fully OpenAI-compatible.

        The Gateway authenticates the request, resolves the provider via the
        routing table, enforces rate and spend limits, proxies the request
        through the appropriate provider adapter, and emits telemetry
        asynchronously.
      tags:
        - Chat
      security:
        - apiKeyAuth: []
      parameters:
        - $ref: "#/components/parameters/X-TechVera-Routing-Policy"
        - $ref: "#/components/parameters/X-TechVera-Tags"
        - $ref: "#/components/parameters/X-TechVera-Fallback"
        - $ref: "#/components/parameters/Traceparent"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatCompletionRequest"
            examples:
              non-streaming:
                summary: Non-streaming request
                value:
                  model: "mistral-ai/mistral-large-latest"
                  messages:
                    - role: "system"
                      content: "You are a helpful assistant."
                    - role: "user"
                      content: "Explain EU data sovereignty in two sentences."
                  temperature: 0.7
                  max_tokens: 256
              streaming:
                summary: Streaming request
                value:
                  model: "openai/gpt-4o"
                  messages:
                    - role: "user"
                      content: "Write a short poem about cloud computing."
                  stream: true
                  stream_options:
                    include_usage: true
      responses:
        "200":
          description: |
            Successful chat completion response. For non-streaming requests,
            the full response is returned as JSON with `X-TechVera-*` headers
            including token counts and cost. For streaming requests, the
            response is delivered as Server-Sent Events (SSE).
          headers:
            X-TechVera-Request-Id:
              $ref: "#/components/headers/X-TechVera-Request-Id"
            X-TechVera-Provider:
              $ref: "#/components/headers/X-TechVera-Provider"
            X-TechVera-Region:
              $ref: "#/components/headers/X-TechVera-Region"
            X-TechVera-Model:
              $ref: "#/components/headers/X-TechVera-Model"
            X-TechVera-Jurisdiction:
              $ref: "#/components/headers/X-TechVera-Jurisdiction"
            X-TechVera-Input-Tokens:
              $ref: "#/components/headers/X-TechVera-Input-Tokens"
            X-TechVera-Output-Tokens:
              $ref: "#/components/headers/X-TechVera-Output-Tokens"
            X-TechVera-Cost-EUR:
              $ref: "#/components/headers/X-TechVera-Cost-EUR"
            X-TechVera-Trace-Id:
              $ref: "#/components/headers/X-TechVera-Trace-Id"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatCompletionResponse"
              examples:
                success:
                  summary: Non-streaming response
                  value:
                    id: "chatcmpl-abc123"
                    object: "chat.completion"
                    created: 1711632000
                    model: "mistral-large-latest"
                    choices:
                      - index: 0
                        message:
                          role: "assistant"
                          content: "EU data sovereignty means that data generated within the European Union is stored, processed, and governed under EU law. This ensures compliance with GDPR and protects citizens' data from extraterritorial access."
                        finish_reason: "stop"
                    usage:
                      prompt_tokens: 28
                      completion_tokens: 42
                      total_tokens: 70
                    system_fingerprint: "fp_abc123"
            text/event-stream:
              schema:
                type: string
                description: |
                  SSE stream of `ChatCompletionChunk` objects. Each event is
                  prefixed with `data: ` followed by a JSON object. The stream
                  terminates with `data: [DONE]`. When `stream_options.include_usage`
                  is true, the final chunk before `[DONE]` includes a `usage` object.
              examples:
                streaming:
                  summary: Streaming SSE response (abbreviated)
                  value: |
                    data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1711632000,"model":"mistral-large-latest","choices":[{"index":0,"delta":{"role":"assistant","content":"EU"},"finish_reason":null}]}

                    data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1711632000,"model":"mistral-large-latest","choices":[{"index":0,"delta":{"content":" data"},"finish_reason":null}]}

                    data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1711632000,"model":"mistral-large-latest","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":28,"completion_tokens":42,"total_tokens":70,"cost_provider_eur":0.00187,"cost_customer_eur":0.00234}}

                    data: [DONE]
        "400":
          description: Invalid request — malformed body or invalid model format.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                invalid_model:
                  summary: Invalid model format
                  value:
                    error:
                      code: "invalid_model"
                      message: "Model parameter 'foobar' does not match any supported routing tier format. Expected '{provider}/{model}'."
                      request_id: "tvr-req-a1b2c3d4"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          description: Spend limit exceeded for the API key or organization.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                spend_limit:
                  summary: Spend limit exceeded
                  value:
                    error:
                      code: "spend_limit_exceeded"
                      message: "Monthly spend limit of EUR 500.00 reached for this API key. Contact your organization admin to increase the limit."
                      request_id: "tvr-req-a1b2c3d4"
        "404":
          description: No provider available for the requested model and jurisdiction.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                model_not_found:
                  summary: Model not found
                  value:
                    error:
                      code: "model_not_found"
                      message: "No provider available for model 'openai/gpt-5-nonexistent' in any configured jurisdiction."
                      request_id: "tvr-req-a1b2c3d4"
        "429":
          description: |
            Rate limit exceeded. The `error.code` distinguishes between
            TechVera's own rate limit (`rate_limit_exceeded`) and an upstream
            provider rate limit (`provider_rate_limited`).
          headers:
            Retry-After:
              description: Suggested wait time in seconds before retrying.
              schema:
                type: integer
                example: 5
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                techvera_rate_limit:
                  summary: TechVera rate limit
                  value:
                    error:
                      code: "rate_limit_exceeded"
                      message: "Rate limit exceeded for this API key. Limit: 100 requests/minute."
                      request_id: "tvr-req-a1b2c3d4"
                provider_rate_limit:
                  summary: Upstream provider rate limit
                  value:
                    error:
                      code: "provider_rate_limited"
                      message: "Upstream provider 'mistral-ai' returned a rate limit error. Retry after a brief delay."
                      request_id: "tvr-req-a1b2c3d4"
        "502":
          description: |
            Upstream provider error. Possible causes:
            - `no_provider_available`: All providers for the requested model
              and jurisdiction are currently unavailable (health checks
              failing, connectivity issues). Distinct from quota exhaustion.
            - `provider_quota_exhausted`: All providers have exhausted their
              quota and no fallback provider is available.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                no_provider_available:
                  summary: No provider available
                  value:
                    error:
                      code: "no_provider_available"
                      message: "All providers for model 'openai/gpt-4o' in jurisdiction 'eu-sovereign' are currently unavailable. Retry later."
                      request_id: "tvr-req-a1b2c3d4"
                quota_exhausted:
                  summary: Provider quota exhausted
                  value:
                    error:
                      code: "provider_quota_exhausted"
                      message: "All configured providers for model 'openai/gpt-4o' have exhausted their quota. No fallback available."
                      request_id: "tvr-req-a1b2c3d4"
        "504":
          description: Provider request timed out and no fallback available.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                timeout:
                  summary: Provider timeout
                  value:
                    error:
                      code: "provider_timeout"
                      message: "Upstream provider did not respond within the configured timeout. No fallback available."
                      request_id: "tvr-req-a1b2c3d4"

  /v1/embeddings:
    post:
      operationId: createEmbedding
      summary: Create embeddings
      description: |
        Generates embedding vectors for the given input text. The request
        and response formats are OpenAI-compatible. Embedding requests are
        synchronous only (no streaming mode). Providers that do not support
        embeddings will be rejected with a clear error.
      tags:
        - Embeddings
      security:
        - apiKeyAuth: []
      parameters:
        - $ref: "#/components/parameters/X-TechVera-Routing-Policy"
        - $ref: "#/components/parameters/X-TechVera-Tags"
        - $ref: "#/components/parameters/X-TechVera-Fallback"
        - $ref: "#/components/parameters/Traceparent"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EmbeddingRequest"
            examples:
              single_string:
                summary: Single text input
                value:
                  model: "openai/text-embedding-3-small"
                  input: "TechVera provides EU-sovereign AI infrastructure."
              array_input:
                summary: Array of texts
                value:
                  model: "openai/text-embedding-3-small"
                  input:
                    - "First document to embed."
                    - "Second document to embed."
                  encoding_format: "float"
      responses:
        "200":
          description: Successful embedding response.
          headers:
            X-TechVera-Request-Id:
              $ref: "#/components/headers/X-TechVera-Request-Id"
            X-TechVera-Provider:
              $ref: "#/components/headers/X-TechVera-Provider"
            X-TechVera-Region:
              $ref: "#/components/headers/X-TechVera-Region"
            X-TechVera-Model:
              $ref: "#/components/headers/X-TechVera-Model"
            X-TechVera-Jurisdiction:
              $ref: "#/components/headers/X-TechVera-Jurisdiction"
            X-TechVera-Input-Tokens:
              $ref: "#/components/headers/X-TechVera-Input-Tokens"
            X-TechVera-Cost-EUR:
              $ref: "#/components/headers/X-TechVera-Cost-EUR"
            X-TechVera-Trace-Id:
              $ref: "#/components/headers/X-TechVera-Trace-Id"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EmbeddingResponse"
              examples:
                success:
                  summary: Embedding response
                  value:
                    object: "list"
                    data:
                      - object: "embedding"
                        index: 0
                        embedding: [0.0023, -0.0091, 0.0152, -0.0034, 0.0078]
                    model: "text-embedding-3-small"
                    usage:
                      prompt_tokens: 8
                      total_tokens: 8
        "400":
          description: Invalid request — malformed body, invalid model, or unsupported provider.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                unsupported:
                  summary: Provider does not support embeddings
                  value:
                    error:
                      code: "invalid_model"
                      message: "Provider 'anthropic' does not support embedding requests."
                      request_id: "tvr-req-e5f6g7h8"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/SpendLimitExceeded"
        "404":
          $ref: "#/components/responses/ModelNotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "502":
          $ref: "#/components/responses/ProviderError"

  /v1/models:
    get:
      operationId: listModels
      summary: List available models
      description: |
        Returns the TechVera model catalog in OpenAI-compatible format.
        Each model entry uses the TechVera routing tier format as its `id`
        (e.g. `mistral-ai/mistral-large-latest`). The list reflects models
        available to the authenticated API key based on its organization's
        configured providers and jurisdictions.
      tags:
        - Models
      security:
        - apiKeyAuth: []
      responses:
        "200":
          description: List of available models.
          headers:
            X-TechVera-Request-Id:
              $ref: "#/components/headers/X-TechVera-Request-Id"
            X-TechVera-Trace-Id:
              $ref: "#/components/headers/X-TechVera-Trace-Id"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ModelListResponse"
              examples:
                success:
                  summary: Model list
                  value:
                    object: "list"
                    data:
                      - id: "mistral-ai/mistral-large-latest"
                        object: "model"
                        created: 1711632000
                        owned_by: "mistral-ai"
                        jurisdictions: ["eu-sovereign"]
                        providers:
                          - provider_id: "mistral-ai"
                            provider_name: "Mistral AI"
                        capabilities: ["chat", "tools"]
                      - id: "openai/gpt-4o"
                        object: "model"
                        created: 1711632000
                        owned_by: "openai"
                        jurisdictions: ["eu-hosted"]
                        providers:
                          - provider_id: "openai"
                            provider_name: "OpenAI"
                        capabilities: ["chat", "tools", "vision"]
                      - id: "azure-eu/gpt-4o"
                        object: "model"
                        created: 1711632000
                        owned_by: "azure-eu"
                        jurisdictions: ["eu-sovereign", "eu-hosted"]
                        providers:
                          - provider_id: "azure-eu"
                            provider_name: "Azure EU (France Central)"
                        capabilities: ["chat", "tools", "vision"]
                      - id: "openai/text-embedding-3-small"
                        object: "model"
                        created: 1711632000
                        owned_by: "openai"
                        jurisdictions: ["eu-hosted"]
                        providers:
                          - provider_id: "openai"
                            provider_name: "OpenAI"
                        capabilities: ["embeddings"]
        "401":
          $ref: "#/components/responses/Unauthorized"

  /health:
    get:
      operationId: healthCheck
      summary: Kubernetes liveness probe
      description: |
        Returns HTTP 200 if the Gateway process is alive and able to handle
        requests. This endpoint does not check downstream dependencies.
        Used as the Kubernetes liveness probe target.
      tags:
        - Health
      security: []
      responses:
        "200":
          description: Service is alive.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"
              examples:
                healthy:
                  value:
                    status: "ok"
        "503":
          description: Service is not healthy.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"
              examples:
                unhealthy:
                  value:
                    status: "error"

  /ready:
    get:
      operationId: readinessCheck
      summary: Kubernetes readiness probe
      description: |
        Returns HTTP 200 if the Gateway has completed its startup sequence
        and is ready to accept traffic. Specifically, the Gateway must have
        successfully loaded its initial caches (routing table, API key
        registry, pricing rules, provider health, margin configuration)
        from the Data Service before reporting ready. All five caches
        must be loaded for the Gateway to report ready. Used as the
        Kubernetes readiness probe target.
      tags:
        - Health
      security: []
      responses:
        "200":
          description: Service is ready to accept traffic.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReadinessResponse"
              examples:
                ready:
                  value:
                    status: "ready"
                    checks:
                      routing_table: true
                      api_key_cache: true
                      pricing_cache: true
                      provider_health_cache: true
                      margin_config_cache: true
        "503":
          description: |
            Service is not ready. One or more required caches have not been
            loaded. Kubernetes will not route traffic to this instance.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReadinessResponse"
              examples:
                not_ready:
                  value:
                    status: "not_ready"
                    checks:
                      routing_table: true
                      api_key_cache: false
                      pricing_cache: false
                      provider_health_cache: true
                      margin_config_cache: false

components:
  securitySchemes:
    apiKeyAuth:
      type: http
      scheme: bearer
      bearerFormat: "tvr-..."
      description: |
        TechVera API key with the `tvr-` prefix. Passed as a Bearer token
        in the Authorization header. Keys prefixed with `test-` are scoped
        to the test jurisdiction.

  parameters:
    X-TechVera-Routing-Policy:
      name: X-TechVera-Routing-Policy
      in: header
      required: false
      description: |
        Per-request routing policy override. Overrides the default policy
        configured on the API key or organization.
      schema:
        type: string
        enum:
          - cost-optimized
          - latency-optimized
          - pinned
      example: "cost-optimized"

    X-TechVera-Tags:
      name: X-TechVera-Tags
      in: header
      required: false
      description: |
        Customer-defined metadata tags as a JSON string. Used for cost
        attribution, reporting, and internal analytics. Tags are stored
        with the telemetry event but do not affect routing.
      schema:
        type: string
      example: '{"cost_center": "team-a", "project": "chatbot"}'

    X-TechVera-Fallback:
      name: X-TechVera-Fallback
      in: header
      required: false
      description: |
        Set to `false` to disable automatic fallback routing for this
        request. If the primary provider fails, the error is returned
        directly instead of trying the fallback chain.
      schema:
        type: string
        enum:
          - "true"
          - "false"
        default: "true"
      example: "false"

    Traceparent:
      name: traceparent
      in: header
      required: false
      description: |
        W3C Trace Context `traceparent` header. When provided, the Gateway
        creates a child span under the client's trace so that TechVera
        processing is visible in the client's own APM/observability tool.
        The trace ID is propagated to the upstream provider.
      schema:
        type: string
        pattern: "^00-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$"
      example: "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"

  headers:
    X-TechVera-Request-Id:
      description: |
        Unique TechVera request identifier. Present on every response.
        Use this value when contacting support or correlating logs.
      schema:
        type: string
        pattern: "^tvr-req-[a-z0-9]+$"
      example: "tvr-req-a1b2c3d4"

    X-TechVera-Provider:
      description: |
        The upstream LLM provider that served this request
        (e.g. `mistral-ai`, `openai`, `azure-eu`).
      schema:
        type: string
      example: "mistral-ai"

    X-TechVera-Region:
      description: |
        The cloud region of the provider endpoint that served this request.
      schema:
        type: string
      example: "eu-fr-par"

    X-TechVera-Model:
      description: |
        The actual model name as known by the provider (without the
        provider prefix). This may differ from the requested model if
        the Gateway resolved a tier 2/3 routing request.
      schema:
        type: string
      example: "mistral-large-latest"

    X-TechVera-Jurisdiction:
      description: |
        The data sovereignty jurisdiction under which this request was
        processed (e.g. `eu-sovereign`, `eu-hosted`).
      schema:
        type: string
      example: "eu-sovereign"

    X-TechVera-Input-Tokens:
      description: |
        Number of input (prompt) tokens consumed. Present on non-streaming
        responses only. For streaming, usage is in the final SSE chunk.
      schema:
        type: integer
      example: 142

    X-TechVera-Output-Tokens:
      description: |
        Number of output (completion) tokens generated. Present on
        non-streaming responses only. For streaming, usage is in the
        final SSE chunk.
      schema:
        type: integer
      example: 583

    X-TechVera-Cost-EUR:
      description: |
        Total customer-facing cost of this request in EUR. Present on
        non-streaming responses only. For streaming, cost data is in
        the final SSE chunk.
      schema:
        type: number
        format: double
      example: 0.00234

    X-TechVera-Trace-Id:
      description: |
        OpenTelemetry trace ID for this request. Always present. Use
        this to look up the full distributed trace in the observability
        stack or to correlate with your own tracing if you sent a
        `traceparent` header.
      schema:
        type: string
        pattern: "^[0-9a-f]{32}$"
      example: "0af7651916cd43dd8448eb211c80319c"

  schemas:
    # --------------- Chat Completion ---------------

    ChatCompletionRequest:
      type: object
      required:
        - model
        - messages
      properties:
        model:
          type: string
          description: |
            Model identifier in TechVera routing tier format. For Tier 1
            (Initial release): `{provider}/{model}`.
          example: "mistral-ai/mistral-large-latest"
        messages:
          type: array
          description: List of messages comprising the conversation.
          minItems: 1
          items:
            $ref: "#/components/schemas/ChatMessage"
        temperature:
          type: number
          minimum: 0
          maximum: 2
          default: 1
          description: Sampling temperature between 0 and 2.
        top_p:
          type: number
          minimum: 0
          maximum: 1
          default: 1
          description: Nucleus sampling parameter.
        n:
          type: integer
          minimum: 1
          default: 1
          description: Number of completions to generate.
        stream:
          type: boolean
          default: false
          description: |
            If true, partial message deltas are sent as Server-Sent Events.
        stream_options:
          $ref: "#/components/schemas/StreamOptions"
        stop:
          description: Up to 4 sequences where the API will stop generating.
          oneOf:
            - type: string
            - type: array
              items:
                type: string
              maxItems: 4
        max_tokens:
          type: integer
          minimum: 1
          description: Maximum number of tokens to generate.
        presence_penalty:
          type: number
          minimum: -2
          maximum: 2
          default: 0
          description: Penalizes new tokens based on their presence in the text so far.
        frequency_penalty:
          type: number
          minimum: -2
          maximum: 2
          default: 0
          description: Penalizes new tokens based on their frequency in the text so far.
        logit_bias:
          type: object
          additionalProperties:
            type: number
          description: |
            Mapping of token IDs to bias values (-100 to 100). Not all
            providers support this parameter.
        user:
          type: string
          description: |
            Unique identifier for the end-user. Passed through to the
            provider for abuse monitoring.
        tools:
          type: array
          items:
            $ref: "#/components/schemas/Tool"
          description: List of tools the model may call.
        tool_choice:
          description: Controls which tool is called by the model.
          oneOf:
            - type: string
              enum:
                - none
                - auto
                - required
            - $ref: "#/components/schemas/ToolChoice"
        response_format:
          $ref: "#/components/schemas/ResponseFormat"
        seed:
          type: integer
          description: |
            Seed for deterministic sampling. Not all providers support
            this parameter.

    StreamOptions:
      type: object
      properties:
        include_usage:
          type: boolean
          default: false
          description: |
            If true, the final streaming chunk includes a `usage` object
            with token counts. Recommended for cost tracking with streaming.

    ChatMessage:
      type: object
      required:
        - role
        - content
      properties:
        role:
          type: string
          enum:
            - system
            - user
            - assistant
            - tool
          description: The role of the message author.
        content:
          description: The content of the message.
          oneOf:
            - type: string
            - type: array
              items:
                $ref: "#/components/schemas/ContentPart"
        name:
          type: string
          description: Optional name of the message author.
        tool_calls:
          type: array
          items:
            $ref: "#/components/schemas/ToolCall"
          description: Tool calls generated by the model (assistant messages only).
        tool_call_id:
          type: string
          description: The ID of the tool call this message is a response to (tool messages only).

    ContentPart:
      type: object
      required:
        - type
      properties:
        type:
          type: string
          enum:
            - text
            - image_url
        text:
          type: string
          description: Text content (when type is `text`).
        image_url:
          type: object
          properties:
            url:
              type: string
              format: uri
              description: URL of the image or a base64-encoded data URI.
            detail:
              type: string
              enum:
                - auto
                - low
                - high
              default: auto

    Tool:
      type: object
      required:
        - type
        - function
      properties:
        type:
          type: string
          enum:
            - function
        function:
          $ref: "#/components/schemas/FunctionDefinition"

    FunctionDefinition:
      type: object
      required:
        - name
      properties:
        name:
          type: string
          description: The name of the function.
        description:
          type: string
          description: A description of what the function does.
        parameters:
          type: object
          description: JSON Schema describing the function parameters.

    ToolChoice:
      type: object
      required:
        - type
        - function
      properties:
        type:
          type: string
          enum:
            - function
        function:
          type: object
          required:
            - name
          properties:
            name:
              type: string

    ToolCall:
      type: object
      required:
        - id
        - type
        - function
      properties:
        id:
          type: string
          description: Unique identifier for this tool call.
        type:
          type: string
          enum:
            - function
        function:
          type: object
          required:
            - name
            - arguments
          properties:
            name:
              type: string
            arguments:
              type: string
              description: JSON string of the function arguments.

    ResponseFormat:
      type: object
      properties:
        type:
          type: string
          enum:
            - text
            - json_object
          description: The format the model should output.

    ChatCompletionResponse:
      type: object
      required:
        - id
        - object
        - created
        - model
        - choices
        - usage
      properties:
        id:
          type: string
          description: Unique identifier for this completion.
          example: "chatcmpl-abc123"
        object:
          type: string
          const: "chat.completion"
        created:
          type: integer
          description: Unix timestamp of when the completion was created.
          example: 1711632000
        model:
          type: string
          description: The model that generated the completion.
          example: "mistral-large-latest"
        choices:
          type: array
          items:
            $ref: "#/components/schemas/ChatCompletionChoice"
        usage:
          $ref: "#/components/schemas/Usage"
        system_fingerprint:
          type: string
          description: Provider-specific system fingerprint, if available.

    ChatCompletionChoice:
      type: object
      required:
        - index
        - message
        - finish_reason
      properties:
        index:
          type: integer
        message:
          $ref: "#/components/schemas/ChatMessage"
        finish_reason:
          type: string
          enum:
            - stop
            - length
            - tool_calls
            - content_filter
          description: The reason the model stopped generating.

    ChatCompletionChunk:
      type: object
      description: |
        A single chunk in a streaming chat completion response. Delivered
        as an SSE event with `data:` prefix.
      required:
        - id
        - object
        - created
        - model
        - choices
      properties:
        id:
          type: string
          example: "chatcmpl-abc123"
        object:
          type: string
          const: "chat.completion.chunk"
        created:
          type: integer
        model:
          type: string
        choices:
          type: array
          items:
            $ref: "#/components/schemas/ChatCompletionChunkChoice"
        usage:
          description: |
            Token usage statistics. Present only in the final chunk when
            `stream_options.include_usage` is true. When present, includes
            TechVera cost extension fields.
          allOf:
            - $ref: "#/components/schemas/Usage"
            - $ref: "#/components/schemas/StreamingCostExtension"

    ChatCompletionChunkChoice:
      type: object
      required:
        - index
        - delta
      properties:
        index:
          type: integer
        delta:
          $ref: "#/components/schemas/ChatMessageDelta"
        finish_reason:
          type: string
          enum:
            - stop
            - length
            - tool_calls
            - content_filter
            - null

    ChatMessageDelta:
      type: object
      description: Partial message content in a streaming chunk.
      properties:
        role:
          type: string
          enum:
            - assistant
        content:
          type: string
        tool_calls:
          type: array
          items:
            $ref: "#/components/schemas/ToolCallDelta"

    ToolCallDelta:
      type: object
      properties:
        index:
          type: integer
        id:
          type: string
        type:
          type: string
          enum:
            - function
        function:
          type: object
          properties:
            name:
              type: string
            arguments:
              type: string

    Usage:
      type: object
      required:
        - prompt_tokens
        - completion_tokens
        - total_tokens
      properties:
        prompt_tokens:
          type: integer
          description: Number of tokens in the prompt.
          example: 28
        completion_tokens:
          type: integer
          description: Number of tokens in the generated completion.
          example: 42
        total_tokens:
          type: integer
          description: Total tokens used (prompt + completion).
          example: 70

    StreamingCostExtension:
      type: object
      description: |
        TechVera extension fields for streaming cost data. These fields are
        only present in the final SSE chunk when `usage` is populated
        (i.e. when `stream_options.include_usage` is true). They are
        TechVera-specific extensions to the OpenAI streaming format and
        will not be present in standard OpenAI responses.
      properties:
        cost_provider_eur:
          type: number
          format: double
          description: |
            Provider cost for this request in EUR (what TechVera pays
            the upstream LLM provider).
          example: 0.00187
        cost_customer_eur:
          type: number
          format: double
          description: |
            Customer cost for this request in EUR (provider cost plus
            the configured margin).
          example: 0.00234

    # --------------- Embeddings ---------------

    EmbeddingRequest:
      type: object
      required:
        - model
        - input
      properties:
        model:
          type: string
          description: |
            Model identifier in TechVera routing tier format.
          example: "openai/text-embedding-3-small"
        input:
          description: |
            The text to embed. Can be a single string or an array of strings.
          oneOf:
            - type: string
            - type: array
              items:
                type: string
        encoding_format:
          type: string
          enum:
            - float
            - base64
          default: float
          description: The format of the returned embeddings.
        dimensions:
          type: integer
          minimum: 1
          description: |
            The number of dimensions for the output embeddings. Only
            supported by models that allow dimension reduction.
        user:
          type: string
          description: Unique identifier for the end-user.

    EmbeddingResponse:
      type: object
      required:
        - object
        - data
        - model
        - usage
      properties:
        object:
          type: string
          const: "list"
        data:
          type: array
          items:
            $ref: "#/components/schemas/EmbeddingObject"
        model:
          type: string
          description: The model used for embedding.
          example: "text-embedding-3-small"
        usage:
          $ref: "#/components/schemas/EmbeddingUsage"

    EmbeddingObject:
      type: object
      required:
        - object
        - index
        - embedding
      properties:
        object:
          type: string
          const: "embedding"
        index:
          type: integer
          description: Index of the embedding in the input list.
        embedding:
          description: The embedding vector.
          oneOf:
            - type: array
              items:
                type: number
                format: float
            - type: string
              description: Base64-encoded embedding (when encoding_format is `base64`).

    EmbeddingUsage:
      type: object
      required:
        - prompt_tokens
        - total_tokens
      properties:
        prompt_tokens:
          type: integer
          description: Number of tokens in the input.
          example: 8
        total_tokens:
          type: integer
          description: Total tokens used.
          example: 8

    # --------------- Models ---------------

    ModelListResponse:
      type: object
      required:
        - object
        - data
      properties:
        object:
          type: string
          const: "list"
        data:
          type: array
          items:
            $ref: "#/components/schemas/ModelObject"

    ModelObject:
      type: object
      required:
        - id
        - object
        - created
        - owned_by
      properties:
        id:
          type: string
          description: |
            Model identifier in TechVera routing tier format
            (e.g. `mistral-ai/mistral-large-latest`).
          example: "mistral-ai/mistral-large-latest"
        object:
          type: string
          const: "model"
        created:
          type: integer
          description: Unix timestamp of when the model was added to TechVera.
          example: 1711632000
        owned_by:
          type: string
          description: The provider that owns the model.
          example: "mistral-ai"
        jurisdictions:
          type: array
          description: |
            TechVera extension. List of data sovereignty jurisdictions in
            which this model is available. This field is a TechVera-specific
            extension to the OpenAI model list format.
          items:
            type: string
          example: ["eu-sovereign", "eu-hosted"]
        providers:
          type: array
          description: |
            TechVera extension. List of providers that serve this model.
            This field is a TechVera-specific extension to the OpenAI
            model list format.
          items:
            type: object
            required:
              - provider_id
              - provider_name
            properties:
              provider_id:
                type: string
                description: Internal provider identifier.
                example: "mistral-ai"
              provider_name:
                type: string
                description: Human-readable provider name.
                example: "Mistral AI"
        capabilities:
          type: array
          description: |
            TechVera extension. List of capabilities supported by this
            model. This field is a TechVera-specific extension to the
            OpenAI model list format.
          items:
            type: string
          example: ["chat", "tools", "vision"]

    # --------------- Health ---------------

    HealthResponse:
      type: object
      required:
        - status
      properties:
        status:
          type: string
          enum:
            - ok
            - error
          description: Liveness status of the Gateway process.

    ReadinessResponse:
      type: object
      required:
        - status
        - checks
      properties:
        status:
          type: string
          enum:
            - ready
            - not_ready
          description: Overall readiness status.
        checks:
          type: object
          description: Status of individual readiness checks.
          required:
            - routing_table
            - api_key_cache
            - pricing_cache
            - provider_health_cache
            - margin_config_cache
          properties:
            routing_table:
              type: boolean
              description: Whether the routing table cache has been loaded.
            api_key_cache:
              type: boolean
              description: Whether the API key registry cache has been loaded.
            pricing_cache:
              type: boolean
              description: Whether the pricing rules cache has been loaded.
            provider_health_cache:
              type: boolean
              description: Whether the provider health status cache has been loaded.
            margin_config_cache:
              type: boolean
              description: Whether the margin configuration cache has been loaded.

    # --------------- Errors ---------------

    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          $ref: "#/components/schemas/ErrorObject"

    ErrorObject:
      type: object
      required:
        - code
        - message
        - request_id
      properties:
        code:
          type: string
          description: |
            Machine-readable error code. One of: `invalid_api_key`,
            `rate_limit_exceeded`, `provider_rate_limited`,
            `spend_limit_exceeded`, `invalid_model`, `model_not_found`,
            `no_provider_available`, `provider_quota_exhausted`,
            `provider_timeout`, `invalid_request_error`, `server_error`.
          enum:
            - invalid_api_key
            - rate_limit_exceeded
            - provider_rate_limited
            - spend_limit_exceeded
            - invalid_model
            - model_not_found
            - no_provider_available
            - provider_quota_exhausted
            - provider_timeout
            - invalid_request_error
            - server_error
          example: "invalid_api_key"
        message:
          type: string
          description: Human-readable error description.
          example: "The provided API key is not valid."
        request_id:
          type: string
          description: TechVera request ID for support correlation.
          pattern: "^tvr-req-[a-z0-9]+$"
          example: "tvr-req-a1b2c3d4"

  responses:
    Unauthorized:
      description: Invalid or missing API key.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          examples:
            invalid_key:
              summary: Invalid API key
              value:
                error:
                  code: "invalid_api_key"
                  message: "The provided API key is not valid."
                  request_id: "tvr-req-a1b2c3d4"

    SpendLimitExceeded:
      description: Spend limit exceeded for the API key or organization.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          examples:
            spend_limit:
              summary: Spend limit exceeded
              value:
                error:
                  code: "spend_limit_exceeded"
                  message: "Monthly spend limit reached for this API key."
                  request_id: "tvr-req-a1b2c3d4"

    ModelNotFound:
      description: No provider available for the requested model.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          examples:
            not_found:
              summary: Model not found
              value:
                error:
                  code: "model_not_found"
                  message: "No provider available for the requested model in any configured jurisdiction."
                  request_id: "tvr-req-a1b2c3d4"

    RateLimited:
      description: Rate limit exceeded (TechVera or upstream provider).
      headers:
        Retry-After:
          description: Suggested wait time in seconds.
          schema:
            type: integer
            example: 5
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          examples:
            rate_limit:
              summary: Rate limit exceeded
              value:
                error:
                  code: "rate_limit_exceeded"
                  message: "Rate limit exceeded. Please retry after the indicated delay."
                  request_id: "tvr-req-a1b2c3d4"

    ProviderError:
      description: |
        All providers failed, unavailable, or quota exhausted.
        `no_provider_available` indicates all providers are down or
        unreachable. `provider_quota_exhausted` indicates all providers
        have used up their allocated quota.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          examples:
            no_provider_available:
              summary: No provider available
              value:
                error:
                  code: "no_provider_available"
                  message: "All providers for the requested model are currently unavailable. Retry later."
                  request_id: "tvr-req-a1b2c3d4"
            provider_error:
              summary: Provider quota exhausted
              value:
                error:
                  code: "provider_quota_exhausted"
                  message: "All configured providers have exhausted their quota. No fallback available."
                  request_id: "tvr-req-a1b2c3d4"
