API reference

Base URL https://api.suisse-speech.ch/v1 · X-API-Key

Generated from the OpenAPI 3.0.3 description of Suisse Speech API 1.9.0. Download it as openapi.json to generate a client or import it into your API tool.

About this API

Speech-to-text and text-to-speech for Swiss German, its regional dialects and other languages, 60 in all. Both directions are available in two modes:

  • batch — send a whole file or a whole text, get the whole result
  • realtime — stream over a WebSocket and receive results as they are produced

Since 1.9.0 there is a third product on top of the two: voice agents (GET /agent/stream), realtime speech-to-speech over one WebSocket. The caller's audio goes in, the agent's spoken answer comes out, following the instructions and tools you give it. Billed per second of connected session time.

Start with GET /capabilities: it lists every voice, language, dialect, audio format, parameter range and error code this deployment accepts, so a client never has to hard-code them.

All endpoints require an X-API-Key header with a valid sv_* API key. The environment travels with the key: sv_… is live, sv_test_… is the sandbox — not billed, no allowance consumed, its own budget and concurrency slice. A sandbox key always receives structurally identical synthetic audio and transcripts without any engine being called: free, instant and deterministic, for continuous integration. Real voices and recognition need a live key. The header X-Suisse-Sandbox-Mode: synthetic is still accepted but no longer needed. Every response reports which environment answered in X-Suisse-Environment. WebSocket endpoints additionally accept Sec-WebSocket-Protocol: suisse.v1, key.<api key> for browser clients.

Limits apply per API key: 600 requests/minute with a burst limit of 25 requests/second, a ceiling on simultaneous requests, and a daily budget of audio seconds. An evaluation key additionally carries a fixed allowance of audio hours. See limits and usage_today in GET /capabilities, and the remaining allowance in GET /usage. Synthesis and recognition are both metered in audio seconds, to the second, with no minimum per request.

Reading a 429. Four different things answer with status 429, and error.code in the response body says which one it is:

  • *.rate_limited - too many requests in the window, or the daily audio budget is spent. Retry after Retry-After.
  • *.capacity - the service is momentarily busy. Retry after Retry-After.
  • *.plan_limit_reached - too many simultaneous requests on this key. Reduce parallelism; retrying at the same parallelism does not help.
  • *.entitlement_exhausted - the evaluation allowance of audio hours is used up. Retrying does not help; contact us to extend it.

An HTTP client that reports only the status line hides this distinction, so log the response body of every 429.

Synthesised audio and transcripts are processed in memory and not stored; identical texts are synthesised again each time. Every synthesis response reports cache as miss (the field stays for compatibility).

Keeping audio of fixed prompts (opt-in per key). On the customer's written instruction we enable, for a named API key, that synthesised audio is kept when a request asks for it (cache: use | refresh | off, cache_ttl_s): encrypted, for that key alone, for at most 30 days, deletable at any time (DELETE /tts/cache). Only such a key sees the cache states off, hit, partial, miss, refresh and the fields cache_entry and cache_expires_at. Without the cache switched on for your key these fields are ignored and responses report "miss".

Simultaneous work is admitted up to a published ceiling and refused beyond it with 429 and a Retry-After header, rather than queued — a queue would turn a capacity problem into a latency problem for the requests that did get in. error.detail names which ceiling was reached: key_capacity (reduce your own parallelism), service_capacity or host_load (back off and retry). The current ceilings are in capacity in GET /capabilities, and are what the deployment enforces today rather than a contractual entitlement.

A narrative integration guide, including worked examples and client behaviour for each error class, ships with the service as API_GUIDE.md.

POST/ttsSynthesise speech from text (batch)

https://api.suisse-speech.ch/v1/tts

Send the complete text, receive the complete audio.

By default the response body is the audio itself, with the media type of the requested format and the metadata in X-Suisse-* headers. Set output: "json" (or send Accept: application/json) to receive a JSON envelope with base64 audio instead.

Models

The API provides two specialized speech engines:

  • 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.
  • swift — Low-latency engine optimized for real-time conversational streaming, telephone interactions, and instant responses.

One voice in both modes (since 1.7.0, enabled per account): swift and studio speak with the same voices, both stream on /tts/stream and both render in batch. Every passage then starts 60 ms before its first sound and ends 200 ms after its last, every voice and language is delivered at one loudness, and a sentence sent whole is rendered in one piece. GET /capabilities shows the modes per model for your key.

Swiss German Dialects

language selects the language, dialect the regional variety within it. Swiss German (de-CH) is dialect-aware:

  • standard: supra-regional Swiss German (default).
  • zurich: Zurich dialect (Züritüütsch).
  • bern: Bernese dialect (Bärndütsch).
  • basel: Basel dialect (Baaseldütsch).
  • luzern: Lucerne dialect (Lozärnerdütsch). Both models render the text into the dialect - its own words and forms, not an accent - so the text may be written in Standard German. swift does it in real time on the stream; studio does it with the highest fidelity and is the one to use for prepared audio. The compact form language: "de-CH-zurich" is accepted as an alternative to the two fields.

Regional varieties and accents

The catalogue has two levels. language is a code that may carry a region, and a regional standard variety is its own code: it-CH, fr-CA, en-GB, en-IN, de-AT. dialect is a variety inside one language: de-CH + stgallen, fr-CH + geneve, en-GB + scottish. The compact form <language>-<dialect> (de-CH-stgallen) is accepted wherever a language code is, by recognition too.

Switzerland: Swiss German in its regional dialects and as Swiss High German (de-CH + hochdeutsch, spoken word for word), Swiss French with the accents of the Romandie, and Swiss Italian (it-CH). Swiss German dialects are rendered into the dialect; every other variety keeps the words exactly as written and changes only pronunciation, melody and rhythm.

GET /capabilities is the list. Every language and dialect there carries a status: verified is measured in production, supported is rendered on a best-effort basis and no quality figure is published for it.

Following the language of a conversation

Recognition reports the language it detected in lang, in the same codes this endpoint accepts as language, the compact dialect form included. A client that lists its target locales as recognition candidates (lang = de-CH-zurich,fr-CH,en) receives exactly one of them back and can pass it here unchanged. No mapping table and no server-side session is involved: every request stands on its own. The reply text may be written in Standard German; for de-CH it is spoken in the chosen dialect. For High German use de-CH-hochdeutsch (Swiss High German) or de.

Custom Style Prompting

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.

Markup

A text value whose first element is <speak> is parsed as markup. Each construct is realised for real rather than passed on as a hint:

element effect
<break time="500ms"/> silence of exactly that length in the audio
<say-as interpret-as="digits|telephone|characters|ordinal|date|time|currency|number"> rewrites the text into its spoken form
<sub alias="Aktiengesellschaft">AG</sub> speaks the alias
<prosody rate pitch volume> real rate, pitch and gain changes on that passage
<emphasis level="strong"> delivery directive on that passage
<lang xml:lang="de-CH-bern"> switches language and dialect mid-text
<mark name="x"/> returns the offset of that point in marks
<p> <s> paragraph and sentence pauses

say-as with digits or telephone speaks Swiss numbers the way the our phone agents do: dialect digit words and 3-3-2-2 grouping, so +41 31 533 22 02 in Bernese becomes "null drüü eis, füf drüü drüü, zwöi zwöi, null zwöi".

Elements that are not supported are rejected by name with an explanation in error.detail, never silently ignored. <audio> and <phoneme> are refused deliberately; use <sub alias> to respell a word. Call GET /capabilities for the authoritative list.

Metering

Billed in produced audio seconds, measured to the second, with no minimum per request.

Request body · application/json · SynthesisRequest

FieldTypeDescription
model"studio" | "swift"

