Docs
Voice agent API quickstart
Speech-to-text, an LLM and text-to-speech through one Speko key, with the response beside every request.
Setup
export SPEKO_API_KEY=sk_live_...
pip install openaiSpeech-to-text
import os
from openai import OpenAI
speko = OpenAI(api_key=os.environ["SPEKO_API_KEY"], base_url="https://api.speko.ai/v1")
with open("sample.wav", "rb") as audio:
out = speko.audio.transcriptions.create(
# "auto" ranks eligible models. To pin a route, use the id from a GET /v1/models row whose routable field is true.
# Russian: pin openai:gpt-4o-transcribe and add language="ru".
model="auto",
file=audio,
)
print(out.text)Response
x-route: Deepgram/nova-3
{"text": "What time do you close on Saturday?"}LLM
import os
from openai import OpenAI
speko = OpenAI(api_key=os.environ["SPEKO_API_KEY"], base_url="https://api.speko.ai/v1")
reply = speko.chat.completions.create(
model="auto",
messages=[{"role": "user", "content": "What time do you close on Saturday?"}],
)
print(reply.choices[0].message.content)Response
x-route: OpenAI/gpt-5-mini
{"object": "chat.completion", "choices": [{"finish_reason": "stop",
"message": {"role": "assistant", "content": "We close at six on Saturday."}}]}Text-to-speech
import os
from openai import OpenAI
speko = OpenAI(api_key=os.environ["SPEKO_API_KEY"], base_url="https://api.speko.ai/v1")
speech = speko.audio.speech.create(
model="auto",
# A preset, not a pin: each provider answers it on its own voice.
voice="alloy",
input="We close at six on Saturday.",
response_format="pcm",
)
speech.write_to_file("reply.pcm")Response
x-route: Cartesia/sonic-3.5
content-type: audio/pcm;rate=24000
reply.pcm - 24 kHz mono PCMRoutes
| POST /v1/transcribe | Transcribe raw audio with cross-provider routing |
|---|---|
| POST /v1/synthesize | Synthesize raw PCM with cross-provider routing |
| POST /v1/audio/transcriptions | Transcribe audio |
| POST /v1/audio/speech | Synthesize speech |
| POST /v1/chat/completions | Create a chat completion |
| GET /v1/models | List measured models |
| GET /v1/routing/preview | Preview a route without upstream traffic |
| POST /v1/realtime/sessions | Mint a 60-second browser credential |
| GET /v1/realtime | Open a speech-to-speech WebSocket |
| GET /v1/transcribe/stream | Open a realtime transcription WebSocket |
| GET /healthz | Check routing readiness |
| GET /v1/synthesize/stream | Open an incremental TTS WebSocket |
| POST /v1/synthesize/stream | Synthesize PCM, chunked as it decodes |
| POST /v1/audio/speech/stream | OpenAI-compatible streaming speech |
Request headers
| X-Speko-Objective | latency, quality, cost, balanced |
|---|---|
| X-Speko-Language | BCP 47 language tag used for benchmark selection and provider transcription. |
| X-Speko-Allow | Comma-separated providers or provider:model ids to allow. |
| X-Speko-Deny | Comma-separated providers or provider:model ids to exclude. |
| X-Speko-Max-Price | Maximum published benchmark price for the request stage: STT in USD per minute, LLM in USD per 1M tokens, or TTS in USD per 1M characters. Candidates without a published price are excluded when this constraint is set. |
| X-Speko-Provider-Options | Provider-specific settings to forward, as a JSON object keyed by provider name: {"deepgram":{"endpointing":1200},"smallest":{"max_words":24}}. At most 4096 bytes. |
Response headers
| X-Route | Serving provider and model on success; last attempted route on terminal upstream failure, or none/none if no route was attempted. |
|---|---|
| X-Route-Reason | objective:score=..;lang=xx(measured|english-proxy|none);model-pin=.. |
| X-Speko-Failover-Count | Recorded failed or circuit-bypassed candidates before success, or before a terminal failure was returned. |
| X-Speko-First-Byte-Ms | Router-observed elapsed time at the selected adapter boundary. Successful direct HTTP routes include the first upstream body frame; buffered or translated adapters can include the completed upstream operation; terminal failures can omit this header. |
Errors
| 400 | invalid_request_body | Fix the body. |
|---|---|---|
| 400 | invalid_routing_header | Fix the named header. |
| 401 | invalid_api_key | Replace the bearer key. |
| 413 | request_too_large | Over the 25 MiB replay limit. |
| 502 | all_upstreams_failed | Every candidate failed. Retry with backoff. |
| 503 | no_candidate | Nothing routable matches. Widen the constraints. |
Agents
Read https://api.speko.ai/openapi.json and wire Speko into this repo: one key for speech-to-text, the LLM and text-to-speech.