Yoob

API reference

Every endpoint of the Yoob voice API. All of them live under https://tts-api.yoob.com/v1 and take an API key as Authorization: Bearer …, except health.

Create speech

POST/v1/audio/speech

Turns text into audio. OpenAI-compatible, with Yoob's options added. See the text to speech guide.

Body (JSON)

inputstringrequired
The text to speak, up to 4,096 characters, with any tags.
voicestringrequired
A voice id from List voices, such as w1243. {"id": "w1243"} is accepted too.
modelstring
yoob-tts-1, the default. Echoed back in x-yoob-model.
qualitystring
best (default), best_all or fast. See Quality.
response_formatstring
wav (default), pcm, mp3, opus, aac or flac. Always 48 kHz mono.
streamboolean
Send the audio chunk by chunk as it is rendered. Default false.
speednumber
0.5 to 2.0, default 1.0.
instructionsstring
Free-text delivery, up to 300 characters.
seedinteger | string
Makes the render reproducible. A string of 1–64 characters of [A-Za-z0-9_.:-] works as well.
pickstring
default or expressive: how best ranks its takes.
request_idstring
Your id for the request, 1–64 characters of [A-Za-z0-9._-], so it can be cancelled.
bash
curl https://tts-api.yoob.com/v1/audio/speech \
  -H "Authorization: Bearer $YOOB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "yoob-tts-1", "voice": "w1243", "input": "[joy] Hello!", "quality": "fast"}' \
  --output hello.wav

Response

The audio, with these headers:

content-type: audio/wav
x-request-id: req_b731ca0a249f49e69c89
x-yoob-seed: 1778055142
x-yoob-quality: fast
x-yoob-sample-rate: 48000
x-yoob-audio-seconds: 1.04
x-yoob-chunks: 1

x-yoob-audio-seconds is sent when the whole file is returned at once, not with stream.

Stream speech (WebSocket)

WS/v1/audio/speech/stream

Voices text that is still being written, one connection per reply. Authenticate with the Authorization header, or put api_key in the config message. See Streaming text in.

You sendWhen
{"voice", "quality", "response_format", ...}First: the config, the same fields as Create speech except input, plus min_words (default 3) and normalize (default true). response_format is pcm (default), mp3 or opus.
{"text": "..."}Text as it is written
{"flush": true}Speak what is buffered now
{"end": true}The end of the text; may also carry text and a flush
You receiveWhen
{"type": "ready", "request_id", "sample_rate": 48000, "seed", ...}After the config
{"type": "chunk", "index", "text", "audio_seconds"}Before each chunk's audio, which follows as binary frames
{"type": "done", "chunks", "audio_seconds", "picks"}All audio sent; the socket closes with 1000
{"type": "error", "error": {"message", "code"}}Then the socket closes

The binary frames of all chunks together form one continuous stream. 30 s with no message counts as the end; at most 20,000 characters per connection; closing the socket cancels the reply.

Error codeCloseWhen
invalid_api_key1008Missing or wrong key
bad_config1008The config is invalid
bad_message1008An invalid message, or over the character cap
engine_loading1013The engine is starting; retry shortly
busy1013Too many streams are open
synthesis_failed1011Rendering failed

Cancel speech

POST/v1/audio/speech/cancel

Stops a running speech request by the request_id you gave it, as a disconnect would but at once. Idempotent. A request cancelled before it starts streaming answers 409 request_cancelled.

Body (JSON)

request_idstring
One id to cancel.
request_idsstring[]
Or up to 64 of them.
json
{"cancelled": true}                           // running, and now cancelled
{"cancelled": false}                          // no running request has that id
{"cancelled": {"reply-41": false, "reply-42": true}}   // for request_ids

List voices

GET/v1/voices
json
{
  "object": "list",
  "data": [
    {"id": "w1243", "object": "voice", "gender": "female", "tier": "product", "description": "Woman, mid twenties, American, mid-range pitch; breathy, intimate, hushed. ..."},
    ...
  ]
}

List models

GET/v1/models

Also GET /v1/models/yoob-tts-1 for one model.

json
{"object": "list", "data": [
  {"id": "yoob-tts-1", "object": "model", "created": 1760745600, "owned_by": "yoob"},
  {"id": "yoob-asr-1", "object": "model", "created": 1760745600, "owned_by": "yoob"}
]}

Create transcription

POST/v1/audio/transcriptions

Transcribes audio in any common format, at most 25 MB: up to 60 minutes with yoob-asr-2, up to 60 seconds with yoob-asr-1. See the speech to text guide and emotion recognition.

Multipart form (as the OpenAI SDKs send it)

fileformrequired
The audio file.
modelform
yoob-asr-2 (most accurate, text only) or yoob-asr-1 (the default; adds emotion, tags and the voice profile in verbose_json).
response_formatform
json (default), text or verbose_json.

Or a raw body

Content-Typeheaderrequired
The audio's type, such as audio/wav or audio/mpeg. The body is the file.
modelquery
As above.
response_formatquery
As above.
bash
curl "https://tts-api.yoob.com/v1/audio/transcriptions?response_format=json" \
  -H "Authorization: Bearer $YOOB_API_KEY" \
  -H "Content-Type: audio/wav" \
  --data-binary @clip.wav

Response

json
{"text": "We still got some work to do and I look forward to getting that work done."}