Speech synthesis model:

  • studio (default): State-of-the-art expressive voice engine with authentic Swiss German dialect translation and nuanced prosody.
  • swift: Ultra-low latency engine optimized for real-time conversational streaming and telephony. Which modes each model offers to your key is listed in GET /capabilities (text_to_speech.models[].modes). On an account with one voice in both modes (since 1.7.0, enabled per account) both models stream on /tts/stream, render in batch and speak with the same voices.
default "studio"
textrequiredstring

The text to speak. Rendered verbatim; never interpreted as an instruction.

max 8000 chars
voicestring

Voice id from GET /tts/voices.

default "anna"
languagestring

Language code. de-CH-zurich is accepted as a compact language+dialect form.

default "de-CH"
dialectstring

Variety within the language. GET /capabilities lists the dialects of every language, each with a status (verified or supported). The hand-tuned Swiss German (de-CH) dialects are:

  • standard: supra-regional Swiss German (default).
  • zurich: Zurich dialect (Züritüütsch).
  • bern: Bernese dialect (Bärndütsch).
  • basel: Basel dialect (Baaseldütsch).
  • luzern: Lucerne dialect (Lozärnerdütsch). Both models render the text INTO the dialect, so it may be written in Standard German: swift does it in real time, studio with the highest fidelity for prepared audio. Further de-CH values: hochdeutsch - Swiss High German, spoken word for word - and more regions (stgallen, aargau, graubuenden, wallis, ...). Other languages have accents of their own, for example fr-CH + geneve or en-GB + scottish. standard is accepted with every language and means its standard variety: with en, fr, it or de it is the same as sending no dialect.
default "standard"
speednumber

Speaking rate multiplier (0.5 = half speed, 1.0 = normal pace, 2.0 = double speed). swift applies it exactly. studio has the voice perform it, so the pace changes audibly but not by the exact factor (measured: 1.5 about 1.25 times faster, 0.7 about 0.8 times as fast).

default 1 · min 0.5 · max 2
stylestring

Free-text delivery prompt (up to 600 chars) steering tone, emotion, and pacing. Examples:

  • "freundlich, warm und einladend"
  • "ruhig und geduldig, kurze Pausen nach jedem Satz"
  • "professionell und sachlich, klare Aussprache" Steers delivery only; the spoken content always renders the text verbatim.
max 600 chars
format"wav" | "mp3" | "opus" | "pcm16" | "mulaw" | "alaw"

mp3 is constant bitrate: 96 kbit/s at 44.1 and 48 kHz, 80 kbit/s at 32 kHz.

default "mp3"
sample_rate_hzinteger

Output sample rate. Permitted values depend on format; GET /tts/voices lists them with each format's default (mp3 44100, wav and pcm16 24000, opus 48000, mulaw and alaw 8000).

output"audio" | "json"

audio returns the raw bytes, json a base64 envelope with metadata.

default "audio"
cache"off" | "use" | "refresh"

The key's own cache, for keys for which keeping audio was enabled on written instruction (cache.enabled in GET /capabilities). use: serve the kept rendering of exactly this request, or render and keep it; refresh: render anew and replace it; off: read nothing, keep nothing. Without the field the key's default applies (cache.default_mode, normally off). A value that is not one of these is refused with 400 tts.invalid_request. Without the cache switched on for your key these fields are ignored and responses report "miss".

cache_ttl_sinteger

How long a new rendering is kept, in seconds: from 60 to the key's cache.max_ttl_s (30 days at most); by default cache.default_ttl_s (7 days unless agreed otherwise). With a request served from the cache it sets the expiry anew. Out of range: 400 (only for a key with the cache; ignored on every other key).

min 60 · max 2592000

Responses

StatusDescription
200

Synthesised audio. Body is the raw audio unless output: "json".

Schema: file
400

Invalid parameters (unknown voice, language, dialect, format or speed)

Schema: Error
401

Authentication required

Schema: Error
413

Text exceeds the length limit

Schema: Error
429

Rate limit, daily audio-seconds budget, or concurrency ceiling. tts.rate_limited for the first two (a spent budget says so in error.detail, and its Retry-After reaches to 00:00 UTC), tts.plan_limit_reached or tts.capacity for the third; error.detail names the ceiling. Honour Retry-After.

Schema: Error
503

Service temporarily unavailable

Schema: Error
504

Request timed out

Schema: Error
WS/tts/streamSynthesise speech from text (realtime, WebSocket)

wss://api.suisse-speech.ch/v1/tts/stream

Open a socket, push text as it becomes available, receive audio while the rest of the text is still being written. Lower time-to-first-audio than batch, and the right mode when the text comes from a live source.

Authenticate with the X-API-Key request header, or — from a browser, which cannot set handshake headers — with the subprotocol field: Sec-WebSocket-Protocol: suisse.v1, key.<api key>.

Client to server

{"type":"session.start","model":"swift","voice":"anna","language":"de-CH","dialect":"zurich",
 "speed":1.0,"style":"freundlich","format":"pcm16","sample_rate_hz":24000}
{"type":"text.append","text":"Grüezi. "}
{"type":"text.flush"}
{"type":"session.end"}

Add "transport":"binary" to session.start to receive audio as raw binary WebSocket frames instead of base64 inside JSON — a third fewer bytes and no decode step. Control frames stay JSON, so a binary frame is always audio, in order, in the encoding session.ready names. The default is json, so existing clients are unaffected.

Models: swift, and studio on an account with one voice in both modes (GET /capabilities lists the modes per model for your key); studio on any other key is answered with tts.invalid_request.

Text is synthesised segment by segment at sentence boundaries. Send text.flush to force a partial segment out without ending the session. A text.append whose content is a complete <speak> document is expanded into passages immediately, so markup works in this mode too.

Every segment is an independent performance, so a reply sent sentence by sentence can change slightly in pitch, pace and loudness between its sentences. For one continuous performance, send the whole reply as "text" in session.start, or add "passages":"turn" to session.start: appended text is then held and rendered as one segment at text.flush (text left unflushed for 2 s is rendered anyway). The default "passages":"sentence" is unchanged; session.ready echoes the value in effect.

Server to client

{"type":"session.ready","request_id":"...","voice":"anna","language":"de-CH",
 "dialect":"zurich","encoding":"pcm16","sample_rate_hz":24000,"frame_bytes":1024}
{"type":"audio.chunk","seq":1,"encoding":"pcm16","sample_rate_hz":24000,"data":"<base64>"}
{"type":"segment.done","characters":42,"duration_seconds":2.31,"cache":"miss"}
{"type":"mark","name":"after_ref","offset_seconds":7.15}
{"type":"session.complete","duration_seconds":7.94,"frames":48,"cache":{"hits":0,"misses":3}}
{"type":"error","error":{"code":"tts.unavailable","message":"...","request_id":"..."}}

Audio frames are a fixed frame_bytes in size apart from the final one. Concatenate the decoded data values in seq order to reconstruct the stream.

A key that keeps audio of fixed prompts

