Speko Voice API

The Voice API from Speko — 8 operation(s) for voice.

OpenAPI Specification

speko-voice-api-openapi.yml Raw ↑
openapi: 3.1.0
info:
  title: Speko Agents Voice API
  version: 1.0.0
  description: Public REST API for the Speko voice gateway. Routes, transcribes, synthesizes, and completes via the highest-scoring provider for your routing intent, with transparent server-side failover.
  contact:
    email: abat@speko.ai
servers:
- url: https://api.speko.dev
  description: Production
security:
- bearerAuth: []
tags:
- name: Voice
paths:
  /v1/sessions:
    post:
      summary: Create a real-time voice session
      description: Mints browser-safe media transport credentials, persists the pipeline config, and dispatches an agent worker. Use the returned `transportToken` and `transportUrl` with `@spekoai/client` to join from a browser.
      operationId: createSession
      tags:
      - Voice
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateSessionRequest'
      responses:
        '201':
          description: Session created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateSessionResponse'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/SessionCreateFailed'
    get:
      summary: List voice sessions
      description: Returns sessions visible to the calling key, newest first. Supports cursor pagination and filtering by `status`, `kind`, and `agent`. All filters combine via AND.
      operationId: listSessions
      tags:
      - Voice
      parameters:
      - in: query
        name: cursor
        required: false
        schema:
          type: string
        description: Opaque cursor returned by a prior call. Omit on the first page.
      - in: query
        name: limit
        required: false
        schema:
          type: integer
          minimum: 1
          maximum: 100
          default: 20
        description: Max number of entries to return.
      - in: query
        name: status
        required: false
        schema:
          type: string
        description: Filter by session lifecycle status (e.g. `active`, `ended`, `failed`).
      - in: query
        name: kind
        required: false
        schema:
          type: string
        description: Filter by session kind.
      - in: query
        name: agent
        required: false
        schema:
          type: string
        description: Filter to sessions started against this agent. Matches sessions whose `pipelineConfig.agentId` equals this value. Combines with other filters via AND.
      responses:
        '200':
          description: Page of session entries.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionEntries'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/transcribe:
    post:
      summary: Transcribe audio (speech to text)
      description: 'Routes the request to the best STT provider for your `(language, region, optimizeFor)` intent, with automatic failover to runner-up providers before the first event is emitted. Body is binary audio. Routing intent goes in the `x-speko-intent` header (JSON). The response is a Server-Sent Events stream: `meta`, `transcript`, `done`, and `error`.'
      operationId: transcribe
      tags:
      - Voice
      parameters:
      - in: header
        name: x-speko-intent
        required: true
        schema:
          type: string
        description: 'JSON-encoded `RoutingIntent`. Example: `{"language":"en-US","region":"global"}`.'
      - in: header
        name: x-speko-constraints
        required: false
        schema:
          type: string
        description: 'Optional JSON-encoded `PipelineConstraints`. Each `allowedProviders` entry is either `"<vendor>"` (any model from that vendor) or `"<vendor>:<model>"` (a specific model). Example: `{"allowedProviders":{"stt":["deepgram:nova-3"]}}`.'
      - in: header
        name: x-speko-stt-options
        required: false
        schema:
          type: string
        description: 'Optional JSON-encoded STT options. Example: `{"keywords":["Speko","Ava Martinez"]}`.'
      - in: header
        name: Content-Type
        required: false
        schema:
          type: string
        description: Audio MIME type, e.g. `audio/wav`, `audio/mpeg`, `audio/pcm;rate=16000`.
      requestBody:
        required: true
        description: Binary audio. WAV, MP3, or raw PCM accepted.
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
          audio/wav:
            schema:
              type: string
              format: binary
          audio/mpeg:
            schema:
              type: string
              format: binary
      responses:
        '200':
          description: Transcript event stream
          content:
            text/event-stream:
              schema:
                type: string
                description: 'SSE events: `meta` with provider/model, zero or more `transcript` events, final `done` containing a `TranscribeResponse`, or `error` after streaming has started.'
        '400':
          $ref: '#/components/responses/InvalidAudio'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/TranscribeFailed'
  /v1/voices:
    get:
      summary: List TTS voices
      description: 'Returns the curated catalog of TTS voices grouped by provider, plus the list of TTS providers Speko routes to. ElevenLabs is included in `providers` with `voicesFetchedLive: true` because its voice library is account-scoped — fetch it directly from `https://api.elevenlabs.io/v1/voices` with the org''s key.'
      operationId: listVoices
      tags:
      - Voice
      parameters:
      - name: provider
        in: query
        required: false
        schema:
          type: string
        description: Filter to a single provider's voices. Accepts either the routing key (`cartesia`, `xai`, `alibaba`, `openai`, `inworld`, `elevenlabs`) or the catalog suffix form (`xai-tts`, `alibaba-tts`, `openai-tts`).
      responses:
        '200':
          description: Voice catalog
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VoicesListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/synthesize:
    post:
      summary: Synthesize speech (text to speech)
      description: Routes the request to the best TTS provider for your intent, with automatic failover before the first audio chunk. Returns chunked binary audio. The `Content-Type` header (mirrored in `X-Speko-Audio-Format`) tells you the format.
      operationId: synthesize
      tags:
      - Voice
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SynthesizeRequest'
      responses:
        '200':
          description: Chunked audio bytes
          headers:
            Content-Type:
              schema:
                type: string
              description: Audio MIME type, e.g. `audio/pcm;rate=24000` or `audio/mpeg`.
            X-Speko-Audio-Format:
              schema:
                type: string
              description: Mirrors `Content-Type` for client-side dispatch.
            X-Speko-Provider:
              schema:
                type: string
              description: Provider that actually handled the request.
            X-Speko-Model:
              schema:
                type: string
              description: Specific model used.
            X-Speko-Failover-Count:
              schema:
                type: integer
              description: Number of providers tried before success.
            X-Speko-Scores-Run-Id:
              schema:
                type: string
              description: Benchmark snapshot id used for routing.
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/SynthesizeFailed'
  /v1/complete:
    post:
      summary: Generate an LLM completion
      description: 'Single-turn LLM call routed to the best provider for your intent, with automatic failover before the first event is emitted. The response is a Server-Sent Events stream: `meta`, `delta`, `tool_call`, `server_tool_call`, `done`, and `error`.'
      operationId: complete
      tags:
      - Voice
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CompleteRequest'
      responses:
        '200':
          description: Completion event stream
          content:
            text/event-stream:
              schema:
                type: string
                description: 'SSE events: `meta`, zero or more `delta` and tool events, final `done` containing a `CompleteResponse`, or `error` after streaming has started.'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/CompleteFailed'
  /v1/sessions/{id}/recording:
    get:
      summary: Get a signed URL for the session recording
      description: Returns a 302 redirect to a short-lived (5 minute) signed Google Cloud Storage URL for the mixed-mono Opus recording of this session. Follow the redirect to download the file — clients should pass the equivalent of `curl -L`. The signed URL is single-use within its TTL window; refetch this endpoint to get a fresh one. Recordings are retained for 30 days after the session ends, then deleted by a GCS lifecycle policy.
      operationId: getSessionRecording
      tags:
      - Voice
      parameters:
      - in: path
        name: id
        required: true
        schema:
          type: string
          format: uuid
        description: Session id.
      responses:
        '302':
          description: Redirect to a signed GCS URL valid for 5 minutes. The URL is returned in the `Location` header; the response body is empty.
          headers:
            Location:
              description: Short-lived signed GCS URL. Follow to download the recording.
              schema:
                type: string
                format: uri
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: 'No recording exists for this session — either the id is unknown to the calling organization, recording was suppressed (`recordingStatus: "suppressed"`), the session is still in flight (`pending` / `uploading`), or the upload failed (`failed`). Inspect the parent session entry to disambiguate.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                default:
                  value:
                    error: recording not available
                    code: RECORDING_NOT_AVAILABLE
  /v1/sessions/{id}/turns:
    post:
      summary: Ingest finalized transcript turns
      description: 'Append finalized STT and LLM turns to a session''s transcript. Worker-only — the agent worker batches and debounces (~200ms) finalized turns and POSTs them here. Interim STT partials are not accepted; only finalized turns are persisted. Idempotent on `(session_id, index)`: re-posting a turn with the same index replaces the existing entry, so retries on transient errors are safe.'
      operationId: postSessionTurns
      tags:
      - Voice
      parameters:
      - in: path
        name: id
        required: true
        schema:
          type: string
          format: uuid
        description: Session id.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PostSessionTurnsRequest'
      responses:
        '202':
          description: Turns accepted for persistence. Response body is empty.
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: Session id is unknown to the calling organization.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                default:
                  value:
                    error: session not found
                    code: SESSION_NOT_FOUND
  /v1/sessions/{id}/transcript:
    get:
      summary: Get a session's full transcript
      description: Returns every finalized STT and LLM turn captured during the session, sorted by `index` ascending. The dashboard's session detail page surfaces these under the Transcript card. S2S sessions don't currently produce transcripts (the audio path bypasses our STT/LLM components) — expect an empty `entries` array for those. Interim STT partials are never returned; only finalized turns make it into this response.
      operationId: getSessionTranscript
      tags:
      - Voice
      parameters:
      - in: path
        name: id
        required: true
        schema:
          type: string
          format: uuid
        description: Session id.
      responses:
        '200':
          description: Full transcript for the session, ordered by `index`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionTranscript'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: Session id is unknown to the calling organization.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                default:
                  value:
                    error: session not found
                    code: SESSION_NOT_FOUND