verbose_json adds caption, segments (with caption, tags, emotion, emotion_probs, words, voice and p_human), words, duration and speaker. A full example is in the guide.

Stream transcription (WebSocket)

WS/v1/audio/transcriptions/stream

Live speech to text, up to 60 s of audio per session. Authenticate with the Authorization header, or send {"type": "auth", "api_key": "..."} as the first message. See Realtime.

wss://tts-api.yoob.com/v1/audio/transcriptions/stream
You sendWhat
Binary framesRaw PCM: 16 kHz, signed 16-bit little-endian, mono. Even length, at most 1 s (32,000 bytes) each
{"type": "ping", "t": <number>}Answered with {"type": "pong", "t", "stream_t"}
{"type": "end"}End the current utterance now; the session stays open
{"type": "stop"}Finish: the last utterance is finalised, then done, then the socket closes

Anything else ends the session with an error (bad_message, or bad_frame for audio of the wrong shape). Events sent back are listed in Realtime events.

json
{"type": "ready", "id": "34f8d689b6b7", "sample_rate": 16000, "max_seconds": 60.0, "partial_every": 0.3}
{"type": "vad", "speech": true, "t": 0.064, "utt": 1}
{"type": "partial", "utt": 1, "start": 0.0, "end": 0.32, "caption": "[emo:neutral] Hello?", "text": "Hello?", "emotion": "neutral", ...}
{"type": "final", "utt": 1, "reason": "silence", "caption": "[emo:joy] Hello there. I am so happy you came today.", ...}
{"type": "final_meta", "utt": 1, "voice": {"f0_hz": 237.9, "wpm": 234.9, "snr_db": 56.9}, "p_human": 0.0002}
{"type": "speaker", "seconds": 4.5, "gender": {"m": 0.0069, "f": 0.9931, "o": 0}, "accent": {"us": 0.9953, ...}, ...}
{"type": "done"}

Duotone call (WebSocket)

WS/v1/duotone

A live voice call you can interrupt, one connection per call. Authenticate with the Authorization header, or put api_key in the config. See Use the Duotone socket.

You sendWhen
{"voice", "quality", "greeting", "instructions", "history", "llm"}First, within 10 s: the config. Every field is optional; see the config fields.
binary framesThe caller's microphone: s16le mono 16 kHz, at most 1 s each, for the whole call
{"type": "text", "text": "..."}A typed turn; cuts in like speech
{"type": "stop"}Hang up
You receiveWhen
{"type": "ready", "session_id", "sample_rate": 48000, "input_sample_rate": 16000, "max_seconds": 600}After the config
{"type": "user", "final", "caption", "text", "emotion"}What the caller is saying, live; final at the end of each turn
{"type": "reply", "id", "delta"}The reply as it is written, voice tags included
binary framesThe reply's audio: s16le mono 48 kHz
{"type": "reply_done", "id", "text", "interrupted"}The reply is complete, or was cut off by the caller
{"type": "error", "code", "message"}A failed turn (the call goes on), or a refused call (the socket then closes)
{"type": "done", "usage"}The call has ended; the socket closes with 1000
json
{"type": "done", "usage": {"listened_seconds": 41.2, "spoken_characters": 612,
  "llm_input_tokens": 5120, "llm_output_tokens": 186, "llm": "yoob"}}

llm is yoob when Yoob's model wrote the replies, and yours when the config brought one; its tokens are then not billed. Calls end after 600 s.

Error codeCloseWhen
bad_config1008No config within 10 s, or it is not a JSON object
invalid_api_key1008Missing or wrong key
insufficient_credits1008The key's workspace is out of credits
engine_loading1013The voice engine is starting; retry shortly
busy1013Every live session is in use; retry shortly
not_configured1011No language model is available; bring your own in llm
bad_frame–An audio frame of an odd length or over 1 s; the call goes on
reply_failed–One reply failed; the call goes on
internal_error1011The call failed

Health

GET/health

No key needed, and not under /v1: https://tts-api.yoob.com/health. Answers 200 when the service is ready and 503 otherwise.

json
{"status": "ok", "model": "yoob-tts-1", "queue": {"running": 0, "waiting": 0, "max_waiting": 16, "streams": 0, "max_streams": 16}}

Errors

Errors use OpenAI's shape. param names the field at fault, when there is one.

json
{"error": {"message": "'input' is required and must be a non-empty string.", "type": "invalid_request_error", "param": "input", "code": null}}
StatuscodeWhen
400null, input_too_long, invalid_voiceA missing or bad field; see param
401missing_api_key, invalid_api_keyNo key, or one that is wrong or revoked
404not_found, model_not_foundUnknown endpoint or model
409request_id_in_use, request_cancelledA request_id already running, or cancelled
429rate_limited, queue_full, busyToo many requests, sessions or waiting renders; retry after Retry-After
500synthesis_failed, internal_errorThe engine failed. A stream that fails part-way just ends early
503engine_loadingRight after a restart, for about 30 s

Rate limits

LimitValue
Requests300 a minute per key and per IP address, with bursts of up to 50 over that
SocketsEach WebSocket counts once, however much is sent over it
Realtime transcriptionShared capacity; a session over it is refused with busy

A 429 carries a Retry-After header in seconds; wait that long before retrying. For conversations, prefer the speech socket over one request per sentence: it counts once per reply.