Only for a key for which this was enabled on written instruction (every other key's frames are exactly as above): cache (use | refresh | off) and cache_ttl_s in session.start set the session's mode, echoed in session.ready; cache on a text.append applies to that text, on a text.flush to the text it releases; a passage made of text sent with different values takes the most cautious (off before refresh before use). A passage served from the cache arrives at once. segment.done then carries cache (off, hit, miss, refresh) with cache_expires_at and cache_entry, and session.complete counts {"hits":1,"misses":2,"refreshed":0,"off":3}. Without the cache switched on for your key these fields are ignored and responses report "miss".

Responses

StatusDescription
101

Switching Protocols

401

Authentication required

GET/tts/voicesList voices, languages and audio formats

https://api.suisse-speech.ch/v1/tts/voices

The voice catalogue on its own, for clients that only need to populate a picker. A subset of GET /capabilities.

Responses

StatusDescription
200

Voice catalogue

Schema: object
401

Authentication required

Schema: Error
POST/tts/prewarmPrewarm phrases - only for a key that keeps audio of fixed prompts

https://api.suisse-speech.ch/v1/tts/prewarm

For every key without its own cache synthesised audio is processed in memory and not stored, so prewarming stores nothing: every phrase comes back skipped, and nothing is synthesised or billed, as before.

For a key that keeps audio of fixed prompts (enabled on written instruction), the phrases are rendered with the given parameters and kept (cache defaults to use: only what is not kept yet is rendered; refresh renders every phrase anew; off is refused). warmed is billed once, already_warm is free. A kept phrase is served to requests with "cache": "use" and exactly the same model, voice, language, dialect, speed, style and text.

Needs a live key: a sandbox key never reaches the engine, so there would be nothing to warm.

Request body · application/json

FieldTypeDescription
model"studio" | "swift"

Speech model to prewarm (studio or swift).

default "studio"
phrasesrequiredstring[]

The passages to synthesise ahead of time.

voicestring
default "anna"
languagestring
default "de-CH"
dialectstring
default "standard"
speednumber
default 1
stylestring
cache"use" | "refresh"

Only for a key that keeps audio; ignored on every other key.

default "use"
cache_ttl_sinteger

How long the phrases are kept; up to the key's cache.max_ttl_s.

min 60

Responses

StatusDescription
200

What was warmed

Schema: object
400

No phrases, too many phrases, a sandbox key, or (on a key that keeps audio) cache "off" or an invalid cache / cache_ttl_s

429

Rate limited or allowance exhausted

POST/sttTranscribe an audio file

https://api.suisse-speech.ch/v1/stt

Sync transcription for short audio (≤ 25 MB, ≤ 4 min). Pass audio as multipart/form-data with field audio. Optional fields: lang (default de-CH), vocabulary (JSON array of up to 200 strings to bias recognition). Longer audio is refused at once with 413 stt.audio_too_large and a detail saying so: submit a recognition job (POST /jobs, kind=stt) instead.

Multilingual recognition

lang takes one language code, several candidates separated by commas (de-CH,fr-CH,en), or auto (short for de-CH,fr-CH,it,en). With more than one candidate the spoken language is detected automatically, the transcript is written in that language, and the result reports it in lang. The reported value is always one of the candidates given, and is accepted unchanged as language by POST /tts and /tts/stream. Name only the languages you expect: fewer candidates means a steadier result on short utterances.

A candidate may carry a region or a dialect (fr-CH, de-CH-zurich) and is reported back exactly so, which lets the choice of dialect travel with the code. Recognition reports the language, not the dialect that was spoken.

Swiss German dialects and Standard German are both recognised under de-CH and are written in Standard German. gsw, gsw-CH and gsw-CH-<dialect> are accepted as Swiss German, like de-CH.

Swiss German or High German

With swiss_german_detection: true (alias variety_detection), recognition also tells whether Swiss German (de-CH, de-CH-<dialect>, gsw, gsw-CH) or High German (de, de-DE, de-AT, de-CH-hochdeutsch) was spoken: the transcript carries variety (see the Variety schema), decided over the whole recording, and with a code of each listed lang reports the candidate of the variety heard. Undecided, lang is the first German candidate listed, as without detection. Off unless sent as true; available on request (write to info@suisse-it.ch and we enable it for your account). Only a recording recognised as German is checked: with other languages among the candidates, an English, French or Italian recording is neither checked nor delayed; a recording recognised as German is decided from its first seconds of speech. For German it costs time: for an utterance with less than about 4 s of speech the final can come up to about 1 s later (at most max_final_delay_ms in GET /capabilities); longer utterances are usually not delayed. Under about 1 s of speech (a single "ja" or "merci") nothing is decided and lang is the first German candidate listed.

Request body · multipart/form-data

FieldTypeDescription
audiorequiredfile

The recording, up to 25 MB and 4 minutes, in any common container (WAV at any rate, bit depth and channel count, MP3, Ogg/Opus or Vorbis, FLAC, M4A/AAC, WebM, AIFF, A-law/µ-law WAV, or a video file with an audio track). The content decides, not the file name. Headerless PCM is refused with 415; wrap it in a WAV header. Shorter than 0.1 s is refused with 400 stt.invalid_audio.

langstring

One code, comma-separated candidates (de-CH,fr-CH,en, at most 8) or auto. See "Multilingual recognition" above. A code the catalogue does not know is refused with 400 stt.invalid_request before recognition starts, never sent to the engine as a guess.

default "de-CH"
swiss_german_detectionboolean

Off unless true: omitted or false reports the first listed candidate, as before, also when lang lists a Swiss German and a High German code. true adds variety; with a Swiss German code (de-CH, de-CH-, gsw, gsw-CH) and a High German code (de, de-DE, de-AT, de-CH-hochdeutsch) listed, lang reports the candidate of the variety heard, with one German code it stays as given. Enabled per account, on request (write to info@suisse-it.ch): true on an account without it, or without a German candidate, is refused with 400 stt.invalid_request and a detail. Only a recording recognised as German is checked: with other languages among the candidates, English, French or Italian is neither checked nor delayed; a recording recognised as German is decided from its first seconds of speech. For German it costs time: for an utterance with less than about 4 s of speech the final can come up to about 1 s later (at most max_final_delay_ms in GET /capabilities); longer utterances are usually not delayed. Under about 1 s of speech (a single "ja" or "merci") nothing is decided and lang is the first German candidate listed.

variety_detectionboolean

The earlier name of swiss_german_detection, accepted as the same field. Both with different values is refused with 400.

conversation_idstring

One decision per call (since 1.8.0): your identifier of the call, 1-128 visible ASCII characters without spaces, the same on every request of the call. With swiss_german_detection: true, the first decided German result of the call decides it; every later German result of the call (any stream or recording) carries that variety at once (variety.source: call, not delayed). While the decision is new, the next German utterances are checked in the background: one agreeing settles it, two disagreeing correct it once, early in the call. Matched within your account only; kept as a one-way hash with the decided variety for two hours after the decision. A whole number counts as its digits; any other value is ignored, never refused (decided per utterance, no source).

max 128 chars
vocabularystring

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, a 201st term or a term over 100 characters is refused with 400 rather than ignored or applied in part.

Responses

StatusDescription
200

Transcript

Schema: Transcript
400

Invalid request

Schema: Error
401

Authentication required

Schema: Error
413

Audio too large: the upload is over 25 MB or the recording over 4 minutes (stt.audio_too_large; detail says which and points to POST /jobs).

Schema: Error
415

Unsupported audio format (the upload could not be decoded as audio)

Schema: Error
429

Rate limit or daily audio budget (stt.rate_limited), the plan's ceiling on simultaneous requests (stt.plan_limit_reached; detail states the number) or the service momentarily full (stt.capacity; detail is service_capacity or host_load). Honour Retry-After.

Schema: Error
503

Service temporarily unavailable

Schema: Error
504

Request timed out

Schema: Error
WS/stt/streamStreaming transcription (WebSocket)

wss://api.suisse-speech.ch/v1/stt/stream

WebSocket endpoint at wss://api.suisse-speech.ch/v1/stt/stream. Upgrade with the X-API-Key header (or ?api_key=sv_... query param).

Client → server messages

{"type":"config","lang":"de-CH","vocabulary":["..."]}     // first message
{"type":"config","lang":"de-CH,de-DE","swiss_german_detection":true} // with Swiss German / High German detection
{"type":"config","lang":"de-DE,de-CH","swiss_german_detection":true,"conversation_id":"call-7f3c2a1e"} // ... decided once per call
{"type":"config","lang":"de-CH","finalize_ack":true}       // voice bots: finalize commits at once (the default), acknowledged
{"type":"config","lang":"de-CH","finalize_mode":"pause"}   // live captions: finalize waits for the next pause
{"type":"audio.append","data":"<base64 PCM16 16kHz mono>"} // chunks
{"type":"finalize"}                                        // optional: commit what has been heard
{"type":"audio.end"}                                       // signal end

Binary frames are also accepted as raw PCM16 audio; a frame need not end on a sample boundary, and audio sent before config (or before session.ready) is kept and recognised in order. The subprotocol reply is always suisse.v1; the key carried in Sec-WebSocket-Protocol is never echoed. The upgrade response carries X-Suisse-Environment, and X-Suisse-Key-Status on a superseded key.

Server → client messages

{"type":"session.ready","request_id":"...","finalize_mode":"immediate"} // the mode in force; "finalize_ack":true when acknowledged
{"type":"transcript.partial","text":"...","words":[...],"lang":"de-CH","duration_s":1.2}
{"type":"transcript.final","text":"...","words":[...],"lang":"de-CH","duration_s":3.4}
{"type":"transcript.final","text":"...","words":[...],"lang":"de-CH","duration_s":5.1,"forced":true}
{"type":"transcript.final","text":"...","lang":"de-CH","variety":{"spoken":"swiss_german","confidence":0.97,"decided":true},"words":[...],"duration_s":3.4}
{"type":"finalize.done","request_id":"...","committed":true,"ms":38}  // finalize_ack: one per finalize, after its finals
{"type":"session.complete","request_id":"..."}
{"type":"error","code":"stt.upstream_unavailable","message":"...","request_id":"..."}

Partial events are emitted every 250 ms; the first one as soon as the recogniser has heard a word, which can be before the first tick. A final always ends on a word boundary (unless you send finalize in the middle of a word). The normal close code is 1000. A config naming a language the catalogue does not list, or a malformed vocabulary, is answered with an error frame stt.invalid_request (with detail) before the socket closes.

Getting finals sooner

Without a commit a word is final about five to six seconds after it was spoken. A commit makes final what has been heard so far: send finalize (below), or let an option in config commit for you (all off by default):

  • "finalize_on_pause": true commits at pauses in speech: as soon as the speaker pauses after a sentence the recogniser holds to be complete (typically 0.7 s after the sentence ends), or after 0.8 s of pause. For live captions and live translation of continuous speech. finalize_pause_ms (300–2000, default 800) sets that pause: shorter commits more often, with more fragments cut inside a sentence (each forced: true); out of range is refused with stt.invalid_request. Measured on a continuously speaking preacher: 800 ms → a word final 5.2 s after it was spoken (median), every sentence end kept; 600 ms → 4.7 s, 12 of 15 sentence ends; 400 ms → 2.5 s, 3 of 15 (the commit comes before the recogniser has decided the sentence is over). For a pipeline that reads punctuation, 600 is the practical floor.
  • "endpointing": true (with endpoint_delay_ms, 500–3000) commits at the end of a turn in a dialogue. It does not fire inside a monologue.

{"type":"finalize"} asks for one commit. "finalize_mode" in config says when it is applied (since 1.8.0):

  • "immediate" (the default): at once, whether or not your audio goes on, and the finals of everything heard so far follow as soon as the recogniser confirms the commit - 0.03 s after the finalize (median; 0.07 s at the 95th percentile), as after audio.end (2,400 telephone turns, measured on the service without the network between you and us). For voice bots and push-to-talk: send it at your own end of speech, after about 200 ms of silence; counted from the end of speech, add that silence (or, with endpointing, the recogniser's end-of-turn decision). A finalize in the middle of a word cuts the word there. At most one commit a second: a finalize within a second of the previous one is applied the moment that second is over; repeated requests before it is applied are one commit.
  • "pause": at the next pause in which the sentence is complete, never in the middle of a word or a sentence; repeated requests before that are one request. The pause is heard in the audio you send: keep sending audio after finalize (silence is fine) - if your audio stops, the commit is applied one second later. For live captions and live translation, which finalize in the middle of speech.

A final produced by a commit carries "forced": true. Its punctuation is the recogniser's reading of the speech: a sentence end it adds only because of the commit is removed - except with "immediate" after at least 200 ms of silence, where the commit keeps it, as audio.end would. "finalize_ack": true (with "immediate") answers every finalize with exactly one {"type":"finalize.done","request_id":"...","committed":true,"ms":38} after the finals of its commit: committed: false when there was nothing left to commit or audio.end took it (then before session.complete); ms is the time from your finalize to this event on our side. Without the flag no such event is sent. session.ready names the mode in force (finalize_mode, and finalize_ack: true when the events will come). Any other value of finalize_mode is ignored (the default applies, as without the field). With Swiss German detection a German final can still wait for its variety (with conversation_id only until the call is decided), and finalize.done comes after it: nothing overtakes a final.

Knowing the session is working

"status_interval_ms": 5000 in config (1000–60000, off by default) sends {"type":"session.status","request_id":"...","audio_ms":184000,"processed_ms":183920,"last_word_ms_ago":1250,"stalled":false} at that interval: audio received, audio the recogniser has worked through (it keeps up to within about a second, also on silence and music), time since the last word (null before the first), and stalled: true when audio arrives but the recogniser has not moved through it for 5 s - reconnect then. No words while processed_ms keeps up means no speech is heard in the audio. Out of range is refused with stt.invalid_request.

Multilingual recognition

lang in config takes one code, comma-separated candidates ("de-CH,fr-CH,en") or "auto", exactly as on POST /stt. Every transcript.partial and transcript.final then carries the language detected for that stretch of speech in lang, so a change of language during a session is visible as it happens.

Swiss German or High German

With "swiss_german_detection": true in config (off unless sent), every German transcript.final carries variety (see the Variety schema), and with a Swiss German and a High German candidate (for example "de-CH,de-DE") lang reports the candidate of the variety heard. Partials carry no variety (they report the first listed German candidate); the variety is decided on finals, and while a German final waits for it, the events after it wait too (the order is kept). Only utterances recognised as German are checked: with other languages among the candidates (for example "de-CH,de-DE,en,fr-CH,it-CH"), English, French and Italian utterances are never checked; their finals wait only behind a pending German final. For German it costs time: for an utterance with less than about 4 s of speech the final can come up to about 1 s later (at most max_final_delay_ms in GET /capabilities); longer utterances are usually not delayed. With conversation_id (below) the wait ends with the first decision of the call. Under about 1 s of speech (a single "ja" or "merci") nothing is decided and lang is the first German candidate listed. "swiss_german_detection" (alias "variety_detection") in config has the same meaning as on POST /stt. Available on request: write to info@suisse-it.ch and we enable it for your account; without it, a config with "swiss_german_detection": true is answered with an error frame stt.invalid_request (with detail) before session.ready. Accuracy: see the Variety schema.

One decision per call (since 1.8.0): send the same "conversation_id" (1-128 visible ASCII characters, no spaces) in the config of every stream of a call. Until a German final of the call is decided, each is judged on its own (variety.source: "utterance"); the first decided one decides the call, and every later German final of the call - in this stream or any later one, or a POST /stt with the same id - carries that variety at once, decided: true, source: "call", without the delay detection otherwise adds. While the decision is new, the next German utterances are checked in the background (nothing waits): one agreeing settles it, two disagreeing correct it once, early in the call. A whole number counts as its digits; any other value is ignored, never refused (decided per utterance, no source). Recommended for voice bots: until the call is decided, list first the variety you would rather answer in when unsure (for example "de-DE,de-CH").

Responses

StatusDescription
101

Switching Protocols

401

Authentication required

WS/agent/streamRealtime voice agent (WebSocket)

wss://api.suisse-speech.ch/v1/agent/stream

WebSocket endpoint at wss://api.suisse-speech.ch/v1/agent/stream (since 1.9.0): speech in, speech out. Our recognition hears the user; the agent answers in the voice, language and dialect of the session, follows your instructions and calls your tools. Authenticate as on the other sockets: the X-API-Key header, Sec-WebSocket-Protocol: suisse.v1, key.<api key> or ?api_key=. The subprotocol reply is always suisse.v1; the key is never echoed. A browser or app uses an ephemeral token from POST /agent/tokens instead of the key, in the same places (key.<token>, ?api_key=<token>); it is consumed by the handshake.

Released per account. A key of an account without the voice agent gets {"type":"error","code":"agent.not_enabled",...} and the close code 1008. GET /capabilities says it in agent.enabled_for_your_account. Sandbox keys (sv_test_) get a synthetic session with the same events (a synthetic transcript, a short test tone as the answer, "sandbox": true in every event), never billed.

The client keeps the agent's voice out of its microphone (in a browser: open the microphone with getUserMedia's cancellation of played-back audio switched on, or use a headset): otherwise the agent hears itself and interrupts itself.

Client → server messages

{"type":"session.start",                       // exactly once, first
 "instructions":"You are the reception of ...", // up to 16,000 characters
 "language":"de-CH","dialect":"zurich",         // GET /capabilities agent.languages / agent.dialects
 "voice":"anna",                                // agent.voices; default anna
 "greeting":"Grüezi, wie chan ich Ihne hälfe?", // optional: the agent speaks first, word for word
 "input":{"encoding":"pcm16","sample_rate_hz":16000},
 "output":{"encoding":"pcm16","sample_rate_hz":24000,"transport":"json"},
 "turn_taking":{"mode":"auto","interruptions":true,"patience":"short","idle_prompt_s":10,"idle_end_s":45},
 "tools":[{"name":"book_slot","description":"...","parameters":{"type":"object","properties":{}}}],
 "vocabulary":["Suisse IT"],"max_duration_s":900,"transcripts":true,
 "languages":["de-CH","fr-CH","it-CH"],           // up to 4: the user may switch between them
 "pronunciations":[{"text":"SIT","say":"Suisse I T"}],
 "speed":1.0,                                   // 0.85–1.20
 "end_conversation":true,                       // the agent may end the conversation (see Call control)
 "transfer_targets":[{"name":"Support","description":"Technical questions"}]}
{"type":"audio.append","data":"<base64 audio in the input format>"}  // or binary frames
{"type":"input.commit"}                        // turn_taking.mode manual: the user is done
{"type":"text.input","text":"..."}             // a user message as text
{"type":"context.add","text":"..."}            // background knowledge, no reply
{"type":"reply","instructions":"..."}          // the agent speaks now (instructions optional)
{"type":"say","text":"..."}                    // the agent says this text word for word
{"type":"tool.result","call_id":"tc_...","output":{...}}  // a string or any JSON
{"type":"interrupt"}                           // stop the agent now
{"type":"playback.mark","reply_id":"rp_...","played_ms":1840}  // optional: what was played of that reply
{"type":"config.update","instructions":"...","tools":[...]}
{"type":"session.end"}

Defaults (each field of session.start is optional): language de-CH, dialect standard (de-CH only: standard, zurich, bern, basel, luzern), voice anna; input pcm16 at 16000 Hz; output pcm16 at 24000 Hz, transport json; turn_taking mode auto, interruptions true, patience short, idle_prompt_s 10 (5–60), idle_end_s 45 (15–300, longer than idle_prompt_s); max_duration_s 900 (60–1800); transcripts true; languages the session language only (up to 4 of agent.languages, the session language first; without language the first listed); pronunciations none (up to 100, text up to 64, say up to 128 characters); speed 1.0 (0.85–1.20, on top of the voice's own tempo); end_conversation true; transfer_targets none (up to 10, name up to 64, description up to 256 characters). Encodings pcm16, mulaw, alaw (mulaw and alaw 8000 Hz only); sample rates 8000, 16000, 24000, 48000. A field this protocol does not define is refused with agent.invalid_request; detail names its position, never its name or value.

Server → client messages

{"type":"session.ready","request_id":"req_...","session_id":"req_...","voice":"anna","language":"de-CH","dialect":"standard",
 "input":{"encoding":"pcm16","sample_rate_hz":16000},
 "output":{"encoding":"pcm16","sample_rate_hz":24000,"transport":"json","frame_ms":40,"frame_bytes":1920},
 "turn_taking":{"mode":"auto","interruptions":true,"patience":"short","idle_prompt_s":10,"idle_end_s":45},
 "languages":["de-CH"],"speed":1.0,"end_conversation":true,"transfer_targets":0,"pronunciations":0,
 "tools":1,"transcripts":true,"max_duration_s":900,"limited_by":"max_duration_s"}
{"type":"user.speech_started"}  {"type":"user.speech_stopped"}
{"type":"user.transcript.partial","text":"..."}  {"type":"user.transcript.final","text":"...","language":"de-CH"}
{"type":"reply.started","reply_id":"rp_..."}
{"type":"audio.chunk","reply_id":"rp_...","seq":1,"data":"<base64, 40 ms>"}  // transport json; binary: raw frames
{"type":"reply.transcript.delta","reply_id":"rp_...","text":"..."}
{"type":"reply.audio_done","reply_id":"rp_..."}
{"type":"reply.done","reply_id":"rp_...","status":"completed","transcript":"..."}  // completed | interrupted | cancelled | failed; transcript only with transcripts true
{"type":"audio.clear","reply_id":"rp_..."}     // barge-in: empty the playback buffer now
{"type":"tool.call","call_id":"tc_...","name":"book_slot","arguments":{"day":"2026-11-03"},"reply_id":"rp_..."}
{"type":"transfer.requested","request_id":"req_...","target":"Support","reason":"...","reply_id":"rp_..."}
{"type":"session.end_requested","request_id":"req_...","reason":"goodbye"}  // goodbye | user_request | closing_confirmed | idle
{"type":"session.warning","code":"agent.duration_ending","seconds_left":60}
{"type":"session.status","request_id":"req_...","state":"listening","recognition":"ok","connected_s":60,"remaining_s":840,"open_tool_calls":0}
{"type":"error","code":"agent.invalid_request","message":"...","detail":"...","request_id":"req_..."}
{"type":"session.complete","request_id":"req_...","duration_s":184.2,"billable_seconds":184.2,
 "end_reason":"goodbye",                        // after session.end_requested; "restart" when the service restarts; "unavailable" when it cannot continue
 "transcript":[{"role":"agent","text":"Grüezi ...","at_ms":420},{"role":"user","text":"...","at_ms":3100}]}

Every audio frame holds 40 ms in the output format (frame_bytes; the last frame of a reply may be shorter). With transport binary, reply.started comes before a reply's first frame and reply.audio_done after its last. After audio.clear no frame of that reply follows.

Pacing. Audio leaves on the service's own clock: the first up to 200 ms of a reply at once, then a frame every 40 ms at about 1.25 times realtime, never more than about 1.5 s ahead of your playback. Your playback buffer stays small, and audio.clear takes effect at once. A reply's events keep their order with its audio: reply.audio_done and reply.done come after its last frame, and the next reply's reply.started after the previous reply's reply.done. user.speech_started and user.speech_stopped also come on this 40 ms clock. While more than about 1.5 s of audio still waits to be sent, the next reply starts only once it has (it is not lost). Transcript deltas that wait behind audio may arrive merged into one. A reply's audio is checked against its own transcript before it is sent, so its first frame comes a moment after the first words of the transcript are ready.

Transcripts. With transcripts false there are no user.transcript.* and no reply.transcript.delta events, reply.done carries no transcript and session.complete none either.

Turn-taking. auto: the agent answers when the user has finished (patience short, normal, long: how soon). manual: send input.commit when the user is done. With interruptions true the user can interrupt the agent (two words, a stop word such as "stopp" or "moment", or longer speech; a short "mhm" does not): you get audio.clear and reply.done with status interrupted. playback.mark makes the cut exact when your playback lags. A mark counts for its own reply only; a mark for a reply that no longer plays (interrupted, cleared, or long finished) is accepted and ignored, never an error. Only a mark that goes back or exceeds the audio sent for a reply still playing is agent.invalid_request.

Tools. tool.call.arguments is always a JSON object. Answer every call_id once with tool.result within 30 s; later, the agent tells the user it did not work and the call is closed (tool.result then: agent.invalid_request, detail "call expired"). A call stays valid after an interruption.

Tool parameters use a subset of JSON Schema: type (object, string, number, integer, boolean, array), properties, required, enum, description, items, minimum, maximum, maxLength, nested at most 6 levels. The keywords of everyday strict schemas additionalProperties, title, default, examples, $schema, minLength, minItems and maxItems are accepted and ignored (removed before the schema is used; validate your tool's input yourself). Anything else (for example oneOf, anyOf, allOf, $ref, pattern, format) is refused with agent.invalid_request; detail names its position. end_conversation and transfer_to_human are reserved names. A call of a reply that was interrupted or cut never reaches you.

Call control. Two built-in functions the agent calls itself: end_conversation (with end_conversation true, the default) ends only on the user's own words: a goodbye, an explicit wish to end, or a clear no to the agent's closing question; otherwise the agent asks first. Then session.end_requested, the goodbye plays (estimated from the audio sent, or your playback.mark), session.complete and close 1000. transfer_to_human (only with transfer_targets) sends transfer.requested with the target's name and a short reason; the agent says a short handover line and the session goes on: your telephony makes the transfer and then ends the session.

Silence (turn_taking mode auto). The clock starts with the first exchange and runs only while nobody speaks, nothing is played and no tool call is open: after idle_prompt_s the agent asks once, in the user's language, whether they are still there; after idle_end_s it says goodbye and the session ends (session.end_requested, reason idle).

Languages. With several languages recognition listens for all of them, user.transcript.final carries the language heard, and the agent answers in the user's language, one language per reply; for Swiss German it keeps the chosen dialect.

Transcript. With transcripts true, session.complete carries transcript: every user turn and what the agent actually played (an interrupted reply up to where it was heard), with at_ms since session.ready (at most 1,000 entries and 200,000 characters, then transcript_truncated: true).

session.status comes every 60 s and when recognition changes: state listening, thinking or speaking; recognition ok or degraded (then the agent answers from the audio alone, without user.transcript.*); connected_s, remaining_s, open_tool_calls.

Warnings (session.warning, the session goes on): agent.duration_ending 60 s before max_duration_s; agent.entitlement_exhausted when the credit runs out (the session ends 30 s later or at max_duration_s when the credit set it); agent.usage_limit when the session's usage protection applies (it ends 30 s later); agent.audio_dropped when your connection reads audio too slowly (the excess is dropped), or a single reply runs more than about 30 s ahead of your playback: then audio.clear (only if audio of that reply already reached you) and reply.done with status interrupted follow, and the conversation continues from what the listener heard.

Close codes: 1000 normal; 1001 service restarting (reconnect; the session is ended properly first: session.complete with end_reason "restart", its seconds booked); 1009 a frame larger than the socket's ceiling (65 KB; standard WebSocket "message too big", the frame is not read); 1008 policy (not enabled, a malformed session.start, no session.start within 10 s, more than 50 refused frames within 10 s: over the frame rate, too large or unreadable); 1011 error (agent.unavailable; a running session first gets session.complete with end_reason "unavailable"); 1008 also for agent.forbidden (a locked token's field changed); 4000 agent.duration_limit; 4001 agent.timeout (no audio and no message for 120 s); 4002 agent.usage_limit; 4003 agent.entitlement_exhausted. A session that ends with an error sends the error frame first.

Limits: instructions 16,000 characters; up to 32 tools, 16,000 characters together, instructions and tools 24,000; tool names ^[a-zA-Z0-9_-]{1,64}$, descriptions 1,024 characters, parameters nested at most 6 levels; a text frame 64 KB and a binary frame 64 KB (a larger frame up to 65 KB gets agent.invalid_request or agent.invalid_audio, above that the socket closes with 1009); at most 100 frames per second, text and binary together, every frame counted (also one that is too large or cannot be read; the excess is dropped); audio at most 1.2 times realtime; 20 control messages per second (a frame that cannot be read counts as one); reply, say and text.input together 30 per minute; 10 config.update and 50 context.add per session. Errors that do not end the session come at most once per code per second; further ones in that second are dropped silently.

Billing: connected seconds from session.ready to the end, per second, no minimum; a start that fails before session.ready costs nothing. The account's remaining credit can shorten max_duration_s (limited_by: "credit"): each session holds the credit for its limit, so sessions running at the same time share the balance and never overdraw it; under one minute of credit left for a new session it is refused with agent.entitlement_exhausted. If the balance of a prepaid account cannot be read or held at the start, the session is refused with agent.unavailable (close 1011): retry shortly. A session is billed at the price in force when it started.

Responses

StatusDescription
101

Switching Protocols

401

Authentication required (agent.unauthenticated): no valid key, or a token that is unknown, used or expired

429

No capacity, the plan's simultaneous sessions (X-Suisse-Capacity-Reason), or the per-key session budget; Retry-After says when to try again. A token is used up by the handshake even then: mint a new one.

503

A token was offered while tokens are unavailable (agent.unavailable)

POST/agent/tokensEphemeral token for the voice agent

https://api.suisse-speech.ch/v1/agent/tokens

Your server mints a short-lived token with its API key and hands it to a browser or app, which opens /agent/stream with it instead of the key (Sec-WebSocket-Protocol: suisse.v1, key.<token> or ?api_key=<token>). A token is sat_ followed by 32 URL-safe characters, valid for 60 seconds and for one handshake, for the account and environment of the key that minted it (a sandbox key mints a sandbox token). It is stored only as a hash.

Optional JSON body (Content-Type: application/json): session presets session.start fields; the client's own session.start is applied over them. The endpoint is strict: only session and lock at the top level, no query parameters, and a body that is not JSON is refused; the preset is checked with the rules of session.start for the fields it carries (an unknown field, a wrong type, an unknown voice, language or dialect), all with agent.invalid_request (400) at minting, not later. With lock: true the preset instructions, tools, voice, language, dialect and greeting are fixed (absent ones stay at their defaults): a session.start that sends another value is refused with agent.forbidden (close 1008), and config.update and reply.instructions are refused with agent.forbidden during the session.

Parameters

NameTypeDescription
X-API-Keyrequiredstring · header

Request body · application/json

FieldTypeDescription
sessionobject

session.start fields (without type)

lockboolean
default false

Responses

StatusDescription
201

The token

Schema: object
400

agent.invalid_request: a body or session field that is not accepted (by position)

401

agent.unauthenticated

403

agent.not_enabled: the voice agent is not released for this account

429

agent.rate_limited

503

agent.unavailable: tokens cannot be issued just now; retry shortly

GET/jobsList recent jobs for this key

https://api.suisse-speech.ch/v1/jobs

Parameters

NameTypeDescription
limitinteger · query

Responses

StatusDescription
200

Jobs, newest first

Schema: object
POST/jobsSubmit an asynchronous job (synthesis or recognition)

https://api.suisse-speech.ch/v1/jobs

For text long enough that holding a connection open for the whole synthesis is the wrong shape. Acknowledged in milliseconds; the audio is collected later from /jobs/{id}/audio.

A synthesis job (JSON) takes the fields of POST /tts, model included. A recognition job (multipart, kind=stt) takes the fields of POST /stt and answers with the recognition error codes (stt.*).

Everything is validated at submission, so a bad voice or malformed markup is a 400 here rather than a failure discovered later.

Results are kept for 48 hours. Up to 200 jobs may be queued per key. Jobs queue behind interactive traffic and are metered identically.

Request body · application/json

Request body · multipart/form-data

FieldTypeDescription
kind"stt"
default "stt"
audiorequiredfile

The recording, up to 200 MB and 3 hours, in any common container (as POST /stt takes).

langstring

As on POST /stt; a code the catalogue does not know is refused here, at submission.

default "de-CH"
swiss_german_detectionboolean

As on POST /stt: Swiss German or High German, decided over the recording and reported in the transcript; only a recording recognised as German is checked. Refused at submission when the account does not have it (available on request). variety_detection is accepted as the same field.

vocabularystring

Terms to favour, as a JSON array or a comma-separated list: up to 200 terms of at most 100 characters.

callback_urlstring

Responses

StatusDescription
202

Accepted

Schema: Job
400

Invalid parameters

Schema: Error
401

Authentication required

Schema: Error
413

The recording exceeds the upload limit (stt.audio_too_large)

Schema: Error
429

Rate limit, or too many jobs already queued

Schema: Error
GET/jobs/{id}Job status, and the result when it has finished

https://api.suisse-speech.ch/v1/jobs/{id}

Parameters

NameTypeDescription
idrequiredstring · path

Responses

StatusDescription
200

Job

Schema: Job
400

No such job

Schema: Error
GET/jobs/{id}/audioCollect the audio of a finished synthesis job

https://api.suisse-speech.ch/v1/jobs/{id}/audio

Parameters

NameTypeDescription
idrequiredstring · path

Responses

StatusDescription
200

The audio

Schema: file
400

Not finished, not found, or the result has expired

Schema: Error
GET/capabilitiesDiscover everything the speech API accepts

https://api.suisse-speech.ch/v1/capabilities

Runtime discovery. Returns every voice, language, dialect, audio format, parameter range and error code the service accepts, plus the calling key's own quota for the current day.

Read this instead of hard-coding values: a new voice or language appears here without any client change.

Responses

StatusDescription
200

Capability document

Schema: Capabilities
401

Authentication required

Schema: Error
GET/usageWhat this key has consumed

https://api.suisse-speech.ch/v1/usage

Read from the billing ledger, not from the rate limiter's expiring counters, so these are the same numbers an invoice is built from, in the same unit. A customer billed by the second is entitled to reconcile continuously rather than at month end.

Sandbox traffic is reported separately and is never billed.

Parameters

NameTypeDescription
fromstring · query

Start of the window. Defaults to the start of the current month.

tostring · query

End of the window. Defaults to now.

granularity"total" | "day" · query

Add day to break the window down by day.

Responses

StatusDescription
200

Consumption for the window

Schema: object
400

Invalid window

401

Missing or invalid key

DELETE/tts/cacheDelete everything this key keeps

https://api.suisse-speech.ch/v1/tts/cache

Deletes every kept rendering of this key at once: they are unreadable from that moment and removed from disk. The key keeps its setting; renderings requested with "cache": "use" afterwards are kept afresh. Recorded, without content, in the account's activity. Works on every key (deleted is 0 when nothing is kept).

Responses

StatusDescription
200

What was deleted

Schema: object
401

Missing or invalid key

DELETE/tts/cache/{id}Delete one kept rendering

https://api.suisse-speech.ch/v1/tts/cache/{id}

The cache_entry of a response (ce_ and 32 hexadecimal digits). deleted is 0 when it is not (or no longer) kept for this key.

Parameters

NameTypeDescription
idrequiredstring · path

Responses

StatusDescription
200

What was deleted

Schema: object
400

Not a cache entry id

401

Missing or invalid key

GET/tts/cacheWhat this key keeps (counts and expiries, never texts)

https://api.suisse-speech.ch/v1/tts/cache

Whether this key keeps audio of fixed prompts (enabled per key on written instruction), its default mode and expiry, and what it holds: the number of kept renderings, their size and audio seconds, and when the next and the last of them expire. Never a text, never a list of entries.

Responses

StatusDescription
200

The key's cache

Schema: object
401

Missing or invalid key

Schemas

Word
FieldTypeDescription
wrequiredstring

The word.

startrequirednumber

Start time in seconds from the audio start.

endrequirednumber

End time in seconds.

confrequirednumber

Confidence in [0,1].

min 0 · max 1
Transcript
FieldTypeDescription
textrequiredstring
langrequiredstring

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. With a Swiss German and a High German candidate (and variety detection enabled for the account), the variety heard decides which of them is reported (see variety).

e.g. "de-CH"
wordsrequiredWord[]
duration_srequirednumber

Audio duration in seconds: the length of the recording, silence included, which is what is billed.

varietyVariety

Swiss German (dialect) or High German, decided from the audio. Present only when the request sent swiss_german_detection: true and German was heard. Only speech recognised as German is checked: with other languages among the candidates, English, French and Italian are never checked (their finals wait only behind a pending German final, to keep the order). Available on request: write to info@suisse-it.ch and we enable it for your account. Measured per utterance on telephone calls of 202 real callers: Swiss German (dialect) reported as swiss_german in 92 %, High German of speakers from Germany and Austria as high_german in 88 %; High German spoken by Swiss speakers as swiss_german about half the time.

e.g. {"spoken":"swiss_german","confidence":0.97,"decided":true}
sandboxboolean

Present, and true, only when a sandbox key answered: the transcript is synthetic and nothing was recognised. A real transcript never carries this key.

Variety

Swiss German (dialect) or High German, decided from the audio. Present only when the request sent swiss_german_detection: true and German was heard. Only speech recognised as German is checked: with other languages among the candidates, English, French and Italian are never checked (their finals wait only behind a pending German final, to keep the order). Available on request: write to info@suisse-it.ch and we enable it for your account. Measured per utterance on telephone calls of 202 real callers: Swiss German (dialect) reported as swiss_german in 92 %, High German of speakers from Germany and Austria as high_german in 88 %; High German spoken by Swiss speakers as swiss_german about half the time.

FieldTypeDescription
spokenrequired"swiss_german" | "high_german"

The variety heard, also when undecided (then a lean). High German spoken with a Swiss accent counts as high_german, but is reported as swiss_german about half the time.

confidencerequirednumber

How sure the decision is, from 0.5 to 1.

min 0.5 · max 1
decidedrequiredboolean

false: too little speech (for example a single word such as "ja" or "merci") or unclear audio. lang is then the first German candidate listed, exactly as without detection.

source"utterance" | "call"

Only on a request that sent conversation_id (one decision per call, since 1.8.0). utterance: this result was judged on its own (the call was not decided yet). call: the decision the call already had - at once, not delayed by detection, always decided: true.

Error
FieldTypeDescription
errorrequiredobject
Voice
FieldTypeDescription
idrequiredstring

Value to pass as voice.

e.g. "anna"
namerequiredstring
e.g. "Anna"
genderrequired"female" | "male" | "neutral"
timbrerequiredstring
e.g. "warm"
pacerequiredstring
e.g. "medium"
descriptionrequiredstring
best_forrequiredstring[]
languagesrequiredstring[]
Dialect
FieldTypeDescription
idrequiredstring

Value to pass as dialect.

e.g. "zurich"
namerequiredstring
e.g. "Züridütsch"
regionstring
e.g. "Zürich"
Language
FieldTypeDescription
coderequiredstring
e.g. "de-CH"
namerequiredstring
e.g. "Swiss German"
statusrequired"verified" | "supported"

verified — run and measured in production. supported — accepted by the service, no published quality figure.

dialectsrequiredDialect[]
default_dialectstring
e.g. "standard"
AudioFormat
FieldTypeDescription
idrequired"wav" | "mp3" | "opus" | "pcm16" | "mulaw" | "alaw"
mimerequiredstring
e.g. "audio/mpeg"
extensionrequiredstring
e.g. "mp3"
descriptionstring
sample_rates_hzrequiredinteger[]
default_sample_rate_hzrequiredinteger
e.g. 24000
streamablerequiredboolean
SynthesisRequest
FieldTypeDescription
model"studio" | "swift"

Speech synthesis model:

  • studio (default): State-of-the-art expressive voice engine with authentic Swiss German dialect translation and nuanced prosody.
  • swift: Ultra-low latency engine optimized for real-time conversational streaming and telephony. Which modes each model offers to your key is listed in GET /capabilities (text_to_speech.models[].modes). On an account with one voice in both modes (since 1.7.0, enabled per account) both models stream on /tts/stream, render in batch and speak with the same voices.
default "studio"
textrequiredstring

The text to speak. Rendered verbatim; never interpreted as an instruction.

max 8000 chars
voicestring

Voice id from GET /tts/voices.

default "anna"
languagestring

Language code. de-CH-zurich is accepted as a compact language+dialect form.

default "de-CH"
dialectstring

Variety within the language. GET /capabilities lists the dialects of every language, each with a status (verified or supported). The hand-tuned Swiss German (de-CH) dialects are:

  • standard: supra-regional Swiss German (default).
  • zurich: Zurich dialect (Züritüütsch).
  • bern: Bernese dialect (Bärndütsch).
  • basel: Basel dialect (Baaseldütsch).
  • luzern: Lucerne dialect (Lozärnerdütsch). Both models render the text INTO the dialect, so it may be written in Standard German: swift does it in real time, studio with the highest fidelity for prepared audio. Further de-CH values: hochdeutsch - Swiss High German, spoken word for word - and more regions (stgallen, aargau, graubuenden, wallis, ...). Other languages have accents of their own, for example fr-CH + geneve or en-GB + scottish. standard is accepted with every language and means its standard variety: with en, fr, it or de it is the same as sending no dialect.
default "standard"
speednumber

Speaking rate multiplier (0.5 = half speed, 1.0 = normal pace, 2.0 = double speed). swift applies it exactly. studio has the voice perform it, so the pace changes audibly but not by the exact factor (measured: 1.5 about 1.25 times faster, 0.7 about 0.8 times as fast).

default 1 · min 0.5 · max 2
stylestring

Free-text delivery prompt (up to 600 chars) steering tone, emotion, and pacing. Examples:

  • "freundlich, warm und einladend"
  • "ruhig und geduldig, kurze Pausen nach jedem Satz"
  • "professionell und sachlich, klare Aussprache" Steers delivery only; the spoken content always renders the text verbatim.
max 600 chars
format"wav" | "mp3" | "opus" | "pcm16" | "mulaw" | "alaw"

mp3 is constant bitrate: 96 kbit/s at 44.1 and 48 kHz, 80 kbit/s at 32 kHz.

default "mp3"
sample_rate_hzinteger

Output sample rate. Permitted values depend on format; GET /tts/voices lists them with each format's default (mp3 44100, wav and pcm16 24000, opus 48000, mulaw and alaw 8000).

output"audio" | "json"

audio returns the raw bytes, json a base64 envelope with metadata.

default "audio"
cache"off" | "use" | "refresh"

The key's own cache, for keys for which keeping audio was enabled on written instruction (cache.enabled in GET /capabilities). use: serve the kept rendering of exactly this request, or render and keep it; refresh: render anew and replace it; off: read nothing, keep nothing. Without the field the key's default applies (cache.default_mode, normally off). A value that is not one of these is refused with 400 tts.invalid_request. Without the cache switched on for your key these fields are ignored and responses report "miss".

cache_ttl_sinteger

How long a new rendering is kept, in seconds: from 60 to the key's cache.max_ttl_s (30 days at most); by default cache.default_ttl_s (7 days unless agreed otherwise). With a request served from the cache it sets the expiry anew. Out of range: 400 (only for a key with the cache; ignored on every other key).

min 60 · max 2592000
SynthesisResult
FieldTypeDescription
request_idrequiredstring
voicerequiredstring
e.g. "anna"
languagerequiredstring
e.g. "de-CH"
dialectstring
e.g. "zurich"
speednumber
e.g. 1
charactersrequiredinteger

Characters of spoken text, after markup has been expanded.

passagesinteger

Number of separately rendered passages. 1 for plain text.

cache"miss" | "off" | "hit" | "partial" | "refresh"

miss: no audio is stored for this key (kept for compatibility). For a key that keeps audio of fixed prompts: hit served from the key's cache, partial some passages from it, miss rendered now and kept, refresh rendered anew and kept in place of the earlier rendering, off nothing read or kept for this request.

cache_expires_atstring

Only for a key that keeps audio, when the answer was served from or kept in its cache: when that rendering expires (the soonest, for a document of several passages).

cache_entrystring

Only for a key that keeps audio: the kept rendering, for DELETE /tts/cache/{id}. Opaque.

e.g. "ce_4f1c9a2b7d3e5f60718293a4b5c6d7e8"
cache_entriesstring[]

Instead of cache_entry when the document was made of several passages: their entries in document order.

marksobject[]

Present when the document contained elements.

audiorequiredobject
Job
FieldTypeDescription
idrequiredstring
e.g. "job_50f665b6efff3fbfb6b58fdf"
kindrequired"tts" | "stt"
statusrequired"queued" | "running" | "succeeded" | "failed"
environment"live" | "sandbox"
created_atrequiredstring
started_atstring
finished_atstring
audio_secondsnumber
result_expires_atstring
audioobject

Present once the job has succeeded.

callbackobject

Delivery state, when the job carried a callback_url.

cache"off" | "hit" | "partial" | "miss" | "refresh"

Only for a synthesis job submitted with a key that keeps audio of fixed prompts: what the key's cache did, as POST /tts reports it. With cache_expires_at and cache_entry (or cache_entries).

cache_expires_atstring
cache_entrystring
cache_entriesstring[]
marksobject[]

Present once a synthesis job has succeeded and its document contained elements: the same offsets POST /tts reports.

transcriptobject

Present on a succeeded recognition job read with GET /jobs/{id}, the same shape as POST /stt answers with; the list (GET /jobs) carries has_transcript instead.

has_transcriptboolean

Recognition jobs: whether the transcript is still held (results are kept for 48 hours).

errorobject
Capabilities

Runtime description of the whole service. Generated from the same catalogues the request handlers validate against, so it cannot drift from the implementation.

FieldTypeDescription
apiobject
authenticationobject
text_to_speechobject
speech_to_textobject
limitsobject
capacityobject

Concurrency ceilings this deployment enforces, and how much is in flight.

cacheobject

The calling key's own cache. For a key without it (every key unless enabled on written instruction): enabled false, all counters 0, no audio stored. For a key that keeps audio of fixed prompts: enabled true, the counters of that key, its settings and what it holds (counts and expiries, never texts).

usage_todayobject

Where the calling key stands against its daily budgets, both directions, in UTC days.

errorsobject[]