Jobs and callbacks
https://api.suisse-speech.ch/v1 · X-API-KeyBatch and realtime both hold a connection for the length of the work. That is right for a sentence and wrong for a chapter: the caller waits, every proxy in between gets a chance to time the request out, and a dropped socket throws away work that was nearly finished. Submit a job instead.
Synthesis jobs
# submit: answers in milliseconds
curl -X POST https://api.suisse-speech.ch/v1/jobs \
-H "X-API-Key: $SUISSE_SPEECH_KEY" -H 'Content-Type: application/json' \
-d '{"kind":"tts","text":"…long text…","voice":"lea","language":"de-CH","format":"mp3"}'
# → 202 { "id": "job_…", "status": "queued" } Location: /api/v1/jobs/job_…
curl -H "X-API-Key: $SUISSE_SPEECH_KEY" https://api.suisse-speech.ch/v1/jobs/job_…
# → { "status": "succeeded", "audio_seconds": 8.9,
# "audio": { "url": "/api/v1/jobs/job_…/audio", "byte_length": 106580 } }
curl -H "X-API-Key: $SUISSE_SPEECH_KEY" https://api.suisse-speech.ch/v1/jobs/job_…/audio --output out.mp3
Links in responses start with /api/v1; on api.suisse-speech.ch that prefix
works exactly like /v1.
status is queued, running, succeeded or failed. Poll the job URL; a
few seconds between polls is plenty and the rate limit is generous enough not to
be the thing you have to think about.
Everything is validated when you submit, not when the job runs: an unknown
voice or malformed markup comes back as a 400 while you are still listening,
not an hour later in a status field.
Results are kept for 48 hours, then the audio is deleted and the job row remains as history. Up to 200 jobs may be queued at once per key.
Jobs are metered exactly like a synchronous request, and they queue behind interactive traffic on purpose: a large batch will never delay a realtime session.
Recognition jobs
Long recordings go the same way, as a multipart upload:
curl -X POST https://api.suisse-speech.ch/v1/jobs \
-H "X-API-Key: $SUISSE_SPEECH_KEY" \
-F kind=stt -F audio=@interview.wav -F lang=de-CH
# → 202 { "id": "job_…", "status": "queued" }
curl -H "X-API-Key: $SUISSE_SPEECH_KEY" https://api.suisse-speech.ch/v1/jobs/job_…
# → { "status": "succeeded", "audio_seconds": 3612.4,
# "transcript": { "text": "…", "words": [ … ] } }
This runs on a batch-optimised path measured at roughly a third of real time
(an hour of audio in about twenty minutes) rather than the path used by
POST /stt, which runs at close to the length of the recording. Use
POST /stt for short clips where a single round trip is simpler, and a job for
anything long.
Uploads up to 200 MB. The recording is deleted once the job finishes, on the failure path as well as on success.
Callbacks
Rather than polling, give the job a callback_url and we will POST to it when
the job reaches a terminal state:
{ "kind": "tts", "text": "…", "callback_url": "https://your.example/hooks/speech" }
{ "event": "job.succeeded",
"job": { "id": "job_…", "status": "succeeded", "audio_seconds": 8.9, "audio": { … } },
"sent_at": "2026-09-03T21:40:11.204Z" }
Verify the signature. Every delivery carries:
X-Suisse-Webhook-Timestamp: 1788470411
X-Suisse-Webhook-Signature: sha256=<hex>
where the signature is HMAC_SHA256(secret, "<timestamp>.<raw body>"). Compare
in constant time against the raw body, before parsing it, and reject a
timestamp more than a few minutes old: the timestamp is inside the signed
material precisely so a captured delivery cannot be replayed at you later. Ask
us for your signing secret.
Requirements on the URL, all checked when you submit rather than when we
deliver, so a mistake is a 400 you see immediately:
httpsonly;- it must resolve to a public address: loopback, private, link-local, carrier-grade NAT and cloud metadata ranges are refused;
- no credentials in the URL;
- redirects are not followed.
Four attempts with exponential backoff, ten-second timeout. A 4xx that is not
408 or 429 is treated as final and not retried.
A callback is a courtesy, never the source of truth. If every delivery
fails, the job is still complete and still collectable from the job endpoint,
which is what your reconciliation should read. GET /jobs/{id} reports the
delivery state under callback.