components:
  schemas:
    ChatMessage:
      type: object
      required:
      - role
      - content
      properties:
        role:
          type: string
          enum:
          - system
          - user
          - assistant
          - tool
        content:
          type: string
        toolCalls:
          type: array
          items:
            $ref: '#/components/schemas/ChatToolCall'
        toolCallId:
          type: string
          description: 'Required on `role: tool`; pairs with a prior assistant tool call id.'
        isError:
          type: boolean
          description: Marks a tool message as an error result.
    Error:
      type: object
      required:
      - error
      - code
      properties:
        error:
          type: string
          description: Human-readable message.
        code:
          type: string
          description: Machine-readable code.
    RoutingIntent:
      type: object
      required:
      - language
      properties:
        language:
          type: string
          pattern: ^[a-z]{2}(-[A-Z]{2})?$
          description: BCP-47 language tag, e.g. `en`, `en-US`, `es-MX`.
          examples:
          - en-US
        region:
          type: string
          enum:
          - global
          - us-east4
          - europe-west3
          - asia-southeast1
          default: global
          description: Region whose streaming-latency measurements should be used. `global` falls through to batch-mode rows when no per-region data matches.
        optimizeFor:
          type: string
          enum:
          - balanced
          - accuracy
          - latency
          - cost
          default: balanced
    SynthesizeRequest:
      type: object
      required:
      - text
      - intent
      properties:
        text:
          type: string
          minLength: 1
          maxLength: 50000
        intent:
          $ref: '#/components/schemas/RoutingIntent'
        voice:
          type: string
          description: Provider-specific voice id. Speko falls back to a sane default per provider when omitted.
        model:
          type: string
          minLength: 1
          maxLength: 128
          description: Optional upstream model name (e.g. `eleven_multilingual_v2`, `sonic-2`, `gpt-4o-mini-tts`, `qwen3-tts-flash`). When set, overrides the selector's chosen model on the primary candidate only — failover candidates still use the selector's model so a model intended for provider A is not sent to provider B.
        speed:
          type: number
          minimum: 0.5
          maximum: 2
        constraints:
          $ref: '#/components/schemas/PipelineConstraints'
    VoicesListResponse:
      type: object
      required:
      - voices
      - providers
      properties:
        voices:
          type: array
          items:
            $ref: '#/components/schemas/VoiceCatalogEntry'
        providers:
          type: array
          items:
            $ref: '#/components/schemas/VoicesProviderEntry'
    SessionEntries:
      type: object
      required:
      - entries
      properties:
        entries:
          type: array
          items:
            $ref: '#/components/schemas/SessionEntry'
        nextCursor:
          type: string
          nullable: true
          description: Cursor to pass to the next call. Null/absent when there are no more pages.
    TurnIngest:
      type: object
      required:
      - index
      - source
      - text
      - startedAt
      properties:
        index:
          type: integer
          minimum: 0
          description: Monotonically increasing position of this turn within the session, starting at 0. The `(session_id, index)` pair is the idempotency key — re-posting a turn with the same index replaces the prior entry.
        source:
          type: string
          enum:
          - user
          - agent
          - system
          description: Who produced this turn. `user` = finalized STT from the caller. `agent` = finalized LLM output spoken by the agent. `system` = synthetic entries (e.g. session boundary markers).
        text:
          type: string
          minLength: 1
          maxLength: 16384
          description: Finalized transcript text. Interim STT partials are not accepted.
        startedAt:
          type: string
          format: date-time
          description: Wall-clock time at which the turn began, ISO 8601.
        endedAt:
          type: string
          format: date-time
          nullable: true
          description: Wall-clock time at which the turn ended, ISO 8601. Null while the turn is still considered open (e.g. a streaming agent reply that hasn't fully closed).
        provider:
          type: string
          nullable: true
          description: Provider that produced this turn (e.g. `deepgram`, `openai`). Null for `system` turns.
        model:
          type: string
          nullable: true
          description: Specific model id within the provider (e.g. `nova-3`, `gpt-4o-mini`). Null for `system` turns.
    SessionEntry:
      type: object
      required:
      - sessionId
      - createdAt
      properties:
        sessionId:
          type: string
          format: uuid
        createdAt:
          type: string
          format: date-time
        status:
          type: string
          description: Lifecycle status of the session.
        kind:
          type: string
          description: Session kind.
        pipelineConfig:
          type: object
          description: Snapshot of the pipeline config the session was minted with.
          properties:
            agentId:
              type: string
              nullable: true
              description: The agent the session was started against, if any. Filtered on by the `?agent=` query param.
        recordingObjectPath:
          type: string
          nullable: true
          description: GCS object path of the recorded audio file, when a recording exists. Null while the recording is still being produced, when recording is suppressed for the organization, or when the upload failed. Use `GET /v1/sessions/{id}/recording` to fetch a short-lived signed URL — never construct one yourself.
        recordingDurationMs:
          type: integer
          nullable: true
          description: Duration of the finalized recording in milliseconds. Null until `recordingStatus` is `ready`.
        recordingStatus:
          type: string
          nullable: true
          enum:
          - pending
          - uploading
          - ready
          - failed
          - suppressed
          description: Lifecycle of this session's recording. `pending` after the call ends and before the agent worker has finished assembling the file. `uploading` while the file is being pushed to GCS. `ready` once it is fully persisted and downloadable via the signed-URL endpoint. `failed` if the upload errored — the recording is unrecoverable. `suppressed` for organizations whose `recordingEnabled` flag is false; no file was ever produced. Null on legacy sessions that predate the recording feature.
    ChatTool:
      type: object
      required:
      - name
      - description
      - parameters
      properties:
        name:
          type: string
        description:
          type: string
        parameters:
          type: object
          additionalProperties: true
          description: JSON Schema parameters object.
        executionMode:
          type: string
          enum:
          - inline
          - webhook
          - builtin
          description: Where the tool runs. Omit for inline execution.
        source:
          type: object
          additionalProperties: true
          description: Webhook, builtin, or inline execution source config.
    PipelineConstraints:
      type: object
      properties:
        allowedProviders:
          type: object
          description: Restrict candidate pool per modality. Router still ranks by score. Each entry is either `"<vendor>"` (vendor wildcard — allow any model from that vendor) or `"<vendor>:<model>"` (allow only that specific model). Failover stays active across all entries in the layer.
          properties:
            stt:
              type: array
              items:
                type: string
                description: Allowed STT provider entry. Either a vendor id (e.g. `"deepgram"` — any Deepgram model) or `"<vendor>:<model>"` (e.g. `"deepgram:nova-3"` — only Nova-3, no fallback to other Deepgram models within the vendor). Both forms can be mixed in the same array.
                examples:
                - deepgram
                - deepgram:nova-3
                - assemblyai
            llm:
              type: array
              items:
                type: string
                description: Allowed LLM provider entry. Either a vendor id (e.g. `"openai"` — any OpenAI model) or `"<vendor>:<model>"` (e.g. `"openai:gpt-5"` — only that model). Both forms can be mixed in the same array.
                examples:
                - openai
                - openai:gpt-5
                - anthropic
            tts:
              type: array
              items:
                type: string
                description: Allowed TTS provider entry. Either a vendor id (e.g. `"elevenlabs"` — any ElevenLabs model) or `"<vendor>:<model>"` (e.g. `"elevenlabs:eleven_flash_v2_5"` — only that model). Both forms can be mixed in the same array.
                examples:
                - elevenlabs
                - elevenlabs:eleven_flash_v2_5
                - cartesia
            s2s:
              type: array
              items:
                type: string
                description: Allowed S2S provider entry. Either a vendor id (e.g. `"openai"` — any OpenAI realtime model) or `"<vendor>:<model>"` (e.g. `"openai:gpt-realtime"` — only that model). Both forms can be mixed in the same array.
                examples:
                - openai
                - openai:gpt-realtime
    SessionTranscript:
      type: object
      required:
      - entries
      properties:
        entries:
          type: array
          description: Finalized turns for this session, ordered by `index` ascending.
          items:
            $ref: '#/components/schemas/TurnEntry'
    VoiceCatalogEntry:
      type: object
      required:
      - vendor
      - id
      - name
      properties:
        vendor:
          type: string
          description: Routing-key vendor (matches `allowedProviders.tts` entries).
        id:
          type: string
          description: Voice id passed through to the provider's TTS API.
        name:
          type: string
          description: Human-readable label.
    CreateSessionResponse:
      type: object
      required:
      - sessionId
      - transportToken
      - transportUrl
      - conversationToken
      - livekitUrl
      - roomName
      - identity
      - expiresAt
      properties:
        sessionId:
          type: string
          format: uuid
        transportToken:
          type: string
          description: Browser-safe media transport token.
        transportUrl:
          type: string
          format: uri
          description: Media transport URL.
        conversationToken:
          type: string
          deprecated: true
          description: Compatibility alias for `transportToken`.
        livekitUrl:
          type: string
          format: uri
          deprecated: true
          description: Compatibility alias for `transportUrl`.
        roomName:
          type: string
          examples:
          - speko_...
        identity:
          type: string
        expiresAt:
          type: string
          format: date-time
    PostSessionTurnsRequest:
      type: object
      required:
      - turns
      properties:
        turns:
          type: array
          minItems: 1
          maxItems: 200
          description: Batch of finalized turns to ingest. Workers should debounce (~200ms) and batch — single-turn POSTs are accepted but inefficient at scale.
          items:
            $ref: '#/components/schemas/TurnIngest'
    TurnEntry:
      type: object
      required:
      - id
      - index
      - source
      - text
      - startedAt
      properties:
        id:
          type: string
          description: Server-assigned id for this turn entry. Stable across reads.
        index:
          type: integer
          minimum: 0
          description: Monotonically increasing position of this turn within the session, starting at 0.
        source:
          type: string
          enum:
          - user
          - agent
          - system
          description: Who produced this turn.
        text:
          type: string
          minLength: 1
          maxLength: 16384
          description: Finalized transcript text.
        startedAt:
          type: string
          format: date-time
          description: Wall-clock time at which the turn began, ISO 8601.
        endedAt:
          type: string
          format: date-time
          nullable: true
          description: Wall-clock time at which the turn ended, ISO 8601. Null if the turn is still open.
        provider:
          type: string
          nullable: true
          description: Provider that produced this turn. Null for `system` turns.
        model:
          type: string
          nullable: true
          description: Specific model id within the provider. Null for `system` turns.
    CompleteRequest:
      type: object
      required:
      - messages
      - intent
      properties:
        messages:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/ChatMessage'
        intent:
          $ref: '#/components/schemas/RoutingIntent'
        temperature:
          type: number
          minimum: 0
          maximum: 2
        maxTokens:
          type: integer
          minimum: 1
        systemPrompt:
          type: string
          description: Forwarded via the canonical system-prompt slot. Don't *also* prepend the same content as a `system` message.
        constraints:
          $ref: '#/components/schemas/PipelineConstraints'
        reasoningEffort:
          type: string
          enum:
          - none
          - minimal
          - low
          - medium
          - high
          - xhigh
        tools:
          type: array
          items:
            $ref: '#/components/schemas/ChatTool'
        toolChoice:
          oneOf:
          - type: string
            enum:
            - auto
            - none
            - required
          - type: object
            additionalProperties: true
        parallelToolCalls:
          type: boolean
        maxToolHops:
          type: integer
          minimum: 1
          maximum: 16
    ChatToolCall:
      type: object
      required:
      - id
      - name
      - args
      properties:
        id:
          type: string
        name:
          type: string
        args:
          type: string
          description: JSON-encoded tool arguments.
    CreateSessionRequest:
      type: object
      description: Cascade-mode session creation body. Exactly one of `agentId` or `intent` must be supplied. When `agentId` is given, the persisted agent's `systemPrompt`, `voice`, `intent`, and `llmOptions` seed the session as defaults; any per-call field on this body still overrides the agent's stored value.
      properties:
        agentId:
          type: string
          description: Optional pointer to a persisted `agent` row whose fields seed this session. When supplied, the agent's `systemPrompt`, `voice`, `intent`, and `llmOptions` become defaults; any per-call field on this body overrides the agent's stored value. When absent, `intent` is required.
        intent:
          $ref: '#/components/schemas/RoutingIntent'
        constraints:
          $ref: '#/components/schemas/PipelineConstraints'
        voice:
          type: string
          description: Force a specific TTS voice id.
        systemPrompt:
          type: string
          description: Initial agent instructions.
        llm:
          type: object
          properties:
            temperature:
              type: number
              minimum: 0
              maximum: 2
            maxTokens:
              type: integer
              minimum: 1
        ttsOptions:
          type: object
          properties:
            sampleRate:
              type: integer
              minimum: 1
            speed:
              type: number
              minimum: 0.5
              maximum: 2
        metadata:
          type: object
          additionalProperties: true
          description: Arbitrary key/value pairs stored on the session row.
        ttlSeconds:
          type: integer
          default: 900
          minimum: 1
          maximum: 86400
          description: Token TTL.
        identity:
          type: string
          minLength: 1
          maxLength: 128
          description: Transport participant identity. Defaults to `user_<uuid>`.
    VoicesProviderEntry:
      type: object
      required:
      - key
      - name
      - models
      - voicesFetchedLive
      properties:
        key:
          type: string
        name:
          type: string
        models:
          type: array
          items:
            type: string
        voicesFetchedLive:
          type: boolean
          description: '`true` when the provider''s voice library is account-scoped and must be fetched live from the provider (currently ElevenLabs).'
  responses:
    SynthesizeFailed:
      description: No TTS provider available for the intent, no default voice for chosen provider, or every candidate failed.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            default:
              value:
                error: All providers failed
                code: SYNTHESIZE_FAILED
    SessionCreateFailed:
      description: Token mint failed, agent dispatch failed, or DB insert failed.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            default:
          

# --- truncated at 32 KB (33 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/speko/refs/heads/main/openapi/speko-voice-api-openapi.yml