openapi: 3.1.0
info:
  title: AudioLab API
  version: 1.0.0
  summary: AudioLab speech, language, embeddings, moderation, documents, and media API.
  description: |
    AudioLab exposes stable APIs for transcription, speech generation, speech-to-speech conversations, provider routing,
    API key management, model discovery, usage, and async submission.

    The v1 contract supports text, speech, embeddings, moderation, documents, images, and videos. It preserves
    OpenAI-style multipart transcription and chat compatibility, with native
    provider controls, metered usage, and inspectable route traces.
  contact:
    name: AudioLab
  license:
    name: Proprietary
    identifier: LicenseRef-Proprietary
servers:
  - url: https://api.tryaudiolab.ai
    description: Production
  - url: http://localhost:3000
    description: Local development
security:
  - bearerAuth: []
tags:
  - name: Inference
    description: Direct native media and visual inference.
  - name: Videos
    description: Funded asynchronous video generation and authenticated downloads.
  - name: Documents
    description: Native document text and layout extraction.
  - name: Reranking
    description: Document relevance scoring; endpoint admission depends on reviewed provider billing.
  - name: Images
    description: Direct image generation and durable request status.
  - name: Health
    description: Service health and readiness.
  - name: Auth
    description: Dashboard sign-in and sessions.
  - name: Workspaces
    description: Workspace creation and membership context.
  - name: Keys
    description: API key minting, listing, update, and revocation.
  - name: Audio
    description: Audio transcription, text-to-speech generation, and speech-to-speech conversations.
  - name: Speech to Speech
    description: Native audio-in/audio-out model turns and realtime sessions.
  - name: Embeddings
    description: Input-only text embeddings for retrieval and search.
  - name: Batches
    description: Asynchronous LLM batches from uploaded JSONL files, OpenAI-compatible.
  - name: Moderation
    description: Provider-native text classifications.
  - name: Catalog
    description: Model, provider, and endpoint discovery.
  - name: Requests
    description: Request logs and route traces.
  - name: Traces
    description: Trace destinations that receive each finished request's trace. Dashboard sessions only.
  - name: Usage
    description: Usage, credits, and ledger records.
  - name: MCP
    description: The hosted Model Context Protocol server, for AI clients and agents.
  -
    name: "Routers"
    description: "Workspace-owned named LLM router control plane. Requires an active workspace membership with owner, admin, developer, or viewer role. Billing-only members and inference API keys are rejected. workspace_id is always required; a router ID from a different authorized workspace returns 404. Requires an active owner, admin, or developer session in the named workspace; viewer and billing-only roles cannot mutate. Inference API keys are rejected. For cookie sessions, every POST and PATCH requires browser same-origin proof: Sec-Fetch-Site: same-origin, or Origin equal to the configured public request origin. A Bearer sess.* session does not use cookie authentication. JSON request bodies are limited to 131072 bytes. Drafts are independent of immutable published revisions. Catalog discovery, validation and deterministic simulation never call a model. Publication refuses unavailable models. Call a published router through Chat Completions, Responses or Messages with an inference API key and model router/<workspace-local-slug>; send router_revision to pin a revision, metadata for conditions and session_id for sticky A/B assignment. See the Named Routers guide (/docs/named-routers). Every mutation uses optimistic generation; create/duplicate/publish also use durable command idempotency."
