{
  "openapi": "3.0.3",
  "info": {
    "title": "Suisse Speech API",
    "version": "1.5.0",
    "description": "Speech-to-text and text-to-speech for Swiss German, its regional dialects\nand other languages. Both directions are available in two modes:\n\n  * **batch**    — send a whole file or a whole text, get the whole result\n  * **realtime** — stream over a WebSocket and receive results as they are produced\n\nStart with `GET /capabilities`: it lists every voice, language, dialect,\naudio format, parameter range and error code this deployment accepts, so a\nclient never has to hard-code them.\n\nAll endpoints require an `X-API-Key` header with a valid `sv_*` API key.\nThe environment travels with the key: `sv_…` is live, `sv_test_…` is the\nsandbox — not billed, no allowance consumed, its own budget and concurrency\nslice. A sandbox key always receives structurally identical synthetic\naudio and transcripts without any engine being called: free, instant and\ndeterministic, for continuous integration. Real voices and recognition need\na live key. The header `X-Suisse-Sandbox-Mode: synthetic` is still accepted\nbut no longer needed. Every\nresponse reports which environment answered in `X-Suisse-Environment`.\nWebSocket endpoints additionally accept\n`Sec-WebSocket-Protocol: suisse.v1, key.<api key>` for browser clients.\n\nLimits apply per API key: 600 requests/minute with a burst limit of 25\nrequests/second, a ceiling on simultaneous requests, and a daily budget of\naudio seconds. An evaluation key additionally carries a fixed allowance of audio\nhours. See `limits` and `usage_today` in `GET /capabilities`, and the remaining\nallowance in `GET /usage`. Synthesis and recognition are both metered in audio\nseconds, to the second, with no minimum per request.\n\n**Reading a 429.** Four different things answer with status 429, and `error.code`\nin the response body says which one it is:\n\n  * `*.rate_limited` - too many requests in the window, or the daily audio budget\n    is spent. Retry after `Retry-After`.\n  * `*.capacity` - the service is momentarily busy. Retry after `Retry-After`.\n  * `*.plan_limit_reached` - too many simultaneous requests on this key. Reduce\n    parallelism; retrying at the same parallelism does not help.\n  * `*.entitlement_exhausted` - the evaluation allowance of audio hours is used\n    up. Retrying does not help; contact us to extend it.\n\nAn HTTP client that reports only the status line hides this distinction, so log\nthe response body of every 429.\n\nSynthesised audio and transcripts are processed in memory and not stored;\nidentical texts are synthesised again each time. Every synthesis response\nreports `cache` as `miss` (the field stays for compatibility).\n\nSimultaneous work is admitted up to a published ceiling and refused beyond it\nwith `429` and a `Retry-After` header, rather than queued — a queue would\nturn a capacity problem into a latency problem for the requests that did get\nin. `error.detail` names which ceiling was reached: `key_capacity` (reduce\nyour own parallelism), `service_capacity` or `host_load` (back off and\nretry). The current ceilings are in `capacity` in `GET /capabilities`, and\nare what the deployment enforces today rather than a contractual\nentitlement.\n\nA narrative integration guide, including worked examples and client\nbehaviour for each error class, ships with the service as `API_GUIDE.md`.\n"
  },
  "servers": [
    {
      "url": "https://api.suisse-speech.ch/v1",
      "description": "Production"
    }
  ],
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key"
      }
    },
    "schemas": {
      "Word": {
        "type": "object",
        "required": [
          "w",
          "start",
          "end",
          "conf"
        ],
        "properties": {
          "w": {
            "type": "string",
            "description": "The word."
          },
          "start": {
            "type": "number",
            "description": "Start time in seconds from the audio start."
          },
          "end": {
            "type": "number",
            "description": "End time in seconds."
          },
          "conf": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "Confidence in [0,1]."
          }
        }
      },
      "Transcript": {
        "type": "object",
        "required": [
          "text",
          "lang",
          "words",
          "duration_s"
        ],
        "properties": {
          "text": {
            "type": "string"
          },
          "lang": {
            "type": "string",
            "description": "Language of the transcript. When the request named several candidate languages (or `auto`), this is the language that was detected - always one of the candidates, and always a code that synthesis accepts as `language`, so it can be passed on unchanged.",
            "example": "de-CH"
          },
          "words": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Word"
            }
          },
          "duration_s": {
            "type": "number",
            "description": "Audio duration in seconds."
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "stt.invalid_audio",
                  "stt.unsupported_format",
                  "stt.audio_too_large",
                  "stt.rate_limited",
                  "stt.unauthenticated",
                  "stt.forbidden",
                  "stt.upstream_unavailable",
                  "stt.timeout",
                  "stt.internal_error",
                  "stt.invalid_request",
                  "tts.invalid_request",
                  "tts.text_required",
                  "tts.text_too_long",
                  "tts.unknown_voice",
                  "tts.unknown_language",
                  "tts.unsupported_format",
                  "tts.invalid_speed",
                  "tts.style_too_long",
                  "tts.encode_failed",
                  "tts.invalid_ssml",
                  "tts.capacity",
                  "stt.capacity",
                  "tts.plan_limit_reached",
                  "stt.plan_limit_reached",
                  "tts.entitlement_exhausted",
                  "stt.entitlement_exhausted",
                  "tts.unauthenticated",
                  "tts.forbidden",
                  "tts.rate_limited",
                  "tts.unavailable",
                  "tts.timeout",
                  "tts.internal_error"
                ]
              },
              "message": {
                "type": "string"
              },
              "request_id": {
                "type": "string",
                "nullable": true
              },
              "detail": {
                "type": "string",
                "nullable": true,
                "description": "Explanation of a problem in the request itself, e.g. which markup element was rejected."
              }
            }
          }
        }
      },
      "Voice": {
        "type": "object",
        "required": [
          "id",
          "name",
          "gender",
          "timbre",
          "pace",
          "description",
          "best_for",
          "languages"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "anna",
            "description": "Value to pass as `voice`."
          },
          "name": {
            "type": "string",
            "example": "Anna"
          },
          "gender": {
            "type": "string",
            "enum": [
              "female",
              "male",
              "neutral"
            ]
          },
          "timbre": {
            "type": "string",
            "example": "warm"
          },
          "pace": {
            "type": "string",
            "example": "medium"
          },
          "description": {
            "type": "string"
          },
          "best_for": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "languages": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "Dialect": {
        "type": "object",
        "required": [
          "id",
          "name"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "zurich",
            "description": "Value to pass as `dialect`."
          },
          "name": {
            "type": "string",
            "example": "Züridütsch"
          },
          "region": {
            "type": "string",
            "nullable": true,
            "example": "Zürich"
          }
        }
      },
      "Language": {
        "type": "object",
        "required": [
          "code",
          "name",
          "status",
          "dialects"
        ],
        "properties": {
          "code": {
            "type": "string",
            "example": "de-CH"
          },
          "name": {
            "type": "string",
            "example": "Swiss German"
          },
          "status": {
            "type": "string",
            "enum": [
              "verified",
              "supported"
            ],
            "description": "`verified` — run and measured in production.\n`supported` — accepted by the service, no published quality figure.\n"
          },
          "dialects": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Dialect"
            }
          },
          "default_dialect": {
            "type": "string",
            "nullable": true,
            "example": "standard"
          }
        }
      },
      "AudioFormat": {
        "type": "object",
        "required": [
          "id",
          "mime",
          "extension",
          "sample_rates_hz",
          "default_sample_rate_hz",
          "streamable"
        ],
        "properties": {
          "id": {
            "type": "string",
            "enum": [
              "wav",
              "mp3",
              "opus",
              "pcm16",
              "mulaw",
              "alaw"
            ]
          },
          "mime": {
            "type": "string",
            "example": "audio/mpeg"
          },
          "extension": {
            "type": "string",
            "example": "mp3"
          },
          "description": {
            "type": "string"
          },
          "sample_rates_hz": {
            "type": "array",
            "items": {
              "type": "integer"
            }
          },
          "default_sample_rate_hz": {
            "type": "integer",
            "example": 24000
          },
          "streamable": {
            "type": "boolean"
          }
        }
      },
      "SynthesisRequest": {
        "type": "object",
        "required": [
          "text"
        ],
        "properties": {
          "model": {
            "type": "string",
            "default": "studio",
            "enum": [
              "studio",
              "swift"
            ],
            "description": "Speech synthesis model:\n- `studio` (default): State-of-the-art expressive voice engine with authentic Swiss German dialect translation and nuanced prosody.\n- `swift`: Ultra-low latency engine optimized for real-time conversational streaming and telephony.\n"
          },
          "text": {
            "type": "string",
            "maxLength": 8000,
            "description": "The text to speak. Rendered verbatim; never interpreted as an instruction."
          },
          "voice": {
            "type": "string",
            "default": "anna",
            "description": "Voice id from `GET /tts/voices`."
          },
          "language": {
            "type": "string",
            "default": "de-CH",
            "description": "Language code. `de-CH-zurich` is accepted as a compact language+dialect form."
          },
          "dialect": {
            "type": "string",
            "default": "standard",
            "description": "Variety within the language. `GET /capabilities` lists the dialects of every\nlanguage, each with a `status` (`verified` or `supported`). The hand-tuned\nSwiss German (`de-CH`) dialects are:\n- `standard`: supra-regional Swiss German (default).\n- `zurich`: Zurich dialect (Züritüütsch).\n- `bern`: Bernese dialect (Bärndütsch).\n- `basel`: Basel dialect (Baaseldütsch).\n- `luzern`: Lucerne dialect (Lozärnerdütsch).\nBoth models render the text INTO the dialect, so it may be written in Standard German:\n`swift` does it in real time, `studio` with the highest fidelity for prepared audio.\nFurther `de-CH` values: `hochdeutsch` - Swiss High German, spoken word for word - and\nmore regions (`stgallen`, `aargau`, `graubuenden`, `wallis`, ...). Other languages have\naccents of their own, for example `fr-CH` + `geneve` or `en-GB` + `scottish`.\n"
          },
          "speed": {
            "type": "number",
            "default": 1,
            "minimum": 0.5,
            "maximum": 2,
            "description": "Speaking rate multiplier (0.5 = half speed, 1.0 = normal pace, 2.0 = double speed)."
          },
          "style": {
            "type": "string",
            "maxLength": 600,
            "description": "Free-text delivery prompt (up to 600 chars) steering tone, emotion, and pacing.\nExamples:\n- \"freundlich, warm und einladend\"\n- \"ruhig und geduldig, kurze Pausen nach jedem Satz\"\n- \"professionell und sachlich, klare Aussprache\"\nSteers delivery only; the spoken content always renders the text verbatim.\n"
          },
          "format": {
            "type": "string",
            "default": "mp3",
            "enum": [
              "wav",
              "mp3",
              "opus",
              "pcm16",
              "mulaw",
              "alaw"
            ]
          },
          "sample_rate_hz": {
            "type": "integer",
            "description": "Output sample rate. Permitted values depend on `format`."
          },
          "output": {
            "type": "string",
            "default": "audio",
            "enum": [
              "audio",
              "json"
            ],
            "description": "`audio` returns the raw bytes, `json` a base64 envelope with metadata."
          }
        }
      },
      "SynthesisResult": {
        "type": "object",
        "required": [
          "request_id",
          "voice",
          "language",
          "audio",
          "characters"
        ],
        "properties": {
          "request_id": {
            "type": "string"
          },
          "voice": {
            "type": "string",
            "example": "anna"
          },
          "language": {
            "type": "string",
            "example": "de-CH"
          },
          "dialect": {
            "type": "string",
            "nullable": true,
            "example": "zurich"
          },
          "speed": {
            "type": "number",
            "example": 1
          },
          "characters": {
            "type": "integer",
            "description": "Characters of spoken text, after markup has been expanded."
          },
          "passages": {
            "type": "integer",
            "description": "Number of separately rendered passages. 1 for plain text."
          },
          "cache": {
            "type": "string",
            "enum": [
              "hit",
              "partial",
              "miss"
            ],
            "description": "Always `miss`: no audio is stored on this service. Kept for compatibility."
          },
          "marks": {
            "type": "array",
            "description": "Present when the document contained <mark> elements.",
            "items": {
              "type": "object",
              "required": [
                "name",
                "offset_seconds"
              ],
              "properties": {
                "name": {
                  "type": "string",
                  "example": "after_ref"
                },
                "offset_seconds": {
                  "type": "number",
                  "example": 7.15
                }
              }
            }
          },
          "audio": {
            "type": "object",
            "required": [
              "encoding",
              "media_type",
              "sample_rate_hz",
              "channels",
              "duration_seconds",
              "byte_length"
            ],
            "properties": {
              "encoding": {
                "type": "string",
                "example": "mp3"
              },
              "media_type": {
                "type": "string",
                "example": "audio/mpeg"
              },
              "sample_rate_hz": {
                "type": "integer",
                "example": 24000
              },
              "channels": {
                "type": "integer",
                "example": 1
              },
              "duration_seconds": {
                "type": "number",
                "example": 2.314
              },
              "byte_length": {
                "type": "integer"
              },
              "data": {
                "type": "string",
                "format": "byte",
                "description": "Base64 audio. Present only when `output` is `json`."
              }
            }
          }
        }
      },
      "Job": {
        "type": "object",
        "required": [
          "id",
          "kind",
          "status",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "job_50f665b6efff3fbfb6b58fdf"
          },
          "kind": {
            "type": "string",
            "enum": [
              "tts"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "running",
              "succeeded",
              "failed"
            ]
          },
          "environment": {
            "type": "string",
            "enum": [
              "live",
              "sandbox"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "started_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "finished_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "audio_seconds": {
            "type": "number",
            "nullable": true
          },
          "result_expires_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "audio": {
            "type": "object",
            "description": "Present once the job has succeeded.",
            "properties": {
              "encoding": {
                "type": "string"
              },
              "media_type": {
                "type": "string"
              },
              "sample_rate_hz": {
                "type": "integer"
              },
              "byte_length": {
                "type": "integer"
              },
              "url": {
                "type": "string"
              }
            }
          },
          "callback": {
            "type": "object",
            "nullable": true,
            "description": "Delivery state, when the job carried a callback_url.",
            "properties": {
              "delivered_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "attempts": {
                "type": "integer"
              },
              "error": {
                "type": "string",
                "nullable": true
              }
            }
          },
          "transcript": {
            "type": "object",
            "nullable": true,
            "description": "Present on a finished recognition job."
          },
          "error": {
            "type": "object",
            "nullable": true,
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "Capabilities": {
        "type": "object",
        "description": "Runtime description of the whole service. Generated from the same\ncatalogues the request handlers validate against, so it cannot drift\nfrom the implementation.\n",
        "properties": {
          "api": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              },
              "base_url": {
                "type": "string"
              },
              "documentation": {
                "type": "string"
              },
              "specification": {
                "type": "string"
              }
            }
          },
          "authentication": {
            "type": "object",
            "properties": {
              "scheme": {
                "type": "string"
              },
              "header": {
                "type": "string"
              },
              "websocket": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "text_to_speech": {
            "type": "object",
            "properties": {
              "modes": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "enum": [
                        "batch",
                        "realtime"
                      ]
                    },
                    "transport": {
                      "type": "string",
                      "enum": [
                        "https",
                        "websocket"
                      ]
                    },
                    "endpoint": {
                      "type": "string"
                    },
                    "description": {
                      "type": "string"
                    }
                  }
                }
              },
              "parameters": {
                "type": "object",
                "description": "Per-parameter type, default, range and enum."
              },
              "voices": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Voice"
                }
              },
              "languages": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Language"
                }
              },
              "formats": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/AudioFormat"
                }
              },
              "realtime_protocol": {
                "type": "object",
                "description": "Frame vocabulary of the realtime WebSocket transport."
              }
            }
          },
          "speech_to_text": {
            "type": "object",
            "properties": {
              "modes": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "enum": [
                        "batch",
                        "realtime"
                      ]
                    },
                    "transport": {
                      "type": "string",
                      "enum": [
                        "https",
                        "websocket"
                      ]
                    },
                    "endpoint": {
                      "type": "string"
                    },
                    "description": {
                      "type": "string"
                    }
                  }
                }
              },
              "parameters": {
                "type": "object"
              },
              "languages": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              }
            }
          },
          "limits": {
            "type": "object",
            "properties": {
              "metering_unit": {
                "type": "string",
                "example": "audio_seconds"
              },
              "metering_granularity": {
                "type": "string",
                "example": "second"
              },
              "minimum_billable_unit": {
                "type": "string",
                "nullable": true
              },
              "tts_daily_audio_seconds": {
                "type": "integer"
              },
              "tts_max_text_characters": {
                "type": "integer"
              },
              "stt_max_upload_bytes": {
                "type": "integer"
              },
              "requests_per_minute": {
                "type": "integer"
              }
            }
          },
          "capacity": {
            "type": "object",
            "description": "Concurrency ceilings this deployment enforces, and how much is in flight.",
            "properties": {
              "cores": {
                "type": "integer"
              },
              "in_flight": {
                "type": "integer"
              },
              "peak_in_flight": {
                "type": "integer"
              },
              "load_1m": {
                "type": "number"
              },
              "limits": {
                "type": "object"
              },
              "rejected": {
                "type": "object"
              }
            }
          },
          "cache": {
            "type": "object",
            "description": "Synthesis cache counters. The cache is switched off on this service: no audio is stored.",
            "properties": {
              "enabled": {
                "type": "boolean"
              },
              "hits": {
                "type": "integer"
              },
              "misses": {
                "type": "integer"
              },
              "hit_rate": {
                "type": "number",
                "nullable": true
              },
              "seconds_served_from_cache": {
                "type": "number"
              }
            }
          },
          "usage_today": {
            "type": "object",
            "properties": {
              "tts_audio_seconds_used": {
                "type": "integer"
              },
              "tts_audio_seconds_limit": {
                "type": "integer"
              },
              "tts_audio_seconds_available": {
                "type": "integer"
              }
            }
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "code": {
                  "type": "string"
                },
                "http_status": {
                  "type": "integer"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  },
  "security": [
    {
      "ApiKey": []
    }
  ],
  "paths": {
    "/stt": {
      "post": {
        "summary": "Transcribe an audio file",
        "description": "Sync transcription for short audio (≤ 25 MB, ≤ 5 min). Pass audio as `multipart/form-data`\nwith field `audio`. Optional fields: `lang` (default `de-CH`), `vocabulary`\n(JSON array of up to 200 strings to bias recognition).\n\n**Multilingual recognition**\n\n`lang` takes one language code, several candidates separated by commas\n(`de-CH,fr-CH,en`), or `auto` (short for `de-CH,fr-CH,it,en`). With more than\none candidate the spoken language is detected automatically, the transcript is\nwritten in that language, and the result reports it in `lang`. The reported\nvalue is always one of the candidates given, and is accepted unchanged as\n`language` by `POST /tts` and `/tts/stream`. Name only the languages you expect:\nfewer candidates means a steadier result on short utterances.\n\nA candidate may carry a region or a dialect (`fr-CH`, `de-CH-zurich`) and is\nreported back exactly so, which lets the choice of dialect travel with the code.\nRecognition reports the language, not the dialect that was spoken.\n\nSwiss German dialects and Standard German are both recognised under `de-CH`\nand are written in Standard German.\n",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "audio"
                ],
                "properties": {
                  "audio": {
                    "type": "string",
                    "format": "binary",
                    "description": "PCM16 16kHz mono WAV is recommended. WAV / Opus / FLAC accepted."
                  },
                  "lang": {
                    "type": "string",
                    "default": "de-CH",
                    "description": "One code, comma-separated candidates (`de-CH,fr-CH,en`) or `auto`. See \"Multilingual recognition\" above."
                  },
                  "vocabulary": {
                    "type": "string",
                    "description": "Terms to bias recognition towards - names, places, product words. Either a JSON array of strings or a comma-separated list; at most 200 terms of 100 characters each. A value that is neither is refused with 400 rather than ignored."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Transcript",
            "headers": {
              "X-Suisse-Request-Id": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Transcript"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Audio too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Unsupported audio format",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/stt/stream": {
      "get": {
        "summary": "Streaming transcription (WebSocket)",
        "description": "WebSocket endpoint at `wss://api.suisse-speech.ch/api/v1/stt/stream`. Upgrade with\nthe `X-API-Key` header (or `?api_key=sv_...` query param).\n\n```\n{\"type\":\"config\",\"lang\":\"de-CH\",\"vocabulary\":[\"...\"]}     // first message\n{\"type\":\"audio.append\",\"data\":\"<base64 PCM16 16kHz mono>\"} // chunks\n{\"type\":\"audio.end\"}                                       // signal end\n```\nBinary frames are also accepted as raw PCM16 audio after `config`.\n\n```\n{\"type\":\"session.ready\",\"request_id\":\"...\"}\n{\"type\":\"transcript.partial\",\"text\":\"...\",\"words\":[...],\"lang\":\"de-CH\",\"duration_s\":1.2}\n{\"type\":\"transcript.final\",\"text\":\"...\",\"words\":[...],\"lang\":\"de-CH\",\"duration_s\":3.4}\n{\"type\":\"session.complete\",\"request_id\":\"...\"}\n{\"type\":\"error\",\"code\":\"stt.upstream_unavailable\",\"message\":\"...\",\"request_id\":\"...\"}\n```\n\nPartial events are emitted on a fixed 250 ms cadence.\n\n**Multilingual recognition**\n\n`lang` in `config` takes one code, comma-separated candidates\n(`\"de-CH,fr-CH,en\"`) or `\"auto\"`, exactly as on `POST /stt`. Every\n`transcript.partial` and `transcript.final` then carries the language detected\nfor that stretch of speech in `lang`, so a change of language during a session\nis visible as it happens.\n",
        "responses": {
          "101": {
            "description": "Switching Protocols"
          },
          "401": {
            "description": "Authentication required"
          }
        }
      }
    },
    "/jobs": {
      "post": {
        "summary": "Submit an asynchronous synthesis job",
        "description": "For text long enough that holding a connection open for the whole\nsynthesis is the wrong shape. Acknowledged in milliseconds; the audio is\ncollected later from `/jobs/{id}/audio`.\n\nEverything is validated at submission, so a bad voice or malformed\nmarkup is a `400` here rather than a failure discovered later.\n\nResults are kept for 48 hours. Up to 200 jobs may be queued per key.\nJobs queue behind interactive traffic and are metered identically.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/SynthesisRequest"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "kind": {
                        "type": "string",
                        "enum": [
                          "tts",
                          "stt"
                        ],
                        "default": "tts"
                      },
                      "callback_url": {
                        "type": "string",
                        "format": "uri",
                        "description": "Where to POST a signed notification when the job finishes.\nhttps only, must resolve to a public address, no credentials\nin the URL, redirects not followed — all checked at submission.\nThe delivery carries X-Suisse-Webhook-Signature, which is\nsha256=HMAC_SHA256(secret, \"<timestamp>.<raw body>\").\nA callback is a courtesy; the job endpoint stays authoritative.\n"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string"
                },
                "description": "Where to poll for this job."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "400": {
            "description": "Invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit, or too many jobs already queued",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "summary": "List recent jobs for this key",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 25,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Jobs, newest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "jobs": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Job"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/jobs/{id}": {
      "get": {
        "summary": "Job status, and the result when it has finished",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Job",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "400": {
            "description": "No such job",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/jobs/{id}/audio": {
      "get": {
        "summary": "Collect the audio of a finished synthesis job",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The audio",
            "headers": {
              "X-Suisse-Audio-Duration": {
                "schema": {
                  "type": "string"
                }
              },
              "X-Suisse-Audio-Encoding": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "audio/mpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "audio/wav": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Not finished, not found, or the result has expired",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/usage": {
      "get": {
        "summary": "What this key has consumed",
        "description": "Read from the billing ledger, not from the rate limiter's expiring\ncounters, so these are the same numbers an invoice is built from, in the\nsame unit. A customer billed by the second is entitled to reconcile\ncontinuously rather than at month end.\n\nSandbox traffic is reported separately and is never billed.\n",
        "tags": [
          "Account"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "from",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Start of the window. Defaults to the start of the current month."
          },
          {
            "in": "query",
            "name": "to",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "End of the window. Defaults to now."
          },
          {
            "in": "query",
            "name": "granularity",
            "schema": {
              "type": "string",
              "enum": [
                "total",
                "day"
              ]
            },
            "description": "Add `day` to break the window down by day."
          }
        ],
        "responses": {
          "200": {
            "description": "Consumption for the window",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "request_id": {
                      "type": "string"
                    },
                    "unit": {
                      "type": "string",
                      "example": "audio_seconds"
                    },
                    "granularity": {
                      "type": "string",
                      "example": "second"
                    },
                    "minimum_billable_unit": {
                      "type": "number",
                      "nullable": true,
                      "description": "Null: there is no minimum per request."
                    },
                    "window": {
                      "type": "object",
                      "properties": {
                        "from": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "to": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "total_billable_seconds": {
                          "type": "number"
                        },
                        "breakdown": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "direction": {
                                "type": "string",
                                "enum": [
                                  "stt",
                                  "tts"
                                ]
                              },
                              "mode": {
                                "type": "string",
                                "enum": [
                                  "batch",
                                  "realtime"
                                ]
                              },
                              "requests": {
                                "type": "integer"
                              },
                              "billable_seconds": {
                                "type": "number"
                              },
                              "audio_seconds": {
                                "type": "number"
                              },
                              "cached_seconds": {
                                "type": "number",
                                "description": "Always 0: no audio is stored on this service. Kept for compatibility."
                              }
                            }
                          }
                        },
                        "sandbox": {
                          "type": "object",
                          "description": "Sandbox traffic, reported for completeness and never billed.",
                          "properties": {
                            "requests": {
                              "type": "integer"
                            },
                            "audio_seconds": {
                              "type": "number"
                            },
                            "billed": {
                              "type": "boolean",
                              "example": false
                            }
                          }
                        }
                      }
                    },
                    "entitlements": {
                      "type": "array",
                      "description": "Contractual allowances in force. `evaluation` is a hard cap:\nonce consumed, requests are refused with 429 rather than\nbilled. `committed` is trued up and never refuses.\n`credit` is a prepaid balance shared by all services and\nstated in credits: every service draws on it at its own rate\n(see the console), and once it is used up new requests are\nrefused with 429 until it is topped up. Requests in progress\nare completed. A `credit` entry carries the `*_credits` and\n`used_percent` fields instead of the `*_seconds` fields.\n",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "integer"
                          },
                          "kind": {
                            "type": "string",
                            "enum": [
                              "evaluation",
                              "committed",
                              "credit"
                            ]
                          },
                          "direction": {
                            "type": "string",
                            "enum": [
                              "stt",
                              "tts",
                              "any"
                            ]
                          },
                          "granted_seconds": {
                            "type": "number"
                          },
                          "used_seconds": {
                            "type": "number",
                            "nullable": true
                          },
                          "remaining_seconds": {
                            "type": "number",
                            "nullable": true
                          },
                          "valid_until": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "unit": {
                            "type": "string",
                            "enum": [
                              "credits"
                            ],
                            "description": "Only for kind `credit`."
                          },
                          "granted_credits": {
                            "type": "number",
                            "description": "Only for kind `credit`: the prepaid balance, including top-ups."
                          },
                          "used_credits": {
                            "type": "number",
                            "nullable": true
                          },
                          "remaining_credits": {
                            "type": "number",
                            "nullable": true
                          },
                          "used_percent": {
                            "type": "number",
                            "nullable": true,
                            "description": "Share of the prepaid balance that is used, 0 to 100."
                          },
                          "enforcement": {
                            "type": "string",
                            "enum": [
                              "hard",
                              "soft"
                            ]
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid window"
          },
          "401": {
            "description": "Missing or invalid key"
          }
        }
      }
    },
    "/capabilities": {
      "get": {
        "summary": "Discover everything the speech API accepts",
        "description": "Runtime discovery. Returns every voice, language, dialect, audio format,\nparameter range and error code the service accepts, plus the calling\nkey's own quota for the current day.\n\nRead this instead of hard-coding values: a new voice or language appears\nhere without any client change.\n",
        "responses": {
          "200": {
            "description": "Capability document",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Capabilities"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/tts/prewarm": {
      "post": {
        "summary": "Prewarm phrases - stores nothing on this service",
        "description": "On this service synthesised audio is processed in memory and not stored, so prewarming\nstores nothing: every phrase comes back `skipped`, and nothing is synthesised or billed.\nThe endpoint stays for compatibility.\n\nNeeds a live key: a sandbox key never reaches the engine, so there would\nbe nothing to warm.\n",
        "tags": [
          "Synthesis"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "phrases"
                ],
                "properties": {
                  "model": {
                    "type": "string",
                    "default": "studio",
                    "enum": [
                      "studio",
                      "swift"
                    ],
                    "description": "Speech model to prewarm (studio or swift)."
                  },
                  "phrases": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "minItems": 1,
                    "maxItems": 200,
                    "description": "The passages to synthesise ahead of time."
                  },
                  "voice": {
                    "type": "string",
                    "default": "anna"
                  },
                  "language": {
                    "type": "string",
                    "default": "de-CH"
                  },
                  "dialect": {
                    "type": "string",
                    "default": "standard"
                  },
                  "speed": {
                    "type": "number",
                    "default": 1
                  },
                  "style": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What was warmed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "request_id": {
                      "type": "string"
                    },
                    "warmed": {
                      "type": "integer",
                      "description": "Always 0 on this service: nothing is synthesised ahead or stored."
                    },
                    "already_warm": {
                      "type": "integer",
                      "description": "Always 0 on this service: nothing is stored."
                    },
                    "failed": {
                      "type": "integer"
                    },
                    "skipped": {
                      "type": "integer"
                    },
                    "billed_seconds": {
                      "type": "number"
                    },
                    "free_seconds": {
                      "type": "number"
                    },
                    "phrases": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "index": {
                            "type": "integer"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "warmed",
                              "already_warm",
                              "failed",
                              "skipped"
                            ]
                          },
                          "audio_seconds": {
                            "type": "number"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "No phrases, too many phrases, or a sandbox key"
          },
          "429": {
            "description": "Rate limited or allowance exhausted"
          }
        }
      }
    },
    "/tts/voices": {
      "get": {
        "summary": "List voices, languages and audio formats",
        "description": "The voice catalogue on its own, for clients that only need to populate a\npicker. A subset of `GET /capabilities`.\n",
        "responses": {
          "200": {
            "description": "Voice catalogue",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "voices",
                    "languages",
                    "formats",
                    "default_voice"
                  ],
                  "properties": {
                    "voices": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Voice"
                      }
                    },
                    "languages": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Language"
                      }
                    },
                    "formats": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AudioFormat"
                      }
                    },
                    "default_voice": {
                      "type": "string",
                      "example": "anna"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/tts": {
      "post": {
        "summary": "Synthesise speech from text (batch)",
        "description": "Send the complete text, receive the complete audio.\n\nBy default the response body is the audio itself, with the media type of\nthe requested format and the metadata in `X-Suisse-*` headers. Set\n`output: \"json\"` (or send `Accept: application/json`) to receive a JSON\nenvelope with base64 audio instead.\n\nTwo speech models are available:\n* `studio` (default) — Our premier expressive engine. Natural prosody, nuanced delivery, and native support for regional Swiss German dialect translation and nuances. Recommended for production announcements, IVR greetings, audio content, and high-fidelity speech.\n* `swift` — Low-latency engine optimized for real-time conversational streaming, telephone interactions, and instant responses.\n\n`language` selects the language, `dialect` the regional variety within it. Swiss German (`de-CH`) is dialect-aware:\n* `standard`: supra-regional Swiss German (default).\n* `zurich`: Zurich dialect (`Züritüütsch`).\n* `bern`: Bernese dialect (`Bärndütsch`).\n* `basel`: Basel dialect (`Baaseldütsch`).\n* `luzern`: Lucerne dialect (`Lozärnerdütsch`).\nBoth models render the text into the dialect - its own words and forms, not an accent -\nso the text may be written in Standard German. `swift` does it in real time on the\nstream; `studio` does it with the highest fidelity and is the one to use for prepared audio.\nThe compact form `language: \"de-CH-zurich\"` is accepted as an alternative to the two fields.\n\n**Regional varieties and accents**\n\nThe catalogue has two levels. `language` is a code that may carry a region, and a\nregional standard variety is its own code: `it-CH`, `fr-CA`, `en-GB`, `en-IN`,\n`de-AT`. `dialect` is a variety inside one language: `de-CH` + `stgallen`,\n`fr-CH` + `geneve`, `en-GB` + `scottish`. The compact form `<language>-<dialect>`\n(`de-CH-stgallen`) is accepted wherever a language code is, by recognition too.\n\nSwitzerland: Swiss German in its regional dialects and as Swiss High German\n(`de-CH` + `hochdeutsch`, spoken word for word), Swiss French with the accents of\nthe Romandie, and Swiss Italian (`it-CH`). Swiss German dialects are rendered into\nthe dialect; every other variety keeps the words exactly as written and changes\nonly pronunciation, melody and rhythm.\n\n`GET /capabilities` is the list. Every language and dialect there carries a\n`status`: `verified` is measured in production, `supported` is rendered on a\nbest-effort basis and no quality figure is published for it.\n\n**Following the language of a conversation**\n\nRecognition reports the language it detected in `lang`, in the same codes this\nendpoint accepts as `language`, the compact dialect form included. A client that\nlists its target locales as recognition candidates (`lang` = `de-CH-zurich,fr-CH,en`)\nreceives exactly one of them back and can pass it here unchanged. No mapping table\nand no server-side session is involved: every request stands on its own. The reply\ntext may be written in Standard German; for `de-CH` it is spoken in the chosen dialect.\nFor High German use `de-CH-hochdeutsch` (Swiss High German) or `de`.\n\n`style` is a free-text delivery prompt (up to 600 characters) — e.g. \"freundlich und herzlich\", \"ruhig und geduldig, kurze Pausen nach jedem Satz\", or \"professionell und sachlich\". It steers how the text is spoken, never what is spoken: the supplied text is always rendered verbatim.\n\nA `text` value whose first element is `<speak>` is parsed as markup.\nEach construct is realised for real rather than passed on as a hint:\n\n| element | effect |\n|---|---|\n| `<break time=\"500ms\"/>` | silence of exactly that length in the audio |\n| `<say-as interpret-as=\"digits\\|telephone\\|characters\\|ordinal\\|date\\|time\\|currency\\|number\">` | rewrites the text into its spoken form |\n| `<sub alias=\"Aktiengesellschaft\">AG</sub>` | speaks the alias |\n| `<prosody rate pitch volume>` | real rate, pitch and gain changes on that passage |\n| `<emphasis level=\"strong\">` | delivery directive on that passage |\n| `<lang xml:lang=\"de-CH-bern\">` | switches language and dialect mid-text |\n| `<mark name=\"x\"/>` | returns the offset of that point in `marks` |\n| `<p>` `<s>` | paragraph and sentence pauses |\n\n`say-as` with `digits` or `telephone` speaks Swiss numbers the way our\nphone agents do: dialect digit words and 3-3-2-2 grouping,\nso `+41 31 533 22 02` in Bernese becomes\n\"null drüü eis, füf drüü drüü, zwöi zwöi, null zwöi\".\n\nElements that are not supported are **rejected by name** with an\nexplanation in `error.detail`, never silently ignored. `<audio>` and\n`<phoneme>` are refused deliberately; use `<sub alias>` to respell a word.\nCall `GET /capabilities` for the authoritative list.\n\nBilled in produced audio seconds, measured to the second, with no\nminimum per request.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SynthesisRequest"
              },
              "examples": {
                "swiss_german_zurich_studio": {
                  "summary": "Swiss German, Zurich dialect (Studio model)",
                  "value": {
                    "model": "studio",
                    "text": "Grüezi, Sie sind mit der Terminzentrale verbunden.",
                    "voice": "anna",
                    "language": "de-CH",
                    "dialect": "zurich",
                    "format": "mp3"
                  }
                },
                "realtime_swift": {
                  "summary": "Fast synthesis (Swift model)",
                  "value": {
                    "model": "swift",
                    "text": "Einen Moment bitte, ich verbinde Sie.",
                    "voice": "reto",
                    "language": "de-CH",
                    "dialect": "standard",
                    "format": "mp3"
                  }
                },
                "telephony_mulaw": {
                  "summary": "Bern dialect, telephony-ready audio",
                  "value": {
                    "text": "Guete Tag, mir rüefed Sie i de nächschte Minute zrugg.",
                    "voice": "reto",
                    "language": "de-CH",
                    "dialect": "bern",
                    "format": "mulaw",
                    "speed": 0.95
                  }
                },
                "with_style_prompt": {
                  "summary": "Style prompt and JSON envelope",
                  "value": {
                    "text": "Ihr Termin am Dienstag um zehn Uhr ist bestätigt.",
                    "voice": "lea",
                    "language": "de-CH",
                    "dialect": "standard",
                    "style": "ruhig und geduldig, kurze Pausen nach jedem Satz",
                    "format": "wav",
                    "sample_rate_hz": 16000,
                    "output": "json"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Synthesised audio. Body is the raw audio unless `output: \"json\"`.\n",
            "headers": {
              "X-Suisse-Request-Id": {
                "schema": {
                  "type": "string"
                }
              },
              "X-Suisse-Audio-Duration": {
                "schema": {
                  "type": "string"
                },
                "description": "Duration of the returned audio in seconds."
              },
              "X-Suisse-Audio-Encoding": {
                "schema": {
                  "type": "string"
                }
              },
              "X-Suisse-Audio-Sample-Rate": {
                "schema": {
                  "type": "string"
                }
              },
              "X-Suisse-Voice": {
                "schema": {
                  "type": "string"
                }
              },
              "X-Suisse-Cache": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "hit",
                    "partial",
                    "miss"
                  ]
                },
                "description": "Always `miss`: no audio is stored on this service. Kept for compatibility."
              }
            },
            "content": {
              "audio/mpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "audio/wav": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "audio/ogg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "audio/basic": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SynthesisResult"
                }
              }
            }
          },
          "400": {
            "description": "Invalid parameters (unknown voice, language, dialect, format or speed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Text exceeds the length limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit, daily audio-seconds budget, or concurrency ceiling.\n`tts.rate_limited` for the first two, `tts.capacity` for the third;\n`error.detail` names the ceiling. Honour `Retry-After`.\n",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying."
              },
              "X-Suisse-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "504": {
            "description": "Request timed out",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/tts/stream": {
      "get": {
        "summary": "Synthesise speech from text (realtime, WebSocket)",
        "description": "Open a socket, push text as it becomes available, receive audio while the\nrest of the text is still being written. Lower time-to-first-audio than\nbatch, and the right mode when the text comes from a live source.\n\nAuthenticate with the `X-API-Key` request header, or — from a browser,\nwhich cannot set handshake headers — with the subprotocol field:\n`Sec-WebSocket-Protocol: suisse.v1, key.<api key>`.\n\n```\n{\"type\":\"session.start\",\"model\":\"swift\",\"voice\":\"anna\",\"language\":\"de-CH\",\"dialect\":\"zurich\",\n \"speed\":1.0,\"style\":\"freundlich\",\"format\":\"pcm16\",\"sample_rate_hz\":24000}\n{\"type\":\"text.append\",\"text\":\"Grüezi. \"}\n{\"type\":\"text.flush\"}\n{\"type\":\"session.end\"}\n```\nAdd `\"transport\":\"binary\"` to `session.start` to receive audio as raw\nbinary WebSocket frames instead of base64 inside JSON — a third fewer\nbytes and no decode step. Control frames stay JSON, so a binary frame is\nalways audio, in order, in the encoding `session.ready` names. The\ndefault is `json`, so existing clients are unaffected.\n\nText is synthesised segment by segment at sentence boundaries. Send\n`text.flush` to force a partial segment out without ending the session.\nA `text.append` whose content is a complete `<speak>` document is expanded\ninto passages immediately, so markup works in this mode too.\n\n```\n{\"type\":\"session.ready\",\"request_id\":\"...\",\"voice\":\"anna\",\"language\":\"de-CH\",\n \"dialect\":\"zurich\",\"encoding\":\"pcm16\",\"sample_rate_hz\":24000,\"frame_bytes\":4096}\n{\"type\":\"audio.chunk\",\"seq\":1,\"encoding\":\"pcm16\",\"sample_rate_hz\":24000,\"data\":\"<base64>\"}\n{\"type\":\"segment.done\",\"characters\":42,\"duration_seconds\":2.31,\"cache\":\"miss\"}\n{\"type\":\"mark\",\"name\":\"after_ref\",\"offset_seconds\":7.15}\n{\"type\":\"session.complete\",\"duration_seconds\":7.94,\"frames\":48,\"cache\":{\"hits\":0,\"misses\":3}}\n{\"type\":\"error\",\"error\":{\"code\":\"tts.unavailable\",\"message\":\"...\",\"request_id\":\"...\"}}\n```\nAudio frames are a fixed `frame_bytes` in size apart from the final one.\nConcatenate the decoded `data` values in `seq` order to reconstruct the\nstream.\n",
        "responses": {
          "101": {
            "description": "Switching Protocols"
          },
          "401": {
            "description": "Authentication required"
          }
        }
      }
    }
  }
}