paths:
  /v1/inference:
    post:
      tags: [Inference]
      summary: Run native media inference
      operationId: createInference
      description: |
        Run a direct native image, video, music, audio processing, image classification or visual question-answering model.
        Requires inference.write, funded credits, Idempotency-Key and NATIVE_INFERENCE_ENABLED.
        Also accepts /api/v1/inference. Use the model catalog to select an inference-protocol model.

        Image inputs must be an inline 1024×1024 PNG, JPEG or WebP, up to 4 MiB.
        Image generation returns one square 1024 image. Video generation returns one fixed five-second
        clip. Music duration is model-specific. Classification accepts 2–32 unique labels.
        ElevenLabs supports music, sound effects, voice conversion, isolation and dialogue.
        Conversion and isolation accept a mono 16 kHz PCM16 WAV data URL up to 60 seconds;
        conversion also requires a voice ID. Dialogue accepts up to 2,000 characters across
        ten voices. Music accepts 3–60 seconds; sound effects accept 0.5–30 seconds.
        Output media is returned as base64. No arbitrary URLs, callbacks or native options are accepted.
        Published model-specific units determine billing; provider cost estimates are not billing receipts.
        Ambiguous outcomes retain their hold. Reusing the idempotency key never repeats generation.
      parameters:
        - in: header
          name: Idempotency-Key
          required: true
          schema: { type: string, minLength: 1, maxLength: 255 }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [model]
              properties:
                provider: { $ref: "#/components/schemas/RegionProviderPreferences" }
                model: { type: string, description: Model ID from the catalog. }
                prompt: { type: string, maxLength: 1024, description: Required for generation and visual questions; limited to 1024 UTF-8 bytes. }
                image: { type: string, description: "Inline base64 raster; required for image editing, classification and visual questions." }
                labels:
                  type: array
                  minItems: 2
                  maxItems: 32
                  uniqueItems: true
                  items: { type: string, minLength: 1, maxLength: 80 }
                audio: { type: string, maxLength: 2600000, description: Inline mono 16 kHz PCM16 WAV data URL for voice conversion or isolation. }
                voice: { type: string, maxLength: 128, description: Provider voice ID for conversion. }
                duration_ms: { type: integer, minimum: 500, maximum: 60000, description: Output duration for ElevenLabs music or sound effects. }
                instrumental: { type: boolean, description: ElevenLabs music only. }
                loop: { type: boolean, description: ElevenLabs sound effects only. }
                dialogue:
                  type: array
                  minItems: 1
                  maxItems: 32
                  items:
                    type: object
                    additionalProperties: false
                    required: [text, voice]
                    properties:
                      text: { type: string, minLength: 1, maxLength: 2000 }
                      voice: { type: string, minLength: 1, maxLength: 128 }
                lyrics: { type: string, maxLength: 2000, description: Optional for supported non-ElevenLabs music models. }
                seed: { type: integer, minimum: 0, maximum: 2147483647 }
            example:
              model: deepinfra/black-forest-labs/FLUX-1-schnell
              prompt: A mountain lake at sunrise.
      responses:
        '200':
          description: A model-specific media, text or classification response, with an AudioLab request ID.
          content:
            application/json:
              schema:
                type: object
                required: [id, model]
                properties:
                  id: { type: string }
                  model: { type: string }
                  data: { description: Media encoded as b64_json with mime_type. }
                  text: { type: string }
                  results: { type: array, items: { type: object } }
                  duration: { type: number }
        '409': { description: This idempotency key already owns a request; generation is not repeated. }
        '422': { description: Invalid input or unsupported model controls. }
        '503': { description: "Provider access, funding, or reconciliation prevents completion." }
  /v1/inference/requests/{request_id}:
    get:
      tags: [Inference]
      summary: Get native inference status
      operationId: getInference
      description: Read owned request state and settlement without repeating inference. Requires requests.read.
      parameters:
        - in: path
          name: request_id
          required: true
          schema: { type: string }
      responses:
        '200': { description: "Owned request state, units and settlement." }
        '404': { description: Request not found for this API key. }
  /v1/videos/generations:
    post:
      tags: [Videos]
      summary: Generate a video
      operationId: createVideo
      description: |
        Create one text-to-video job using a direct xAI or Google Veo model. Requires videos.write,
        an Idempotency-Key, funded credits and VIDEOS_ENABLED. Also available under /api/v1.
        A committed request authorizes exactly one native generation. Repeat the same
        request and idempotency key to read its status without generating again.
        The response contains an AudioLab job ID. Poll its status, then download content.

        Prompts are limited to 1024 UTF-8 bytes. xAI admits 1–15 seconds at 480p square;
        Veo admits 4, 6 or 8 seconds at 720p 16:9. No input media, edits or provider options.
        A hold covers the reviewed provider maximum. xAI reports native monetary usage;
        Veo uses completed output seconds at the frozen published tariff. AudioLab charges 15% less than that tariff. An ambiguous dispatch retains its hold for reconciliation.
        Closing the client does not cancel provider generation. Content URLs expire;
        download promptly. Generation never restarts because content expired.
      parameters:
        - in: header
          name: Idempotency-Key
          required: true
          schema: { type: string, minLength: 1, maxLength: 255 }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [model, prompt, duration]
              properties:
                provider: { $ref: "#/components/schemas/RegionProviderPreferences" }
                model:
                  type: string
                  enum: [xai/grok-imagine-video, xai/grok-imagine-video-1.5, xai/grok-imagine-video-1.5-preview, xai/grok-imagine-video-1.5-2026-05-30]
                prompt: { type: string, minLength: 1, maxLength: 1024 }
                duration: { type: integer, minimum: 1, maximum: 15 }
                resolution: { type: string, const: 480p, default: 480p }
                aspect_ratio: { type: string, const: '1:1', default: '1:1' }
      responses:
        '200': { description: Previously completed video request; no regeneration. }
        '202': { description: Generation accepted; poll the Location header for status. }
        '400': { description: Missing or malformed idempotency key. }
        '401': { description: Invalid API key. }
        '402': { description: Insufficient credits for the hold. }
        '403': { description: 'Scope, privacy, key-expiry or spending restriction.' }
        '409': { description: Idempotency key conflicts with another request. }
        '422': { description: 'Unsupported model, media or generation options.' }
        '429': { description: Workspace concurrency or generation limit reached. }
        '503': { description: Disabled model or unresolved provider response; inspect job status. }
  /v1/videos/{request_id}:
    get:
      tags: [Videos]
      summary: Get video status
      operationId: getVideo
      description: |
        Requires the originating API key and requests.read. Status may advance a due
        native poll. It never starts another generation. Background recovery continues
        after disconnect or restart, including when new video generation is disabled.
      parameters:
        - in: path
          name: request_id
          required: true
          schema: { type: string }
      responses:
        '200': { description: 'Current state, progress, settled customer cost and authenticated content path.' }
        '401': { description: Invalid API key. }
        '403': { description: Missing requests.read scope. }
        '404': { description: Video request not found for this key. }
  /v1/videos/{request_id}/content:
    get:
      tags: [Videos]
      summary: Download a video
      operationId: getVideoContent
      description: Download a settled successful job using its originating API key and requests.read. No provider credentials are exposed.
      parameters:
        - in: path
          name: request_id
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Generated MP4 attachment.
          content:
            video/mp4:
              schema: { type: string, format: binary }
        '401': { description: Invalid API key. }
        '403': { description: Missing requests.read scope. }
        '404': { description: Video request not found for this key. }
        '409': { description: Video is not successfully settled. }
        '502': { description: Output expired or failed download validation; generation is not retried. }
  /v1/ocr:
    post:
      tags: [Documents]
      summary: Extract document text
      operationId: createOcr
      description: |
        Extract Markdown and page layout from a PDF or image using a pinned Mistral OCR model.
        Requires ocr.write, credits, an Idempotency-Key, and OCR_ENABLED.
        Also accepts /api/v1/ocr. Integration preview until staging certification.

        Send an inline base64 PDF up to 4 MiB or PNG/JPEG up to 1 MiB and 2048 pixels per side.
        Remote URLs and provider file IDs are not accepted. pages selects 1–32 unique, increasing,
        zero-based page indexes (0–999); images require [0]. The hold covers the selected page count.
        Final cost uses the native pages_processed count at the published rate minus 15%.
        Output is returned once. Reusing the idempotency key returns status without another provider call.
        Unknown provider outcomes retain their hold until reconciled.
      parameters:
        - in: header
          name: Idempotency-Key
          required: true
          schema: { type: string, minLength: 1, maxLength: 255 }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [model, document, pages]
              properties:
                provider: { $ref: "#/components/schemas/RegionProviderPreferences" }
                model:
                  type: string
                  enum: [mistral/mistral-ocr-2512, mistral/mistral-ocr-3-0, mistral/mistral-ocr-3, mistral/mistral-ocr-4-0, mistral/mistral-ocr-4-1, mistral/mistral-ocr-4, mistral/mistral-ocr-latest]
                document:
                  oneOf:
                    - type: object
                      additionalProperties: false
                      required: [type, document_url]
                      properties:
                        type: { type: string, const: document_url }
                        document_url: { type: string, description: Inline application/pdf base64 data URL. }
                    - type: object
                      additionalProperties: false
                      required: [type, image_url]
                      properties:
                        type: { type: string, const: image_url }
                        image_url: { type: string, description: Inline image/png or image/jpeg base64 data URL. }
                pages:
                  type: array
                  minItems: 1
                  maxItems: 32
                  uniqueItems: true
                  items: { type: integer, minimum: 0, maximum: 999 }
            example:
              model: mistral/mistral-ocr-4-1
              document: { type: document_url, document_url: 'data:application/pdf;base64,BASE64_PDF' }
              pages: [0]
      responses:
        '200':
          description: Extracted pages and native usage.
          content:
            application/json:
              schema:
                type: object
                required: [id, model, pages, usage_info]
                properties:
                  id: { type: string }
                  model: { type: string }
                  pages:
                    type: array
                    items:
                      type: object
                      properties:
                        index: { type: integer }
                        markdown: { type: string }
                        dimensions: { type: object }
                        images: { type: array, items: { type: object } }
                  usage_info:
                    type: object
                    properties:
                      pages_processed: { type: integer }
                      doc_size_bytes: { type: integer }
        '409': { description: Existing idempotent request; native inference is not repeated. }
        '422': { description: Unsupported or invalid document or page selection. }
        '503': { description: Disabled route or unresolved provider outcome. }
        default: { $ref: '#/components/responses/ErrorResponse' }
  /v1/ocr/requests/{request_id}:
    get:
      tags: [Documents]
      summary: Get OCR request status
      operationId: getOcr
      description: Requires requests.read and the API key that created this request. No document contents are retained for replay.
      parameters:
        - in: path
          name: request_id
          required: true
          schema: { type: string }
      responses:
        '200': { description: Durable processing and settlement status. }
        '404': { description: Request not found for this key and workspace. }
        default: { $ref: '#/components/responses/ErrorResponse' }
  /v1/rerank:
    post:
      tags: [Reranking]
      summary: Rank document relevance
      operationId: createRerank
      description: |
        Rank up to eight short documents with a direct DeepInfra reranker. Requires
        rerank.write, credits, Idempotency-Key and RERANK_ENABLED. Each query and
        document is limited to 256 ASCII characters, excluding angle brackets.
        A full native sequence is funded for each query/document pair. Only the
        native input-token counter determines the charge. Integration preview
        until deployed staging acceptance; also accepts /api/v1/rerank.
      parameters:
        - in: header
          name: Idempotency-Key
          required: true
          schema: { type: string, minLength: 1, maxLength: 255 }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [model, query, documents]
              properties:
                provider: { $ref: "#/components/schemas/RegionProviderPreferences" }
                model: { type: string }
                query: { type: string, minLength: 1, maxLength: 256 }
                documents: { type: array, minItems: 1, maxItems: 8, items: { type: string, minLength: 1, maxLength: 256 } }
                top_n: { type: integer, minimum: 1, maximum: 8, description: Cannot exceed the supplied document count. }
      responses:
        '200':
          description: Sorted relevance scores and complete native token usage.
          content:
            application/json:
              schema:
                type: object
                required: [id, model, results, usage]
                properties:
                  id: { type: string }
                  model: { type: string }
                  results:
                    type: array
                    items:
                      type: object
                      required: [index, relevance_score]
                      properties:
                        index: { type: integer, minimum: 0 }
                        relevance_score: { type: number, minimum: 0, maximum: 1 }
                  usage:
                    type: object
                    properties:
                      input_tokens: { type: integer }
        '409': { description: Existing idempotent request; native inference is not repeated. }
        '422': { description: Provider contract unavailable or unsupported request. }
        '503': { description: Disabled route or unresolved provider outcome. }
        default: { $ref: '#/components/responses/ErrorResponse' }
  /v1/rerank/requests/{request_id}:
    get:
      tags: [Reranking]
      summary: Get rerank request status
      operationId: getRerank
      description: Requires requests.read and the API key that created the request. Query and document text are not retained for replay.
      parameters:
        - in: path
          name: request_id
          required: true
          schema: { type: string }
      responses:
        '200': { description: Durable processing and settlement status. }
        '404': { description: Request not found for this key and workspace. }
        default: { $ref: '#/components/responses/ErrorResponse' }
  /v1/images/generations:
    post:
      tags: [Images]
      summary: Generate an image
      operationId: createImage
      description: |
        Generate one square 1k image through a direct xAI, fal or MiniMax endpoint. Requires
        an explicitly scoped images.write API key, credits, and an enabled
        IMAGES_ENABLED deployment. Also accepts /api/v1/images/generations.
        Integration preview until deployed staging acceptance.

        Idempotency-Key is required. One durable request authorizes one native
        generation; duplicate submissions return status and never regenerate.
        Output bytes are returned once and are not retained for replay.
        Native monetary usage determines the charge. A temporary wallet hold
        covers the reviewed maximum; unresolved requests retain their hold.
        A known failed output records provider expense without a customer charge.

        The initial profile supports a 1024-byte UTF-8 prompt, n=1, resolution=1k,
        aspect_ratio=1:1 and response_format=b64_json. Image 2.0 uses low quality.
        Direct fal FLUX profiles generate one 1024x1024 PNG with the safety
        checker enabled. Returned native billable units determine the charge
        at the published megapixel rate; no output URL is fetched.
        MiniMax image-01 uses one 1024x1024 base64 image, prompt optimization off,
        and its native successful-image count at the published per-image rate.
        Editing, input images, provider storage and arbitrary controls are excluded.
        Workspace limits are 20 requests per minute, four pending requests and
        $5 of provider expense/exposure per day; older unresolved holds still count.
      parameters:
        - in: header
          name: Idempotency-Key
          required: true
          schema: { type: string, minLength: 1, maxLength: 255 }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [model, prompt]
              properties:
                provider: { $ref: "#/components/schemas/RegionProviderPreferences" }
                model:
                  type: string
                  enum: [xai/grok-imagine-image, xai/grok-imagine-image-2026-03-02, xai/grok-imagine-image-2.0, xai/grok-imagine-image-quality, xai/grok-imagine-image-quality-20260403, xai/grok-imagine-image-quality-latest, xai/grok-imagine-image-pro, fal/fal-ai/flux/schnell, fal/fal-ai/flux/dev, fal/fal-ai/flux-1/krea, fal/fal-ai/flux-2, minimax/image-01]
                prompt: { type: string, minLength: 1, maxLength: 1024 }
                n: { type: integer, const: 1, default: 1 }
                resolution: { type: string, const: 1k, default: 1k }
                aspect_ratio: { type: string, const: '1:1', default: '1:1' }
                response_format: { type: string, const: b64_json, default: b64_json }
      responses:
        '200':
          description: Validated image with committed accounting.
          headers:
            x-request-id: { schema: { type: string }, description: AudioLab image request ID. }
          content:
            application/json:
              schema:
                type: object
                required: [created, model, data, usage]
                properties:
                  created: { type: integer }
                  model: { type: string }
                  data:
                    type: array
                    minItems: 1
                    maxItems: 1
                    items:
                      type: object
                      required: [b64_json, mime_type]
                      properties:
                        b64_json: { type: string, contentEncoding: base64 }
                        mime_type: { type: string, enum: [image/png, image/jpeg, image/webp] }
                  usage:
                    type: object
                    properties:
                      images: { type: integer, const: 1 }
                      cost: { type: string, description: Customer charge in USD. }
        '400': { description: Missing or invalid idempotency key. }
        '401': { description: Invalid API key. }
        '402': { description: Insufficient credits for the reviewed hold. }
        '403': { description: 'Scope, privacy or spend-limit restriction.' }
        '409': { description: Request already exists or idempotency ownership conflicts; no regeneration. }
        '422': { description: Invalid or unsupported model or input. }
        '429': { description: Workspace generation limit reached. }
        '502': { description: Known provider expense but unusable output; customer charge waived. }
        '503': { description: 'Disabled contract, unresolved expense or provider cap review; inspect request status.' }
  /v1/images/requests/{request_id}:
    get:
      tags: [Images]
      summary: Get image request status
      operationId: getImageRequest
      description: |
        Read the durable state with the originating API key and requests.read
        scope. Does not dispatch inference or expose provider credentials, raw
        errors, prompts, images or native costs. Also available under /api/v1.
      parameters:
        - in: path
          name: request_id
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Owned image request metadata.
          content:
            application/json:
              schema:
                type: object
                properties:
                  id: { type: string }
                  model: { type: string }
                  state: { type: string, enum: [dispatching, unknown, succeeded, failed, review] }
                  settled: { type: boolean }
                  cost: { type: [string, 'null'], description: Settled customer USD; null while unresolved. }
                  created_at: { type: string, format: date-time }
        '401': { description: Invalid API key. }
        '403': { description: Missing requests.read scope. }
        '404': { description: Request does not belong to this API key. }
  /v1/moderations:
    post:
      tags: [Moderation]
      summary: Classify text or images
      operationId: createModeration
      description: |
        Classify text through an exact OpenAI or Mistral moderation model.
        OpenAI also accepts one inline image, optionally accompanied by one text part.
        Requires an API key with the opt-in moderation.write scope and an enabled
        direct-core Router deployment. Also accepts /api/v1/moderations.
        Integration preview until staging acceptance and release.

        These reviewed endpoints are free; no wallet debit or token usage is
        fabricated. Workspace limits are 60 requests per minute, 1000 per day,
        and four concurrent calls. Privacy settings apply before dispatch.
        Inputs and classifications are not persisted; request metadata appears in Logs.

        Text is limited to 16384 UTF-8 bytes. Images must be inline base64 PNG,
        JPEG, or WebP, at most 1 MiB and 2048 by 2048 pixels. Remote URLs,
        animations, batches, streaming, automatic retry, and Idempotency-Key
        are unsupported. The JSON body limit is 1500000 bytes.
        OpenAI returns flagged, nullable illicit categories, confidence scores
        and category_applied_input_types. Mistral returns its own category names
        and scores, without a synthesized flagged field. A flagged classification
        is a successful response; provider errors never mean content is safe.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [model, input]
              properties:
                provider: { $ref: "#/components/schemas/RegionProviderPreferences" }
                model:
                  type: string
                  enum: [openai/omni-moderation-2024-09-26, mistral/mistral-moderation-2603]
                input:
                  oneOf:
                    - { type: string, minLength: 1, maxLength: 16384 }
                    - type: array
                      minItems: 1
                      maxItems: 2
                      description: OpenAI only. At most one text part and one image part.
                      items:
                        oneOf:
                          - type: object
                            additionalProperties: false
                            required: [type, text]
                            properties:
                              type: { type: string, const: text }
                              text: { type: string, minLength: 1, maxLength: 16384 }
                          - type: object
                            additionalProperties: false
                            required: [type, image_url]
                            properties:
                              type: { type: string, const: image_url }
                              image_url:
                                type: object
                                additionalProperties: false
                                required: [url]
                                properties:
                                  url: { type: string, maxLength: 1398200, description: "Inline base64 PNG, JPEG, or WebP data URL." }
      responses:
        '200':
          description: Native provider classification. Costs are zero.
          content:
            application/json:
              schema:
                type: object
                required: [id, model, results]
                properties:
                  id: { type: string }
                  model: { type: string }
                  results:
                    type: array
                    minItems: 1
                    maxItems: 1
                    items:
                      type: object
                      required: [categories, category_scores]
                      properties:
                        flagged: { type: boolean, description: OpenAI only. }
                        categories:
                          type: object
                          additionalProperties: { type: [boolean, 'null'] }
                        category_scores:
                          type: object
                          additionalProperties: { type: number, minimum: 0, maximum: 1 }
                        category_applied_input_types:
                          type: object
                          additionalProperties:
                            type: array
                            items: { type: string, enum: [text] }
        '401': { description: Invalid API key. }
        '403': { description: "Scope, workspace, key lifetime or privacy restriction." }
        '413': { description: Request body exceeds its limit. }
        '422': { description: Invalid request or provider-rejected input. }
        '429': { description: Workspace or provider rate limit. }
        '502': { description: Invalid or failed provider response. }
        '503': { description: Model is not enabled or journal is unavailable. }
        '504': { description: Provider exceeded the complete response deadline. }
  /v1/chat/completions:
    post:
      tags: [Audio]
      summary: Generate audio with Chat Completions
      operationId: createAudioChat
      description: |
        This entry documents the dedicated openai/gpt-audio-1.5-chat profile on the
        enabled LLM gateway. It sends one plain user string, up to 4096 UTF-8 bytes,
        to native gpt-audio-1.5 and returns text plus alloy WAV audio in JSON.
        Requires a funded workspace and a key with llm.write. Also available under /api/v1.

        Set modalities to ["text", "audio"] and audio to {"voice":"alloy","format":"wav"}.
        The effective output cap is fixed at 16384. An explicit max_tokens or
        max_completion_tokens must equal 16384; omission uses that value. The example
        sets max_tokens explicitly. Streaming, input audio, conversation history,
        tools, plugins, fallbacks, custom voices and sampling overrides are rejected
        for this profile. These audio options do not apply to other Chat models.

        The response preserves choices[0].message.audio.id, data, expires_at and
        transcript. data is base64-encoded WAV; expires_at is the native Unix timestamp.
        Inclusive prompt/completion usage includes separate audio and text token
        counters. Native token expense minus 15% determines the charge. A hold covers
        the full reviewed model bounds before one native request; missing or invalid
        output/usage retains the hold without a retry. This profile remains subject
        to deployment, credential and admission gates.
      parameters:
        - in: header
          name: Idempotency-Key
          description: Use a stable unique key; a duplicate returns 409 without another native request.
          schema: { type: string, maxLength: 255 }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [model, messages, modalities, audio]
              properties:
                model: { type: string, const: openai/gpt-audio-1.5-chat }
                messages:
                  type: array
                  minItems: 1
                  maxItems: 1
                  items:
                    type: object
                    additionalProperties: false
                    required: [role, content]
                    properties:
                      role: { type: string, const: user }
                      content:
                        type: string
                        minLength: 1
                        maxLength: 4096
                        description: Nonblank plain text, at most 4096 UTF-8 bytes.
                max_tokens: { type: integer, const: 16384, default: 16384 }
                max_completion_tokens:
                  type: integer
                  const: 16384
                  description: Alternative name for the same fixed output cap.
                stream: { type: boolean, const: false, default: false }
                modalities:
                  type: array
                  const: [text, audio]
                  items: { type: string, enum: [text, audio] }
                audio:
                  type: object
                  additionalProperties: false
                  required: [voice, format]
                  properties:
                    voice: { type: string, const: alloy }
                    format: { type: string, const: wav }
              example:
                model: openai/gpt-audio-1.5-chat
                messages: [{ role: user, content: Say hello. }]
                max_tokens: 16384
                stream: false
                modalities: [text, audio]
                audio: { voice: alloy, format: wav }
      responses:
        '200':
          description: Completed JSON Chat response with native audio and validated token usage.
          content:
            application/json:
              schema:
                type: object
                required: [choices, usage]
                properties:
                  choices:
                    type: array
                    minItems: 1
                    maxItems: 1
                    items:
                      type: object
                      required: [message]
                      properties:
                        message:
                          type: object
                          required: [role, audio]
                          properties:
                            role: { type: string, const: assistant }
                            content: { type: [string, 'null'] }
                            audio:
                              type: object
                              required: [id, data, expires_at, transcript]
                              properties:
                                id: { type: string }
                                data: { type: string, contentEncoding: base64, description: Complete WAV audio. }
                                expires_at: { type: integer, description: Native expiry as Unix seconds. }
                                transcript: { type: string }
                  usage:
                    type: object
                    description: Inclusive native token totals and prompt/completion audio and text subsets.
        '400': { description: Invalid or unsupported audio Chat request. }
        '401': { description: Invalid gateway credential. }
        '402': { description: Insufficient credits for the full reservation. }
        '403': { description: Key or workspace restrictions. }
        '409': { description: Duplicate idempotency key; provider inference is not repeated. }
        '429': { description: Rate or concurrency limit. }
        '503': { description: Disabled admission or unresolved native output/usage; unknown attempts retain their hold. }
  /v1/embeddings:
    post:
      tags: [Embeddings]
      summary: Create text embeddings
      operationId: createEmbeddings
      description: |
        Synchronous direct OpenAI and Google Gemini text embeddings. Requires an enabled LLM
        gateway, a funded workspace, and a key with llm.write. Also accepts
        /api/v1/embeddings on the LLM gateway. Integration preview until the
        gateway and API edge are released to the selected environment.

        OpenAI accepts up to 32 text inputs, 8192 tokens per input, and 65536 tokens in
        total. Gemini Embedding 2 accepts one text input, at most 16384 UTF-8 bytes
        and 8192 tokens, verified through its native countTokens endpoint before
        inference. Gemini requires the direct-core admission profile. Request bodies are limited to 1 MiB. Token-ID arrays are not yet
        supported. Vectors use float arrays or little-endian float32 base64;
        official OpenAI SDK defaults are supported. Dimensions may be shortened
        for OpenAI 3-series and Gemini Embedding 2. No media, streaming, native Batch API, or automatic retry.

        Customer cost is native input cost minus 15%. Fractions smaller than a
        wallet microdollar accumulate across the workspace, including across API
        keys; key budgets use exact accrued spend. No per-request minimum fee.
        Unknown usage retains its spending hold for reconciliation.
      parameters:
        - in: header
          name: Idempotency-Key
          description: Duplicate requests return 409 and never repeat provider inference.
          schema: { type: string, maxLength: 255 }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [model, input]
              properties:
                provider: { $ref: "#/components/schemas/RegionProviderPreferences" }
                model:
                  type: string
                  description: Bare native model IDs are also accepted.
                  enum: [openai/text-embedding-3-small, openai/text-embedding-3-large, openai/text-embedding-ada-002, google/gemini-embedding-2, text-embedding-3-small, text-embedding-3-large, text-embedding-ada-002, gemini-embedding-2]
                input:
                  oneOf:
                    - { type: string, minLength: 1 }
                    - type: array
                      minItems: 1
                      maxItems: 32
                      items: { type: string, minLength: 1 }
                encoding_format:
                  type: string
                  enum: [float, base64]
                  default: float
                dimensions:
                  type: integer
                  minimum: 1
                  maximum: 3072
                  description: 3-small permits 1–1536; 3-large permits 1–3072; Gemini Embedding 2 permits 128–3072. Omit for ada-002 (1536).
                user: { type: string, maxLength: 256 }
            example:
              model: openai/text-embedding-3-small
              input: [Build search with AudioLab.]
              dimensions: 64
              encoding_format: float
      responses:
        '200':
          description: Validated vectors and authoritative native input usage, durably settled.
          content:
            application/json:
              schema:
                type: object
                required: [object, model, data, usage]
                properties:
                  object: { type: string, const: list }
                  model: { type: string }
                  data:
                    type: array
                    items:
                      type: object
                      required: [object, index, embedding]
                      properties:
                        object: { type: string, const: embedding }
                        index: { type: integer, minimum: 0 }
                        embedding:
                          oneOf:
                            - { type: array, items: { type: number } }
                            - { type: string, contentEncoding: base64 }
                  usage:
                    type: object
                    required: [prompt_tokens, total_tokens]
                    properties:
                      prompt_tokens: { type: integer, minimum: 1 }
                      total_tokens: { type: integer, minimum: 1 }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '402': { $ref: '#/components/responses/PaymentRequired' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { description: Request already has a reservation; provider inference is not repeated. }
        '413': { description: Request exceeds the 1 MiB body limit. }
        '429': { $ref: '#/components/responses/RateLimited' }
        '502': { description: Invalid vectors or native provider error. }
        '503': { $ref: '#/components/responses/Unavailable' }

  /healthz:
    get:
      tags:
        - Health
      summary: Check service health.
      operationId: getHealth
      security: []
      responses:
        "200":
          description: Service is healthy.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Health"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /mcp:
    post:
      tags:
        - MCP
      summary: Send an MCP message.
      description: |
        The hosted Model Context Protocol server, over Streamable HTTP (protocol 2025-11-25 and earlier versions).
        It is stateless: no Mcp-Session-Id is issued, and every request carries the API key as a bearer token.
        Send one JSON-RPC message per request with `Accept: application/json, text/event-stream`; requests are
        answered as a server-sent event stream with keep-alive comments, notifications with 202.

        Tools run as the key through the same API routes as direct requests, with the same scopes, holds, billing and
        errors. `tools/list` returns only the tools the key's scopes allow. A failed tool call is a tool result with
        `isError: true` whose `structuredContent.error` is the API's error (`status`, `code`, `message`, `param`,
        `request_id`). Each MCP request counts once against the key's rate limits. Dashboard sessions and browser
        origins are refused. GET and DELETE answer 405: there is no server event stream and no session to end.
        See the MCP Server guide (/docs/mcp) for the tools and their costs.
      operationId: postMcpMessage
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/McpMessage"
      responses:
        "200":
          description: The JSON-RPC response, as a server-sent event stream.
          content:
            text/event-stream:
              schema:
                type: string
        "202":
          description: A notification or response from the client was accepted.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "406":
          description: The Accept header does not allow both application/json and text/event-stream.
        "413":
          description: "The body exceeds 39,146,840 bytes: a 25 MiB audio file as base64, plus 4 MiB for the rest of the message."
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /mcp/files/{workspace_id}/{file}:
    get:
      tags:
        - MCP
      summary: Download a file an MCP tool made.
      description: Speech from the MCP `text_to_speech` tool, at the signed link in its result. The link needs no key and stops working after one hour.
      operationId: getMcpFile
      security: []
      parameters:
        - name: workspace_id
          in: path
          required: true
          schema:
            type: string
        - name: file
          in: path
          required: true
          schema:
            type: string
          example: req_0a1b2c3d4e5f6a7b.mp3
        - name: expires
          in: query
          required: true
          description: Unix time the link expires.
          schema:
            type: integer
        - name: signature
          in: query
          required: true
          schema:
            type: string
      responses:
        "200":
          description: The file.
          content:
            audio/*:
              schema:
                type: string
                format: binary
        "404":
          description: The link is invalid or has expired.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          $ref: "#/components/responses/RateLimited"

  /v1/files:
    post:
      tags: [Batches]
      summary: Upload a file
      operationId: uploadFile
      description: |
        Upload a JSONL batch input file as multipart/form-data with purpose=batch and a file
        field. Requires an API key with llm.write. Also available under /api/v1. Files are
        limited to 50 MB and are kept until you delete them. Refused under zero data
        retention, because a batch stores its requests and results.

        Each line is {"custom_id", "method": "POST", "url", "body"}: custom_id is unique in
        the file, url is the batch's endpoint, and body is the request that endpoint takes.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [purpose, file]
              properties:
                purpose: { type: string, const: batch }
                file: { type: string, format: binary, description: 'JSONL requests, at most 50 MB.' }
      responses:
        '200':
          description: The stored file.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/LlmFile' }
        '400': { description: 'Missing file, empty file or another purpose.' }
        '401': { description: Invalid API key. }
        '403': { description: 'Missing llm.write scope, or zero data retention is on.' }
        '413': { description: File larger than 50 MB. }
    get:
      tags: [Batches]
      summary: List files
      operationId: listFiles
      description: Files in the key's workspace, newest first. Requires llm.write. Batch outputs have purpose batch_output.
      parameters:
        - { in: query, name: purpose, schema: { type: string, enum: [batch, batch_output] } }
        - { in: query, name: limit, schema: { type: integer, minimum: 1, maximum: 10000, default: 100 } }
        - { in: query, name: after, description: File ID to list after., schema: { type: string } }
      responses:
        '200':
          description: One page of files.
          content:
            application/json:
              schema:
                type: object
                required: [object, data, has_more]
                properties:
                  object: { type: string, const: list }
                  data: { type: array, items: { $ref: '#/components/schemas/LlmFile' } }
                  has_more: { type: boolean }
        '401': { description: Invalid API key. }
        '403': { description: Missing llm.write scope. }
  /v1/files/{file_id}:
    parameters:
      - { in: path, name: file_id, required: true, schema: { type: string } }
    get:
      tags: [Batches]
      summary: Get a file
      operationId: getFile
      description: A file's metadata. Requires llm.write.
      responses:
        '200':
          description: The file.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/LlmFile' }
        '401': { description: Invalid API key. }
        '404': { description: File not found in this workspace. }
    delete:
      tags: [Batches]
      summary: Delete a file
      operationId: deleteFile
      description: Delete a file and its stored content. A batch already created from it keeps running. Requires llm.write.
      responses:
        '200':
          description: Deleted.
          content:
            application/json:
              schema:
                type: object
                required: [id, object, deleted]
                properties:
                  id: { type: string }
                  object: { type: string, const: file }
                  deleted: { type: boolean, const: true }
        '401': { description: Invalid API key. }
        '404': { description: File not found in this workspace. }
  /v1/files/{file_id}/content:
    get:
      tags: [Batches]
      summary: Get file content
      operationId: getFileContent
      description: |
        Download a file's JSONL. A batch output file has one line per finished request:
        {"id", "custom_id", "response": {"status_code", "request_id", "body"}, "error": null}.
        The error file has failed requests, with a response or an error {"code", "message"}.
        Requires llm.write.
      parameters:
        - { in: path, name: file_id, required: true, schema: { type: string } }
      responses:
        '200':
          description: JSONL content.
          content:
            application/jsonl:
              schema: { type: string }
        '401': { description: Invalid API key. }
        '404': { description: File not found in this workspace. }
  /v1/batches:
    post:
      tags: [Batches]
      summary: Create a batch
      operationId: createBatch
      description: |
        Run every request in an uploaded file within 24 hours. Requires llm.write and a
        funded workspace. Also available under /api/v1. A file holds at most 10,000
        requests of up to 1 MB each, all for one endpoint, each naming one model:
        routers, fallback models, plugins, web search and streaming are refused.

        Each request is its own funded request with its own hold, run as the key that
        created the batch. Chat Completions requests for OpenAI, Gemini and Anthropic models
        with a reviewed batch price run as provider batches and are charged at that
        provider's batch price (half the standard price); every other request runs like a
        live call at its normal price. Usage and cost appear on each result. A batch whose
        requests could not all be held at once is refused with 402 before anything runs.
        Requests a provider errors, cancels or expires are not charged.

        Refused under zero data retention: a batch stores its requests and results.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [input_file_id, endpoint, completion_window]
              properties:
                input_file_id: { type: string }
                endpoint: { type: string, enum: [/v1/chat/completions, /v1/responses, /v1/messages, /v1/embeddings] }
                completion_window: { type: string, const: 24h }
                metadata:
                  type: object
                  maxProperties: 16
                  additionalProperties: { type: string, maxLength: 512 }
            example:
              input_file_id: file-abc123
              endpoint: /v1/chat/completions
              completion_window: 24h
      responses:
        '200':
          description: The batch, in progress.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/LlmBatch' }
        '400': { description: 'Invalid file line, named with its line number, or an unsupported model or endpoint.' }
        '401': { description: Invalid API key. }
        '402': { description: The workspace or key cannot hold every request at once. }
        '403': { description: 'Missing llm.write scope, or zero data retention is on.' }
        '404': { description: Input file not found. }
    get:
      tags: [Batches]
      summary: List batches
      operationId: listBatches
      description: Batches in the key's workspace, newest first. Requires llm.write.
      parameters:
        - { in: query, name: limit, schema: { type: integer, minimum: 1, maximum: 100, default: 20 } }
        - { in: query, name: after, description: Batch ID to list after., schema: { type: string } }
      responses:
        '200':
          description: One page of batches.
          content:
            application/json:
              schema:
                type: object
                required: [object, data, has_more]
                properties:
                  object: { type: string, const: list }
                  data: { type: array, items: { $ref: '#/components/schemas/LlmBatch' } }
                  first_id: { type: [string, 'null'] }
                  last_id: { type: [string, 'null'] }
                  has_more: { type: boolean }
        '401': { description: Invalid API key. }
  /v1/batches/{batch_id}:
    get:
      tags: [Batches]
      summary: Get a batch
      operationId: getBatch
      description: A batch's status and counts. Once it is completed, cancelled or expired, output_file_id and error_file_id name its result files. Requires llm.write.
      parameters:
        - { in: path, name: batch_id, required: true, schema: { type: string } }
      responses:
        '200':
          description: The batch.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/LlmBatch' }
        '401': { description: Invalid API key. }
        '404': { description: Batch not found in this workspace. }
  /v1/batches/{batch_id}/cancel:
    post:
      tags: [Batches]
      summary: Cancel a batch
      operationId: cancelBatch
      description: |
        Stop a batch. Requests that have not started never run and are not charged;
        provider batches already submitted are cancelled with the provider, and whatever
        finished before that is kept and charged. The batch moves to cancelling, then
        cancelled with its result files. Requires llm.write.
      parameters:
        - { in: path, name: batch_id, required: true, schema: { type: string } }
      responses:
        '200':
          description: The batch, cancelling or already finished.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/LlmBatch' }
        '401': { description: Invalid API key. }
        '404': { description: Batch not found in this workspace. }

  /v1/auth/logout:
    post:
      tags:
        - Auth
      summary: Revoke the current dashboard session and clear its cookie.
      operationId: createLogout
      security: []
      responses:
        "204":
          description: Session cleared.
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/auth/sso:
    post:
      tags:
        - Auth
      summary: Exchange a trusted AudioLab identity ticket for a dashboard session.
      description: Internal server-to-server endpoint used by the AudioLab web application.
      operationId: createAudioLabSession
      security: []
      parameters:
        - name: Authorization
          in: header
          required: true
          schema:
            type: string
            pattern: '^AudioLabSSO\\s+.+'
      responses:
        "204":
          description: Router session cookie created.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/auth/oauth/providers:
    get:
      tags:
        - Auth
      summary: List dashboard OAuth providers.
      operationId: listOAuthProviders
      security: []
      responses:
        "200":
          description: OAuth provider availability.
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/OAuthProviderStatus"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/auth/oauth/{provider}/start:
    parameters:
      - $ref: "#/components/parameters/OAuthProvider"
    get:
      tags:
        - Auth
      summary: Start dashboard OAuth sign-in.
      operationId: startOAuth
      security: []
      responses:
        "302":
          description: Redirect to the upstream OAuth provider.
          headers:
            Location:
              schema:
                type: string
              description: Provider authorization URL.
        "400":
          $ref: "#/components/responses/BadRequest"
        "503":
          $ref: "#/components/responses/Unavailable"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/auth/oauth/{provider}/callback:
    parameters:
      - $ref: "#/components/parameters/OAuthProvider"
    get:
      tags:
        - Auth
      summary: Complete dashboard OAuth sign-in.
      operationId: completeOAuth
      security: []
      parameters:
        - name: code
          in: query
          required: false
          schema:
            type: string
        - name: state
          in: query
          required: false
          schema:
            type: string
        - name: error
          in: query
          required: false
          schema:
            type: string
        - name: error_description
          in: query
          required: false
          schema:
            type: string
      responses:
        "302":
          description: Redirect back to the dashboard with a session or OAuth error in the URL fragment.
          headers:
            Location:
              schema:
                type: string
              description: Dashboard URL.
        "400":
          $ref: "#/components/responses/BadRequest"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/me:
    get:
      tags:
        - Auth
      summary: Get the authenticated principal.
      operationId: getMe
      responses:
        "200":
          description: Current user session or API key context.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MeRes"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/workspaces:
    get:
      tags:
        - Workspaces
      summary: List workspaces for the signed-in user.
      operationId: listWorkspaces
      responses:
        "200":
          description: Workspace list.
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Workspace"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"
    post:
      tags:
        - Workspaces
      summary: Create a workspace for the signed-in user.
      operationId: createWorkspace
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WorkspaceReq"
      responses:
        "201":
          description: Workspace created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Workspace"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/workspaces/{workspace_id}:
    parameters:
      - $ref: "#/components/parameters/WorkspacePathId"
    get:
      tags:
        - Workspaces
      summary: Get a workspace for the signed-in user.
      operationId: getWorkspace
      responses:
        "200":
          description: Workspace metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Workspace"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/workspaces/{workspace_id}/guardrails:
    parameters:
      - $ref: "#/components/parameters/WorkspacePathId"
    get:
      tags:
        - Workspaces
      summary: Get a workspace's budgets and access policy.
      operationId: getWorkspaceGuardrails
      description: Daily, weekly and monthly budgets with this period's spend and when each period resets, and the access policy. Any member's dashboard session can read them. Periods are UTC calendar days, weeks starting Monday, and months.
      responses:
        "200":
          description: Budgets, spend and access policy.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkspaceGuardrails"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
    patch:
      tags:
        - Workspaces
      summary: Set a workspace's budgets or access policy.
      operationId: updateWorkspaceGuardrails
      description: Owners and admins only. A budget set to a number replaces that period's limit, null removes it, and periods left out stay as they are. access_policy replaces the whole policy; an empty object or null removes it. An invalid rule is refused with 400 and param naming it, such as access_policy.allowed_models.1.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GuardrailsPatch"
            examples:
              guardrails:
                value:
                  budgets: { daily: 25, monthly: 500 }
                  access_policy: { allowed_models: ["anthropic/*", "openai/gpt-5*"], blocked_providers: ["xai"], blocked_products: ["videos"] }
      responses:
        "200":
          description: Saved budgets, spend and access policy.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkspaceGuardrails"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

  /v1/account/privacy:
    get:
      tags: [Workspaces]
      summary: Get the signed-in account's privacy restrictions.
      operationId: getAccountPrivacy
      responses:
        "200":
          description: Account privacy and maximum key lifetime.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AccountPrivacy"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
    patch:
      tags: [Workspaces]
      summary: Update the signed-in account's privacy restrictions.
      operationId: updateAccountPrivacy
      description: Applies to owned workspaces and keys created by this account. Workspace restrictions cannot loosen account rules. Requires a dashboard session; API keys cannot change account policy.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AccountPrivacy"
      responses:
        "200":
          description: Saved policy. Unspecified fields remain unchanged.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AccountPrivacy"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /v1/account/key-policy:
    get:
      tags: [Keys]
      summary: Get the effective lifetime cap for new keys in a workspace.
      operationId: getKeyPolicy
      parameters:
        - name: workspace_id
          in: query
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Strictest owner and creator lifetime restriction.
          content:
            application/json:
              schema:
                type: object
                required: [max_key_days]
                properties:
                  max_key_days:
                    type: [integer, "null"]
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

  /v1/settings:
    get:
      tags:
        - Workspaces
      summary: Get workspace settings merged with defaults.
      operationId: getWorkspaceSettings
      parameters:
        - name: workspace_id
          in: query
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Workspace settings.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkspaceSettings"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/InternalError"
    patch:
      tags:
        - Workspaces
      summary: Merge one or more workspace settings sections.
      operationId: updateWorkspaceSettings
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: "#/components/schemas/WorkspaceSettings"
                - type: object
                  required:
                    - workspace_id
                  properties:
                    workspace_id:
                      type: string
      responses:
        "200":
          description: Updated workspace settings.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkspaceSettings"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/InternalError"
  /v1/settings/data-region:
    get:
      tags: [Workspaces]
      summary: Get the workspace's data region.
      operationId: getDataRegion
      description: Requires a dashboard session with read access to the workspace. A region of null means requests route to any region.
      parameters:
        - name: workspace_id
          in: query
          required: true
          schema:
            type: string
      responses:
        "200":
          description: The workspace's data region.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DataRegionSetting"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
    patch:
      tags: [Workspaces]
      summary: Set or clear the workspace's data region.
      operationId: updateDataRegion
      description: "Requires a dashboard session with write access to the workspace. With mode only, every request from the workspace routes only to endpoints documented as processing in the region, and a request no such endpoint can serve is refused with 400 data_region_unavailable. With mode prefer, those endpoints are tried first. A request's provider.data_region can narrow the region but never widen it. Send region null to clear it."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [workspace_id, region]
              properties:
                workspace_id:
                  type: string
                region:
                  type: [string, "null"]
                  description: us, eu or a documented country code; null clears it.
                  examples: [eu]
                mode:
                  type: string
                  enum: [only, prefer]
                  default: only
      responses:
        "200":
          description: The saved data region.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DataRegionSetting"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

  "/v1/routers/catalog":
    get:
      tags:
        - "Routers"
      summary: "List workspace-authorized LLM models."
      description: "Requires an active workspace membership with owner, admin, developer, or viewer role. Billing-only members and inference API keys are rejected. workspace_id is always required; a router ID from a different authorized workspace returns 404. Lists exact catalog IDs, protocols, capabilities, and current eligibility reasons. This endpoint evaluates each model independently of any draft. The default resolver returns an empty list and null catalog_revision until an approved, enabled capability and positive-price catalog is configured. A nonempty list alone does not mean inference is enabled."
      operationId: "getRouterCatalog"
      security:
        -
          routerSession: []
        -
          routerCookie: []
        -
          routerDevCookie: []
      parameters:
        -
          "$ref": "#/components/parameters/RouterWorkspace"
      responses:
        "200":
          description: "Operation completed."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/RouterCatalog"
        "400":
          "$ref": "#/components/responses/BadRequest"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "429":
          "$ref": "#/components/responses/RateLimited"
        "500":
          "$ref": "#/components/responses/InternalError"
  "/v1/routers":
    get:
      tags:
        - "Routers"
      summary: "List named routers in a workspace."
      description: "Requires an active workspace membership with owner, admin, developer, or viewer role. Billing-only members and inference API keys are rejected. workspace_id is always required; a router ID from a different authorized workspace returns 404. Results include mutable drafts and current optimistic generations, ordered by router ID ascending. Pass next_cursor as cursor with the same filters to continue. Archived routers are omitted by default."
      operationId: "listNamedRouters"
      security:
        -
          routerSession: []
        -
          routerCookie: []
        -
          routerDevCookie: []
      parameters:
        -
          "$ref": "#/components/parameters/RouterWorkspace"
        -
          name: "status"
          in: "query"
          schema:
            type: "string"
            enum:
              - "active"
              - "archived"
              - "all"
            default: "active"
        -
          name: "search"
          in: "query"
          description: "Case-insensitive literal substring of name or slug."
          schema:
            type: "string"
            minLength: 0
            maxLength: 120
            default: ""
        -
          name: "cursor"
          in: "query"
          description: "Exclusive router ID cursor returned in next_cursor."
          schema:
            type: "string"
            minLength: 1
            maxLength: 120
            pattern: "^[a-zA-Z0-9_-]+$"
        -
          name: "limit"
          in: "query"
          schema:
            type: "integer"
            minimum: 1
            maximum: 100
            default: 50
      responses:
        "200":
          description: "Operation completed."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/RouterList"
        "400":
          "$ref": "#/components/responses/BadRequest"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "429":
          "$ref": "#/components/responses/RateLimited"
        "500":
          "$ref": "#/components/responses/InternalError"
    post:
      tags:
        - "Routers"
      summary: "Create an unpublished named router."
      description: "Requires an active owner, admin, or developer session in the named workspace; viewer and billing-only roles cannot mutate. Inference API keys are rejected. For cookie sessions, every POST and PATCH requires browser same-origin proof: Sec-Fetch-Site: same-origin, or Origin equal to the configured public request origin. A Bearer sess.* session does not use cookie authentication. JSON request bodies are limited to 131072 bytes. An omitted draft creates one default route with an empty candidate pool. Structurally valid incomplete drafts may be saved; validation reports blockers and publication rejects them. The workspace-local slug is immutable and remains reserved after archive. The alias is router/<slug>; revision is a separate field, never appended to the alias. Maximum 200 router identities per workspace, including archived ones. A draft whose policy names a region no endpoint documents (param policy.regions), or requires zero retention (param policy.zero_retention), is refused with 422; the same applies to update and duplicate."
      operationId: "createNamedRouter"
      security:
        -
          routerSession: []
        -
          routerCookie: []
        -
          routerDevCookie: []
      parameters:
        -
          "$ref": "#/components/parameters/RouterCommandKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/RouterCreate"
      responses:
        "201":
          description: "Unpublished router created."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/NamedRouter"
        "400":
          "$ref": "#/components/responses/BadRequest"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "409":
          "$ref": "#/components/responses/RouterConflict"
        "413":
          "$ref": "#/components/responses/RouterTooLarge"
        "422":
          "$ref": "#/components/responses/RouterIneligible"
        "429":
          "$ref": "#/components/responses/RateLimited"
        "500":
          "$ref": "#/components/responses/InternalError"
  "/v1/routers/{id}":
    parameters:
      -
        "$ref": "#/components/parameters/RouterId"
    get:
      tags:
        - "Routers"
      summary: "Read a named router and its draft."
      description: "Requires an active workspace membership with owner, admin, developer, or viewer role. Billing-only members and inference API keys are rejected. workspace_id is always required; a router ID from a different authorized workspace returns 404. draft is editable state. active_revision points to a separate immutable published document; changing draft or name does not change that document."
      operationId: "getNamedRouter"
      security:
        -
          routerSession: []
        -
          routerCookie: []
        -
          routerDevCookie: []
      parameters:
        -
          "$ref": "#/components/parameters/RouterWorkspace"
      responses:
        "200":
          description: "Operation completed."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/NamedRouter"
        "400":
          "$ref": "#/components/responses/BadRequest"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "429":
          "$ref": "#/components/responses/RateLimited"
        "500":
          "$ref": "#/components/responses/InternalError"
    patch:
      tags:
        - "Routers"
      summary: "Update a router name or draft."
      description: "Requires an active owner, admin, or developer session in the named workspace; viewer and billing-only roles cannot mutate. Inference API keys are rejected. For cookie sessions, every POST and PATCH requires browser same-origin proof: Sec-Fetch-Site: same-origin, or Origin equal to the configured public request origin. A Bearer sess.* session does not use cookie authentication. JSON request bodies are limited to 131072 bytes. Requires the latest expected_generation and at least one of name or draft. draft replaces the entire document; field-wise patching and undocumented null clears are not supported. Success increments generation. The active revision is unchanged. Archived routers cannot be edited."
      operationId: "updateNamedRouter"
      security:
        -
          routerSession: []
        -
          routerCookie: []
        -
          routerDevCookie: []
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/RouterUpdate"
      responses:
        "200":
          description: "Operation completed."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/NamedRouter"
        "400":
          "$ref": "#/components/responses/BadRequest"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "409":
          "$ref": "#/components/responses/RouterConflict"
        "413":
          "$ref": "#/components/responses/RouterTooLarge"
        "422":
          "$ref": "#/components/responses/RouterIneligible"
        "429":
          "$ref": "#/components/responses/RateLimited"
        "500":
          "$ref": "#/components/responses/InternalError"
  "/v1/routers/{id}/validate":
    parameters:
      -
        "$ref": "#/components/parameters/RouterId"
    post:
      tags:
        - "Routers"
      summary: "Validate a draft or immutable revision."
      description: |
        Requires an active workspace membership with owner, admin, developer, or viewer role. Billing-only members and inference API keys are rejected. workspace_id is always required; a router ID from a different authorized workspace returns 404. Cookie-authenticated POST requests also require the same-origin proof described by the Routers tag.

        This operation does not write a revision or call a provider. Omit both revision and config to validate the saved draft. config supplies a transient document and cannot be combined with revision. expected_generation optionally rejects a stale editor.

        valid describes structural/semantic document validity; publishable additionally requires active workspace entitlement and every model to satisfy current catalog, global, workspace, protocol, capability, context, native price, parameter, region, retention, deadline and fallback restrictions. Empty candidate pools produce blocking issues.
      operationId: "validateNamedRouter"
      security:
        -
          routerSession: []
        -
          routerCookie: []
        -
          routerDevCookie: []
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/RouterInspect"
      responses:
        "200":
          description: "Operation completed."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/RouterValidation"
        "400":
          "$ref": "#/components/responses/BadRequest"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "409":
          "$ref": "#/components/responses/RouterConflict"
        "413":
          "$ref": "#/components/responses/RouterTooLarge"
        "422":
          "$ref": "#/components/responses/RouterIneligible"
        "429":
          "$ref": "#/components/responses/RateLimited"
        "500":
          "$ref": "#/components/responses/InternalError"
  "/v1/routers/{id}/simulate":
    parameters:
      -
        "$ref": "#/components/parameters/RouterId"
    post:
      tags:
        - "Routers"
      summary: "Explain routing without running inference."
      description: |
        Requires an active workspace membership with owner, admin, developer, or viewer role. Billing-only members and inference API keys are rejected. workspace_id is always required; a router ID from a different authorized workspace returns 404. Cookie-authenticated POST requests also require the same-origin proof described by the Routers tag.

        This operation does not write a revision or call a provider. Evaluates the first matching route, then deterministically assigns an integer bucket 0–99 to its weighted variants. Assignment is scoped by workspace, router, draft generation/config hash or immutable revision, route, and assignment_key. Auto mode ranks eligible candidates by exact native token cost; ordered mode preserves candidate order.

        The response includes the route trace, field-wise effective defaults, eligible/excluded models and validation issues. Empty candidate pools remain explainable. Malformed request or configuration envelopes return 400; invalid simulation facts and selection violations return issues with selection null. Metadata never grants workspace or model entitlement. inference_enabled is always false because simulation never calls a model. To see which A/B variant a caller gets in production, simulate the published revision (revision) with the caller's session_id as assignment_key; assignment is scoped to the revision, so a draft simulation can differ.
      operationId: "simulateNamedRouter"
      security:
        -
          routerSession: []
        -
          routerCookie: []
        -
          routerDevCookie: []
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/RouterSimulate"
      responses:
        "200":
          description: "Operation completed."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/RouterSimulation"
        "400":
          "$ref": "#/components/responses/BadRequest"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "409":
          "$ref": "#/components/responses/RouterConflict"
        "413":
          "$ref": "#/components/responses/RouterTooLarge"
        "422":
          "$ref": "#/components/responses/RouterIneligible"
        "429":
          "$ref": "#/components/responses/RateLimited"
        "500":
          "$ref": "#/components/responses/InternalError"
  "/v1/routers/{id}/publish":
    parameters:
      -
        "$ref": "#/components/parameters/RouterId"
    post:
      tags:
        - "Routers"
      summary: "Publish the current draft as an immutable revision."
      description: |
        Requires an active owner, admin, or developer session in the named workspace; viewer and billing-only roles cannot mutate. Inference API keys are rejected. For cookie sessions, every POST and PATCH requires browser same-origin proof: Sec-Fetch-Site: same-origin, or Origin equal to the configured public request origin. A Bearer sess.* session does not use cookie authentication. JSON request bodies are limited to 131072 bytes.

        Requires the current generation and Idempotency-Key. Revalidates all routes, variants and fallback candidates against current workspace and catalog evidence, then atomically creates the next immutable revision, advances active_revision/generation and records an audit event. The draft is preserved. Invalid or ineligible publication creates no revision or command claim. At most 1000 revisions are retained per router.

        After publication, Chat Completions, Responses and Messages requests with model router/<slug> use the new revision immediately. Requests that send router_revision keep the revision they pin. Every request rechecks the revision against current policy, so a model switched off later makes the revision fail with 422 until you publish a fix or roll back.
      operationId: "publishNamedRouter"
      security:
        -
          routerSession: []
        -
          routerCookie: []
        -
          routerDevCookie: []
      parameters:
        -
          "$ref": "#/components/parameters/RouterCommandKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/RouterChange"
      responses:
        "200":
          description: "Operation completed."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/RouterPublication"
        "400":
          "$ref": "#/components/responses/BadRequest"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "409":
          "$ref": "#/components/responses/RouterConflict"
        "413":
          "$ref": "#/components/responses/RouterTooLarge"
        "422":
          "$ref": "#/components/responses/RouterIneligible"
        "429":
          "$ref": "#/components/responses/RateLimited"
        "500":
          "$ref": "#/components/responses/InternalError"
  "/v1/routers/{id}/revisions":
    parameters:
      -
        "$ref": "#/components/parameters/RouterId"
    get:
      tags:
        - "Routers"
      summary: "List immutable router revisions."
      description: "Requires an active workspace membership with owner, admin, developer, or viewer role. Billing-only members and inference API keys are rejected. workspace_id is always required; a router ID from a different authorized workspace returns 404. Returns up to 50 revisions in descending revision order and the current router generation. Pass next_cursor as before to read older revisions. History remains readable after archive."
      operationId: "listRouterRevisions"
      security:
        -
          routerSession: []
        -
          routerCookie: []
        -
          routerDevCookie: []
      parameters:
        -
          "$ref": "#/components/parameters/RouterWorkspace"
        -
          name: "before"
          in: "query"
          description: "Exclusive revision cursor; omit for the newest page."
          schema:
            type: "integer"
            minimum: 1
            maximum: 1000
      responses:
        "200":
          description: "Operation completed."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/RouterRevisionList"
        "400":
          "$ref": "#/components/responses/BadRequest"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "429":
          "$ref": "#/components/responses/RateLimited"
        "500":
          "$ref": "#/components/responses/InternalError"
  "/v1/routers/{id}/revisions/{revision}":
    parameters:
      -
        "$ref": "#/components/parameters/RouterId"
      -
        "$ref": "#/components/parameters/RouterRevisionId"
    get:
      tags:
        - "Routers"
      summary: "Read an immutable router revision."
      description: "Requires an active workspace membership with owner, admin, developer, or viewer role. Billing-only members and inference API keys are rejected. workspace_id is always required; a router ID from a different authorized workspace returns 404. Returns the revision document and current router generation. Reading historical configuration does not authorize execution. Future runtime resolution of a historical pin also requires the router to remain active and all current policy and model eligibility checks to pass."
      operationId: "getRouterRevision"
      security:
        -
          routerSession: []
        -
          routerCookie: []
        -
          routerDevCookie: []
      parameters:
        -
          "$ref": "#/components/parameters/RouterWorkspace"
      responses:
        "200":
          description: "Operation completed."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/RouterRevisionRead"
        "400":
          "$ref": "#/components/responses/BadRequest"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "429":
          "$ref": "#/components/responses/RateLimited"
        "500":
          "$ref": "#/components/responses/InternalError"
  "/v1/routers/{id}/rollback":
    parameters:
      -
        "$ref": "#/components/parameters/RouterId"
    post:
      tags:
        - "Routers"
      summary: "Restore a prior configuration as a new revision."
      description: "Requires an active owner, admin, or developer session in the named workspace; viewer and billing-only roles cannot mutate. Inference API keys are rejected. For cookie sessions, every POST and PATCH requires browser same-origin proof: Sec-Fetch-Site: same-origin, or Origin equal to the configured public request origin. A Bearer sess.* session does not use cookie authentication. JSON request bodies are limited to 131072 bytes. Requires the latest expected_generation and an existing revision. Revalidates that historical configuration under current policy and catalog evidence; retired or ineligible models cannot be resurrected. Success creates a new immutable revision with restored_from, advances active_revision and generation, and preserves the independent editable draft. It never mutates or deletes history. A repeated request with the old generation returns 409. New requests to router/<slug> on any LLM API use the restored revision immediately."
      operationId: "restoreRouterRevision"
      security:
        -
          routerSession: []
        -
          routerCookie: []
        -
          routerDevCookie: []
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/RouterRestore"
      responses:
        "200":
          description: "Operation completed."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/RouterPublication"
        "400":
          "$ref": "#/components/responses/BadRequest"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "409":
          "$ref": "#/components/responses/RouterConflict"
        "413":
          "$ref": "#/components/responses/RouterTooLarge"
        "422":
          "$ref": "#/components/responses/RouterIneligible"
        "429":
          "$ref": "#/components/responses/RateLimited"
        "500":
          "$ref": "#/components/responses/InternalError"
  "/v1/routers/{id}/duplicate":
    parameters:
      -
        "$ref": "#/components/parameters/RouterId"
    post:
      tags:
        - "Routers"
      summary: "Copy configuration into an unpublished router."
      description: "Requires an active owner, admin, or developer session in the named workspace; viewer and billing-only roles cannot mutate. Inference API keys are rejected. For cookie sessions, every POST and PATCH requires browser same-origin proof: Sec-Fetch-Site: same-origin, or Origin equal to the configured public request origin. A Bearer sess.* session does not use cookie authentication. JSON request bodies are limited to 131072 bytes. Requires the source current generation, a new name/slug, and Idempotency-Key. Omit revision to copy the draft, or choose a published revision. The copy receives a new identity, generation 1, empty revision history and null active_revision. It does not inherit an active endpoint. Archived source routers cannot be duplicated."
      operationId: "duplicateNamedRouter"
      security:
        -
          routerSession: []
        -
          routerCookie: []
        -
          routerDevCookie: []
      parameters:
        -
          "$ref": "#/components/parameters/RouterCommandKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/RouterDuplicate"
      responses:
        "201":
          description: "Unpublished router created."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/NamedRouter"
        "400":
          "$ref": "#/components/responses/BadRequest"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "409":
          "$ref": "#/components/responses/RouterConflict"
        "413":
          "$ref": "#/components/responses/RouterTooLarge"
        "422":
          "$ref": "#/components/responses/RouterIneligible"
        "429":
          "$ref": "#/components/responses/RateLimited"
        "500":
          "$ref": "#/components/responses/InternalError"
  "/v1/routers/{id}/archive":
    parameters:
      -
        "$ref": "#/components/parameters/RouterId"
    post:
      tags:
        - "Routers"
      summary: "Archive a router and disable alias resolution."
      description: "Requires an active owner, admin, or developer session in the named workspace; viewer and billing-only roles cannot mutate. Inference API keys are rejected. For cookie sessions, every POST and PATCH requires browser same-origin proof: Sec-Fetch-Site: same-origin, or Origin equal to the configured public request origin. A Bearer sess.* session does not use cookie authentication. JSON request bodies are limited to 131072 bytes. Requires the latest expected_generation. Sets archived_at and increments generation. Draft and immutable history remain readable, and the slug remains reserved. Both active and explicitly pinned revision resolution are denied after archive. There is no restore-from-archive operation."
      operationId: "archiveNamedRouter"
      security:
        -
          routerSession: []
        -
          routerCookie: []
        -
          routerDevCookie: []
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/RouterChange"
      responses:
        "200":
          description: "Operation completed."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/NamedRouter"
        "400":
          "$ref": "#/components/responses/BadRequest"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "409":
          "$ref": "#/components/responses/RouterConflict"
        "413":
          "$ref": "#/components/responses/RouterTooLarge"
        "422":
          "$ref": "#/components/responses/RouterIneligible"
        "429":
          "$ref": "#/components/responses/RateLimited"
        "500":
          "$ref": "#/components/responses/InternalError"

  /v1/keys:
    get:
      tags:
        - Keys
      summary: List API keys for a workspace.
      operationId: listKeys
      parameters:
        - $ref: "#/components/parameters/WorkspaceId"
      responses:
        "200":
          description: Redacted API keys.
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/ApiKey"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/InternalError"
    post:
      tags:
        - Keys
      summary: Mint a new API key.
      operationId: createKey
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/KeyReq"
      responses:
        "201":
          description: API key created. The raw key is returned once.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/KeyCreateRes"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/keys/default/claim:
    post:
      tags:
        - Keys
      summary: Claim the signed-in owner's pending default API key.
      operationId: claimDefaultKey
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DefaultKeyClaim"
      responses:
        "200":
          description: Pending default key reveal. It remains retryable until acknowledged.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/KeyCreateRes"
        "204":
          description: No pending default key reveal exists for this owner and workspace.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/keys/default/ack:
    post:
      tags:
        - Keys
      summary: Confirm storage and destroy a pending encrypted default-key reveal.
      operationId: acknowledgeDefaultKey
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DefaultKeyAck"
      responses:
        "204":
          description: Reveal acknowledged or already absent.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/keys/{key_id}:
    parameters:
      - $ref: "#/components/parameters/KeyId"
    get:
      tags:
        - Keys
      summary: Get one redacted API key.
      operationId: getKey
      responses:
        "200":
          description: Redacted key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiKey"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
    patch:
      tags:
        - Keys
      summary: Update key metadata, limits, or state.
      operationId: updateKey
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/KeyPatch"
      responses:
        "200":
          description: Key updated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiKey"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
    delete:
      tags:
        - Keys
      summary: Revoke an API key.
      operationId: revokeKey
      responses:
        "204":
          description: Key revoked.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/audio/transcriptions:
    post:
      tags:
        - Audio
      summary: Create a transcription.
      operationId: createTranscription
      description: |
        Transcribe audio with a direct provider model from /v1/models. Supported
        controls and input limits are model-specific. Duration-priced contracts use
        canonical mono PCM16 WAV and either native duration or validated PCM samples.
        Token-priced models use native usage. Published tariffs minus 15% apply before
        account credits; provider invoice APIs are not required.
        Sarvam saaras-v3-batch and saaras-v4-batch support recordings up to two hours
        with ?async=true. Poll /v1/audio/transcriptions/{id} using the same API key.
        Async requests require a healthy worker and workspace audio storage. Native
        job identity is persisted before polling; ambiguous jobs are not resubmitted.
        Use /v1/audio/transcriptions/stream for dedicated real-time recognition.
        Uploaded audio is limited to 500,000,000 bytes, as a file or as decoded
        base64; each model's own max_bytes in /v1/models is usually lower.
      parameters:
        - name: async
          in: query
          required: false
          schema:
            type: string
            enum:
              - "true"
              - "1"
          description: Queue the transcription and return a request id instead of waiting for completion. Returns 503 without storing or queueing the request when no healthy background worker is polling the durable queue.
        - name: mode
          in: query
          required: false
          schema:
            type: string
            enum:
              - async
          description: Alternative async queueing flag.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/TranscriptionForm"
          application/json:
            schema:
              $ref: "#/components/schemas/TranscriptionReq"
      responses:
        "200":
          description: Transcription completed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Transcription"
            text/plain:
              schema:
                type: string
            text/vtt:
              schema:
                type: string
            application/x-subrip:
              schema:
                type: string
        "202":
          description: Transcription queued.
          content:
            application/json:
              schema:
                type: object
                required:
                  - id
                  - status
                properties:
                  id:
                    type: string
                  status:
                    type: string
                    const: queued
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/AudioTooLarge"
        "415":
          $ref: "#/components/responses/UnsupportedMedia"
        "422":
          $ref: "#/components/responses/Unprocessable"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
        "501":
          $ref: "#/components/responses/NotImplemented"
        "502":
          $ref: "#/components/responses/ProviderError"
        "503":
          $ref: "#/components/responses/Unavailable"

  /v1/audio/transcriptions/stream:
    get:
      tags:
        - Audio
      summary: Upgrade to a live PCM transcription WebSocket session.
      description: |
        Reviewed streaming contracts are funded before connecting to the provider.
        Direct streaming supports the catalogued Deepgram, Cartesia, ElevenLabs,
        Mistral, AssemblyAI, Speechmatics, Smallest, Gladia, Google Cloud, Alibaba,
        Sarvam, Reson8, OpenAI, Soniox, Azure, xAI and Amazon Transcribe models. Sessions are limited to five minutes of
        mono PCM16 input. Complete native termination settles the frozen streaming
        tariff; interrupted or ambiguous outcomes retain the reservation.
        Authenticate the HTTP upgrade with `Authorization: Bearer <api-key>`.
        Native browser WebSocket cannot set that header; use a trusted server or
        a WebSocket client that supports custom upgrade headers. Never put an API
        key in the URL. The first client text frame is `session.start`, followed
        by raw mono PCM16 audio frames and `session.finish`. `session.start` accepts `provider.data_region`, as a request does.
      operationId: streamTranscription
      responses:
        "101":
          description: WebSocket protocol upgrade accepted.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/audio/transcriptions/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    get:
      tags:
        - Audio
      summary: Get transcription status and result.
      operationId: getTranscription
      responses:
        "200":
          description: Transcription status and stored result, when available.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TranscriptionResult"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/audio/speech:
    post:
      tags:
        - Audio
      summary: Generate speech from text.
      description: |
        Generate speech with an exact provider model or tts/auto. Responses stream
        binary audio unless a supported provider transport explicitly returns JSON.
        Native voice IDs are accepted for exact models; tts/auto chooses a portable
        voice. Model-specific formats, languages and limits are exposed in the catalog.
        Published provider tariffs minus 15% apply before account credits. Reservations
        precede provider calls and native completion settles usage.

        MiniMax speech-2.8, speech-2.6 and speech-02 HD/Turbo support background jobs
        using the -async model suffix and ?async=true, up to 50,000 characters. The
        202 response contains the request ID. Poll GET /v1/audio/speech/{id} with the
        same API key to download the completed result; it expires after 24 hours.
        Async requests require a healthy worker and workspace audio storage.

        Azure neural and neural-hd-flash accept their corresponding native voice IDs.
        Alibaba CosyVoice 3.5 requires a provisioned custom voice and a separately
        configured Beijing credential; Singapore credentials are not interchangeable.
      operationId: createSpeech
      parameters:
        - name: async
          in: query
          schema:
            type: string
            enum: ["true", "1"]
          description: Queue a supported MiniMax -async model instead of waiting.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SpeechReq"
      responses:
        "200":
          description: Generated audio stream. Content-Type reflects response_format.
          headers:
            x-request-id:
              schema:
                type: string
            x-costream-model:
              schema:
                type: string
            x-costream-provider-request-id:
              description: Upstream provider request ID when available.
              schema:
                type: string
            dg-request-id:
              description: Native Deepgram request ID for Aura requests.
              schema:
                type: string
            dg-model-uuid:
              description: Native Deepgram model UUID for Aura requests.
              schema:
                type: string
            dg-model-name:
              description: Native Deepgram Aura voice/model name.
              schema:
                type: string
            dg-char-count:
              description: Deepgram billable source-character count after removing pronunciation markup.
              schema:
                type: integer
            dg-pronunciations-applied:
              description: Number of Deepgram inline IPA overrides applied.
              schema:
                type: integer
            dg-speed-used:
              description: Effective Deepgram speaking-rate multiplier when returned upstream.
              schema:
                type: number
            dg-pronunciation-warnings:
              description: Non-fatal Deepgram IPA warnings when returned upstream.
              schema:
                type: string
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ElevenTiming"
                  - $ref: "#/components/schemas/DeepinfraResponse"
                  - $ref: "#/components/schemas/DeepgramCallback"
                  - $ref: "#/components/schemas/HumeResponse"
                  - $ref: "#/components/schemas/InworldResponse"
                  - $ref: "#/components/schemas/MinimaxResponse"
                  - $ref: "#/components/schemas/MurfResponse"
                  - $ref: "#/components/schemas/XaiTiming"
                  - type: string
                    description: Chunked ElevenLabs JSON timing objects from stream_with_timing.
            text/event-stream:
              schema:
                type: string
                description: Native OpenAI, Cartesia, or MiniMax SSE audio, metadata, timestamp, completion, and error events.
            application/x-json-stream:
              schema:
                type: string
                description: Amazon Polly sentence, SSML, viseme, or word speech marks, one JSON object per line.
            application/x-ndjson:
              schema:
                type: string
                description: Native Hume or Inworld streamed-JSON audio, timestamp, usage, and status objects, one JSON object per line.
        "202":
          description: Background speech job queued. Poll the returned id.
          content:
            application/json:
              schema:
                type: object
                required: [id, status]
                properties:
                  id:
                    type: string
                  status:
                    type: string
                    enum: [queued]
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "403":
          $ref: "#/components/responses/Forbidden"
        "415":
          $ref: "#/components/responses/UnsupportedMedia"
        "422":
          $ref: "#/components/responses/Unprocessable"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/ProviderError"
        "503":
          $ref: "#/components/responses/Unavailable"

  /v1/audio/speech/{id}:
    get:
      tags: [Audio]
      summary: Poll or download a background speech job.
      operationId: getSpeechJob
      description: Use the same workspace API key that submitted the job. Successful audio expires after 24 hours. Failed jobs return a status and error code.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Completed binary audio, or JSON with failed status and error.
        "202":
          description: The job is queued or running.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/audio/speech/stream:
    get:
      tags:
        - Audio
      summary: Stream text input to Alibaba Qwen, Amazon Polly, ElevenLabs, Cartesia, Deepgram, Hume, Inworld, MiniMax, Murf, Soniox, or xAI speech generation over WebSocket.
      description: |
        Streams text to an exact provider speech model over WebSocket and forwards the provider's native audio, timing, and completion frames.
        Reviewed streaming contracts are funded before connecting to the provider.
        Sessions are limited to five minutes, 8,000 submitted characters and
        32 MiB of output. Final native completion settles the frozen tariff;
        interrupted or ambiguous outcomes retain the reservation. Use the exact
        supported selector and provider-native control frames below.

        Authenticate the HTTP upgrade with `Authorization: Bearer <api-key>`.
        Native browser WebSocket cannot set that header; use a trusted server or
        a WebSocket client that supports custom upgrade headers. Never put an API
        key in the URL. The first client frame must be a
        `session.start` object with an exact Alibaba Qwen3 realtime, Amazon Polly,
        ElevenLabs, Cartesia, Deepgram, Hume, Inworld, MiniMax, Murf, Soniox, or
        xAI model. `session.start` accepts `provider.data_region`, as a request does.

        Alibaba accepts native `input_text_buffer.append`,
        `input_text_buffer.commit`, `input_text_buffer.clear`, and
        `session.finish` events. Its native session, response, base64 audio,
        completion, usage, and error objects are forwarded unchanged. Qwen3
        Flash uses built-in voices; Instruct adds common `instructions` and
        `optimize_instructions`; VC and VD require a custom voice ID created for
        the exact Alibaba enrollment model.

        Amazon Polly uses its generative-only HTTP/2 bidirectional stream. Send
        `{ "type": "input_text", "text": "...", "flush": true }`, optional
        `{ "type": "flush" }`, then `{ "type": "session.finish" }`; audio is
        emitted as base64 `audio` events and the final provider character count
        is emitted as `stream.closed`.

        ElevenLabs standard and multi-context payloads are proxied according to
        their native protocols. Cartesia accepts its current generation and
        cancellation payloads, supports multiplexed context IDs, and uses the
        AudioLab-only `{ "type": "session.finish" }` control to drain active
        contexts and settle the session.

        Deepgram accepts native `Speak`,
        `Flush`, `Clear`, and `Close` controls; raw upstream audio is emitted as
        `{ "type": "Audio", "audio": "<base64>" }`, while Metadata, Flushed,
        Cleared, and Warning objects retain their native fields. Its start frame
        requires a full Aura voice id (for example `aura-2-thalia-en`) and a
        `pcm`, `ulaw`, or `alaw` response format. `session.finish`
        maps to Deepgram's graceful `Close`.

        Hume accepts native InputMessage text,
        voice, description, speed, trailing-silence, flush, and close controls;
        `session.finish` maps to Hume's native `close:true`.

        Inworld supports up
        to five multiplexed contexts using native `create`, `send_text`,
        `flush_context`, and `close_context` objects; AudioLab forwards native
        context, audio, alignment, usage, and status results, and `session.finish`
        drains all open contexts. Inworld TTS-2 start-frame instructions are
        translated to bracketed steering on the first text frame in each context;
        TTS 1.5 Max and Mini reject steering.

        xAI accepts native `text.delta`,
        `text.done`, and `text.clear` controls across multiple utterances on one
        connection; use `session.finish` after the last `audio.done` or
        `audio.clear`.

        MiniMax accepts text or native `task_continue` frames,
        maps `session.finish` to `task_finish`, and forwards `task_continued`,
        `task_finished`, and task errors.

        Murf accepts native text, voice_config,
        clear, context_id, end, min_buffer_size, and max_buffer_delay_in_ms
        fields (including camelCase `voiceId`), multiplexes independent contexts, and forwards native base64
        audio and final events.

        Soniox tts-rt-v2 accepts `{"text":"...","text_end":true}` after
        `session.start`. `session.finish` ends input. Native `audio_end` followed by
        `terminated` completes the stream; output is capped at two minutes and
        charged by generated audio duration at the published tariff.

        Provider audio, timing, alignment,
        flush, completion, and error objects are forwarded without changing
        their provider fields.
      operationId: streamSpeech
      responses:
        "101":
          description: WebSocket protocol upgrade accepted.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/ProviderError"
        "503":
          $ref: "#/components/responses/Unavailable"

  /v1/audio/speech-to-speech:
    post:
      tags:
        - Speech to Speech
      summary: Create one speech-to-speech turn.
      description: |
        Sends one recorded utterance to an OpenAI Realtime model and returns
        the spoken reply as WAV or raw PCM16 at 24000 Hz, with its transcript
        and the settled usage. Requires an `audio.write` key and a deployment
        with REALTIME_ENABLED. Each request is a new conversation; use the
        WebSocket stream to keep context across turns.

        Models: `openai/gpt-realtime-2.1-mini`, `openai/gpt-realtime-2.1`,
        `openai/gpt-realtime-2` and `openai/gpt-realtime-1.5`. Routing aliases
        such as `s2s/auto` are not available.

        `input_audio` is base64 mono 16-bit PCM at 24000 Hz, 0.1 to 60
        seconds: a WAV file (`format: wav`) or raw little-endian samples
        (`format: pcm16`, `sample_rate: 24000`). Other formats, sample rates
        and channel counts are rejected. `temperature`, `feature` and other
        fields have no reviewed price and return 422; `provider` takes only
        `data_region`.

        Before connecting to OpenAI, the request holds the model's full
        context at its highest input rate plus `max_output_tokens` at its
        highest output rate, minus 15%. At the default 1024-token cap that is
        about USD 3.54 for 2 and 2.1, 1.11 for 2.1 Mini and 0.93 for
        1.5. The response returns after OpenAI's final usage is recorded: the
        charge is that usage at OpenAI's published text, audio and cached
        rates minus 15%, and the rest of the hold is released. Reasoning tokens
        are part of the text output. Per workspace, at most four turns can be
        unsettled, twenty can start per minute, and known expense plus open
        holds is limited to USD 20 a day, shared with the WebSocket.

        With `stream: true` the response is `text/event-stream`. Each event's
        `data` is JSON with a `type`: `response.created`, then
        `output_audio.delta` (base64 PCM16 at 24000 Hz) and
        `output_transcript.delta` as they arrive, then `response.done` with the
        settled usage, or `error`. Streamed audio counts as delivered: a
        response cancelled or failed after a delta is charged for its known
        usage. A buffered request whose client disconnects first is cancelled
        and not charged.

        A reused `Idempotency-Key` (1 to 160 printable ASCII characters) never
        generates again; it returns 409 naming the original request. Audio is
        not stored. If OpenAI's final usage is lost, the error names the
        request and its hold stays until resolved; check
        `GET /v1/requests/{request_id}` before retrying.
      operationId: createSpeechToSpeech
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional key, 1 to 160 printable ASCII characters. A reuse returns 409 instead of a second generation.
          schema:
            type: string
            minLength: 1
            maxLength: 160
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/S2sReq"
            example:
              model: openai/gpt-realtime-2.1-mini
              input_audio:
                data: BASE64_WAV_AUDIO
                format: wav
              voice: marin
              instructions: Answer in one short sentence.
      responses:
        "200":
          description: The turn completed and was settled.
          headers:
            x-request-id:
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/S2sRes"
            text/event-stream:
              schema:
                type: string
                description: With stream true, response.created, output_audio.delta, output_transcript.delta and response.done or error events.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          description: The request body exceeds 38,194,304 bytes.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "415":
          $ref: "#/components/responses/UnsupportedMedia"
        "422":
          $ref: "#/components/responses/Unprocessable"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/ProviderError"
        "503":
          $ref: "#/components/responses/Unavailable"

  /v1/audio/speech-to-speech/stream:
    get:
      tags:
        - Speech to Speech
      summary: Stream a funded audio response over WebSocket.
      description: |
        Requires an enabled REALTIME_ENABLED deployment and audio.write scope.
        Reviewed models include OpenAI gpt-realtime, 1.5, 2, 2.1, 2.1 Mini,
        the pinned 2025-08-28 snapshot, and gemini/gemini-3.1-flash-live-preview.
        REALTIME_ENABLED defaults to false. Up to 100 separately funded manual
        turns share one native conversation over at most 15 minutes. For one
        turn without a socket, use POST /v1/audio/speech-to-speech.
        Authenticate the HTTP upgrade with `Authorization: Bearer <api-key>`.
        Send a unique `Idempotency-Key` header and reuse it only for the same input.
        Native browser WebSocket cannot set that header; use a trusted server or
        a WebSocket client that supports custom upgrade headers. Never put an API
        key in the URL.

        The first client text frame must be a `session.start` object matching
        `S2sStart`, with mono PCM16 at 24000 Hz for OpenAI input or 16000 Hz
        for Gemini input, and 24000 Hz for output. Use turn_detection.type=none
        and input_transcription=false. Gemini requires voice=Puck (or auto) and
        output_transcription=false. It accepts `provider.data_region`, as a
        request does. Set
        max_output_tokens between 1 and 1024. Wait for session.ready, send
        binary audio frames of at most 256 KiB, then input_audio.commit and
        response.create. Input duration must be between 0.1 and 60 seconds.
        No automatic VAD, tools, provider events, session updates, input
        transcription or response overrides are admitted. After response.done
        and input_audio.ready, append and commit the next turn's audio.

        Receive output_audio.delta and optional output_transcript.delta events.
        Gemini uses a persistent native connection with minimal thinking and no
        transcription, proactivity, tools, context compression or resumption.
        Its quote reserves 131072 input tokens plus the inclusive output cap:
        1024 output tokens reserve USD 0.405504 of provider expense. Native
        usage must include complete text/audio and thought accounting on the
        terminal turnComplete frame; earlier periodic usage is insufficient.
        Gemini response.done contains a request-bound local turn ID and
        id_origin=local; it does not invent a provider response ID.

        Final response.done follows durable native usage recording and wallet
        settlement for each turn. Send session.finish to close the session.
        The temporary hold covers the full reviewed context and output cap;
        settlement uses native text, audio and cache token counters at the
        published provider tariff minus 15%. Cancellation charges known delivered
        output. A missing final provider receipt retains the hold; reconnecting
        with the same idempotency key never repeats generation. Replay returns
        session.replayed status and usage, not the original audio.
      operationId: streamSpeechToSpeech
      responses:
        "101":
          description: WebSocket protocol upgrade accepted.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/Unprocessable"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/ProviderError"
        "503":
          $ref: "#/components/responses/Unavailable"

  /v1/audio/conversations/stream:
    get:
      tags:
        - Speech to Speech
      summary: Continuous voice conversations with native turn detection.
      operationId: streamConversation
      description: |
        Requires REALTIME_ENABLED, audio.write, a funded workspace, and a
        configured Hume EVI, Amazon Nova Sonic, Gemini 3.1 Flash Live or
        Gemini 3.5 Live Translate endpoint. Authenticate the
        WebSocket upgrade with Authorization: Bearer <API_KEY> and send an
        Idempotency-Key. Provider credentials stay on the server.

        First send {"type":"session.start","model":"amazon/nova-2-sonic",
        "voice":"matthew","max_seconds":120,"instructions":"Answer briefly."}.
        Input is mono PCM16 at 16000 Hz. max_seconds accepts 10 through 300.
        `session.start` accepts `provider.data_region`, as a request does.
        After session.ready, continuously send binary audio at its capture
        cadence, or input_audio.append with base64 in the audio field. Each
        frame is at most one second. Input_text.append accepts a text field
        for typed input. Native VAD and interruptions preserve the conversation.

        Receive output_audio.delta, input_transcript.delta,
        output_transcript.delta, output_audio.interrupted and output_audio.done.
        Nova Sonic and Gemini return base64 mono PCM16 at 24000 Hz. Hume returns complete
        WAV chunks. An output_audio.done marks a turn, not final billing.
        Send {"type":"session.finish"} after the final turn; session.completed
        follows native usage validation and wallet settlement.

        Gemini Live Translate requires target_language in session.start and
        accepts optional echo_target. It meters audio at the provider's published
        25 tokens per second. Gemini 3.1 Live supports native VAD, interruptions
        and tools declared as {name,description,parameters}. tool.call events
        carry an id, name and arguments; return {"type":"tool.result","id":"...",
        "response":{}}. Tools run in your application. Optional vad accepts
        silence_ms and prefix_ms. Native per-turn context usage determines cost.

        Hume uses a version-pinned EVI 3 configuration without supplemental
        models or tools and its published Scale minute tariff. Nova Sonic uses
        its native text/speech token counters at the us-east-1 published rate.
        The temporary reservation is a risk allowance, not an estimated final
        charge. Session creation is never retried automatically. Duplicate
        idempotency keys return original status without generating again.
        Missing final usage retains the hold. Ended Hume chats can be recovered
        using their owned native receipt; no new conversation is created.
      responses:
        "101":
          description: WebSocket protocol upgrade accepted.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/Unavailable"

  /v1/models:
    get:
      tags:
        - Catalog
      summary: List models and router aliases.
      operationId: listModels
      description: |
        Public, as on OpenRouter: no key or scope is needed. A bearer key that is sent is still
        checked, so a wrong key returns 401 and the list doubles as a connection check. Each
        client address may make 120 requests a minute to the model list, model lookups,
        endpoint lists and model stats together, with or without a key; beyond that they
        return 429.

        Language models come first. Each carries OpenRouter's fields alongside the catalog's:
        created, description, context_length, pricing (USD per token, what AudioLab charges:
        the provider's rate minus 15%), architecture, supported_parameters (what the model's
        contract honours; others are dropped or refused as the Chat Completions reference
        describes), top_provider.max_completion_tokens (the output cap) and expiration_date.
        Input modalities say what a request may carry, so language models list text only.
        costream/auto and costream/fusion are listed when routed execution is enabled; their
        price is "-1" because it is that of the models that answer.

        A model that several hosts serve is listed once, under its host-free ID, with the price
        of its default host (its maker's own when that is a host, otherwise the cheapest), the
        largest context and output cap any host accepts, every parameter some host supports,
        and each host in endpoints. Each host's own ID is not listed but still resolves and
        selects that host alone.

        A model that has been switched off, or whose provider shutdown has passed, is left out.
        A scheduled shutdown shows as expiration_date (YYYY-MM-DD, UTC).
      security:
        - {}
        - bearerAuth: []
      parameters:
        - name: output_modalities
          in: query
          required: false
          schema:
            type: string
            pattern: "^(transcription|audio|embedding|classification|image|video|text|score)(,(transcription|audio|embedding|classification|image|video|text|score))*$"
          description: Comma-separated output modalities. Matches any selected modality.
        - name: task
          in: query
          required: false
          schema:
            $ref: "#/components/schemas/ModelTask"
      responses:
        "200":
          description: Model page.
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Model"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/models/{model_id}:
    parameters:
      - $ref: "#/components/parameters/ModelId"
    get:
      tags:
        - Catalog
      summary: Get model metadata.
      operationId: getModel
      description: |
        Public, like the model list; a key that is sent is checked. A model that has been
        switched off or retired still resolves, with state disabled, its expiration_date and,
        when it was switched off, disabled_reason.
      security:
        - {}
        - bearerAuth: []
      responses:
        "200":
          description: Model metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Model"
        "404":
          $ref: "#/components/responses/NotFound"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/models/{model_id}/endpoints:
    parameters:
      - $ref: "#/components/parameters/ModelId"
    get:
      tags:
        - Catalog
      summary: List endpoints for a model.
      operationId: listModelEndpoints
      description: |
        Public, like the model list; a key that is sent is checked. Send the model ID URL-encoded,
        or as OpenRouter's two path segments: GET /v1/models/openai/gpt-5.5/endpoints.

        A language model returns OpenRouter's endpoints document: the model with an endpoint
        for each host that serves it now, carrying that host's routing tag, published
        quantization, price, context, output cap, supported parameters and expiration_date; its
        uptime, time to first token and throughput over the last 30 minutes; and its uptime over
        the last day (uptime_last_1d). All are measured from AudioLab's own traffic and are null
        without any. A ~author/family-latest alias answers for the newest offered model of its
        family. A language model that is switched off or retired returns 404. Any other model
        returns its endpoint list, empty once the model is withdrawn; an endpoint measured in the
        last day carries its uptime.
      security:
        - {}
        - bearerAuth: []
      responses:
        "200":
          description: Endpoint list, or the endpoints document for a language model.
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                properties:
                  data:
                    oneOf:
                      - type: array
                        items:
                          $ref: "#/components/schemas/Endpoint"
                      - $ref: "#/components/schemas/ModelEndpointsDocument"
        "404":
          $ref: "#/components/responses/NotFound"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/models/{model_id}/stats:
    parameters:
      - $ref: "#/components/parameters/ModelId"
    get:
      tags:
        - Catalog
      summary: Get a model's uptime, latency and throughput.
      operationId: getModelStats
      description: |
        Public, like the model list; a key that is sent is checked. Send the model ID URL-encoded,
        or as two path segments: GET /v1/models/openai/gpt-5.5/stats.

        Published for language, speech and transcription models. The numbers combine customer
        requests with AudioLab's own probes, which send every offered model a tiny request every
        two hours. Uptime is the share of attempts that succeeded; failures the request itself
        caused (invalid or oversized input, a content filter, the caller cancelling) are not
        counted. Latency is the time to first token for language models, the time to first audio
        for speech, and the whole call for transcription. Throughput is output tokens per second
        after the first token, input characters per second of synthesis, or audio seconds
        transcribed per second, from customer requests only: a probe's output is too short to
        measure speed.

        The window is split into intervals (2 hours for 24h, 6 hours for 7d, a day for 30d); the
        last interval is the one in progress. A model served by more than one provider has a
        summary and series for each. A model that several hosts serve (openai/gpt-oss-120b)
        combines every host's numbers and splits them by endpoint, under the routing tag the
        endpoints document uses; a host-pinned ID covers that host alone. Answers are refreshed
        every few minutes and may be cached for a minute.
      security:
        - {}
        - bearerAuth: []
      parameters:
        - name: window
          in: query
          required: false
          schema:
            type: string
            enum: ["24h", "7d", "30d"]
            default: "24h"
      responses:
        "200":
          description: The model's stats for the window.
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                properties:
                  data:
                    $ref: "#/components/schemas/ModelStats"
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          $ref: "#/components/responses/NotFound"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/providers:
    get:
      tags:
        - Catalog
      summary: List providers.
      operationId: listProviders
      responses:
        "200":
          description: Provider list.
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Provider"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/providers/{provider_id}:
    parameters:
      - $ref: "#/components/parameters/ProviderId"
    get:
      tags:
        - Catalog
      summary: Get provider metadata.
      operationId: getProvider
      responses:
        "200":
          description: Provider metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Provider"
        "404":
          $ref: "#/components/responses/NotFound"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/endpoints:
    get:
      tags:
        - Catalog
      summary: List provider endpoints.
      operationId: listEndpoints
      parameters:
        - name: model
          in: query
          required: false
          schema:
            type: string
        - name: provider
          in: query
          required: false
          schema:
            type: string
      responses:
        "200":
          description: Endpoint list.
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Endpoint"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/logs:
    get:
      tags: [Requests]
      summary: List workspace request logs across speech and LLM products.
      description: |
        Returns recorded inference metadata, including failed and pending requests.
        Session callers must provide workspace_id and have read access. API keys
        require requests.read and can only read their own workspace. No prompts,
        audio, internal routing traces or provider costs are returned. Rejections
        before request creation and requests already removed by retention are not
        included. A null cost or unconfirmed outcome is not evidence of success.
      operationId: listLogs
      parameters:
        - name: workspace_id
          in: query
          description: Required for sessions; must match an API key's workspace when supplied.
          schema: { type: string, minLength: 1, maxLength: 256 }
        - name: request_id
          in: query
          description: Exact request ID, including historical generation IDs.
          schema: { type: string, minLength: 1, maxLength: 256 }
        - name: search
          in: query
          description: Literal substring of request ID, model, provider or key name.
          schema: { type: string, maxLength: 120 }
        - name: task
          in: query
          schema: { $ref: "#/components/schemas/LogTask" }
        - name: state
          in: query
          schema: { type: string, enum: [queued, running, succeeded, failed, canceled, unknown] }
        - name: key_id
          in: query
          schema: { type: string, minLength: 1, maxLength: 256 }
        - name: after
          in: query
          description: Inclusive creation time lower bound.
          schema: { type: string, format: date-time }
        - name: before
          in: query
          description: Exclusive creation time upper bound.
          schema: { type: string, format: date-time }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 100, default: 50 }
        - name: cursor
          in: query
          description: Opaque cursor from the previous page; retain the same workspace and filters.
          schema: { type: string, maxLength: 1200 }
      responses:
        "200":
          description: Request metadata ordered newest first, with deterministic pagination.
          headers:
            Cache-Control:
              schema: { type: string, const: no-store }
          content:
            application/json:
              schema:
                type: object
                required: [data, next_cursor]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/LogEntry" }
                  next_cursor:
                    type: [string, "null"]
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/logs/export:
    get:
      tags: [Requests]
      summary: Export workspace request logs as CSV or JSON Lines.
      description: |
        Downloads the same request metadata as List Request Logs for a date range, oldest first, as a
        streamed file attachment. Session callers must provide workspace_id and have read access. API keys
        require requests.read and can only export their own workspace. No prompts, audio or outputs are included.

        An export spans at most 366 days and 100,000 requests. A range with more is refused with 400 before
        anything is sent; choose a shorter range or one product. Without after and before, the export covers
        the last 30 days. One export per workspace runs at a time; another returns 429.

        CSV columns: id, created_at, completed_at, task, protocol, model, resolved_model, provider, state, cost,
        input_tokens, output_tokens, input_chars, audio_seconds, duration_ms, streamed, error_code, key_id,
        key_name. A cell that begins with =, +, -, @ or a tab is prefixed with a single quote so spreadsheets
        do not run it as a formula. JSON Lines has one LogEntry object per line.
      operationId: exportLogs
      parameters:
        - name: workspace_id
          in: query
          description: Required for sessions; must match an API key's workspace when supplied.
          schema: { type: string, minLength: 1, maxLength: 256 }
        - name: format
          in: query
          schema: { type: string, enum: [csv, jsonl], default: csv }
        - name: task
          in: query
          description: Export one product only.
          schema: { $ref: "#/components/schemas/LogTask" }
        - name: state
          in: query
          schema: { type: string, enum: [queued, running, succeeded, failed, canceled, unknown] }
        - name: key_id
          in: query
          schema: { type: string, minLength: 1, maxLength: 256 }
        - name: after
          in: query
          description: Inclusive creation time lower bound. Defaults to 30 days before `before`.
          schema: { type: string, format: date-time }
        - name: before
          in: query
          description: Exclusive creation time upper bound. Defaults to now.
          schema: { type: string, format: date-time }
      responses:
        "200":
          description: The log file, streamed as an attachment named for its range and product.
          headers:
            Cache-Control:
              schema: { type: string, const: no-store }
            Content-Disposition:
              schema: { type: string }
              description: attachment; filename="audiolab-logs-<start>-to-<end>[-<task>].<format>"
          content:
            text/csv:
              schema: { type: string }
            application/x-ndjson:
              schema: { type: string }
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/requests/{request_id}:
    parameters:
      - $ref: "#/components/parameters/RequestId"
    get:
      tags:
        - Requests
      summary: Get request metadata and redacted route trace.
      operationId: getRequest
      responses:
        "200":
          description: Request metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RequestLog"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/traces/destinations:
    get:
      tags: [Traces]
      summary: List a workspace's trace destinations.
      description: |
        Requires a dashboard session with read access to the workspace; API keys are rejected.
        Credentials are never returned: only header names and the last characters of a signing secret or
        Langfuse public key.
      operationId: listTraceDestinations
      parameters:
        - $ref: "#/components/parameters/TraceWorkspace"
      responses:
        "200":
          description: Trace destinations, oldest first.
          headers:
            Cache-Control:
              schema: { type: string, const: no-store }
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/TraceDestination" }
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/InternalError"
    post:
      tags: [Traces]
      summary: Add a trace destination.
      description: |
        Requires an owner, admin or developer session; API keys are rejected. Only an owner or admin can turn on
        include_content. A workspace can have at most 10 destinations.

        The endpoint must be a public https URL on port 443, 4318 or 8443, with no credentials in it. A webhook
        destination is given a signing secret, returned once in signing_secret. A Langfuse destination is named
        by its host only, such as https://cloud.langfuse.com, and authenticates with its public and secret keys.
        Headers, keys and the signing secret are stored encrypted. See the Trace Export guide (/docs/trace-export).
      operationId: createTraceDestination
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/TraceDestinationCreate" }
            example:
              workspace_id: ws_example
              name: Honeycomb
              kind: otlp
              endpoint: https://api.honeycomb.io/v1/traces
              encoding: protobuf
              headers: { x-honeycomb-team: YOUR_HONEYCOMB_API_KEY }
              sample_rate: 1
              products: [llm]
      responses:
        "201":
          description: The destination. A webhook's signing secret is returned once.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TraceDestinationSecret" }
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/traces/destinations/{destination_id}:
    parameters:
      - $ref: "#/components/parameters/TraceDestinationId"
    get:
      tags: [Traces]
      summary: Get a trace destination and its last delivery.
      description: Requires a dashboard session with read access to the workspace; API keys are rejected.
      operationId: getTraceDestination
      parameters:
        - $ref: "#/components/parameters/TraceWorkspace"
      responses:
        "200":
          description: The destination, with counts of traces waiting and dead-lettered.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TraceDestination" }
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
    patch:
      tags: [Traces]
      summary: Update a trace destination.
      description: |
        Requires an owner, admin or developer session; API keys are rejected. Only an owner or admin can turn on
        include_content. Omitted fields keep their values; headers, when sent, replace every saved header.
        rotate_secret replaces a webhook's signing secret and returns the new one once. Turning include_content
        off also strips prompts and outputs from traces still waiting for this destination.
      operationId: updateTraceDestination
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/TraceDestinationUpdate" }
            example: { workspace_id: ws_example, sample_rate: 0.25, enabled: true }
      responses:
        "200":
          description: The updated destination. signing_secret is present only after rotate_secret.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TraceDestinationSecret" }
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
    delete:
      tags: [Traces]
      summary: Delete a trace destination.
      description: |
        Requires an owner, admin or developer session; API keys are rejected. Traces waiting to be delivered to
        the destination are dropped.
      operationId: deleteTraceDestination
      parameters:
        - $ref: "#/components/parameters/TraceWorkspace"
      responses:
        "204":
          description: Deleted.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/traces/destinations/{destination_id}/test:
    parameters:
      - $ref: "#/components/parameters/TraceDestinationId"
    post:
      tags: [Traces]
      summary: Send a test trace to a destination.
      description: |
        Requires an owner, admin or developer session; API keys are rejected. Sends one synthetic trace for model
        audiolab/test now, whether or not the destination is enabled, and reports what the destination answered.
        With include_content on, the test carries a short made-up prompt and output. A workspace can send 10
        tests a minute.
      operationId: testTraceDestination
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [workspace_id]
              properties:
                workspace_id: { type: string, minLength: 1, maxLength: 256 }
      responses:
        "200":
          description: The destination's answer. ok is false when it refused the trace or could not be reached.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TraceTestResult" }
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/traces/destinations/{destination_id}/deliveries:
    parameters:
      - $ref: "#/components/parameters/TraceDestinationId"
    get:
      tags: [Traces]
      summary: List a destination's recent deliveries.
      description: |
        Requires a dashboard session with read access to the workspace; API keys are rejected. Returns delivery
        metadata only, newest first: dead letters by default. A dead letter is a trace that failed six attempts
        or was refused outright; it is kept for 7 days. Delivered traces are kept for 1 day.
      operationId: listTraceDeliveries
      parameters:
        - $ref: "#/components/parameters/TraceWorkspace"
        - name: state
          in: query
          schema: { type: string, enum: [pending, delivered, dead], default: dead }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 100, default: 50 }
      responses:
        "200":
          description: Deliveries in the requested state.
          headers:
            Cache-Control:
              schema: { type: string, const: no-store }
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/TraceDelivery" }
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/usage:
    get:
      tags:
        - Usage
      summary: List settled customer usage across products.
      description: Requires usage.read. Results are ordered by accounting time with an opaque, filter-bound cursor. from is inclusive and to is exclusive. Pending paid requests are excluded; inspect logs for unresolved holds. Legacy event timestamps can be backfilled, so pagination is not a database snapshot.
      operationId: listUsage
      parameters:
        - name: workspace_id
          in: query
          schema:
            type: string
          description: Select an authorized workspace for session callers. API keys are always scoped to their own workspace.
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
        - name: from
          in: query
          required: false
          schema:
            type: string
            format: date-time
        - name: to
          in: query
          required: false
          schema:
            type: string
            format: date-time
      responses:
        "200":
          description: Usage page.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UsagePage"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/usage/{request_id}:
    parameters:
      - $ref: "#/components/parameters/RequestId"
    get:
      tags:
        - Usage
      summary: Get settled usage for one durable request ID.
      description: Use x-request-id from the inference response, not the compatible chat completion body ID. Unsettled requests return 404; logs retain their status. Requires usage.read.
      operationId: getUsage
      responses:
        "200":
          description: Usage row.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Usage"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/ledger:
    get:
      tags:
        - Usage
      summary: List ledger rows.
      operationId: listLedger
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
        - name: view
          in: query
          description: Use transactions for posted charges, purchases, and adjustments, excluding temporary holds and hold releases.
          schema:
            type: string
            enum: [transactions]
      responses:
        "200":
          description: Ledger page.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LedgerPage"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/credits:
    get:
      tags:
        - Usage
      summary: Get current credit balance.
      operationId: getCredits
      responses:
        "200":
          description: Credit balance.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Credits"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/key:
    get:
      tags:
        - Usage
      summary: Get the calling API key.
      description: OpenRouter-compatible key details for the key that authenticates the call. usage is settled spend plus amounts held for requests in progress, across products. A null rate_limit.requests means no per-key limit is set. Needs no extra scope. Also available under /api/v1.
      operationId: getCurrentKey
      responses:
        "200":
          description: Key details.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CurrentKey"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/generation:
    get:
      tags:
        - Usage
      summary: Get one LLM generation.
      description: OpenRouter-compatible generation details from durable accounting records. id accepts the response id (gen-…, msg_gen-…, resp_…) or the x-request-id header. An API key sees its own requests; a dashboard session sees its workspaces'. Unknown IDs and other owners' IDs both return 404. Costs and tokens are null until the request settles. Also available under /api/v1.
      operationId: getGeneration
      parameters:
        - name: id
          in: query
          required: true
          schema:
            type: string
            maxLength: 200
      responses:
        "200":
          description: Generation details.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Generation"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/billing/config:
    get:
      tags:
        - Usage
      summary: Get customer credit-purchase settings.
      operationId: getPaymentConfig
      responses:
        "200":
          description: Payment availability and allowed credit range.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaymentConfig"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/billing/checkout:
    post:
      tags:
        - Usage
      summary: Create a checkout transaction for prepaid API credits.
      operationId: createCreditCheckout
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CheckoutReq"
      responses:
        "201":
          description: Checkout transaction created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CheckoutRes"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "503":
          $ref: "#/components/responses/Unavailable"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/billing/checkout/{transaction_id}/sync:
    post:
      tags:
        - Usage
      summary: Reconcile a returned payment transaction.
      description: |
        Retrieves the transaction from the payment provider and grants credits
        only when it is completed. This is idempotent with webhook fulfillment.
      operationId: syncCreditCheckout
      parameters:
        - name: transaction_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Current purchase state.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreditPurchase"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "503":
          $ref: "#/components/responses/Unavailable"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/billing/auto-top-up:
    get:
      tags:
        - Usage
      summary: Get workspace automatic top-up settings.
      operationId: getAutoTopup
      parameters:
        - name: workspace_id
          in: query
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Current automatic top-up policy and payment method summary.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AutoTopup"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/InternalError"
    patch:
      tags:
        - Usage
      summary: Update or disable workspace automatic top-up.
      operationId: updateAutoTopup
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AutoTopupUpdate"
      responses:
        "200":
          description: Updated automatic top-up policy.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AutoTopup"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "503":
          $ref: "#/components/responses/Unavailable"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/billing/auto-top-up/setup:
    post:
      tags:
        - Usage
      summary: Start reusable payment-method setup for automatic top-up.
      description: |
        Creates the first top-up purchase and opens checkout. The customer must
        opt in to save the payment method for future purchases. Completing it
        adds the purchased credits, stores only processor identifiers and
        non-sensitive display details, then enables the selected policy.
      operationId: createAutoTopupSetup
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AutoTopupSetupReq"
      responses:
        "201":
          description: Initial top-up checkout created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AutoTopupSetupRes"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "503":
          $ref: "#/components/responses/Unavailable"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/billing/auto-top-up/setup/{transaction_id}/sync:
    post:
      tags:
        - Usage
      summary: Reconcile an automatic top-up setup transaction.
      operationId: syncAutoTopupSetup
      parameters:
        - name: transaction_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Enabled automatic top-up policy.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AutoTopup"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "503":
          $ref: "#/components/responses/Unavailable"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/billing/webhook:
    post:
      tags:
        - Usage
      summary: Receive signed Paddle payment lifecycle events.
      description: >-
        Fulfills completed transactions and automatic top-ups, then reconciles
        adjustment and chargeback credit reversals idempotently.
      operationId: receivePaddleWebhook
      security: []
      parameters:
        - name: Paddle-Signature
          in: header
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
      responses:
        "200":
          description: Event received.
          content:
            application/json:
              schema:
                type: object
                required:
                  - received
                properties:
                  received:
                    type: boolean
                    const: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "503":
          $ref: "#/components/responses/Unavailable"
        "500":
          $ref: "#/components/responses/InternalError"

components:
  securitySchemes:
    routerSession:
      type: "http"
      scheme: "bearer"
      bearerFormat: "sess.* dashboard session"
      description: "Dashboard session token from AudioLab sign-in. Named-router control-plane routes reject inference API keys, regardless of their inference scopes."
    routerCookie:
      type: "apiKey"
      in: "cookie"
      name: "__Host-costream_session"
      description: "HttpOnly secure dashboard session cookie used when NODE_ENV=production, including hosted staging. Browser POST/PATCH requests require Sec-Fetch-Site:same-origin or an Origin equal to the configured public request origin. Never expose the cookie to JavaScript."
    routerDevCookie:
      type: "apiKey"
      in: "cookie"
      name: "costream_session"
      description: "Development/test cookie alternative when NODE_ENV is not production. Same-origin POST/PATCH checks still apply."
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API key or session token
      description: |
        Use `Authorization: Bearer <token>`. Dashboard/control-plane routes
        accept session tokens. Data-plane routes accept `sk_*` API keys.

  parameters:
    RouterWorkspace:
      name: "workspace_id"
      in: "query"
      required: true
      schema:
        "$ref": "#/components/schemas/RouterIdentity"
      description: "Explicit workspace scope; the authenticated session must hold a permitted active membership."
    RouterId:
      name: "id"
      in: "path"
      required: true
      schema:
        "$ref": "#/components/schemas/RouterIdentity"
      description: "Router identity in the explicitly supplied workspace."
    RouterRevisionId:
      name: "revision"
      in: "path"
      required: true
      schema:
        type: "integer"
        minimum: 1
        maximum: 1000
      description: "Immutable revision number, separate from router/<slug>."
    RouterCommandKey:
      name: "Idempotency-Key"
      in: "header"
      required: true
      schema:
        type: "string"
        minLength: 1
        maxLength: 255
        pattern: "^[^\\u0000-\\u001f\\u007f]+$"
      description: "Required for create, duplicate and publish. Scoped to workspace, authenticated actor, operation and source router. The same key and normalized request replay the committed response without new side effects; a changed request returns 409. A replay returns the original generation even if the router has since changed or been archived, so reload before the next mutation. Current membership is checked before replay. Failed transactions do not reserve the key. No inference idempotency or automatic activation is implied."
    WorkspaceId:
      name: workspace_id
      in: query
      required: false
      schema:
        type: string
      description: Workspace id. Required when a session spans multiple workspaces.
    WorkspacePathId:
      name: workspace_id
      in: path
      required: true
      schema:
        type: string
      description: Workspace id.
    KeyId:
      name: key_id
      in: path
      required: true
      schema:
        type: string
    ModelId:
      name: model_id
      in: path
      required: true
      schema:
        type: string
      description: Exact model slug or route alias.
    ProviderId:
      name: provider_id
      in: path
      required: true
      schema:
        type: string
    OAuthProvider:
      name: provider
      in: path
      required: true
      schema:
        type: string
        enum:
          - google
          - github
    RequestId:
      name: request_id
      in: path
      required: true
      schema:
        type: string
    Limit:
      name: limit
      in: query
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20
    Cursor:
      name: cursor
      in: query
      required: false
      schema:
        type: string
    TraceWorkspace:
      name: workspace_id
      in: query
      required: true
      schema: { type: string, minLength: 1, maxLength: 256 }
      description: The destination's workspace; the session must be a member.
    TraceDestinationId:
      name: destination_id
      in: path
      required: true
      schema: { type: string, maxLength: 64 }
      description: Trace destination ID (trd_…).

  responses:
    ErrorResponse:
      description: The request could not be completed.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    RouterConflict:
      description: "Stale expected_generation, archived state, reserved slug, or an Idempotency-Key reused with different command content. Re-read current state before editing; do not blindly retry with a new generation."
      content:
        "application/json":
          schema:
            "$ref": "#/components/schemas/Error"
    RouterTooLarge:
      description: "JSON body exceeds 131072 bytes."
      content:
        "application/json":
          schema:
            "$ref": "#/components/schemas/Error"
    RouterIneligible:
      description: "Draft cannot publish or the requested restore is no longer eligible under current policy/catalog. Also used for workspace router or revision limits. Use validate for structured blocking issues. No revision, active-pointer change or idempotency claim is committed on failure."
      content:
        "application/json":
          schema:
            "$ref": "#/components/schemas/Error"
    BadRequest:
      description: Request validation failed.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    Unauthorized:
      description: Authentication is missing or invalid.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    PaymentRequired:
      description: Credits or billing setup is required.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    Forbidden:
      description: Authenticated principal lacks permission.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    NotFound:
      description: Resource was not found.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    Conflict:
      description: Request conflicts with current resource state.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    AudioTooLarge:
      description: >-
        Uploaded audio exceeds 500,000,000 bytes, or the request body exceeds this
        endpoint's limit. The error code is payload_too_large; param is file or
        input_audio.data when the audio itself is too large.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    UnsupportedMedia:
      description: Audio format is unsupported.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    Unprocessable:
      description: Audio could not be processed.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    RateLimited:
      description: Rate limit exceeded.
      headers:
        Retry-After:
          schema:
            type: integer
          description: Seconds to wait before retrying.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    ProviderError:
      description: Upstream provider failed after allowed attempts.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    Unavailable:
      description: No eligible provider endpoint is currently available, or async processing has no healthy worker polling the durable queue.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    NotImplemented:
      description: Endpoint shape is implemented, but required provider execution is not available yet.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    InternalError:
      description: Internal server error.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"

  schemas:
    McpMessage:
      type: object
      description: One JSON-RPC 2.0 message, as the Model Context Protocol defines it.
      required:
        - jsonrpc
      properties:
        jsonrpc:
          type: string
          const: "2.0"
        id:
          description: Present on requests; absent on notifications.
          oneOf:
            - type: string
            - type: integer
        method:
          type: string
          examples:
            - initialize
            - tools/list
            - tools/call
        params:
          type: object
          additionalProperties: true
      examples:
        - jsonrpc: "2.0"
          id: 1
          method: tools/call
          params:
            name: list_models
            arguments:
              task: speech
              limit: 5
    LlmFile:
      type: object
      required: [id, object, bytes, created_at, filename, purpose, status]
      properties:
        id: { type: string, example: file-abc123 }
        object: { type: string, const: file }
        bytes: { type: integer }
        created_at: { type: integer, description: Unix seconds. }
        filename: { type: string }
        purpose: { type: string, enum: [batch, batch_output] }
        status: { type: string, const: processed }
        expires_at: { type: 'null', description: Files are kept until deleted. }
    LlmBatch:
      type: object
      required: [id, object, endpoint, input_file_id, completion_window, status, created_at, request_counts]
      properties:
        id: { type: string, example: batch_abc123 }
        object: { type: string, const: batch }
        endpoint: { type: string }
        errors: { type: 'null' }
        input_file_id: { type: string }
        completion_window: { type: string, const: 24h }
        status: { type: string, enum: [in_progress, finalizing, completed, expired, cancelling, cancelled] }
        output_file_id: { type: [string, 'null'] }
        error_file_id: { type: [string, 'null'] }
        created_at: { type: integer }
        in_progress_at: { type: [integer, 'null'] }
        expires_at: { type: [integer, 'null'] }
        finalizing_at: { type: [integer, 'null'] }
        completed_at: { type: [integer, 'null'] }
        failed_at: { type: 'null' }
        expired_at: { type: [integer, 'null'] }
        cancelling_at: { type: [integer, 'null'] }
        cancelled_at: { type: [integer, 'null'] }
        request_counts:
          type: object
          required: [total, completed, failed]
          properties:
            total: { type: integer }
            completed: { type: integer }
            failed: { type: integer }
        metadata: { type: [object, 'null'], additionalProperties: { type: string } }
    RouterIdentity:
      type: "string"
      minLength: 1
      maxLength: 120
      pattern: "^[a-zA-Z0-9_-]+$"
    RouterSlug:
      type: "string"
      minLength: 1
      maxLength: 64
      pattern: "^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$"
    RouterGeneration:
      type: "integer"
      minimum: 1
      maximum: 2147483647
      description: "Optimistic version of the router record. Every successful mutation increments it. Send the observed value as expected_generation; stale values return 409."
    RouterExpected:
      type: "integer"
      minimum: 1
      maximum: 2147483646
      description: "Current generation observed by the caller. Re-read the router after a 409; do not overwrite a concurrent edit."
    RouterProtocol:
      type: "string"
      enum:
        - "chat"
        - "responses"
        - "messages"
    RouterCapability:
      type: "string"
      enum:
        - "tools"
        - "json_schema"
        - "vision"
        - "reasoning"
        - "streaming"
    RouterParam:
      type: "string"
      enum:
        - "max_tokens"
        - "temperature"
        - "top_p"
        - "system_prompt"
    RouterModelId:
      type: "string"
      minLength: 3
      maxLength: 160
      pattern: "^~?[a-zA-Z0-9][a-zA-Z0-9._:-]*/[a-zA-Z0-9][a-zA-Z0-9._:/-]*$"
      not:
        pattern: "^router/"
      description: "A catalog model ID: a model several hosts serve (its hosts are ranked as for a request), a host's own ID, either with :nitro, :floor, :exacto, :free or :thinking, or a ~author/family-latest alias. An entry is eligible when one endpoint it can route to is. Recursive router aliases are forbidden."
    RouterProviderPreferences:
      type: "object"
      additionalProperties: false
      description: "Provider preferences for a variant's models, as on a request. only, ignore, quantizations, max_price, zdr, data_collection, data_region and allow_fallbacks apply together with the caller's, so the stricter wins; order, sort, preferred_min_throughput and preferred_max_latency replace the caller's."
      properties:
        order: { type: "array", maxItems: 16, items: { type: "string", maxLength: 64 } }
        only: { type: "array", maxItems: 16, items: { type: "string", maxLength: 64 } }
        ignore: { type: "array", maxItems: 16, items: { type: "string", maxLength: 64 } }
        allow_fallbacks: { type: "boolean" }
        sort:
          oneOf:
            - { type: "string", enum: ["price", "throughput", "latency", "exacto"] }
            - type: "object"
              additionalProperties: false
              required: ["by"]
              properties:
                by: { type: "string", enum: ["price", "throughput", "latency", "exacto"] }
                partition: { type: "string", enum: ["model", "none"] }
        quantizations:
          type: "array"
          items: { type: "string", enum: ["int4", "int8", "fp4", "mxfp4", "nvfp4", "fp6", "fp8", "mxfp8", "fp16", "bf16", "fp32", "unknown"] }
        data_collection: { type: "string", enum: ["allow", "deny"] }
        zdr: { type: "boolean" }
        data_region:
          "$ref": "#/components/schemas/DataRegion"
        max_price:
          type: "object"
          additionalProperties: false
          properties:
            prompt: { type: "number", minimum: 0, maximum: 1000000, description: "USD per million input tokens." }
            completion: { type: "number", minimum: 0, maximum: 1000000, description: "USD per million output tokens." }
        preferred_min_throughput:
          description: "Output tokens per second; a number is the p50."
          oneOf:
            - { type: "number", minimum: 0 }
            - { type: "object", additionalProperties: false, minProperties: 1, properties: { p50: { type: "number" }, p75: { type: "number" }, p90: { type: "number" }, p99: { type: "number" } } }
        preferred_max_latency:
          description: "Seconds to first token; a number is the p50."
          oneOf:
            - { type: "number", minimum: 0 }
            - { type: "object", additionalProperties: false, minProperties: 1, properties: { p50: { type: "number" }, p75: { type: "number" }, p90: { type: "number" }, p99: { type: "number" } } }
    RouterUsd:
      type: "string"
      pattern: "^(0|[1-9][0-9]{0,5})(\\.[0-9]{1,12})?$"
      description: "Exact decimal USD per native token. Strings avoid binary floating-point ambiguity. A limit of zero is allowed; approved native catalog prices must be positive."
    RouterDefaults:
      type: "object"
      additionalProperties: false
      required: []
      properties:
        max_tokens:
          type: "integer"
          minimum: 1
          maximum: 1000000
        temperature:
          type: "number"
          minimum: 0
          maximum: 2
        top_p:
          type: "number"
          minimum: 0
          maximum: 1
        system_prompt:
          type: "string"
          minLength: 0
          maxLength: 16000
      description: "Field-wise inheritance: router defaults, then route defaults, then variant defaults, then explicitly permitted request parameters. Omitted fields inherit; null does not clear a field. max_tokens defaults to policy.max_output_tokens and cannot exceed that policy. Every effective and overridable parameter must be supported by every selected model."
    RouterPolicy:
      type: "object"
      additionalProperties: false
      required: []
      properties:
        max_input_tokens:
          type: "integer"
          minimum: 1
          maximum: 1000000
          default: 128000
        max_output_tokens:
          type: "integer"
          minimum: 1
          maximum: 1000000
          default: 8192
        deadline_ms:
          type: "integer"
          minimum: 1000
          maximum: 1800000
          default: 120000
        regions:
          type: "array"
          items:
            "$ref": "#/components/schemas/RouterSlug"
          maxItems: 16
          default: []
          description: "Regions every route requires: us, eu or a documented country code, any of which an endpoint may process in. Empty or global means none. Each model must have an endpoint documented in one of them, and requests can narrow it but never widen it. A region no endpoint documents is refused with 422 on create, update and duplicate, and validate reports it as region_unavailable."
        zero_retention:
          type: "boolean"
          default: false
          description: "Reserved. Router-level zero retention is not available, so true is refused with 422 and validate reports zero_retention_unavailable. Workspace Privacy settings, including zero data retention per provider family, still apply to every route."
        max_price:
          type: "object"
          additionalProperties: false
          required:
            - "input"
            - "output"
          properties:
            input:
              "$ref": "#/components/schemas/RouterUsd"
            output:
              "$ref": "#/components/schemas/RouterUsd"
      description: "Declarative upper limits, intersected with current operator/workspace/model restrictions. A model must support the full configured input plus output context budget. Region and zero-retention requirements cannot widen workspace policy."
    RouterScalar:
      oneOf:
        -
          type: "string"
          minLength: 0
          maxLength: 512
        -
          type: "number"
          minimum: -1000000000000
          maximum: 1000000000000
        -
          type: "boolean"
    RouterField:
      oneOf:
        -
          type: "string"
          enum:
            - "input_tokens"
            - "output_tokens"
            - "protocol"
        -
          type: "string"
          pattern: "^metadata\\.[a-zA-Z][a-zA-Z0-9_-]{0,63}$"
          not:
            enum:
              - "metadata.constructor"
              - "metadata.prototype"
              - "metadata.__proto__"
    RouterCondition:
      description: "Bounded declarative AST: at most 256 condition nodes across the document, depth 8, and 16 children per boolean group. No evaluation of source code, regular expressions, network access or clock values. Routes evaluate in order. Missing fields never satisfy comparisons, including neq; use not/exists explicitly. Numeric comparisons require numeric operands. Metadata is caller-supplied routing data and cannot establish authorization."
      oneOf:
        -
          type: "object"
          additionalProperties: false
          required:
            - "all"
          properties:
            all:
              type: "array"
              items:
                "$ref": "#/components/schemas/RouterCondition"
              maxItems: 16
              minItems: 1
        -
          type: "object"
          additionalProperties: false
          required:
            - "any"
          properties:
            any:
              type: "array"
              items:
                "$ref": "#/components/schemas/RouterCondition"
              maxItems: 16
              minItems: 1
        -
          type: "object"
          additionalProperties: false
          required:
            - "not"
          properties:
            not:
              "$ref": "#/components/schemas/RouterCondition"
        -
          type: "object"
          additionalProperties: false
          required:
            - "field"
            - "op"
          properties:
            field:
              "$ref": "#/components/schemas/RouterField"
            op:
              const: "exists"
              type: "string"
        -
          type: "object"
          additionalProperties: false
          required:
            - "field"
            - "op"
            - "value"
          properties:
            field:
              "$ref": "#/components/schemas/RouterField"
            op:
              const: "in"
              type: "string"
            value:
              type: "array"
              items:
                "$ref": "#/components/schemas/RouterScalar"
              maxItems: 32
              minItems: 1
        -
          type: "object"
          additionalProperties: false
          required:
            - "field"
            - "op"
            - "value"
          properties:
            field:
              "$ref": "#/components/schemas/RouterField"
            op:
              type: "string"
              enum:
                - "eq"
                - "neq"
            value:
              "$ref": "#/components/schemas/RouterScalar"
        -
          type: "object"
          additionalProperties: false
          required:
            - "field"
            - "op"
            - "value"
          properties:
            field:
              "$ref": "#/components/schemas/RouterField"
            op:
              type: "string"
              enum:
                - "lt"
                - "lte"
                - "gt"
                - "gte"
            value:
              type: "number"
              minimum: -1000000000000
              maximum: 1000000000000
    RouterPool:
      oneOf:
        -
          type: "object"
          additionalProperties: false
          required:
            - "mode"
            - "models"
          properties:
            mode:
              type: "string"
              const: "ordered"
            models:
              type: "array"
              items:
                "$ref": "#/components/schemas/RouterModelId"
              maxItems: 32
            provider:
              "$ref": "#/components/schemas/RouterProviderPreferences"
        -
          type: "object"
          additionalProperties: false
          required:
            - "mode"
            - "models"
            - "objective"
          properties:
            mode:
              type: "string"
              const: "auto"
            models:
              type: "array"
              items:
                "$ref": "#/components/schemas/RouterModelId"
              maxItems: 32
            objective:
              type: "string"
              const: "cost"
            provider:
              "$ref": "#/components/schemas/RouterProviderPreferences"
      description: "Empty pools may be saved and simulated but cannot publish. Models must be unique for publication. Ordered mode preserves fallback order. Auto mode currently supports exact token-cost ranking only; quality or latency ranking is not implied. Every candidate is revalidated, including candidates in zero-weight variants."
    RouterVariant:
      type: "object"
      additionalProperties: false
      required:
        - "id"
        - "weight"
        - "selection"
      properties:
        id:
          "$ref": "#/components/schemas/RouterSlug"
        name:
          type: "string"
          minLength: 1
          maxLength: 120
        weight:
          type: "integer"
          minimum: 0
          maximum: 100
        selection:
          "$ref": "#/components/schemas/RouterPool"
        defaults:
          "$ref": "#/components/schemas/RouterDefaults"
      description: "Names default to the variant ID. Variant IDs must be unique within a route. Integer weights across a route must total exactly 100 to validate or publish; zero-weight variants are never selected."
    RouterRoute:
      type: "object"
      additionalProperties: false
      required:
        - "id"
        - "name"
        - "condition"
        - "variants"
      properties:
        id:
          "$ref": "#/components/schemas/RouterSlug"
        name:
          type: "string"
          minLength: 1
          maxLength: 120
        condition:
          oneOf:
            -
              "$ref": "#/components/schemas/RouterCondition"
            -
              type: "null"
        defaults:
          "$ref": "#/components/schemas/RouterDefaults"
        variants:
          type: "array"
          items:
            "$ref": "#/components/schemas/RouterVariant"
          maxItems: 8
      description: "Exactly one route must have condition:null and it must be last. All other routes have an explicit condition; first match wins. Route IDs must be unique."
    RouterConfig:
      type: "object"
      additionalProperties: false
      required:
        - "schema_version"
        - "task"
        - "protocol"
        - "routes"
      properties:
        schema_version:
          type: "integer"
          const: 1
        task:
          type: "string"
          const: "llm"
        protocol:
          "$ref": "#/components/schemas/RouterProtocol"
        requirements:
          type: "array"
          items:
            "$ref": "#/components/schemas/RouterCapability"
          maxItems: 5
          uniqueItems: true
          default: []
        defaults:
          "$ref": "#/components/schemas/RouterDefaults"
        request_overrides:
          type: "array"
          items:
            "$ref": "#/components/schemas/RouterParam"
          maxItems: 4
          uniqueItems: true
          default: []
        policy:
          "$ref": "#/components/schemas/RouterPolicy"
        routes:
          type: "array"
          items:
            "$ref": "#/components/schemas/RouterRoute"
          maxItems: 32
      description: "Versioned declarative LLM router configuration, normalized to at most 65536 UTF-8 JSON bytes. Unknown fields and undocumented null values are rejected. Structurally valid but incomplete drafts are permitted; validation/publication enforce final-default, nonempty pools, uniqueness, exact weights and current model eligibility."
      example:
        schema_version: 1
        task: "llm"
        protocol: "chat"
        requirements: []
        defaults: {}
        request_overrides: []
        policy:
          max_input_tokens: 128000
          max_output_tokens: 8192
          deadline_ms: 120000
          regions: []
          zero_retention: false
        routes:
          -
            id: "default"
            name: "Default"
            condition: null
            defaults: {}
            variants:
              -
                id: "primary"
                name: "Primary"
                weight: 100
                selection:
                  mode: "ordered"
                  models: []
                defaults: {}
    NamedRouter:
      type: "object"
      additionalProperties: false
      required:
        - "id"
        - "workspace_id"
        - "slug"
        - "alias"
        - "name"
        - "task"
        - "generation"
        - "draft"
        - "active_revision"
        - "archived_at"
        - "created_at"
        - "updated_at"
      properties:
        id:
          "$ref": "#/components/schemas/RouterIdentity"
        workspace_id:
          "$ref": "#/components/schemas/RouterIdentity"
        slug:
          "$ref": "#/components/schemas/RouterSlug"
        alias:
          type: "string"
          pattern: "^router/[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$"
          example: "router/coding"
        name:
          type: "string"
          minLength: 1
          maxLength: 120
        task:
          type: "string"
          const: "llm"
        generation:
          "$ref": "#/components/schemas/RouterGeneration"
        draft:
          "$ref": "#/components/schemas/RouterConfig"
        active_revision:
          oneOf:
            -
              type: "integer"
              minimum: 1
              maximum: 1000
            -
              type: "null"
        archived_at:
          oneOf:
            -
              type: "string"
              format: "date-time"
            -
              type: "null"
        created_at:
          type: "string"
          format: "date-time"
        updated_at:
          type: "string"
          format: "date-time"
    RouterRevision:
      type: "object"
      additionalProperties: false
      required:
        - "router_id"
        - "revision"
        - "schema_version"
        - "config"
        - "config_hash"
        - "catalog_revision"
        - "restored_from"
        - "created_by"
        - "created_at"
      properties:
        router_id:
          "$ref": "#/components/schemas/RouterIdentity"
        revision:
          type: "integer"
          minimum: 1
          maximum: 1000
        schema_version:
          type: "integer"
          const: 1
        config:
          "$ref": "#/components/schemas/RouterConfig"
        config_hash:
          type: "string"
          pattern: "^[a-f0-9]{64}$"
        catalog_revision:
          type: "string"
          minLength: 1
          maxLength: 160
        restored_from:
          oneOf:
            -
              type: "integer"
              minimum: 1
              maximum: 1000
            -
              type: "null"
        created_by:
          oneOf:
            -
              "$ref": "#/components/schemas/RouterIdentity"
            -
              type: "null"
        created_at:
          type: "string"
          format: "date-time"
      description: "Immutable publication evidence. config_hash is SHA-256 of the canonical normalized configuration. catalog_revision records publication-time evidence; execution must still revalidate current eligibility. A restore is a new revision referring to its source via restored_from."
    RouterCreate:
      type: "object"
      additionalProperties: false
      required:
        - "workspace_id"
        - "name"
        - "slug"
      properties:
        workspace_id:
          "$ref": "#/components/schemas/RouterIdentity"
        name:
          type: "string"
          minLength: 1
          maxLength: 120
        slug:
          "$ref": "#/components/schemas/RouterSlug"
        task:
          type: "string"
          const: "llm"
        draft:
          "$ref": "#/components/schemas/RouterConfig"
      example:
        workspace_id: "ws_example"
        name: "Coding"
        slug: "coding"
    RouterUpdate:
      type: "object"
      additionalProperties: false
      required:
        - "workspace_id"
        - "expected_generation"
      properties:
        workspace_id:
          "$ref": "#/components/schemas/RouterIdentity"
        expected_generation:
          "$ref": "#/components/schemas/RouterExpected"
        name:
          type: "string"
          minLength: 1
          maxLength: 120
        draft:
          "$ref": "#/components/schemas/RouterConfig"
      anyOf:
        -
          required:
            - "name"
        -
          required:
            - "draft"
    RouterChange:
      type: "object"
      additionalProperties: false
      required:
        - "workspace_id"
        - "expected_generation"
      properties:
        workspace_id:
          "$ref": "#/components/schemas/RouterIdentity"
        expected_generation:
          "$ref": "#/components/schemas/RouterExpected"
    RouterInspect:
      type: "object"
      additionalProperties: false
      required:
        - "workspace_id"
      properties:
        workspace_id:
          "$ref": "#/components/schemas/RouterIdentity"
        expected_generation:
          "$ref": "#/components/schemas/RouterExpected"
        revision:
          type: "integer"
          minimum: 1
          maximum: 1000
        config:
          "$ref": "#/components/schemas/RouterConfig"
      not:
        properties:
          revision: {}
          config: {}
        required:
          - "revision"
          - "config"
    RouterInput:
      type: "object"
      additionalProperties: false
      required: []
      properties:
        assignment_key:
          type: "string"
          minLength: 1
          maxLength: 256
          default: "preview"
        metadata:
          type: "object"
          maxProperties: 32
          propertyNames:
            type: "string"
            pattern: "^[a-zA-Z][a-zA-Z0-9_-]{0,63}$"
            not:
              enum:
                - "constructor"
                - "prototype"
                - "__proto__"
          additionalProperties:
            "$ref": "#/components/schemas/RouterScalar"
          default: {}
        input_tokens:
          type: "integer"
          minimum: 0
          maximum: 1000000
          default: 0
        output_tokens:
          type: "integer"
          minimum: 0
          maximum: 1000000
          default: 0
        protocol:
          "$ref": "#/components/schemas/RouterProtocol"
        parameters:
          "$ref": "#/components/schemas/RouterDefaults"
      description: "Synthetic simulation facts, never authorization claims. Unspecified protocol uses the router protocol. Input/output counts cannot exceed router policy. Parameters must be explicitly allowed by request_overrides and cannot widen policy. No prompt or paid inference is needed."
    RouterSimulate:
      type: "object"
      additionalProperties: false
      required:
        - "workspace_id"
      properties:
        workspace_id:
          "$ref": "#/components/schemas/RouterIdentity"
        expected_generation:
          "$ref": "#/components/schemas/RouterExpected"
        revision:
          type: "integer"
          minimum: 1
          maximum: 1000
        config:
          "$ref": "#/components/schemas/RouterConfig"
        input:
          "$ref": "#/components/schemas/RouterInput"
      not:
        properties:
          revision: {}
          config: {}
        required:
          - "revision"
          - "config"
      example:
        workspace_id: "ws_example"
        expected_generation: 1
        input:
          assignment_key: "preview-session"
          input_tokens: 100
          metadata:
            tier: "pro"
    RouterRestore:
      type: "object"
      additionalProperties: false
      required:
        - "workspace_id"
        - "expected_generation"
        - "revision"
      properties:
        workspace_id:
          "$ref": "#/components/schemas/RouterIdentity"
        expected_generation:
          "$ref": "#/components/schemas/RouterExpected"
        revision:
          type: "integer"
          minimum: 1
          maximum: 1000
    RouterDuplicate:
      type: "object"
      additionalProperties: false
      required:
        - "workspace_id"
        - "expected_generation"
        - "name"
        - "slug"
      properties:
        workspace_id:
          "$ref": "#/components/schemas/RouterIdentity"
        expected_generation:
          "$ref": "#/components/schemas/RouterExpected"
        name:
          type: "string"
          minLength: 1
          maxLength: 120
        slug:
          "$ref": "#/components/schemas/RouterSlug"
        revision:
          type: "integer"
          minimum: 1
          maximum: 1000
    RouterIssue:
      type: "object"
      additionalProperties: false
      required:
        - "code"
        - "path"
        - "message"
      properties:
        code:
          type: "string"
          minLength: 1
          maxLength: 120
        path:
          type: "string"
        message:
          type: "string"
    RouterEligibility:
      type: "object"
      additionalProperties: false
      required:
        - "model"
        - "eligible"
        - "reasons"
      properties:
        model:
          "$ref": "#/components/schemas/RouterModelId"
        eligible:
          type: "boolean"
        reasons:
          type: "array"
          items:
            type: "string"
    RouterValidation:
      type: "object"
      additionalProperties: false
      required:
        - "generation"
        - "valid"
        - "publishable"
        - "catalog_revision"
        - "issues"
        - "models"
      properties:
        generation:
          "$ref": "#/components/schemas/RouterGeneration"
        valid:
          type: "boolean"
        publishable:
          type: "boolean"
        catalog_revision:
          oneOf:
            -
              type: "string"
              minLength: 1
              maxLength: 160
            -
              type: "null"
        issues:
          type: "array"
          items:
            "$ref": "#/components/schemas/RouterIssue"
        models:
          type: "array"
          items:
            "$ref": "#/components/schemas/RouterEligibility"
      description: "valid covers structural and semantic configuration rules; publishable also checks current workspace entitlement and model/catalog restrictions. No provider calls occur. An empty pool is an explicit validation issue, not an implicit default model."
    RouterSelection:
      type: "object"
      additionalProperties: false
      required:
        - "route_id"
        - "variant_id"
        - "bucket"
        - "trace"
        - "models"
        - "mode"
        - "effective_defaults"
        - "eligible_models"
        - "excluded_models"
      properties:
        route_id:
          "$ref": "#/components/schemas/RouterSlug"
        variant_id:
          "$ref": "#/components/schemas/RouterSlug"
        bucket:
          type: "integer"
          minimum: 0
          maximum: 99
        trace:
          type: "array"
          items:
            type: "object"
            additionalProperties: false
            required:
              - "route_id"
              - "matched"
            properties:
              route_id:
                "$ref": "#/components/schemas/RouterSlug"
              matched:
                type: "boolean"
          maxItems: 32
        models:
          type: "array"
          items:
            "$ref": "#/components/schemas/RouterModelId"
          maxItems: 32
        mode:
          type: "string"
          enum:
            - "ordered"
            - "auto"
        effective_defaults:
          "$ref": "#/components/schemas/RouterDefaults"
        eligible_models:
          type: "array"
          items:
            "$ref": "#/components/schemas/RouterModelId"
          maxItems: 32
        excluded_models:
          type: "array"
          items:
            "$ref": "#/components/schemas/RouterEligibility"
          maxItems: 32
    RouterSimulation:
      type: "object"
      additionalProperties: false
      required:
        - "generation"
        - "valid"
        - "publishable"
        - "catalog_revision"
        - "issues"
        - "models"
        - "selection"
        - "inference_enabled"
      properties:
        generation:
          "$ref": "#/components/schemas/RouterGeneration"
        valid:
          type: "boolean"
        publishable:
          type: "boolean"
        catalog_revision:
          oneOf:
            -
              type: "string"
              minLength: 1
              maxLength: 160
            -
              type: "null"
        issues:
          type: "array"
          items:
            "$ref": "#/components/schemas/RouterIssue"
        models:
          type: "array"
          items:
            "$ref": "#/components/schemas/RouterEligibility"
        selection:
          oneOf:
            -
              "$ref": "#/components/schemas/RouterSelection"
            -
              type: "null"
        inference_enabled:
          type: "boolean"
          const: false
          description: "Always false: simulation never calls a model. Published routers run through Chat Completions, Responses and Messages."
      description: "Explains draft or revision selection. Empty or ineligible pools can still produce route/variant selection with no eligible models. Invalid routing/input semantics produce selection:null with issues. No provider execution, accounting charge, mutation or inference activation occurs."
    RouterList:
      type: "object"
      additionalProperties: false
      required:
        - "data"
        - "next_cursor"
      properties:
        data:
          type: "array"
          items:
            "$ref": "#/components/schemas/NamedRouter"
          maxItems: 100
        next_cursor:
          oneOf:
            -
              "$ref": "#/components/schemas/RouterIdentity"
            -
              type: "null"
    RouterPublication:
      type: "object"
      additionalProperties: false
      required:
        - "generation"
        - "router"
        - "revision"
      properties:
        generation:
          "$ref": "#/components/schemas/RouterGeneration"
        router:
          "$ref": "#/components/schemas/NamedRouter"
        revision:
          "$ref": "#/components/schemas/RouterRevision"
    RouterRevisionList:
      type: "object"
      additionalProperties: false
      required:
        - "generation"
        - "data"
        - "next_cursor"
      properties:
        generation:
          "$ref": "#/components/schemas/RouterGeneration"
        data:
          type: "array"
          items:
            "$ref": "#/components/schemas/RouterRevision"
          maxItems: 50
        next_cursor:
          oneOf:
            -
              type: "integer"
              minimum: 1
              maximum: 1000
            -
              type: "null"
    RouterRevisionRead:
      type: "object"
      additionalProperties: false
      required:
        - "generation"
        - "revision"
      properties:
        generation:
          "$ref": "#/components/schemas/RouterGeneration"
        revision:
          "$ref": "#/components/schemas/RouterRevision"
    RouterCatalogItem:
      type: "object"
      additionalProperties: false
      required:
        - "id"
        - "name"
        - "provider"
        - "protocols"
        - "capabilities"
        - "enabled"
        - "reason"
      properties:
        id:
          "$ref": "#/components/schemas/RouterModelId"
        name:
          type: "string"
          minLength: 1
          maxLength: 160
        provider:
          type: "string"
          minLength: 1
          maxLength: 80
        protocols:
          type: "array"
          items:
            "$ref": "#/components/schemas/RouterProtocol"
          maxItems: 3
        capabilities:
          type: "array"
          items:
            "$ref": "#/components/schemas/RouterCapability"
          maxItems: 5
        enabled:
          type: "boolean"
        reason:
          oneOf:
            -
              type: "string"
            -
              type: "null"
    RouterCatalog:
      type: "object"
      additionalProperties: false
      required:
        - "data"
        - "catalog_revision"
      properties:
        data:
          type: "array"
          items:
            "$ref": "#/components/schemas/RouterCatalogItem"
          maxItems: 10000
        catalog_revision:
          oneOf:
            -
              type: "string"
              minLength: 1
              maxLength: 160
            -
              type: "null"
      example:
        data: []
        catalog_revision: null

    Health:
      type: object
      required:
        - ok
        - service
        - time
      properties:
        ok:
          type: boolean
        service:
          type: string
          example: costream-api
        time:
          type: string
          format: date-time
        version:
          type: string

    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              examples:
                - bad_request
                - auth_error
                - insufficient_credits
                - key_limit_exceeded
                - budget_exceeded
                - access_denied
                - unsupported_feature
                - not_implemented
                - provider_timeout
                - data_region_unavailable
            message:
              type: string
            param:
              type:
                - string
                - "null"
            request_id:
              type:
                - string
                - "null"
            metadata:
              type: object
              description: |
                Typed detail for some refusals. budget_exceeded (402): limit (key_budget or workspace_budget) and budget with scope, period, limit, usage and resets_at. key_limit_exceeded (402): limit key_spend_limit. access_denied (403): policy (workspace or key), rule (models, providers or products), product, provider and model.
              additionalProperties: true

    OAuthProviderStatus:
      type: object
      required:
        - id
        - name
        - configured
        - auth_url
      properties:
        id:
          type: string
          enum:
            - google
            - github
        name:
          type: string
        configured:
          type: boolean
        auth_url:
          type:
            - string
            - "null"

    MeRes:
      type: object
      required:
        - principal
      properties:
        principal:
          oneOf:
            - $ref: "#/components/schemas/SessionPrincipal"
            - $ref: "#/components/schemas/KeyPrincipal"

    SessionPrincipal:
      type: object
      required:
        - type
        - user
        - workspaces
      properties:
        type:
          type: string
          const: session
        user:
          $ref: "#/components/schemas/User"
        workspaces:
          type: array
          items:
            $ref: "#/components/schemas/Workspace"

    KeyPrincipal:
      type: object
      required:
        - type
        - key
        - workspace
      properties:
        type:
          type: string
          const: api_key
        key:
          $ref: "#/components/schemas/ApiKey"
        workspace:
          $ref: "#/components/schemas/Workspace"

    User:
      type: object
      required:
        - id
        - email
        - state
        - created_at
      properties:
        id:
          type: string
        email:
          type: string
          format: email
        name:
          type:
            - string
            - "null"
        state:
          $ref: "#/components/schemas/UserState"
        created_at:
          type: string
          format: date-time
        avatar_url:
          type:
            - string
            - "null"
          description: HTTPS URL of the profile picture from the user's identity provider.

    WorkspaceReq:
      type: object
      required:
        - name
      properties:
        name:
          type: string
          minLength: 2
          maxLength: 80
        slug:
          type: string
          pattern: "^[a-z0-9][a-z0-9-]{1,78}[a-z0-9]$"

    BudgetLimits:
      type: object
      description: Spend limits in USD per UTC calendar period. null means no limit.
      required: [daily, weekly, monthly]
      properties:
        daily: { type: [number, "null"], minimum: 0 }
        weekly: { type: [number, "null"], minimum: 0, description: "Weeks start on Monday." }
        monthly: { type: [number, "null"], minimum: 0 }

    BudgetsPatch:
      type: object
      additionalProperties: false
      description: A number sets that period's limit in USD, null removes it, and periods left out stay as they are.
      properties:
        daily: { type: [number, "null"], minimum: 0, maximum: 1000000 }
        weekly: { type: [number, "null"], minimum: 0, maximum: 1000000 }
        monthly: { type: [number, "null"], minimum: 0, maximum: 1000000 }

    PeriodUsage:
      type: object
      description: USD spent in the current UTC day, week and month, and in total, including holds for requests in progress. Spend counts in the period its request started.
      required: [daily, weekly, monthly, total]
      properties:
        daily: { type: number }
        weekly: { type: number }
        monthly: { type: number }
        total: { type: number }

    AccessPolicy:
      type: object
      additionalProperties: false
      description: |
        Which models, providers and products may be used. Each list holds at most 200 entries. A model entry is an exact model ID or a prefix ending in *, such as anthropic/* or openai/gpt-5*. A model matches through any of its IDs, so blocking openai/gpt-oss-20b also blocks every host that serves it. Provider names are lowercase (openai, anthropic, google, deepinfra). Products: llm, embeddings, transcription, speech, speech-to-speech, images, videos, ocr, rerank, moderation, inference. An empty allowed list allows everything; a blocked entry wins over an allowed one.
      properties:
        allowed_models: { type: array, maxItems: 200, items: { type: string, maxLength: 200 } }
        blocked_models: { type: array, maxItems: 200, items: { type: string, maxLength: 200 } }
        allowed_providers: { type: array, maxItems: 200, items: { type: string, maxLength: 200 } }
        blocked_providers: { type: array, maxItems: 200, items: { type: string, maxLength: 200 } }
        allowed_products:
          type: array
          items: { type: string, enum: [llm, embeddings, transcription, speech, speech-to-speech, images, videos, ocr, rerank, moderation, inference] }
        blocked_products:
          type: array
          items: { type: string, enum: [llm, embeddings, transcription, speech, speech-to-speech, images, videos, ocr, rerank, moderation, inference] }

    WorkspaceGuardrails:
      type: object
      required: [workspace_id, budgets, usage, resets_at, access_policy]
      properties:
        workspace_id: { type: string }
        budgets: { $ref: "#/components/schemas/BudgetLimits" }
        usage: { $ref: "#/components/schemas/PeriodUsage" }
        resets_at:
          type: object
          description: When the current day, week and month end, in UTC.
          required: [daily, weekly, monthly]
          properties:
            daily: { type: string, format: date-time }
            weekly: { type: string, format: date-time }
            monthly: { type: string, format: date-time }
        access_policy:
          oneOf:
            - $ref: "#/components/schemas/AccessPolicy"
            - type: "null"

    GuardrailsPatch:
      type: object
      additionalProperties: false
      minProperties: 1
      properties:
        budgets: { $ref: "#/components/schemas/BudgetsPatch" }
        access_policy:
          oneOf:
            - $ref: "#/components/schemas/AccessPolicy"
            - type: "null"

    BudgetState:
      type: object
      required: [scope, period, limit, usage, remaining, resets_at]
      properties:
        scope: { type: string, enum: [key, workspace] }
        period: { type: string, enum: [daily, weekly, monthly, lifetime], description: "lifetime is the key's spend_limit." }
        limit: { type: number }
        usage: { type: number }
        remaining: { type: number }
        resets_at: { type: [string, "null"], format: date-time, description: "null for a lifetime limit." }

    Workspace:
      type: object
      required:
        - id
        - name
        - slug
        - plan
        - state
        - created_at
      properties:
        id:
          type: string
        name:
          type: string
        slug:
          type: string
        plan:
          type: string
          examples:
            - free
            - pro
            - enterprise
        state:
          $ref: "#/components/schemas/WorkspaceState"
        role:
          type: string
          enum:
            - owner
            - admin
            - developer
            - viewer
            - billing
        created_at:
          type: string
          format: date-time

    AccountPrivacy:
      type: object
      additionalProperties: false
      properties:
        zdr:
          type: object
          additionalProperties: false
          description: Unknown retention policies are excluded when the corresponding restriction is enabled. First-party Anthropic, OpenAI and Google AI Studio endpoints are excluded by their switches.
          properties:
            other: { type: boolean }
            anthropic: { type: boolean }
            openai: { type: boolean }
            google: { type: boolean }
            xai: { type: boolean }
        allow_paid_training: { type: boolean }
        allow_free_training: { type: boolean }
        allow_free_publication: { type: boolean }
        max_key_days:
          type: [integer, "null"]
          minimum: 1
          maximum: 3650
          description: Maximum lifetime measured from creation. Null allows any lifetime. Existing nonexpiring or overlong keys are rejected. New keys without an explicit expiry inherit the cap; explicit noncompliant expiries are rejected.

    DataRegionSetting:
      type: object
      required: [region, mode, regions]
      properties:
        region:
          type: [string, "null"]
          description: The workspace's region, or null for any region.
        mode:
          type: string
          enum: [only, prefer]
          description: only routes every request to endpoints documented in the region; prefer tries them first.
        regions:
          type: array
          items: { type: string }
          description: The regions that can be required.
          examples: [[us, eu, cn, in, nl, sg]]

    DataRegion:
      description: "Route only to endpoints documented as processing in this region: us, eu (which includes EU member states), or a country code such as nl, sg, in or cn. A list matches any of its regions. Endpoints whose region is global or unknown never match; global on its own means no requirement. The request's region narrows the workspace's Data Region and a named router's region policy, and never widens them. Fallbacks keep to it. When no endpoint qualifies, the request is refused with 400 data_region_unavailable, param provider.data_region."
      oneOf:
        - type: string
          maxLength: 16
        - type: array
          minItems: 1
          maxItems: 16
          items:
            type: string
            maxLength: 16
      examples: [eu, [us, eu]]

    RegionProviderPreferences:
      type: object
      additionalProperties: false
      description: Provider preferences for a single-endpoint product.
      properties:
        data_region:
          $ref: "#/components/schemas/DataRegion"

    WorkspaceSettings:
      type: object
      properties:
        guardrails:
          type: object
          properties:
            pii:
              type: boolean
            profanity:
              type: boolean
            moderation:
              type: boolean
            lang_enforce:
              type: boolean
            langs:
              type: string
            max_minutes:
              type:
                - number
                - "null"
            blocklist:
              type: string
        privacy:
          type: object
          properties:
            store_transcripts:
              type: boolean
            store_audio:
              type: boolean
            allow_training:
              type: boolean
            zero_retention:
              type: boolean
              description: Zero data retention for every provider family.
            zdr:
              type: object
              description: Zero data retention per provider family for this workspace. It adds to the account's privacy settings and can only restrict. A patch keeps the families it leaves out.
              additionalProperties: false
              properties:
                other: { type: boolean }
                anthropic: { type: boolean }
                openai: { type: boolean }
                google: { type: boolean }
                xai: { type: boolean }
        routing:
          type: object
          properties:
            sort:
              type: string
              enum:
                - balanced
                - price
                - latency
                - speed
                - quality
            order:
              type: string
            ignore:
              type: string
            allowed_models:
              type: string
              maxLength: 4000
              description: Comma- or newline-separated model ID patterns used as a hard allowlist for managed STT and TTS routes. Supports * wildcards; empty means all supported models.
            cost_quality:
              type: integer
              minimum: 0
              maximum: 10
              description: Balanced-sort weighting from pure quality (0) to lowest estimated cost (10).
            prevent_overrides:
              type: boolean
              description: When true, request-level routing preferences cannot replace workspace routing defaults.
            max_price:
              type:
                - number
                - "null"
              description: Maximum estimated STT price per audio minute.
            max_price_1k:
              type:
                - number
                - "null"
              description: Maximum estimated TTS price per 1,000 generated characters.
            fallbacks:
              type: boolean
        settings:
          type: object
          properties:
            default_route:
              type: string
            region:
              type: string
        preferences:
          type: object
          properties:
            route:
              type: string
            format:
              type: string
              enum:
                - json
                - verbose_json
                - text
                - srt
                - vtt
            timestamps:
              type: string
              enum:
                - none
                - segment
                - word
                - both
            density:
              type: string
              enum:
                - comfortable
                - compact
            mask_keys:
              type: boolean

    KeyReq:
      type: object
      required:
        - workspace_id
        - name
      properties:
        workspace_id:
          type: string
        name:
          type: string
          minLength: 1
          maxLength: 120
        kind:
          type: string
          enum:
            - api
        scopes:
          type: array
          items:
            type: string
          default:
            - audio.write
            - models.read
        spend_limit:
          type:
            - number
            - "null"
          minimum: 0
        rate_limit:
          type:
            - integer
            - "null"
          minimum: 1
          description: Requests per minute.
        expires_at:
          type:
            - string
            - "null"
          format: date-time
        budgets:
          $ref: "#/components/schemas/BudgetsPatch"
        access_policy:
          description: The key's own policy. It applies on top of the workspace's, so it can only narrow what the key may use.
          oneOf:
            - $ref: "#/components/schemas/AccessPolicy"
            - type: "null"

    KeyPatch:
      type: object
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 120
        state:
          type: string
          enum:
            - active
            - disabled
            - revoked
        spend_limit:
          type:
            - number
            - "null"
          minimum: 0
        rate_limit:
          type:
            - integer
            - "null"
          minimum: 1
        expires_at:
          type:
            - string
            - "null"
          format: date-time
        budgets:
          $ref: "#/components/schemas/BudgetsPatch"
        access_policy:
          description: Replaces the key's own policy; an empty object or null removes it.
          oneOf:
            - $ref: "#/components/schemas/AccessPolicy"
            - type: "null"

    KeyCreateRes:
      type: object
      required:
        - key
        - value
      properties:
        key:
          $ref: "#/components/schemas/ApiKey"
        value:
          type: string
          description: Raw API key. Manual keys are returned once; pending default keys remain claimable until acknowledged.

    DefaultKeyClaim:
      type: object
      additionalProperties: false
      required:
        - workspace_id
      properties:
        workspace_id:
          type: string

    DefaultKeyAck:
      type: object
      additionalProperties: false
      required:
        - key_id
      properties:
        key_id:
          type: string

    ApiKey:
      type: object
      required:
        - id
        - workspace_id
        - name
        - prefix
        - kind
        - state
        - scopes
        - created_at
      properties:
        id:
          type: string
        workspace_id:
          type: string
        name:
          type: string
        prefix:
          type: string
          description: Non-secret key prefix for display and lookup.
        kind:
          type: string
          enum:
            - api
        state:
          type: string
          enum:
            - active
            - disabled
            - revoked
        scopes:
          type: array
          items:
            type: string
        spend_limit:
          type:
            - number
            - "null"
        rate_limit:
          type:
            - integer
            - "null"
        expires_at:
          type:
            - string
            - "null"
          format: date-time
        last_used_at:
          type:
            - string
            - "null"
          format: date-time
        created_at:
          type: string
          format: date-time
        revoked_at:
          type:
            - string
            - "null"
          format: date-time
        budgets:
          $ref: "#/components/schemas/BudgetLimits"
        access_policy:
          oneOf:
            - $ref: "#/components/schemas/AccessPolicy"
            - type: "null"
        usage:
          $ref: "#/components/schemas/PeriodUsage"

    TranscriptionForm:
      type: object
      required:
        - file
      properties:
        file:
          type: string
          format: binary
        model:
          type: string
          default: auto
          maxLength: 160
          description: Exact provider/model slug, auto, or a managed stt/* alias.
          examples:
            - stt/auto
            - deepgram/nova-3
            - fal/wizper
        language:
          type: string
          examples:
            - en
        prompt:
          type: string
        response_format:
          $ref: "#/components/schemas/ResponseFormat"
        temperature:
          type: number
          minimum: 0
          maximum: 1
        timestamps:
          $ref: "#/components/schemas/TimestampMode"
        diarize:
          type: boolean
        redact:
          type: boolean
        translate:
          type: boolean
        keyterms:
          type: array
          items:
            type: string
        retention:
          $ref: "#/components/schemas/RetentionMode"
        provider:
          type: string
          description: JSON-encoded provider preference object for multipart requests.
        metadata:
          type: string
          description: JSON-encoded metadata object for multipart requests.
        thinking:
          $ref: "#/components/schemas/ThinkingLevel"

    TranscriptionReq:
      type: object
      required:
        - input_audio
      properties:
        model:
          type: string
          default: auto
          maxLength: 160
          description: Exact provider/model slug, auto, or a managed stt/* alias.
          examples:
            - stt/auto
            - deepgram/nova-3
            - fal/wizper
        input_audio:
          $ref: "#/components/schemas/InputAudio"
        language:
          type: string
          description: >
            Optional language hint (e.g. "es", "ja"). With model "auto", providing a
            language skips automatic language detection — lower latency, and the provider
            transcribes against the given language (better accuracy, fewer errors) rather
            than guessing. It is also passed to the selected provider. Omit it to let
            "auto" detect the language.
        prompt:
          type: string
        response_format:
          $ref: "#/components/schemas/ResponseFormat"
        temperature:
          type: number
          minimum: 0
          maximum: 1
        timestamps:
          $ref: "#/components/schemas/TimestampMode"
        diarize:
          type: boolean
        redact:
          type: boolean
        translate:
          type: boolean
        keyterms:
          type: array
          items:
            type: string
        metadata:
          type: object
          additionalProperties:
            type:
              - string
              - number
              - boolean
              - "null"
        retention:
          $ref: "#/components/schemas/RetentionMode"
        provider:
          $ref: "#/components/schemas/ProviderPref"
        thinking:
          $ref: "#/components/schemas/ThinkingLevel"

    SpeechReq:
      type: object
      required:
        - model
        - input
        - voice
      properties:
        model:
          type: string
          maxLength: 160
          description: Exact provider/model slug or a managed TTS alias. Preview, mutable, compatibility-only, and estimated-price models require exact selection unless tts/auto feature routing explicitly ranks them.
          examples:
            - openai/gpt-4o-mini-tts
            - elevenlabs/eleven-flash-v2.5
            - cartesia/sonic-3.5
            - tts/auto
        feature:
          type: string
          description: Case-sensitive TTS routing capability. Valid only with model="tts/auto"; eligible models are tried in feature rank order.
          enum:
            - Acting
            - Expressiveness
            - Voice identity
            - Language stability
            - Reliability
            - Extended long-form
            - Long-form
            - Acoustic quality
        input:
          type: string
          minLength: 1
          maxLength: 50000
        voice:
          oneOf:
            - type: string
              description: Provider-native voice ID/name, or auto where the selected model has a default. resemble/chatterbox-turbo requires a custom DeepInfra voice ID and rejects auto.
            - type: object
              required:
                - id
              properties:
                id:
                  type: string
        response_format:
          type: string
          default: mp3
          description: Supported values are model-specific; inspect the selected model's endpoint limits. Gemini 2.5 TTS accepts only wav or pcm.
          enum:
            - mp3
            - ogg
            - opus
            - aac
            - flac
            - wav
            - pcm
            - ulaw
            - alaw
            - json
        speed:
          type: number
          default: 1
          minimum: 0.25
          maximum: 4
        instructions:
          type: string
          maxLength: 10000
        language:
          type: string
        provider:
          $ref: "#/components/schemas/SpeechProvider"
        metadata:
          type: object
          additionalProperties:
            type:
              - string
              - number
              - boolean
              - "null"

    S2sAudioFormat:
      type: string
      enum: [wav, pcm16]
      description: wav is a WAV file; pcm16 is raw little-endian 16-bit mono samples.

    S2sInputAudio:
      type: object
      additionalProperties: false
      required: [data, format]
      properties:
        data:
          type: string
          contentEncoding: base64
          maxLength: 34000000
          description: Base64 audio without a data-URL prefix. Mono 16-bit PCM at 24000 Hz, 0.1 to 60 seconds.
        format:
          $ref: "#/components/schemas/S2sAudioFormat"
        sample_rate:
          type: integer
          description: Required for pcm16 and must be 24000. A WAV file's header must also say 24000.
        channels:
          type: integer
          const: 1
          default: 1

    S2sOutputAudio:
      type: object
      additionalProperties: false
      properties:
        format:
          allOf:
            - $ref: "#/components/schemas/S2sAudioFormat"
          description: Defaults to wav, or pcm16 when streaming. Streamed audio is always pcm16.
        sample_rate:
          type: integer
          const: 24000

    S2sReq:
      type: object
      additionalProperties: false
      required: [model, input_audio]
      properties:
        provider: { $ref: "#/components/schemas/RegionProviderPreferences" }
        model:
          type: string
          enum:
            - openai/gpt-realtime-2.1-mini
            - openai/gpt-realtime-2.1
            - openai/gpt-realtime-2
            - openai/gpt-realtime-1.5
        input_audio:
          $ref: "#/components/schemas/S2sInputAudio"
        output_audio:
          $ref: "#/components/schemas/S2sOutputAudio"
        voice:
          type: string
          default: auto
          enum: [auto, alloy, ash, ballad, coral, echo, sage, shimmer, verse, marin, cedar]
          description: auto uses marin.
        instructions:
          type: string
          minLength: 1
          maxLength: 4096
          description: System guidance for the reply, up to 4096 UTF-8 bytes. Defaults to "Answer briefly."
        max_output_tokens:
          type: integer
          minimum: 1
          maximum: 1024
          default: 1024
          description: Cap on audio, transcript and reasoning tokens. A reply that reaches it ends with status incomplete.
        stream:
          type: boolean
          default: false
          description: Send the reply as server-sent events instead of one JSON body.

    S2sUsage:
      type: object
      required:
        - input_tokens
        - output_tokens
        - input_audio_tokens
        - input_text_tokens
        - input_cached_audio_tokens
        - input_cached_text_tokens
        - output_audio_tokens
        - output_text_tokens
        - cost
        - currency
      properties:
        input_tokens: { type: integer, minimum: 0 }
        output_tokens: { type: integer, minimum: 0 }
        input_audio_tokens: { type: integer, minimum: 0 }
        input_text_tokens: { type: integer, minimum: 0 }
        input_cached_audio_tokens: { type: integer, minimum: 0 }
        input_cached_text_tokens: { type: integer, minimum: 0 }
        output_audio_tokens: { type: integer, minimum: 0 }
        output_text_tokens: { type: integer, minimum: 0 }
        output_reasoning_tokens:
          type: integer
          minimum: 0
          description: Present when OpenAI reports it; already counted in output_text_tokens.
        cost:
          type: number
          minimum: 0
          description: USD charged for this turn.
        currency: { type: string, const: USD }

    S2sRes:
      type: object
      required: [id, object, created, model, provider, status, audio, transcript, usage]
      properties:
        id:
          type: string
          description: Request ID, also returned in x-request-id.
        object:
          type: string
          const: audio.speech_to_speech
        created:
          type: integer
          description: Unix timestamp in seconds.
        model:
          type: string
        provider:
          type: string
        status:
          type: string
          enum: [completed, incomplete]
          description: incomplete when the reply reached max_output_tokens.
        audio:
          type: object
          additionalProperties: false
          required: [data, format, content_type, sample_rate, channels]
          properties:
            data:
              type: string
              contentEncoding: base64
            format:
              $ref: "#/components/schemas/S2sAudioFormat"
            content_type:
              type: string
              enum: [audio/wav, audio/pcm]
            sample_rate:
              type: integer
              const: 24000
            channels:
              type: integer
              const: 1
        transcript:
          type: [string, "null"]
          description: Text of the spoken reply.
        usage:
          $ref: "#/components/schemas/S2sUsage"
      example:
        id: req_example
        object: audio.speech_to_speech
        created: 1792040000
        model: openai/gpt-realtime-2.1-mini
        provider: openai
        status: completed
        audio:
          data: BASE64_WAV_AUDIO
          format: wav
          content_type: audio/wav
          sample_rate: 24000
          channels: 1
        transcript: The capital of France is Paris.
        usage:
          input_tokens: 69
          output_tokens: 87
          input_audio_tokens: 39
          input_text_tokens: 30
          input_cached_audio_tokens: 0
          input_cached_text_tokens: 0
          output_audio_tokens: 42
          output_text_tokens: 45
          output_reasoning_tokens: 26
          cost: 0.001423
          currency: USD

    SpeechProvider:
      type: object
      properties:
        order:
          type: array
          items:
            type: string
        only:
          type: array
          items:
            type: string
        ignore:
          type: array
          items:
            type: string
        models:
          type: array
          description: Model ID patterns allowed for managed routes. Supports * wildcards.
          items:
            type: string
        sort:
          type: string
          enum:
            - price
            - latency
            - quality
            - balanced
        cost_quality:
          type: integer
          minimum: 0
          maximum: 10
          description: Balanced-sort weighting from quality (0) to cost (10).
        allow_fallbacks:
          type: boolean
        max_price:
          type: object
          properties:
            per_1k_chars:
              type: number
              minimum: 0
        data_region:
          $ref: "#/components/schemas/DataRegion"
        region:
          type: array
          items:
            type: string
          deprecated: true
          description: The older name of data_region, with the same meaning.
        options:
          type: object
          properties:
            alibaba:
              $ref: "#/components/schemas/AlibabaOptions"
            openai:
              $ref: "#/components/schemas/OpenAIOptions"
            amazon:
              $ref: "#/components/schemas/AmazonOptions"
            elevenlabs:
              $ref: "#/components/schemas/ElevenOptions"
            cartesia:
              $ref: "#/components/schemas/CartesiaOptions"
            deepinfra:
              $ref: "#/components/schemas/DeepinfraOptions"
            deepgram:
              $ref: "#/components/schemas/DeepgramOptions"
            gemini:
              $ref: "#/components/schemas/GeminiOptions"
            hume:
              $ref: "#/components/schemas/HumeOptions"
            inworld:
              $ref: "#/components/schemas/InworldOptions"
            minimax:
              $ref: "#/components/schemas/MinimaxOptions"
            murf:
              $ref: "#/components/schemas/MurfOptions"
            mistral:
              $ref: "#/components/schemas/MistralTtsOptions"
            xai:
              $ref: "#/components/schemas/XaiOptions"
          additionalProperties:
            type: object
            additionalProperties: true

    AlibabaLanguage:
      type: string
      enum: [Auto, Chinese, English, German, Italian, Portuguese, Spanish, Japanese, Korean, French, Russian]

    AlibabaOptions:
      type: object
      additionalProperties: false
      properties:
        transport:
          type: string
          enum: [file, sse]
          description: HTTP only. File returns Alibaba's hosted WAV; SSE streams base64 24 kHz PCM, which AudioLab can return directly or transcode.
        language_type:
          $ref: "#/components/schemas/AlibabaLanguage"
        optimize_instructions:
          type: boolean
          description: Qwen3 TTS Instruct models only. Requires common instructions and asks Alibaba to rewrite them for natural delivery.
        mode:
          type: string
          enum: [server_commit, commit]
          default: server_commit
          description: WebSocket only. Server commit balances latency and quality; commit requires explicit input_text_buffer.commit events.
        sample_rate:
          type: integer
          enum: [8000, 16000, 24000, 48000]
          default: 24000
          description: WebSocket only.
        speech_rate:
          type: number
          minimum: 0.5
          maximum: 2
          description: WebSocket only. Overrides common speed.
        volume:
          type: integer
          minimum: 0
          maximum: 100
          description: WebSocket only.
        pitch_rate:
          type: number
          minimum: 0.5
          maximum: 2
          description: WebSocket only.
        bit_rate:
          type: integer
          minimum: 6
          maximum: 510
          description: WebSocket Opus output only, in kbps.

    OpenAIOptions:
      type: object
      additionalProperties: false
      properties:
        stream_format:
          type: string
          enum: [audio, sse]
          default: audio
          description: Native OpenAI stream envelope. SSE is available only on GPT-4o mini TTS models and is passed through as text/event-stream.

    AmazonLanguage:
      type: string
      enum:
        - arb
        - cmn-CN
        - cy-GB
        - da-DK
        - de-DE
        - en-AU
        - en-GB
        - en-GB-WLS
        - en-IN
        - en-US
        - es-ES
        - es-MX
        - es-US
        - fr-CA
        - fr-FR
        - is-IS
        - it-IT
        - ja-JP
        - hi-IN
        - ko-KR
        - nb-NO
        - nl-NL
        - pl-PL
        - pt-BR
        - pt-PT
        - ro-RO
        - ru-RU
        - sv-SE
        - tr-TR
        - en-NZ
        - en-ZA
        - ca-ES
        - de-AT
        - yue-CN
        - ar-AE
        - fi-FI
        - en-IE
        - nl-BE
        - fr-BE
        - cs-CZ
        - de-CH
        - en-SG

    AmazonOptions:
      type: object
      additionalProperties: false
      properties:
        language_code:
          $ref: "#/components/schemas/AmazonLanguage"
        lexicon_names:
          type: array
          maxItems: 5
          items:
            type: string
            pattern: "^[0-9A-Za-z]{1,20}$"
        sample_rate:
          type: integer
          enum: [8000, 16000, 22050, 24000, 44100, 48000]
          description: Valid values depend on response_format. Opus is fixed at 48000; μ-law and A-law at 8000; PCM permits 8000 or 16000.
        speech_mark_types:
          type: array
          minItems: 1
          maxItems: 4
          uniqueItems: true
          items:
            type: string
            enum: [sentence, ssml, viseme, word]
          description: Requires response_format=json and is unavailable on the generative engine.
        text_type:
          type: string
          enum: [text, ssml]
          default: text

    ElevenVoiceSettings:
      type:
        - object
        - "null"
      additionalProperties: false
      properties:
        stability:
          type: [number, "null"]
          minimum: 0
          maximum: 1
        similarity_boost:
          type: [number, "null"]
          minimum: 0
          maximum: 1
        style:
          type: [number, "null"]
          minimum: 0
          maximum: 1
        use_speaker_boost:
          type: [boolean, "null"]
        speed:
          type: [number, "null"]
          minimum: 0.7
          maximum: 1.2

    ElevenDictionary:
      type: object
      additionalProperties: false
      required:
        - pronunciation_dictionary_id
      properties:
        pronunciation_dictionary_id:
          type: string
        version_id:
          type: [string, "null"]

    ElevenOutputFormat:
      type: string
      enum:
        - alaw_8000
        - mp3_22050_32
        - mp3_24000_48
        - mp3_44100_32
        - mp3_44100_64
        - mp3_44100_96
        - mp3_44100_128
        - mp3_44100_192
        - opus_48000_32
        - opus_48000_64
        - opus_48000_96
        - opus_48000_128
        - opus_48000_192
        - pcm_8000
        - pcm_16000
        - pcm_22050
        - pcm_24000
        - pcm_32000
        - pcm_44100
        - pcm_48000
        - ulaw_8000
        - wav_8000
        - wav_16000
        - wav_22050
        - wav_24000
        - wav_32000
        - wav_44100
        - wav_48000

    ElevenOptions:
      type: object
      additionalProperties: false
      properties:
        transport:
          type: string
          description: Defaults to stream, except WAV defaults to create because ElevenLabs streaming endpoints do not expose WAV.
          enum:
            - create
            - create_with_timing
            - stream
            - stream_with_timing
        enable_logging:
          type: boolean
          description: False requests ElevenLabs zero-retention mode; Enterprise only.
        optimize_streaming_latency:
          type: [integer, "null"]
          minimum: 0
          maximum: 4
        output_format:
          $ref: "#/components/schemas/ElevenOutputFormat"
        language_code:
          type: [string, "null"]
        voice_settings:
          $ref: "#/components/schemas/ElevenVoiceSettings"
        pronunciation_dictionary_locators:
          type: [array, "null"]
          maxItems: 3
          items:
            $ref: "#/components/schemas/ElevenDictionary"
        seed:
          type: [integer, "null"]
          minimum: 0
          maximum: 4294967295
        previous_text:
          type: [string, "null"]
        next_text:
          type: [string, "null"]
        previous_request_ids:
          type: [array, "null"]
          maxItems: 3
          items:
            type: string
        next_request_ids:
          type: [array, "null"]
          maxItems: 3
          items:
            type: string
        use_pvc_as_ivc:
          type: boolean
        apply_text_normalization:
          type: string
          enum: [auto, "on", "off"]
        apply_language_text_normalization:
          type: boolean
          description: Currently supported by ElevenLabs for Japanese.
        stability:
          type: number
          minimum: 0
          maximum: 1
          deprecated: true
          description: AudioLab compatibility alias; prefer voice_settings.stability.
        similarity_boost:
          type: number
          minimum: 0
          maximum: 1
          deprecated: true
          description: AudioLab compatibility alias; prefer voice_settings.similarity_boost.
        style:
          type: number
          minimum: 0
          maximum: 1
          deprecated: true
          description: AudioLab compatibility alias; prefer voice_settings.style.
        use_speaker_boost:
          type: boolean
          deprecated: true
          description: AudioLab compatibility alias; prefer voice_settings.use_speaker_boost.

    ElevenAlignment:
      type:
        - object
        - "null"
      properties:
        characters:
          type: array
          items:
            type: string
        character_start_times_seconds:
          type: array
          items:
            type: number
        character_end_times_seconds:
          type: array
          items:
            type: number

    ElevenTiming:
      type: object
      required:
        - audio_base64
      properties:
        audio_base64:
          type: string
          contentEncoding: base64
        alignment:
          $ref: "#/components/schemas/ElevenAlignment"
        normalized_alignment:
          $ref: "#/components/schemas/ElevenAlignment"

    DeepinfraKokoro:
      type: object
      additionalProperties: false
      properties:
        preset_voice:
          type: array
          minItems: 1
          items:
            type: string
            enum:
              - af_alloy
              - af_aoede
              - af_bella
              - af_heart
              - af_jessica
              - af_kore
              - af_nicole
              - af_nova
              - af_river
              - af_sarah
              - af_sky
              - am_adam
              - am_echo
              - am_eric
              - am_fenrir
              - am_liam
              - am_michael
              - am_onyx
              - am_puck
              - am_santa
              - bf_alice
              - bf_emma
              - bf_isabella
              - bf_lily
              - bm_daniel
              - bm_fable
              - bm_george
              - bm_lewis
              - ef_dora
              - em_alex
              - em_santa
              - ff_siwis
              - hf_alpha
              - hf_beta
              - hm_omega
              - hm_psi
              - if_sara
              - im_nicola
              - jf_alpha
              - jf_gongitsune
              - jf_nezumi
              - jf_tebukuro
              - jm_kumo
              - pf_dora
              - pm_alex
              - pm_santa
              - zf_xiaobei
              - zf_xiaoni
              - zf_xiaoxiao
              - zf_xiaoyi
              - zm_yunjian
              - zm_yunxi
              - zm_yunxia
              - zm_yunyang
          description: One or more Kokoro voices to mix. Requires common voice=auto.
        return_timestamps:
          type: boolean
          default: false
          description: native_json only; preserves DeepInfra word timing metadata.
        sample_rate:
          type: integer
        target_min_tokens:
          type: integer
        target_max_tokens:
          type: integer
        absolute_max_tokens:
          type: integer

    DeepinfraChatterbox:
      type: object
      additionalProperties: false
      properties:
        exaggeration:
          type: number
          minimum: 0
          maximum: 1
          default: 0
        cfg:
          type: number
          minimum: 0
          maximum: 1
          default: 0
        temperature:
          type: number
          minimum: 0
          maximum: 2
          default: 0.8
        seed:
          type: integer
          minimum: 0
          maximum: 2147483647
        top_p:
          type: number
          minimum: 0
          maximum: 1
          default: 0.95
        min_p:
          type: number
          minimum: 0
          maximum: 1
          default: 0
        repetition_penalty:
          type: number
          minimum: 0
          maximum: 5
          default: 1.2
        top_k:
          type: integer
          minimum: 0
          maximum: 1000
          default: 1000

    DeepinfraOptions:
      type: object
      additionalProperties: false
      properties:
        transport:
          type: string
          default: openai
          enum: [openai, native_json, native_stream, elevenlabs, elevenlabs_stream]
          description: OpenAI-compatible raw audio, native metadata JSON, Kokoro native streaming, or DeepInfra's buffered/streaming ElevenLabs-compatible route.
        service_tier:
          type: string
          enum: [default, priority, flex]
          description: OpenAI and native transports only.
        version:
          type: string
          minLength: 1
          description: Native transports only; selects a specific DeepInfra model version.
        language_code:
          type: string
          pattern: "^[A-Za-z]{2}$"
          description: ISO 639-1 code for ElevenLabs-compatible transports; must match common language when both are supplied.
        webhook:
          type: string
          format: uri
          minLength: 1
          maxLength: 2083
          description: native_json only; asks DeepInfra to notify this URL when inference completes.
        kokoro:
          $ref: "#/components/schemas/DeepinfraKokoro"
        chatterbox:
          $ref: "#/components/schemas/DeepinfraChatterbox"

    DeepgramOptions:
      type: object
      additionalProperties: false
      properties:
        encoding:
          type: string
          enum: [linear16, mulaw, alaw, mp3, opus, flac, aac]
          description: Native output encoding. It must produce the common response_format together with container.
        container:
          type: string
          enum: [none, wav, ogg]
          description: wav/none for linear16, mulaw, and alaw; ogg for Opus; omitted for MP3, FLAC, and AAC.
        sample_rate:
          type: integer
          enum: [8000, 16000, 22050, 24000, 32000, 48000]
          description: Configurable only for linear16, mu-law, A-law, and FLAC. MP3, Opus, and AAC use fixed provider rates and AudioLab omits this query parameter.
        bit_rate:
          type: integer
          minimum: 4000
          maximum: 650000
          description: MP3 accepts 32000/48000, Opus accepts 4000-650000, and AAC accepts 4000-192000.
        mip_opt_out:
          type: boolean
          default: false
          description: Excludes this request from Deepgram's Model Improvement Program without a PAYG/Growth rate change as of March 5, 2026.
        tag:
          oneOf:
            - type: string
              minLength: 1
              maxLength: 128
            - type: array
              minItems: 1
              items:
                type: string
                minLength: 1
                maxLength: 128
          description: One or more immutable Deepgram usage-reporting tags.
        callback:
          type: string
          format: uri
          maxLength: 2083
          description: Asynchronously sends generated audio to an HTTP(S) URL on port 80, 443, 8080, or 8443; the AudioLab response is Deepgram's immediate JSON request ID.
        callback_method:
          type: string
          enum: [POST, PUT, post, put]
          default: POST
          description: Requires callback.

    DeepgramCallback:
      type: object
      additionalProperties: true
      required: [request_id]
      properties:
        request_id:
          type: string
          description: Deepgram request ID for asynchronous callback generation.

    DeepinfraWord:
      type: object
      required: [id, start, end, text]
      properties:
        id:
          type: integer
        start:
          type: number
        end:
          type: number
        text:
          type: string

    DeepinfraResponse:
      type: object
      required: [audio]
      properties:
        audio:
          type: string
          description: Provider-encoded audio returned by native_json.
        input_character_length:
          type: integer
        output_format:
          type: string
        words:
          type: array
          items:
            $ref: "#/components/schemas/DeepinfraWord"
        request_id:
          type: string
        inference_status:
          type: object
          additionalProperties: true
          properties:
            status:
              type: string
              enum: [unknown, queued, running, succeeded, failed]
            runtime_ms:
              type: integer
            cost:
              type: number
            tokens_generated:
              type: [integer, "null"]
            tokens_input:
              type: [integer, "null"]
            output_length:
              type: [integer, "null"]

    MinimaxLanguage:
      type: string
      enum:
        - Chinese
        - Chinese,Yue
        - English
        - Arabic
        - Russian
        - Spanish
        - French
        - Portuguese
        - German
        - Turkish
        - Dutch
        - Ukrainian
        - Vietnamese
        - Indonesian
        - Japanese
        - Italian
        - Korean
        - Thai
        - Polish
        - Romanian
        - Greek
        - Czech
        - Finnish
        - Hindi
        - Bulgarian
        - Danish
        - Hebrew
        - Malay
        - Persian
        - Slovak
        - Swedish
        - Croatian
        - Filipino
        - Hungarian
        - Norwegian
        - Slovenian
        - Catalan
        - Nynorsk
        - Tamil
        - Afrikaans
        - auto

    MinimaxVoice:
      type: object
      additionalProperties: false
      properties:
        speed:
          type: number
          minimum: 0.5
          maximum: 2
          description: Overrides the common speed for MiniMax.
        vol:
          type: number
          exclusiveMinimum: 0
          maximum: 10
        pitch:
          type: integer
          minimum: -12
          maximum: 12
        emotion:
          type: string
          enum: [happy, sad, angry, fearful, disgusted, surprised, calm, fluent, whisper]
          description: fluent and whisper are limited to Speech-2.6.
        text_normalization:
          type: boolean
          default: false
          description: HTTP English/Chinese digit normalization. The WebSocket equivalent is english_normalization.
        latex_read:
          type: boolean
          default: false
          description: Chinese-only formula reading; forces language_boost=Chinese.

    MinimaxAudio:
      type: object
      additionalProperties: false
      properties:
        sample_rate:
          type: integer
          enum: [8000, 16000, 22050, 24000, 32000, 44100]
        bitrate:
          type: integer
          enum: [32000, 64000, 128000, 256000]
          description: MP3 only.
        format:
          type: string
          enum: [mp3, pcm, flac, wav, pcmu_raw, pcmu_wav, opus]
          description: Must match common response_format. pcmu_raw maps to ulaw; pcmu_wav maps to wav.
        channel:
          type: integer
          enum: [1, 2]
        force_cbr:
          type: boolean
          default: false
          description: Streamed MP3 only; omitted from the WebSocket start schema.

    MinimaxPronunciation:
      type: object
      additionalProperties: false
      required: [tone]
      properties:
        tone:
          type: array
          minItems: 1
          items:
            type: string
            minLength: 1
          description: Rules in original/replacement form; replacement may use text, pinyin, IPA, or Japanese kana.

    MinimaxTimbre:
      type: object
      additionalProperties: false
      required: [voice_id, weight]
      properties:
        voice_id:
          type: string
        weight:
          type: integer
          minimum: 1
          maximum: 100

    MinimaxModify:
      type: object
      additionalProperties: false
      properties:
        pitch:
          type: integer
          minimum: -100
          maximum: 100
        intensity:
          type: integer
          minimum: -100
          maximum: 100
        timbre:
          type: integer
          minimum: -100
          maximum: 100
        sound_effects:
          type: string
          enum: [spacious_echo, auditorium_echo, lofi_telephone, robotic]

    MinimaxOptions:
      type: object
      additionalProperties: false
      properties:
        transport:
          type: string
          default: buffered
          enum: [buffered, stream, json, sse]
          description: Decoded buffered audio, decoded SSE audio, native JSON, or native SSE.
        stream_options:
          type: object
          additionalProperties: false
          properties:
            exclude_aggregated_audio:
              type: boolean
              default: false
          description: stream or sse only. Decoded stream defaults this to true to prevent duplicate final audio.
        voice_setting:
          $ref: "#/components/schemas/MinimaxVoice"
        audio_setting:
          $ref: "#/components/schemas/MinimaxAudio"
        pronunciation_dict:
          $ref: "#/components/schemas/MinimaxPronunciation"
        timbre_weights:
          type: array
          minItems: 1
          maxItems: 4
          items:
            $ref: "#/components/schemas/MinimaxTimbre"
          description: Requires common voice=auto.
        language_boost:
          $ref: "#/components/schemas/MinimaxLanguage"
        voice_modify:
          $ref: "#/components/schemas/MinimaxModify"
        subtitle_enable:
          type: boolean
          default: false
          description: Requires native json or sse so subtitle metadata is preserved.
        subtitle_type:
          type: string
          default: sentence
          enum: [sentence, word, word_streaming]
          description: word_streaming requires stream or sse.
        output_format:
          type: string
          default: hex
          enum: [hex, url]
          description: url is non-streaming, requires json, and is valid upstream for 24 hours.

    MinimaxResponse:
      type: object
      properties:
        data:
          type: [object, "null"]
          properties:
            audio:
              type: string
              description: Hex audio or a hosted URL according to output_format.
            subtitle_file:
              type: string
            status:
              type: integer
              enum: [1, 2]
        trace_id:
          type: string
        extra_info:
          type: object
          additionalProperties: true
        base_resp:
          type: object
          properties:
            status_code:
              type: integer
            status_msg:
              type: string

    MurfOptions:
      type: object
      additionalProperties: false
      properties:
        transport:
          type: string
          default: stream
          enum: [stream, generate, json]
          description: stream is available on both models. generate and json use Gen2 Synthesize Speech; json requires response_format=json and preserves Murf metadata.
        region:
          type: string
          enum: [global, us-east, us-west, in, ca, kr, me, jp, au, eu-central, uk, sa-east]
          description: Streaming HTTP and WebSocket host. Omit to use MURF_STREAM_BASE_URL.
        format:
          type: string
          enum: [MP3, FLAC, WAV, OGG, PCM, ULAW, ALAW]
          description: Audio encoding embedded in a native JSON response. Other transports derive this from response_format.
        channel_type:
          type: string
          default: MONO
          enum: [MONO, STEREO]
        sample_rate:
          type: integer
          enum: [8000, 16000, 24000, 44100, 48000]
          description: Synthesize Speech does not accept 16000 Hz. Falcon 2 defaults to 24000; Gen2 defaults to 44100.
        locale:
          type: string
          description: Voice-supported locale from Murf's live voice catalog. Murf uses en-UK for British English.
        pitch:
          type: integer
          minimum: -50
          maximum: 50
        rate:
          type: integer
          minimum: -50
          maximum: 50
          description: Native percentage rate. Overrides the common speed mapping.
        style:
          type: string
        variation:
          type: integer
          minimum: 0
          maximum: 5
          description: Gen2 only.
        audio_duration:
          type: number
          minimum: 0
          description: Gen2 generate/json only; requests an approximate output duration in seconds.
        encode_as_base64:
          type: boolean
          description: Gen2 generate/json only. Inline Base64 enables Murf's zero-retention mode; AudioLab defaults this to true for generate audio.
        word_durations_as_original_text:
          type: boolean
          description: Gen2 generate/json only; English inputs only.

    MistralTtsOptions:
      type: object
      additionalProperties: false
      properties:
        ref_audio:
          type: string
          minLength: 1
          maxLength: 30000000
          description: Standard base64-encoded one-off reference audio, without a data-URL prefix. Use this while common voice is auto; do not combine it with a saved voice ID.
    MurfWordDuration:
      type: object
      properties:
        word:
          type: string
        startMs:
          type: integer
        endMs:
          type: integer

    MurfResponse:
      type: object
      properties:
        audioFile:
          type: string
          format: uri
        encodedAudio:
          type: string
          description: Base64 audio when encode_as_base64 is enabled.
        audioLengthInSeconds:
          type: number
        remainingCharacterCount:
          type: integer
          format: int64
        wordDurations:
          type: array
          items:
            $ref: "#/components/schemas/MurfWordDuration"
        warning:
          type: string

    HumeVoice:
      oneOf:
        - type: object
          additionalProperties: false
          required: [id]
          properties:
            id:
              type: string
            provider:
              type: string
              enum: [HUME_AI, CUSTOM_VOICE]
        - type: object
          additionalProperties: false
          required: [name]
          properties:
            name:
              type: string
            provider:
              type: string
              enum: [HUME_AI, CUSTOM_VOICE]

    HumeUtterance:
      type: object
      additionalProperties: false
      required: [text]
      properties:
        text:
          type: string
          maxLength: 5000
        description:
          type: string
          maxLength: 1000
        voice:
          $ref: "#/components/schemas/HumeVoice"
        speed:
          type: number
          minimum: 0.5
          maximum: 2
        trailing_silence:
          type: number
          minimum: 0

    HumeContext:
      oneOf:
        - type: object
          additionalProperties: false
          required: [generation_id]
          properties:
            generation_id:
              type: string
        - type: object
          additionalProperties: false
          required: [utterances]
          properties:
            utterances:
              type: array
              minItems: 1
              items:
                $ref: "#/components/schemas/HumeUtterance"

    HumeOptions:
      type: object
      additionalProperties: false
      properties:
        transport:
          type: string
          default: stream_file
          enum: [file, json, stream_file, stream_json]
        version:
          type: string
          enum: ["1", "2"]
          description: Omit to let Hume automatically select the Octave version.
        voice_provider:
          type: string
          enum: [HUME_AI, CUSTOM_VOICE]
          description: AudioLab mapping hint for the common voice field. Names default to HUME_AI. HTTP voice IDs first use CUSTOM_VOICE and retry a zero-byte pre-audio response as HUME_AI. Set this field to pin the mapping; WebSocket voice IDs should always set it explicitly.
        context:
          $ref: "#/components/schemas/HumeContext"
        include_timestamp_types:
          type: array
          maxItems: 2
          uniqueItems: true
          items:
            type: string
            enum: [word, phoneme]
          description: Octave 2 only; requires json or stream_json so timestamp metadata is preserved.
        num_generations:
          type: integer
          minimum: 1
          maximum: 5
        split_utterances:
          type: boolean
          default: true
        strip_headers:
          type: boolean
          default: false
        temperature:
          type: [number, "null"]
          minimum: 0.1
          maximum: 1
        instant_mode:
          type: boolean
          description: Streaming transports only. Requires a voice and exactly one generation.
        trailing_silence:
          type: number
          minimum: 0
          description: Applied to the generated utterance.

    HumeTimestamp:
      type: object
      required: [text, time, type]
      properties:
        text:
          type: string
        type:
          type: string
          enum: [word, phoneme]
        time:
          type: object
          required: [begin, end]
          properties:
            begin:
              type: integer
              description: Milliseconds.
            end:
              type: integer
              description: Milliseconds.

    HumeSnippet:
      type: object
      required: [audio, generation_id, id, text, timestamps, transcribed_text, utterance_index]
      properties:
        audio:
          type: string
          contentEncoding: base64
        generation_id:
          type: string
        id:
          type: string
        text:
          type: string
        timestamps:
          type: array
          items:
            $ref: "#/components/schemas/HumeTimestamp"
        transcribed_text:
          type: [string, "null"]
        utterance_index:
          type: [integer, "null"]

    HumeGeneration:
      type: object
      required: [audio, duration, encoding, file_size, generation_id, snippets]
      properties:
        audio:
          type: string
          contentEncoding: base64
        duration:
          type: number
        encoding:
          type: object
          required: [format, sample_rate]
          properties:
            format:
              type: string
              enum: [mp3, pcm, wav]
            sample_rate:
              type: integer
        file_size:
          type: integer
        generation_id:
          type: string
        snippets:
          type: array
          items:
            type: array
            items:
              $ref: "#/components/schemas/HumeSnippet"

    HumeResponse:
      type: object
      required: [generations, request_id]
      properties:
        generations:
          type: array
          items:
            $ref: "#/components/schemas/HumeGeneration"
        request_id:
          type: [string, "null"]

    InworldAudio:
      type: object
      additionalProperties: false
      properties:
        audioEncoding:
          type: string
          enum: [LINEAR16, MP3, OGG_OPUS, ALAW, MULAW, FLAC, PCM, WAV]
          description: Must map to the common response_format. Inworld WebSocket sessions do not expose FLAC.
        sampleRateHertz:
          type: integer
          enum: [8000, 16000, 22050, 24000, 32000, 44100, 48000]
          description: A-law and mu-law require 8000 Hz; MP3 requires at least 16000 Hz.
        bitRate:
          type: integer
          minimum: 32000
          maximum: 320000
          description: MP3 accepts 32000-320000 and OGG Opus accepts 32000-192000; other encodings reject this field.
        speakingRate:
          type: number
          minimum: 0.5
          maximum: 1.5
          description: Overrides the common speed value.

    InworldOptions:
      type: object
      additionalProperties: false
      properties:
        transport:
          type: string
          default: buffered
          enum: [buffered, stream, json, stream_json]
          description: Decoded buffered audio, decoded HTTP streaming audio, native JSON, or native newline-delimited streaming JSON.
        audioConfig:
          $ref: "#/components/schemas/InworldAudio"
        temperature:
          type: number
          minimum: 0
          maximum: 2
          description: TTS 1.5 Max and Mini only. Zero asks Inworld to use its model default; TTS-2 ignores this field, so AudioLab rejects it there.
        deliveryMode:
          type: string
          enum: [DELIVERY_MODE_UNSPECIFIED, STABLE, BALANCED, CREATIVE]
          description: TTS-2 only. Controls output stability and variation; TTS 1.5 ignores this field, so AudioLab rejects it there.
        timestampType:
          type: string
          enum: [TIMESTAMP_TYPE_UNSPECIFIED, WORD, CHARACTER]
          description: WORD and CHARACTER require json or stream_json so alignment metadata is preserved.
        applyTextNormalization:
          type: string
          enum: [APPLY_TEXT_NORMALIZATION_UNSPECIFIED, "ON", "OFF"]
        timestampTransportStrategy:
          type: string
          enum: [TIMESTAMP_TRANSPORT_STRATEGY_UNSPECIFIED, SYNC, ASYNC]
          description: Requires stream_json and a WORD or CHARACTER timestampType.

    InworldPhone:
      type: object
      properties:
        phoneSymbol:
          type: string
        startTimeSeconds:
          type: number
        durationSeconds:
          type: number
        visemeSymbol:
          type: string

    InworldPhonetic:
      type: object
      properties:
        wordIndex:
          type: integer
        phones:
          type: array
          items:
            $ref: "#/components/schemas/InworldPhone"
        isPartial:
          type: boolean

    InworldWordAlign:
      type: object
      properties:
        words:
          type: array
          items:
            type: string
        wordStartTimeSeconds:
          type: array
          items:
            type: number
        wordEndTimeSeconds:
          type: array
          items:
            type: number
        phoneticDetails:
          type: array
          items:
            $ref: "#/components/schemas/InworldPhonetic"

    InworldCharAlign:
      type: object
      properties:
        characters:
          type: array
          items:
            type: string
        characterStartTimeSeconds:
          type: array
          items:
            type: number
        characterEndTimeSeconds:
          type: array
          items:
            type: number

    InworldTimes:
      type: object
      properties:
        wordAlignment:
          $ref: "#/components/schemas/InworldWordAlign"
        characterAlignment:
          $ref: "#/components/schemas/InworldCharAlign"

    InworldUsage:
      type: object
      properties:
        processedCharactersCount:
          type: integer
          minimum: 0
        modelId:
          type: string

    InworldResponse:
      type: object
      properties:
        audioContent:
          type: string
          contentEncoding: base64
        usage:
          $ref: "#/components/schemas/InworldUsage"
        timestampInfo:
          $ref: "#/components/schemas/InworldTimes"

    XaiOutput:
      type: object
      additionalProperties: false
      properties:
        codec:
          type: string
          enum: [mp3, wav, pcm, mulaw, alaw]
          description: Must match the common response_format; use mulaw for response_format=ulaw.
        sample_rate:
          type: integer
          enum: [8000, 16000, 22050, 24000, 44100, 48000]
        bit_rate:
          type: integer
          enum: [32000, 64000, 96000, 128000, 192000]
          description: MP3 only.

    XaiOptions:
      type: object
      additionalProperties: false
      properties:
        output_format:
          $ref: "#/components/schemas/XaiOutput"
        optimize_streaming_latency:
          type: integer
          minimum: 0
          maximum: 1
          description: 0 preserves quality; 1 reduces first-chunk size for lower time-to-first-audio.
        text_normalization:
          type: boolean
          default: false
        with_timestamps:
          type: boolean
          default: false
          description: REST returns XaiTiming JSON; WebSocket audio.delta events include timing fields.

    XaiTimestamps:
      type: object
      required: [graph_chars, graph_times]
      properties:
        graph_chars:
          type: array
          items:
            type: string
        graph_times:
          type: array
          items:
            type: array
            minItems: 2
            maxItems: 2
            items:
              type: number

    XaiTiming:
      type: object
      required: [audio, content_type, duration, audio_timestamps]
      properties:
        audio:
          type: string
          contentEncoding: base64
        content_type:
          type: string
        duration:
          type: number
        audio_timestamps:
          $ref: "#/components/schemas/XaiTimestamps"

    CartesiaLanguage:
      type: string
      enum:
        - en
        - fr
        - de
        - es
        - pt
        - zh
        - ja
        - hi
        - it
        - ko
        - nl
        - pl
        - ru
        - sv
        - tr
        - tl
        - bg
        - ro
        - ar
        - cs
        - el
        - fi
        - hr
        - ms
        - sk
        - da
        - ta
        - uk
        - hu
        - no
        - vi
        - bn
        - th
        - he
        - ka
        - id
        - te
        - gu
        - kn
        - ml
        - mr
        - pa

    CartesiaGeneration:
      type: object
      additionalProperties: false
      properties:
        volume:
          type: number
          minimum: 0.5
          maximum: 2
          description: Sonic 3 only while Cartesia has volume disabled on Sonic 3.5/latest.
        speed:
          type: number
          minimum: 0.6
          maximum: 1.5
          description: Sonic 3 only while Cartesia has speed disabled on Sonic 3.5/latest. The deprecated upstream top-level speed field is not exposed.
        emotion:
          type: string
          description: Sonic 3 and newer. Unlike speed and volume, emotion remains available on Sonic 3.5.
          enum:
            - neutral
            - happy
            - excited
            - enthusiastic
            - elated
            - euphoric
            - triumphant
            - amazed
            - surprised
            - flirtatious
            - curious
            - content
            - peaceful
            - serene
            - calm
            - grateful
            - affectionate
            - trust
            - sympathetic
            - anticipation
            - mysterious
            - angry
            - mad
            - outraged
            - frustrated
            - agitated
            - threatened
            - disgusted
            - contempt
            - envious
            - sarcastic
            - ironic
            - sad
            - dejected
            - melancholic
            - disappointed
            - hurt
            - guilty
            - bored
            - tired
            - rejected
            - nostalgic
            - wistful
            - apologetic
            - hesitant
            - insecure
            - confused
            - resigned
            - anxious
            - panicked
            - alarmed
            - scared
            - proud
            - confident
            - distant
            - skeptical
            - contemplative
            - determined

    CartesiaEncoding:
      type: string
      enum:
        - pcm_f32le
        - pcm_s16le
        - pcm_mulaw
        - pcm_alaw

    CartesiaRawOutput:
      type: object
      additionalProperties: false
      required: [container, encoding, sample_rate]
      properties:
        container:
          const: raw
        encoding:
          $ref: "#/components/schemas/CartesiaEncoding"
        sample_rate:
          type: integer
          enum: [8000, 16000, 22050, 24000, 44100, 48000]

    CartesiaWavOutput:
      type: object
      additionalProperties: false
      required: [container, encoding, sample_rate]
      properties:
        container:
          const: wav
        encoding:
          $ref: "#/components/schemas/CartesiaEncoding"
        sample_rate:
          type: integer
          enum: [8000, 16000, 22050, 24000, 44100, 48000]

    CartesiaMp3Output:
      type: object
      additionalProperties: false
      required: [container, sample_rate, bit_rate]
      properties:
        container:
          const: mp3
        sample_rate:
          type: integer
          enum: [8000, 16000, 22050, 24000, 44100, 48000]
        bit_rate:
          type: integer
          enum: [32000, 64000, 96000, 128000, 192000]

    CartesiaOutput:
      oneOf:
        - $ref: "#/components/schemas/CartesiaRawOutput"
        - $ref: "#/components/schemas/CartesiaWavOutput"
        - $ref: "#/components/schemas/CartesiaMp3Output"

    CartesiaOptions:
      type: object
      additionalProperties: false
      properties:
        transport:
          type: string
          default: bytes
          enum: [bytes, sse]
        output_format:
          $ref: "#/components/schemas/CartesiaOutput"
        language:
          oneOf:
            - $ref: "#/components/schemas/CartesiaLanguage"
            - type: "null"
        pronunciation_dict_id:
          type: [string, "null"]
          description: Sonic 3 and newer, including Sonic 3.5.
        generation_config:
          $ref: "#/components/schemas/CartesiaGeneration"
        context_id:
          type: [string, "null"]
          description: SSE only.
        add_timestamps:
          type: [boolean, "null"]
          description: SSE only.
        add_phoneme_timestamps:
          type: [boolean, "null"]
          description: SSE only.
        use_normalized_timestamps:
          type: [boolean, "null"]
          description: SSE only.

    GeminiVoice:
      type: string
      enum:
        - Zephyr
        - Puck
        - Charon
        - Kore
        - Fenrir
        - Leda
        - Orus
        - Aoede
        - Callirrhoe
        - Autonoe
        - Enceladus
        - Iapetus
        - Umbriel
        - Algieba
        - Despina
        - Erinome
        - Algenib
        - Rasalgethi
        - Laomedeia
        - Achernar
        - Alnilam
        - Schedar
        - Gacrux
        - Pulcherrima
        - Achird
        - Zubenelgenubi
        - Vindemiatrix
        - Sadachbia
        - Sadaltager
        - Sulafat

    GeminiSpeech:
      type: object
      additionalProperties: false
      required: [voice]
      properties:
        voice:
          $ref: "#/components/schemas/GeminiVoice"
        speaker:
          type: string
          minLength: 1
          maxLength: 128
          description: Required on both entries for two-speaker synthesis; must match the transcript label.
        language:
          type: string
          minLength: 2
          maxLength: 35
          description: BCP-47 language tag. Omit for automatic language detection.

    GeminiResponse:
      type: object
      additionalProperties: false
      required: [type]
      properties:
        type:
          const: audio
        mime_type:
          type: string
          enum: [audio/mp3, audio/ogg_opus, audio/l16, audio/wav, audio/alaw, audio/mulaw]
          description: Must match the common response_format. AudioLab transcodes Gemini's native audio/l16 output when needed.
        sample_rate:
          type: integer
          minimum: 1
          maximum: 384000
          description: Output sample rate applied by AudioLab's streaming transcoder.
        bit_rate:
          type: integer
          minimum: 1
          maximum: 512000
          description: Compressed output only. MP3 supports up to 320000 bps; Opus supports up to 512000 bps.
        delivery:
          type: string
          default: inline
          enum: [inline]
          description: Gemini 3.1 TTS currently emits inline PCM; URI delivery is not supported by the model.

    GeminiOptions:
      type: object
      additionalProperties: false
      properties:
        transport:
          type: string
          default: interactions_stream
          enum: [interactions, interactions_stream, generate_content, stream_generate_content]
          description: Gemini 3.1 supports all four transports. Gemini 2.5 supports unary interactions and generate_content only.
        speech_config:
          type: array
          minItems: 1
          maxItems: 2
          items:
            $ref: "#/components/schemas/GeminiSpeech"
        response_format:
          $ref: "#/components/schemas/GeminiResponse"
        store:
          type: boolean
          default: false
          description: Interactions only. Explicitly opt in to Google's interaction storage.
        max_output_tokens:
          type: integer
          minimum: 1
          maximum: 16384
        seed:
          type: integer
          minimum: 0
          maximum: 4294967295
        stop_sequences:
          type: array
          maxItems: 5
          items:
            type: string
            minLength: 1
            maxLength: 1000
        temperature:
          type: number
          minimum: 0
          maximum: 2
        top_p:
          type: number
          minimum: 0
          maximum: 1


    InputAudio:
      type: object
      required:
        - data
        - format
      properties:
        data:
          type: string
          contentEncoding: base64
        format:
          type: string
          examples:
            - wav
            - mp3
            - m4a
            - webm

    ProviderPref:
      type: object
      properties:
        order:
          type: array
          items:
            type: string
        only:
          type: array
          items:
            type: string
        ignore:
          type: array
          items:
            type: string
        models:
          type: array
          description: Model ID patterns allowed for managed routes. Supports * wildcards.
          items:
            type: string
        sort:
          type: string
          enum:
            - price
            - latency
            - speed
            - quality
            - balanced
        cost_quality:
          type: integer
          minimum: 0
          maximum: 10
          description: Balanced-sort weighting from quality (0) to cost (10).
        allow_fallbacks:
          type: boolean
          default: true
        require_features:
          type: boolean
          default: false
        max_price:
          type: object
          properties:
            per_min:
              type: number
              minimum: 0
        data_region:
          $ref: "#/components/schemas/DataRegion"
        region:
          type: array
          items:
            type: string
          deprecated: true
          description: The older name of data_region, with the same meaning.
        options:
          $ref: "#/components/schemas/SttProviderOptions"

    SttProviderOptions:
      type: object
      description: >
        Native speech-to-text controls, keyed by provider. The selected adapter validates the
        named bag strictly and rejects fields or model/transport combinations that its official
        upstream API does not support. See the provider pages for nested shapes and restrictions.
      properties:
        alibaba:
          $ref: "#/components/schemas/AlibabaSttOptions"
        assemblyai:
          $ref: "#/components/schemas/AssemblyAiSttOptions"
        azure:
          $ref: "#/components/schemas/AzureSttOptions"
        cartesia:
          $ref: "#/components/schemas/CartesiaSttOptions"
        deepgram:
          $ref: "#/components/schemas/DeepgramSttOptions"
        deepinfra:
          $ref: "#/components/schemas/DeepinfraSttOptions"
        elevenlabs:
          $ref: "#/components/schemas/ElevenLabsSttOptions"
        fal:
          $ref: "#/components/schemas/FalSttOptions"
        gemini:
          $ref: "#/components/schemas/GeminiSttOptions"
        gladia:
          $ref: "#/components/schemas/GladiaSttOptions"
        google:
          $ref: "#/components/schemas/GoogleSttOptions"
        mistral:
          $ref: "#/components/schemas/MistralSttOptions"
        openai:
          $ref: "#/components/schemas/OpenAiSttOptions"
        reson8:
          $ref: "#/components/schemas/Reson8SttOptions"
        sarvam:
          $ref: "#/components/schemas/SarvamSttOptions"
        smallest:
          $ref: "#/components/schemas/SmallestSttOptions"
        soniox:
          $ref: "#/components/schemas/SonioxSttOptions"
        speechmatics:
          $ref: "#/components/schemas/SpeechmaticsSttOptions"
        together:
          $ref: "#/components/schemas/TogetherSttOptions"
        xai:
          $ref: "#/components/schemas/XaiSttOptions"
      additionalProperties: false

    AlibabaSttOptions:
      type: object
      description: Batch Qwen/Fun-ASR/Omni and realtime Qwen/Fun-ASR controls; availability is model-specific.
      propertyNames:
        enum: [enable_itn, stream, system_prompt, context, sample_rate, top_p, seed, mode, threshold, silence_duration_ms, corpus, vocabulary_id, semantic_punctuation_enabled, max_sentence_silence, multi_threshold_mode_enabled, heartbeat, speech_noise_threshold]
      additionalProperties: true

    AssemblyAiSttOptions:
      type: object
      description: AssemblyAI async and streaming controls. Agent context, voice focus, prompt, and mode require Universal-3.5 Pro Streaming. language_code is pre-recorded-only and is not accepted here as a realtime native option.
      propertyNames:
        enum: [audio_end_at, audio_start_from, auto_highlights, content_safety, content_safety_confidence, custom_spelling, disfluencies, domain, entity_detection, filter_profanity, format_text, iab_categories, language_codes, language_confidence_threshold, language_detection, language_detection_options, multichannel, punctuate, redact_pii, redact_pii_audio, redact_pii_audio_options, redact_pii_audio_quality, redact_pii_policies, redact_pii_return_unredacted, redact_pii_sub, redact_static_entities, remove_audio_tags, sentiment_analysis, speaker_options, speakers_expected, speech_threshold, speech_understanding, temperature, webhook_auth_header_name, webhook_auth_header_value, webhook_url, agent_context, end_of_turn_confidence_threshold, format_turns, inactivity_timeout, max_speakers, max_turn_silence, min_end_of_turn_silence_when_confident, min_turn_silence, mode, prompt, voice_focus]
      additionalProperties: true

    AzureSttOptions:
      type: object
      description: Speech Realtime, speech-fast, and MAI Transcribe controls. Realtime uses the Speech SDK and ConversationTranscriber; MAI remains file-only.
      propertyNames:
        enum: [channels, diarization, locales, phrase_list, profanity_filter_mode, enhanced_mode, language_id_mode, word_timestamps, endpoint_id, dictation, segmentation_strategy, segmentation_silence_timeout_ms, segmentation_max_time_ms, initial_silence_timeout_ms, start_event_sensitivity, stable_partial_result_threshold, post_processing]
      additionalProperties: true

    CartesiaSttOptions:
      type: object
      description: Cartesia realtime controls; Ink 2 and Ink Whisper expose different turn-detection fields.
      propertyNames:
        enum: [mode, max_silence_duration_secs, min_volume, turn_eager_end_threshold, turn_end_threshold, turn_end_timeout_ms, turn_start_threshold]
      additionalProperties: true

    DeepgramSttOptions:
      type: object
      description: Deepgram Nova batch/live controls plus Flux-only end-of-turn controls. diarize_model alone enables diarization; AudioLab never combines it with the deprecated diarize flag.
      propertyNames:
        enum: [custom_intent, custom_intent_mode, custom_topic, custom_topic_mode, detect_entities, detect_language, diarize_model, dictation, extra, filler_words, intents, measurements, multichannel, numerals, paragraphs, profanity_filter, punctuate, replace, search, sentiment, smart_format, summarize, tag, topics, utterances, utt_split, version, endpointing, mip_opt_out, redact, utterance_end_ms, vad_events, eot_threshold, eager_eot_threshold, eot_timeout_ms, language_hints]
      additionalProperties: true

    DeepinfraSttOptions:
      type: object
      propertyNames:
        enum: [chunk_length_s, chunk_level, service_tier]
      additionalProperties: true

    ElevenLabsSttOptions:
      type: object
      description: Scribe v2 batch and Scribe v2 Realtime controls; each transport accepts only its documented subset. Common keyterms are limited to 1000 entries shorter than 50 characters for batch, or 50 entries of at most 20 characters for realtime.
      propertyNames:
        enum: [additional_formats, detect_speaker_roles, diarization_threshold, enable_logging, entity_detection, entity_redaction, entity_redaction_mode, multichannel_output_style, no_verbatim, num_speakers, seed, tag_audio_events, temperature, timestamps_granularity, use_multi_channel, use_speaker_library, commit_strategy, filter_background_audio, include_language_detection, min_silence_duration_ms, min_speech_duration_ms, previous_text, vad_silence_threshold_secs, vad_threshold]
      additionalProperties: true

    FalSttOptions:
      type: object
      description: Native fields for the five cataloged fal apps. Unknown fal marketplace apps receive only audio_url.
      propertyNames:
        enum: [chunk_level, max_segment_len, merge_chunks, version, batch_size, num_speakers, use_pnc, language, acceleration]
      additionalProperties: true

    GeminiSttOptions:
      type: object
      propertyNames:
        enum: [top_p, top_k, candidate_count, max_output_tokens, stop_sequences, seed, thinking_level, include_thoughts]
      additionalProperties: true

    GladiaSttOptions:
      type: object
      description: Gladia pre-recorded and Solaria realtime controls; region, event, callback, and processing fields are realtime-only.
      propertyNames:
        enum: [language_config, custom_vocabulary, vocabulary_intensity, diarization, translation, pii_redaction, custom_spelling, named_entity_recognition, sentiment_analysis, sentences, punctuation_enhanced, region, endpointing, maximum_duration_without_endpointing, custom_metadata, pre_processing, post_processing, messages_config, callback, callback_config]
      additionalProperties: true

    GoogleSttOptions:
      type: object
      description: Google Cloud Speech-to-Text V2 upload and gRPC-streaming controls. Translation requires Chirp 2; custom prompts and endpointing sensitivity require Chirp 3; voice-activity controls are streaming-only.
      propertyNames:
        enum: [language_codes, profanity_filter, automatic_punctuation, spoken_punctuation, spoken_emojis, multi_channel, max_alternatives, diarization, phrase_boost, translation, denoiser, transcript_normalization, custom_prompt, enable_voice_activity_events, voice_activity_timeout, endpointing_sensitivity]
      additionalProperties: true

    MistralSttOptions:
      type: object
      propertyNames:
        enum: [stream, target_streaming_delay_ms]
      additionalProperties: true

    OpenAiSttOptions:
      type: object
      description: OpenAI batch and GPT-Realtime-Whisper controls; logprob include values differ by transport.
      propertyNames:
        enum: [chunking_strategy, include, known_speaker_names, known_speaker_references, stream, delay]
      additionalProperties: true

    Reson8SttOptions:
      type: object
      description: Reson8 prerecorded, continuous realtime, and turn-boundary controls; diarization and confidence are unavailable on the Turns endpoint.
      propertyNames:
        enum: [encoding, sample_rate, channels, custom_model_id, include_timestamps, include_words, include_language, include_confidence, include_interim, diarize, max_speakers, patterns]
      additionalProperties: true

    SarvamSttOptions:
      type: object
      description: Sarvam batch and realtime controls. Diarization is available only on Saaras v3 Batch.
      propertyNames:
        enum: [mode, with_timestamps, with_diarization, num_speakers, input_audio_codec, high_vad_sensitivity, vad_signals, flush_signal, positive_speech_threshold, negative_speech_threshold, min_speech_frames, first_turn_min_speech_frames, negative_frames_count, negative_frames_window, start_speech_volume_threshold, interrupt_min_speech_frames, pre_speech_pad_frames, num_initial_ignored_frames]
      additionalProperties: true

    SmallestSttOptions:
      type: object
      propertyNames:
        enum: [word_timestamps, diarize, redact_pii, redact_pci, emotion_detection, gender_detection, sentence_timestamps, keywords, format, itn_normalize, profanity_filter, punctuate, capitalize, encoding, max_words, eou_timeout_ms, finalize_on_words]
      additionalProperties: true

    SonioxSttOptions:
      type: object
      propertyNames:
        enum: [client_reference_id, context, enable_language_identification, language_hints, language_hints_strict, translation, webhook_auth_header_name, webhook_auth_header_value, webhook_url, enable_endpoint_detection, endpoint_latency_adjustment_level, endpoint_sensitivity, max_endpoint_delay_ms]
      additionalProperties: true

    SpeechmaticsSttOptions:
      type: object
      propertyNames:
        enum: [additional_vocab, audio_filtering_config, channel_diarization_labels, diarization, domain, enable_entities, output_locale, punctuation_overrides, speaker_diarization_config, transcript_filtering_config, translation_config, conversation_config, max_delay, max_delay_mode]
      additionalProperties: true

    TogetherSttOptions:
      type: object
      propertyNames:
        enum: [max_speakers, min_speakers, max_speech_duration_s, min_silence_duration_ms, min_speech_duration_ms, speech_pad_ms, threshold, turn_detection]
      additionalProperties: true

    XaiSttOptions:
      type: object
      propertyNames:
        enum: [channels, filler_words, format, multichannel, endpointing, smart_turn, smart_turn_timeout]
      additionalProperties: true

    TranscriptionResult:
      type: object
      required:
        - id
        - status
        - model
        - provider
        - resolved_model
        - duration
        - cost
        - error_code
        - created_at
        - completed_at
        - text
        - language
        - segments
        - words
      properties:
        id:
          type: string
        status:
          type: string
          enum:
            - queued
            - running
            - succeeded
            - failed
            - canceled
        model:
          type: string
        provider:
          type:
            - string
            - "null"
        resolved_model:
          type:
            - string
            - "null"
        duration:
          type:
            - number
            - "null"
        cost:
          type:
            - number
            - "null"
        error_code:
          type:
            - string
            - "null"
        created_at:
          type: string
          format: date-time
        completed_at:
          type:
            - string
            - "null"
          format: date-time
        text:
          type:
            - string
            - "null"
        language:
          type:
            - string
            - "null"
        segments:
          type: array
          items:
            type: object
            additionalProperties: true
        words:
          type: array
          items:
            type: object
            additionalProperties: true

    Transcription:
      type: object
      required:
        - id
        - object
        - created
        - model
        - resolved_model
        - provider
        - text
        - usage
        - route
        - warnings
      properties:
        id:
          type: string
        object:
          type: string
          const: transcription
        created:
          type: integer
          description: Unix timestamp in seconds.
        model:
          type: string
        resolved_model:
          type: string
        provider:
          type: string
        text:
          type: string
        language:
          type:
            - string
            - "null"
        duration:
          type:
            - number
            - "null"
        segments:
          type: array
          items:
            $ref: "#/components/schemas/Segment"
        words:
          type: array
          items:
            $ref: "#/components/schemas/Word"
        speakers:
          type: array
          items:
            $ref: "#/components/schemas/Speaker"
        entities:
          type: array
          items:
            $ref: "#/components/schemas/Entity"
        usage:
          $ref: "#/components/schemas/UsageCore"
        route:
          $ref: "#/components/schemas/RouteTrace"
        warnings:
          type: array
          items:
            type: string

    Segment:
      type: object
      required:
        - id
        - start
        - end
        - text
      properties:
        id:
          type: integer
        start:
          type: number
        end:
          type: number
        text:
          type: string
        speaker:
          type:
            - string
            - "null"
        confidence:
          type:
            - number
            - "null"
          minimum: 0
          maximum: 1

    Word:
      type: object
      required:
        - word
        - start
        - end
      properties:
        word:
          type: string
        start:
          type: number
        end:
          type: number
        speaker:
          type:
            - string
            - "null"
        confidence:
          type:
            - number
            - "null"
          minimum: 0
          maximum: 1

    Speaker:
      type: object
      required:
        - id
      properties:
        id:
          type: string
        label:
          type:
            - string
            - "null"

    Entity:
      type: object
      required:
        - text
        - type
      properties:
        text:
          type: string
        type:
          type: string
        start:
          type:
            - number
            - "null"
        end:
          type:
            - number
            - "null"

    UsageCore:
      type: object
      required:
        - seconds
        - billable_seconds
        - cost
        - currency
      properties:
        seconds:
          type: number
        billable_seconds:
          type: integer
        input_chars:
          type: integer
          minimum: 0
        cost:
          type: number
        currency:
          type: string
          default: USD

    RouteTrace:
      type: object
      required:
        - mode
        - reason
        - attempts
      properties:
        mode:
          type: string
          examples:
            - exact
            - auto
            - cheap
            - fast
            - accurate
        reason:
          type: string
        candidates:
          type: integer
        filtered:
          type: array
          items:
            $ref: "#/components/schemas/RouteFilter"
        attempts:
          type: array
          items:
            $ref: "#/components/schemas/RouteAttempt"
        lid:
          $ref: "#/components/schemas/LidTrace"

    LidTrace:
      type: object
      required:
        - status
        - source
        - language
        - confidence
        - margin
        - latency_ms
        - speech_ms
        - cached
        - reason
      properties:
        status:
          type: string
          enum:
            - detected
            - abstained
            - failed
        source:
          type: string
          description: Diagnostic tag derived from the configured ONNX model filename.
          examples:
            - onnx:mms-lid-126-q8
        language:
          type:
            - string
            - "null"
        confidence:
          type:
            - number
            - "null"
        margin:
          type:
            - number
            - "null"
        latency_ms:
          type: integer
        speech_ms:
          type:
            - integer
            - "null"
        cached:
          type: boolean
        reason:
          type:
            - string
            - "null"

    RouteFilter:
      type: object
      required:
        - endpoint
        - reason
      properties:
        endpoint:
          type: string
        reason:
          type: string

    RouteAttempt:
      type: object
      required:
        - provider
        - model
        - status
      properties:
        provider:
          type: string
        model:
          type: string
        endpoint:
          type: string
        status:
          type: string
          enum:
            - success
            - failed
            - skipped
        latency_ms:
          type:
            - integer
            - "null"
        error_code:
          type:
            - string
            - "null"
        status_code:
          description: Upstream HTTP or WebSocket status code for this attempt, when known.
          type:
            - integer
            - "null"

    ModelTask:
      type: string
      enum: [llm, transcription, speech, s2s, embeddings, moderation, images, videos, ocr, rerank, music, audio, classification, vision]

    Model:
      type: object
      required:
        - id
        - name
        - owner
        - task
        - input_modalities
        - output_modalities
        - state
        - endpoints
      properties:
        id:
          type: string
          examples:
            - stt/auto
            - openai/gpt-4o-transcribe
        name:
          type: string
        owner:
          type: string
        task:
          $ref: "#/components/schemas/ModelTask"
        input_modalities:
          type: array
          items:
            type: string
        output_modalities:
          type: array
          items:
            type: string
            enum:
              - transcription
              - audio
              - embedding
              - classification
              - image
              - video
              - text
              - score
        state:
          type: string
          enum:
            - active
            - preview
            - disabled
        languages:
          type: array
          items:
            type: string
        features:
          $ref: "#/components/schemas/Features"
        default_route:
          type:
            - string
            - "null"
        endpoints:
          type: array
          items:
            type: string
        expiration_date:
          type:
            - string
            - "null"
          format: date
          description: The provider's announced shutdown (UTC date), or null.
        disabled_reason:
          type: string
          description: Why a model was switched off; only on a single-model lookup of a withdrawn model.
        canonical_slug:
          type: string
          description: Language models. The same as id.
        created:
          type:
            - integer
            - "null"
          description: Language models. Release date as Unix seconds; null when the maker never published one.
        description:
          type: string
        context_length:
          type: integer
          description: Language models. The most input tokens a request may carry.
        architecture:
          type: object
          description: Language models.
          properties:
            modality: { type: string, examples: ["text+image+file->text"] }
            input_modalities:
              type: array
              description: What a request may carry. image and file (PDF) parts bill as input tokens.
              items: { type: string, enum: [text, image, file, audio] }
            output_modalities: { type: array, items: { type: string } }
            tokenizer: { type: string }
            instruct_type: { type: "null" }
        pricing:
          type: object
          description: >-
            Language models. USD per token as decimal strings, what AudioLab charges (the
            provider's rate minus 15%). "-1" when the price is that of the models that answer.
          additionalProperties: { type: string }
          properties:
            prompt: { type: string, examples: ["0.00000525"] }
            completion: { type: string }
            input_cache_read: { type: string }
            web_search:
              type: string
              description: >-
                USD per web search call, query or grounded request with the model's own search;
                "0" when it cannot search.
              examples: ["0.0105"]
        top_provider:
          type: object
          description: Language models.
          properties:
            context_length: { type: integer }
            max_completion_tokens:
              type: [integer, "null"]
              description: The output cap. A request without max_tokens may generate up to it.
            is_moderated: { type: boolean }
        per_request_limits: { type: "null" }
        supported_parameters:
          type: array
          description: Language models. Request parameters the model's contract honours.
          items: { type: string }
        default_parameters: { type: object }
        supported_endpoints:
          type: array
          description: Set when a model is served on one API only.
          items: { type: string }

    ModelEndpointsDocument:
      type: object
      description: OpenRouter's endpoints document for a language model.
      required:
        - id
        - name
        - endpoints
      properties:
        id: { type: string }
        name: { type: string }
        created: { type: [integer, "null"] }
        description: { type: string }
        architecture: { type: object }
        endpoints:
          type: array
          items:
            type: object
            properties:
              name:
                type: string
                description: The host's name and the ID that selects this host alone.
                examples: ["DeepInfra | deepinfra/openai-gpt-oss-120b"]
              model_name: { type: string }
              context_length: { type: integer }
              pricing: { type: object, additionalProperties: { type: string } }
              provider_name: { type: string }
              tag:
                type: string
                description: >-
                  What provider.order, only and ignore match: the provider's slug, or slug/tag
                  when one host serves the model twice.
                examples: ["deepinfra/turbo"]
              quantization:
                type: [string, "null"]
                description: As the host publishes it; null when it does not say.
                examples: ["fp8"]
              max_completion_tokens: { type: integer }
              max_prompt_tokens: { type: integer }
              supported_parameters: { type: array, items: { type: string } }
              status: { type: integer, description: 0 while the endpoint is offered. }
              uptime_last_30m:
                type: [number, "null"]
                description: >-
                  Percent of requests in the last 30 minutes that did not fail through the
                  host (refused connections, timeouts, interrupted streams, empty answers and
                  HTTP errors other than request errors); null without requests.
              uptime_last_1d:
                type: [number, "null"]
                description: Percentage of attempts that succeeded over the last day, or null without any.
              latency_last_30m:
                description: Time to first token of streamed answers in the last 30 minutes, in milliseconds.
                oneOf:
                  - $ref: "#/components/schemas/Percentiles"
                  - type: "null"
              throughput_last_30m:
                description: Output tokens per second after the first token in the last 30 minutes.
                oneOf:
                  - $ref: "#/components/schemas/Percentiles"
                  - type: "null"
              supports_implicit_caching: { type: boolean }
              expiration_date: { type: [string, "null"], format: date }
              data_region: { $ref: "#/components/schemas/ProcessingRegion" }
              data_region_country: { $ref: "#/components/schemas/ProcessingRegionCountry" }
              data_region_source: { $ref: "#/components/schemas/ProcessingRegionSource" }
              data_region_checked: { $ref: "#/components/schemas/ProcessingRegionChecked" }

    ProcessingRegion:
      type: string
      description: >-
        Where the endpoint processes requests, as its provider documents it for the host AudioLab
        calls: us, eu, a country code, global (several regions with no fixed one) or unknown (not
        documented). Only us, eu and country codes satisfy provider.data_region.
      examples: [us, eu, sg, global, unknown]
    ProcessingRegionCountry:
      type: string
      description: The EU member state, when the provider names one.
      examples: [nl]
    ProcessingRegionSource:
      type: [string, "null"]
      format: uri
      description: The provider's page that documents the region; null when none does.
    ProcessingRegionChecked:
      type: [string, "null"]
      format: date
      description: The day that page was read.

    Percentiles:
      type: object
      required: [p50, p75, p90, p99]
      properties:
        p50: { type: number }
        p75: { type: number }
        p90: { type: number }
        p99: { type: number }

    Provider:
      type: object
      required:
        - id
        - name
        - state
      properties:
        id:
          type: string
        name:
          type: string
        state:
          type: string
          enum:
            - active
            - degraded
            - disabled
        auth_type:
          type: string
        docs_url:
          type: string
          format: uri
        data_policy:
          type: string
          enum:
            - configurable
            - no_training
            - unknown
        regions:
          type: array
          items:
            type: string
          description: The documented processing regions across the provider's endpoints (see ProcessingRegion).

    Endpoint:
      type: object
      required:
        - id
        - task
        - model
        - provider
        - state
        - price
        - features
      properties:
        id:
          type: string
        model:
          type: string
        provider:
          type: string
        task:
          $ref: "#/components/schemas/ModelTask"
        state:
          type: string
          enum:
            - active
            - preview
            - degraded
            - disabled
        regions:
          type: array
          items:
            type: string
          description: The region codes the endpoint satisfies (eu and nl for an endpoint in the Netherlands), or global or unknown.
        data_region: { $ref: "#/components/schemas/ProcessingRegion" }
        data_region_country: { $ref: "#/components/schemas/ProcessingRegionCountry" }
        data_region_source: { $ref: "#/components/schemas/ProcessingRegionSource" }
        data_region_checked: { $ref: "#/components/schemas/ProcessingRegionChecked" }
        features:
          $ref: "#/components/schemas/Features"
        price:
          $ref: "#/components/schemas/Price"
        limits:
          $ref: "#/components/schemas/EndpointLimits"
        latency_ms:
          type:
            - integer
            - "null"
        speed_factor:
          type:
            - number
            - "null"
        err_rate:
          type:
            - number
            - "null"
        uptime:
          type:
            - number
            - "null"
          description: Share of attempts that succeeded over the last day, 0 to 1; null without any.

    ModelStatsSummary:
      type: object
      required: [requests, probes, uptime, latency_p50_ms, latency_p90_ms, throughput_p50]
      properties:
        requests:
          type: integer
          description: Customer attempts measured.
        probes:
          type: integer
          description: Scheduled probes measured.
        uptime:
          type: [number, "null"]
          description: Share of counted attempts that succeeded, 0 to 1.
        latency_p50_ms: { type: [integer, "null"] }
        latency_p90_ms: { type: [integer, "null"] }
        throughput_p50:
          type: [number, "null"]
          description: Median throughput in throughput_unit.

    ModelStatsPoint:
      allOf:
        - type: object
          required: [at]
          properties:
            at:
              type: string
              format: date-time
              description: Start of the interval.
        - $ref: "#/components/schemas/ModelStatsSummary"

    ModelStats:
      type: object
      required: [id, task, window, interval_seconds, latency_metric, throughput_unit, summary, series, providers]
      properties:
        id: { type: string, examples: ["openai/gpt-5.5"] }
        task:
          type: string
          enum: [llm, speech, transcription]
        window:
          type: string
          enum: ["24h", "7d", "30d"]
        interval_seconds: { type: integer, examples: [7200] }
        latency_metric:
          type: string
          enum: [time_to_first_token, time_to_first_audio, total]
        throughput_unit:
          type: string
          enum: [tokens_per_second, characters_per_second, audio_seconds_per_second]
        summary:
          $ref: "#/components/schemas/ModelStatsSummary"
        series:
          type: array
          items:
            $ref: "#/components/schemas/ModelStatsPoint"
        providers:
          type: array
          description: >-
            The same numbers for each provider that served or was probed, most measured first;
            for a model that several hosts serve, for each endpoint.
          items:
            type: object
            required: [provider, summary, series]
            properties:
              provider:
                type: string
                description: >-
                  The provider's slug; for a model that several hosts serve, the endpoint's
                  routing tag (deepinfra/turbo), as in the endpoints document.
                examples: ["openai"]
              endpoint:
                type: string
                description: The endpoint's host-pinned model ID; only for a model that several hosts serve.
                examples: ["deepinfra/openai-gpt-oss-120b-turbo"]
              summary:
                $ref: "#/components/schemas/ModelStatsSummary"
              series:
                type: array
                items:
                  $ref: "#/components/schemas/ModelStatsPoint"

    Features:
      type: object
      properties:
        batch:
          type: boolean
        stream:
          type: boolean
        diarization:
          type: boolean
        word_timestamps:
          type: boolean
        segment_timestamps:
          type: boolean
        confidence:
          type: boolean
        redaction:
          type: boolean
        translation:
          type: boolean
        keyterms:
          type: boolean
        subtitles:
          type: boolean
        duplex:
          type: boolean
        realtime:
          type: boolean
        audio_input:
          type: boolean
        audio_output:
          type: boolean
        tools:
          type: boolean

    Price:
      type: object
      required:
        - unit
        - input_price
        - currency
      properties:
        unit:
          type: string
          enum:
            - second
            - minute
            - hour
            - character
            - request
            - input_token
            - output_token
        input_price:
          type: number
          description: AudioLab customer price per unit in the stated currency, 15% below the provider's price. Native INR/EUR prices settle in USD using the recorded daily reference.
        output_price:
          type:
            - number
            - "null"
          description: AudioLab customer output price per unit in the stated currency, 15% below the provider's price.
        min_bill:
          type:
            - number
            - "null"
        rounding:
          type: string
          enum:
            - none
            - second
            - minute
        currency:
          type: string
          default: USD

    EndpointLimits:
      type: object
      properties:
        max_bytes:
          type:
            - integer
            - "null"
        max_seconds:
          type:
            - integer
            - "null"
        max_chars:
          type:
            - integer
            - "null"
        formats:
          type: array
          items:
            type: string

    LogEntry:
      type: object
      additionalProperties: false
      required: [id, source, workspace_id, key_id, key_name, task, model, resolved_model, provider, state, cost, input_tokens, output_tokens, input_chars, audio_seconds, duration_ms, protocol, streamed, error_code, created_at, completed_at]
      properties:
        id: { type: string }
        source: { type: string, enum: [audio, llm, legacy, moderation, images, videos, secondary] }
        workspace_id: { type: string }
        key_id: { type: [string, "null"] }
        key_name: { type: [string, "null"] }
        task: { $ref: "#/components/schemas/LogTask" }
        model: { type: string }
        resolved_model: { type: [string, "null"] }
        provider: { type: [string, "null"] }
        state: { type: string, enum: [queued, running, succeeded, failed, canceled, unknown] }
        cost: { type: [number, "null"], description: "Settled customer cost in USD when known." }
        input_tokens: { type: [integer, "null"] }
        output_tokens: { type: [integer, "null"] }
        input_chars: { type: [integer, "null"] }
        audio_seconds: { type: [number, "null"] }
        duration_ms: { type: [number, "null"], description: "Time from request creation to completion when known; legacy entries use reported latency." }
        protocol: { type: [string, "null"] }
        streamed: { type: [boolean, "null"] }
        error_code: { type: [string, "null"] }
        created_at: { type: string, format: date-time }
        completed_at: { type: [string, "null"], format: date-time }

    LogTask:
      type: string
      description: A product, as request logs, exports and trace destinations filter by it.
      enum: [speech, transcription, s2s, llm, embeddings, moderation, images, videos, ocr, rerank, inference]

    TraceDestination:
      type: object
      additionalProperties: false
      required: [id, object, workspace_id, name, kind, endpoint, encoding, header_names, secret_hint, sample_rate, products, include_content, enabled, created_at, updated_at, last_delivery, pending, dead]
      properties:
        id: { type: string, description: "trd_ followed by 22 characters.", example: trd_example }
        object: { type: string, enum: [trace_destination] }
        workspace_id: { type: string, example: ws_example }
        name: { type: string, maxLength: 80, example: Honeycomb }
        kind: { type: string, enum: [webhook, otlp, langfuse], description: "webhook: signed JSON. otlp: OTLP/HTTP traces. langfuse: Langfuse's ingestion API.", example: otlp }
        endpoint: { type: string, description: "The URL traces are sent to; for Langfuse, its host.", example: "https://api.honeycomb.io/v1/traces" }
        encoding: { type: string, enum: [json, protobuf], description: "OTLP body encoding; json for other kinds.", example: protobuf }
        header_names: { type: array, items: { type: string }, description: "Names of the saved headers. Values are never returned." }
        secret_hint: { type: [string, "null"], description: "Last four characters of a webhook signing secret or Langfuse public key." }
        sample_rate: { type: number, exclusiveMinimum: 0, maximum: 1, description: "Share of requests sent. Sampling is per request, or per session_id when one is sent." }
        products: { type: array, items: { $ref: "#/components/schemas/LogTask" }, description: "Products traced; empty means all." }
        include_content: { type: boolean, description: "Also send LLM prompts and outputs as text. Never applied under zero data retention." }
        enabled: { type: boolean }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        last_delivery:
          type: object
          additionalProperties: false
          required: [status, status_code, error, attempted_at, delivered_at]
          properties:
            status: { type: [string, "null"], enum: [delivered, retrying, dead, null], description: "The most recent batch's outcome; null before the first." }
            status_code: { type: [integer, "null"], description: "HTTP status the destination answered with.", example: 200 }
            error: { type: [string, "null"], example: null }
            attempted_at: { type: [string, "null"], format: date-time }
            delivered_at: { type: [string, "null"], format: date-time, description: "The last successful delivery." }
        pending: { type: integer, description: "Traces waiting to be delivered or retried." }
        dead: { type: integer, description: "Traces that could not be delivered, kept for 7 days." }

    TraceDestinationSecret:
      allOf:
        - $ref: "#/components/schemas/TraceDestination"
        - type: object
          properties:
            signing_secret: { type: string, description: "A webhook's signing secret (whsec_…), returned only when created or rotated." }

    TraceDestinationCreate:
      type: object
      additionalProperties: false
      required: [workspace_id, name, kind, endpoint]
      properties:
        workspace_id: { type: string, minLength: 1, maxLength: 256 }
        name: { type: string, minLength: 1, maxLength: 80 }
        kind: { type: string, enum: [webhook, otlp, langfuse] }
        endpoint: { type: string, maxLength: 2048, description: "A public https URL on port 443, 4318 or 8443. For Langfuse, the host only." }
        encoding: { type: string, enum: [json, protobuf], default: json, description: "protobuf is for OTLP only." }
        headers:
          type: object
          maxProperties: 20
          additionalProperties: { type: string, maxLength: 4096 }
          description: "Sent with every delivery, such as an API key header. Stored encrypted. Not for Langfuse; framing headers and audiolab-* names are refused."
        public_key: { type: string, maxLength: 200, description: "Langfuse public key (pk-lf-…). Langfuse only, and required." }
        secret_key: { type: string, maxLength: 200, description: "Langfuse secret key (sk-lf-…). Langfuse only, and required." }
        sample_rate: { type: number, exclusiveMinimum: 0, maximum: 1, default: 1 }
        products: { type: array, maxItems: 11, items: { $ref: "#/components/schemas/LogTask" }, default: [] }
        include_content: { type: boolean, default: false, description: "Owners and admins only." }
        enabled: { type: boolean, default: true }

    TraceDestinationUpdate:
      type: object
      additionalProperties: false
      required: [workspace_id]
      properties:
        workspace_id: { type: string, minLength: 1, maxLength: 256 }
        name: { type: string, minLength: 1, maxLength: 80 }
        endpoint: { type: string, maxLength: 2048 }
        encoding: { type: string, enum: [json, protobuf] }
        headers:
          type: object
          maxProperties: 20
          additionalProperties: { type: string, maxLength: 4096 }
          description: Replaces every saved header; omit to keep them.
        public_key: { type: string, maxLength: 200 }
        secret_key: { type: string, maxLength: 200 }
        rotate_secret: { type: boolean, const: true, description: "Replace a webhook's signing secret. The old one stops working at once." }
        sample_rate: { type: number, exclusiveMinimum: 0, maximum: 1 }
        products: { type: array, maxItems: 11, items: { $ref: "#/components/schemas/LogTask" } }
        include_content: { type: boolean, description: "Owners and admins only, to turn on." }
        enabled: { type: boolean }

    TraceDelivery:
      type: object
      additionalProperties: false
      required: [id, request_id, product, state, attempts, status_code, error, created_at, finished_at, next_attempt_at]
      properties:
        id: { type: string, example: trv_example }
        request_id: { type: string, example: lreq_example }
        product: { type: [string, "null"], enum: [speech, transcription, s2s, llm, embeddings, moderation, images, videos, ocr, rerank, inference, null] }
        state: { type: string, enum: [pending, delivered, dead] }
        attempts: { type: integer }
        status_code: { type: [integer, "null"], description: "HTTP status of the last attempt.", example: 503 }
        error: { type: [string, "null"], example: HTTP 503 }
        created_at: { type: string, format: date-time }
        finished_at: { type: [string, "null"], format: date-time }
        next_attempt_at: { type: [string, "null"], format: date-time, description: "When a pending delivery is next tried." }

    TraceTestResult:
      type: object
      additionalProperties: false
      required: [ok, status_code, error, duration_ms, trace_id]
      properties:
        ok: { type: boolean, example: true }
        status_code: { type: [integer, "null"], example: 200 }
        error: { type: [string, "null"], example: null }
        duration_ms: { type: integer, example: 184 }
        trace_id: { type: string, description: "The test trace's id (test_…).", example: test_example }

    RequestLog:
      type: object
      required:
        - id
        - task
        - model
        - state
        - created_at
      properties:
        id:
          type: string
        task:
          type: string
          enum:
            - transcription
            - speech
            - s2s
        model:
          type: string
        resolved_model:
          type:
            - string
            - "null"
        provider:
          type:
            - string
            - "null"
        state:
          type: string
          enum:
            - queued
            - running
            - succeeded
            - failed
            - canceled
        duration:
          type:
            - number
            - "null"
        billable_seconds:
          type:
            - integer
            - "null"
        cost:
          type:
            - number
            - "null"
        input_chars:
          type:
            - integer
            - "null"
        output_bytes:
          type:
            - integer
            - "null"
        route:
          $ref: "#/components/schemas/RouteTrace"
        error_code:
          type:
            - string
            - "null"
        created_at:
          type: string
          format: date-time
        completed_at:
          type:
            - string
            - "null"
          format: date-time

    UsagePage:
      type: object
      required:
        - data
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Usage"
        next_cursor:
          type:
            - string
            - "null"

    Usage:
      type: object
      required: [id, request_id, workspace_id, source, unit, cost, currency, created_at]
      description: Settled customer usage across products. Null dimensions do not apply or were not reported. Pending holds are shown in request logs.
      properties:
        id:
          type: string
        request_id:
          type: string
          description: Durable request ID returned in the x-request-id response header.
        workspace_id:
          type: string
        key_id:
          type: [string, "null"]
        source:
          type: string
          enum: [audio, llm, legacy, images, moderation, secondary]
        task:
          type: [string, "null"]
          enum: [transcription, speech, s2s, llm, embeddings, images, moderation, ocr, rerank, null]
        model:
          type: [string, "null"]
        resolved_model:
          type: [string, "null"]
        provider:
          type: [string, "null"]
        state:
          type: [string, "null"]
          enum: [queued, running, succeeded, failed, canceled, unknown, null]
        error_code:
          type: [string, "null"]
        unit:
          type: string
        cost:
          type: number
          description: Posted customer wallet debit in USD, after fractional carry.
        accrued_cost:
          type: [string, "null"]
          description: Exact per-request customer accrual before carry, with 16 decimal places. Historical rows may not have this value.
        currency:
          type: string
          const: USD
        seconds:
          type: [number, "null"]
        billable_seconds:
          type: [number, "null"]
        input_chars:
          type: [integer, "null"]
        input_tokens:
          type: [integer, "null"]
          description: Input tokens of selected successful outputs, excluding internal and failed attempts.
        cached_tokens:
          type: [integer, "null"]
          description: Input tokens served from cache, when reported by the provider. Null for usage that does not report caching.
        output_tokens:
          type: [integer, "null"]
        reasoning_tokens:
          type: [integer, "null"]
        images:
          type: [integer, "null"]
        created_at:
          type: string
          format: date-time
          description: Settlement time for funded LLM, embeddings and images; usage completion time for audio and moderation; recorded event time for legacy data.

    LedgerPage:
      type: object
      required:
        - data
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/LedgerRow"
        next_cursor:
          type:
            - string
            - "null"

    LedgerRow:
      type: object
      required:
        - id
        - workspace_id
        - kind
        - amount
        - currency
        - balance
        - state
        - created_at
      properties:
        id:
          type: string
        workspace_id:
          type: string
        request_id:
          type:
            - string
            - "null"
        key_id:
          type: [string, "null"]
          description: API key that incurred the charge, when associated with a request.
        key_name:
          type: [string, "null"]
          description: Display name of the API key, including revoked keys.
        purchase_id:
          type: [string, "null"]
          description: Credit purchase associated with this row; null for grants and internal ledger entries.
        kind:
          type: string
          enum:
            - credit
            - reserve
            - charge
            - refund
            - adjust
            - fee
        amount:
          type: number
        currency:
          type: string
        balance:
          type: number
        state:
          type: string
          enum:
            - pending
            - posted
            - void
        note:
          type:
            - string
            - "null"
        created_at:
          type: string
          format: date-time

    Credits:
      type: object
      required:
        - workspace_id
        - balance
        - currency
      properties:
        workspace_id:
          type: string
        balance:
          type: number
        currency:
          type: string
          default: USD

    CurrentKey:
      type: object
      required: [data]
      properties:
        data:
          type: object
          required: [label, name, limit, usage, limit_remaining, credits_remaining, rate_limit, scopes]
          properties:
            label: { type: string }
            name: { type: string }
            limit: { type: [number, "null"], description: "The key's tightest own limit in USD, its lifetime spend_limit or a budget; null when it has none." }
            usage: { type: number, description: "USD spent, including holds for requests in progress." }
            usage_daily: { type: number, description: "USD spent in the current UTC day." }
            usage_weekly: { type: number, description: "USD spent in the current UTC week, from Monday." }
            usage_monthly: { type: number, description: "USD spent in the current UTC month." }
            limit_remaining: { type: [number, "null"], description: "What is left under limit." }
            limit_reset: { type: [string, "null"], enum: [daily, weekly, monthly, null], description: "The period of limit; null for a lifetime limit or none." }
            budgets:
              type: array
              description: Every limit that applies to the key, its own and its workspace's.
              items: { $ref: "#/components/schemas/BudgetState" }
            effective_budget:
              description: The limit with the least left, which stops the key first; null when nothing applies.
              oneOf:
                - $ref: "#/components/schemas/BudgetState"
                - type: "null"
            include_byok_in_limit: { type: boolean }
            is_free_tier: { type: boolean }
            is_provisioning_key: { type: boolean }
            credits_remaining: { type: number, description: "The workspace's credit balance in USD." }
            rate_limit:
              type: object
              required: [requests, interval]
              properties:
                requests: { type: [integer, "null"] }
                interval: { type: string, const: 60s }
            scopes:
              type: array
              items: { type: string }
            expires_at: { type: [string, "null"], format: date-time }

    Generation:
      type: object
      required: [data]
      properties:
        data:
          type: object
          required: [id, request_id, model, billing_state, provider_responses, routing]
          properties:
            id: { type: string, description: "gen-<unix seconds>-<32 hex digits>" }
            request_id: { type: string, description: "The x-request-id of the response." }
            model: { type: [string, "null"] }
            provider_name: { type: [string, "null"] }
            api_type: { type: string, enum: [chat, messages, responses, embeddings] }
            streamed: { type: boolean }
            created_at: { type: string, format: date-time }
            billing_state: { type: string, enum: [pending, settled] }
            total_cost: { type: [number, "null"], description: "Settled customer charge in USD." }
            usage: { type: [number, "null"] }
            is_byok: { type: boolean }
            origin: { type: string }
            latency: { type: [integer, "null"], description: "Milliseconds to the first token, else to completion." }
            generation_time: { type: [integer, "null"] }
            finish_reason: { type: [string, "null"] }
            native_finish_reason: { type: [string, "null"] }
            cancelled: { type: boolean }
            tokens_prompt: { type: [integer, "null"] }
            tokens_completion: { type: [integer, "null"] }
            native_tokens_prompt: { type: [integer, "null"] }
            native_tokens_completion: { type: [integer, "null"] }
            native_tokens_reasoning: { type: [integer, "null"] }
            native_tokens_cached: { type: [integer, "null"] }
            router: { type: [string, "null"] }
            attempts: { type: integer }
            provider_responses:
              type: array
              items:
                type: object
                properties:
                  id: { type: string }
                  provider_name: { type: [string, "null"] }
                  model: { type: [string, "null"] }
                  role: { type: [string, "null"] }
                  status: { type: [integer, "null"], description: "Upstream HTTP status." }
                  outcome: { type: string, enum: [dispatching, unknown, succeeded, failed, review] }
                  failure_cause: { type: [string, "null"] }
                  latency: { type: [integer, "null"] }
                  ttft: { type: [integer, "null"] }
                  finish_reason: { type: [string, "null"] }
            routing:
              type: object
              properties:
                mode: { type: string, enum: [direct, named, auto, fusion, fallback] }
                requested_model: { type: [string, "null"] }
                policy: { type: [string, "null"] }
                released: { type: string, enum: [unresolved, review, abandoned] }

    PaymentConfig:
      type: object
      required:
        - enabled
        - currency
        - min_credits
        - max_credits
        - checkout_token
        - checkout_env
      properties:
        enabled:
          type: boolean
        currency:
          type: string
          const: USD
        min_credits:
          type: integer
        max_credits:
          type: integer
        checkout_token:
          type:
            - string
            - "null"
          description: Public client-side token used to open checkout.
        checkout_env:
          type: string
          enum:
            - sandbox
            - production

    CheckoutReq:
      type: object
      additionalProperties: false
      required:
        - workspace_id
        - amount
      properties:
        workspace_id:
          type: string
        amount:
          type: integer
          minimum: 1
          description: Dollar-for-dollar USD credit amount in whole dollars.

    CreditPurchase:
      type: object
      required:
        - purchase_id
        - transaction_id
        - workspace_id
        - amount
        - credits
        - currency
        - status
        - paid_at
      properties:
        purchase_id:
          type: string
        transaction_id:
          type:
            - string
            - "null"
        workspace_id:
          type: string
        amount:
          type: number
        credits:
          type: number
        currency:
          type: string
          const: USD
        status:
          type: string
          enum:
            - pending
            - open
            - processing
            - paid
            - failed
            - expired
        paid_at:
          type:
            - string
            - "null"
          format: date-time

    CheckoutRes:
      type: object
      required:
        - purchase
        - checkout_url
        - transaction_id
      properties:
        purchase:
          $ref: "#/components/schemas/CreditPurchase"
        checkout_url:
          type: string
          format: uri
        transaction_id:
          type: string

    AutoTopup:
      type: object
      required:
        - available
        - enabled
        - currency
        - customer_id
        - threshold
        - amount
        - payment_method
        - last_status
        - last_error
        - last_charged_at
      properties:
        available:
          type: boolean
        enabled:
          type: boolean
        currency:
          type: string
          const: USD
        customer_id:
          type:
            - string
            - "null"
          pattern: ^ctm_[a-z0-9]{26}$
        threshold:
          type: number
          minimum: 0
        amount:
          type: number
          minimum: 0
        payment_method:
          oneOf:
            - $ref: "#/components/schemas/AutoTopupMethod"
            - type: "null"
        last_status:
          type:
            - string
            - "null"
          enum:
            - pending
            - open
            - processing
            - paid
            - failed
            - expired
            - null
        last_error:
          type:
            - string
            - "null"
        last_charged_at:
          type:
            - string
            - "null"
          format: date-time

    AutoTopupMethod:
      type: object
      additionalProperties: false
      required:
        - type
        - brand
        - last4
        - exp_month
        - exp_year
      properties:
        type:
          type: string
        brand:
          type:
            - string
            - "null"
        last4:
          type:
            - string
            - "null"
          pattern: "^[0-9]{4}$"
        exp_month:
          type:
            - integer
            - "null"
          minimum: 1
          maximum: 12
        exp_year:
          type:
            - integer
            - "null"

    AutoTopupSetupReq:
      type: object
      additionalProperties: false
      required:
        - workspace_id
        - threshold
        - amount
      properties:
        workspace_id:
          type: string
        threshold:
          type: number
          minimum: 0
          description: Workspace balance that triggers an automatic purchase.
        amount:
          type: integer
          minimum: 1
          description: Whole-dollar credits added by each automatic purchase.
        enabled:
          type: boolean
          default: true
          description: Whether the completed setup should enable the policy.

    AutoTopupUpdate:
      allOf:
        - $ref: "#/components/schemas/AutoTopupSetupReq"
        - type: object
          required:
            - enabled

    AutoTopupSetupRes:
      type: object
      required:
        - setup_id
        - checkout_url
        - transaction_id
        - customer_auth_token
      properties:
        setup_id:
          type: string
        checkout_url:
          type: string
          format: uri
        transaction_id:
          type: string
        customer_auth_token:
          type: string
          description: Short-lived token used only by Paddle.js for this checkout.


    ResponseFormat:
      type: string
      enum:
        - json
        - text
        - verbose_json
        - srt
        - vtt
        - diarized_json
      default: json

    TimestampMode:
      type: string
      enum:
        - none
        - segment
        - word
        - both
      default: none

    RetentionMode:
      type: string
      enum:
        - none
        - transcript
        - debug
        - archive
      default: transcript

    ThinkingLevel:
      type: string
      enum:
        - minimal
        - low
        - medium
        - high
      description: >
        Reasoning depth to request from the model. Only honored by
        gemini/gemini-3.1-pro today; every other model rejects it with
        unsupported_feature. Omit it to use the provider's own default.

    UserState:
      type: string
      enum:
        - active
        - disabled

    WorkspaceState:
      type: string
      enum:
        - active
        - suspended
        - closed
