{"openapi":"3.1.0","info":{"title":"latency.cam — Indic Voice AI API","version":"0.1.0","summary":"22 Indian languages understood, 19 speakable across 38 voices, 42 detectable, 23 translatable. One API key, no rate limits.","description":"Speech to text, text to speech, spoken language identification, translation and an\nOpenAI-compatible LLM for Indian languages. Built for live phone calls at 8kHz.\n\nSTATUS: alpha, deployed and serving real inference. Fourteen HTTP endpoints and one\nwebsocket are live. Only /v1/clone returns 501, with an explanation.\n\nFOR LIVE CALLS, USE THE WEBSOCKET, NOT THESE REST ENDPOINTS:\n  wss://api.latency.cam/ws/stream?api_key=YOUR_KEY\nIt is a complete voice agent — voice activity detection, turn taking, transcription,\nthe LLM, filler injection, speech synthesis and barge-in all run server side. The REST\nendpoints below are the building blocks, useful for batch work and custom pipelines.\nThe protocol is documented at https://latency.cam/docs/streaming and in full at\nhttps://latency.cam/llms-full.txt\n\nMEASURED LATENCY, not estimated. Per turn on the live GPU (AWS g5.2xlarge, A10G):\ntranscription 166ms, LLM 387ms, time-to-first-audio 793ms. A cached filler phrase\nplays 1ms after the caller stops speaking, so perceived wait is ~1ms against a real\nreply at ~1.35s. Add ~238ms round trip if your callers are in India; the box is in\nVirginia.\n\nNO RATE LIMITS. Verified by audit: 216 requests including 30-way parallel bursts\nreturned zero 429s and no rate-limit headers. Requests queue; they are never rejected.\n\nThe GPU is switched off when not in use. GET /health needs no key; if it does not\nanswer, turn the box on at https://latency.cam and allow about 3 minutes.","contact":{"url":"https://latency.cam"}},"x-websocket":[{"url":"wss://api.latency.cam/ws/stream","summary":"Full-duplex live call","description":"One websocket per phone call. Push the caller audio in 20ms frames and play whatever comes back. Speech detection, turn taking, transcription, the LLM, filler injection, synthesis and barge-in all happen server side, so a working voice agent is a websocket and an audio pipe — not an orchestration layer you have to build. This is the endpoint to use for calls; the REST endpoints are the building blocks under it.","auth":"api_key query parameter, because browsers cannot set headers on a WebSocket","measured":"Per turn over wss from inside AWS: STT 166ms, LLM 387ms, time-to-first-audio 793ms, turn total ~4.0s for a full sentence reply. Perceived wait is 1ms because a cached filler plays the instant the caller stops. Barge-in cuts the bot off 200ms after the caller starts talking.","configureFrame":{"language":"Caller language, e.g. hi, ta, te, bn, en-IN. Sent in the configure frame.","script":"Script for TTS input. Hindi sounds best from Roman text (\"aapka order\"); other languages use native.","voice":"Exact voice name, e.g. \"Tamil (Male)\". Omit for the default female voice of the language.","llm":"nova-lite, nova-pro, or claude-haiku-4-5.","stt_model":"Omit this. The default detects the caller language itself and is the fastest option.","allowed_languages":"Restrict which languages the agent will switch INTO, e.g. [\"hi\",\"ta\",\"te\"]. The single biggest accuracy lever: detection errors cluster among closely related languages, and naming the two or three your callers actually use removes that whole class of mistake. Empty means any language we can speak.","system_prompt":"Your agent instructions. Ask for replies under about 15 words; long replies sound robotic on a call.","audio_format":"mulaw_8000 is what telephony gives you. Applies to audio in BOTH directions.","silence_ms":"Silence that ends a caller turn. Lower feels snappier but clips people who pause mid-sentence.","min_speech_ms":"Ignore blips shorter than this, so a cough does not open a turn.","interrupt_min_words":"Roughly how much speech is needed to interrupt the bot. 0 means any sound cuts it off. Keep it at 3 or more for Indian callers, who backchannel with \"haan\" and \"ji\" constantly.","use_fillers":"Play a cached acknowledgement the moment a turn ends. This is what removes the perceived wait. Leave it on.","auto_language_detect":"Follow the caller when they change language mid-call. ON by default now: the default speech-to-text model identified the language correctly 44/44 times on real human speech with no language code, at both 16kHz and 8kHz. Detection is free — the model writes what it hears and the script IS the language — so this costs nothing. Turn it off to pin the reply to the configured language.","greeting":"Play a cached greeting on connect.","normalize_numbers":"Rewrite digits as spoken words before synthesis. Leave it on or \"15250\" gets read wrong."},"protocol":["PROTOCOL: send {\"type\":\"configure\",...} first. Audio sent before configure is refused.","Then send binary frames of 20ms audio (160 bytes for 8kHz mulaw). Send them in real time; do not dump a whole file at once or turn detection will see one enormous utterance.","CONTROL FRAMES you can send: configure, say (make the bot speak arbitrary text — use it for opening lines or reading out a CRM lookup), reset (clear conversation history), ping, hangup.","EVENTS you will receive, in order for a normal turn: configured, speech_started, turn_end, filler_played, transcript.final, llm.reply, bot_audio_started, bot_audio_done, turn_complete.","BARGE-IN events: interrupted (the caller took the turn) then speech_abandoned (synthesis for the old turn stopped). Discard any audio you have buffered for the abandoned turn.","OTHER EVENTS: turn_empty (no speech recognised, nothing sent to the LLM), language_detected, error, goodbye, pong.","Every event carries a \"turn\" number. Audio and events for a turn that has been interrupted must be dropped — that is what the number is for.","MID-CALL LANGUAGE SWITCHING WORKS AND IS ON BY DEFAULT. A caller can start in Telugu, switch to Hindi, then to Tamil, and the agent follows each time. Verified on live calls. `language` is still worth setting as the starting language and as a tie-break between Hindi and Marathi, which share a script.","Set `allowed_languages` to the two or three your callers actually use. It is the cheapest accuracy win available: a detection outside that set is reported but ignored, which neutralises the errors that cluster among closely related languages.","If a detected language has no voice we keep the configured language instead of failing the turn, and the language_detected event says exactly why it did not switch.","The server decides when a turn ended, not you. That keeps the protocol simple and means a naive client cannot break turn taking.","Voice activity detection is energy based and adapts to the line noise in the first ~300ms of the call. It is good at turn taking and not good at distinguishing speech from sustained background noise. Silero would be better and is a known gap.","There is no streaming partial transcript. Whisper transcribes complete utterances, so transcript.final arrives once per turn. Adding partials would save roughly 70ms of a ~1500ms turn, which is why it has not been prioritised."],"errors":[{"code":1008,"when":"Bad or missing api_key","fix":"Pass ?api_key=sk-lat-... in the websocket URL. The server sends a JSON error frame explaining this before closing."},{"code":0,"when":"error event with stage=\"stt\"|\"llm\"|\"tts\"","fix":"The call stays open and the turn is abandoned. Read the message field; it carries the real exception. Play a fallback line to the caller."}],"example":"# Websockets are not curl-able. Minimal Python client:\nimport asyncio, json, websockets\n\nasync def call():\n    url = \"wss://api.latency.cam/ws/stream?api_key=\" + KEY\n    async with websockets.connect(url, max_size=None) as ws:\n        await ws.send(json.dumps({\n            \"type\": \"configure\",\n            \"language\": \"hi\", \"script\": \"roman\",\n            \"audio_format\": \"mulaw_8000\",\n            \"system_prompt\": \"You are a delivery helpdesk. Reply in Hindi, under 12 words.\",\n        }))\n        async def send_caller_audio():\n            # 160-byte frames of 8kHz mulaw, one every 20ms\n            for frame in caller_frames():\n                await ws.send(frame)\n                await asyncio.sleep(0.02)\n        asyncio.create_task(send_caller_audio())\n        async for msg in ws:\n            if isinstance(msg, bytes):\n                play_to_caller(msg)       # raw audio, no header\n            else:\n                print(json.loads(msg))    # events\n\nasyncio.run(call())"}],"servers":[{"url":"https://api.latency.cam","description":"Audio and LLM API (runs on the GPU box)"},{"url":"https://latency.cam","description":"Docs, catalog and GPU control (Vercel, always up)"}],"tags":[{"name":"api","description":"Audio, text and LLM endpoints. Require an API key."},{"name":"streaming","description":"Websocket for live calls. Connects directly to the GPU box, bypassing Vercel."},{"name":"control","description":"Status and GPU power. No API key needed."}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Your API key. One header, no rate limits, every endpoint."}},"schemas":{"Model":{"type":"object","description":"An entry from the model catalog.","properties":{"id":{"type":"string"},"task":{"type":"string","enum":["tts","stt","lid","llm","translate","vad","turn"]},"license":{"type":"string"},"licenseRisk":{"type":"string","enum":["clear","caution","blocker"]},"languages":{"type":"array","items":{"type":"string"}},"streaming":{"type":"boolean","description":"False means it cannot be used in a live call."},"caveats":{"type":"array","items":{"type":"string"}}}}}},"security":[{"bearerAuth":[]}],"paths":{"/v1/audio/speech":{"post":{"operationId":"v1_audio_speech","summary":"Text to speech","description":"Synthesise speech in 19 languages across 38 voices. Set stream=true for live calls: audio arrives in chunks as it is generated, so playback starts long before synthesis finishes.\n- NOTE: Emotion tags go at the END of the text: \"Aapka payment ho gaya <happy>\".\n- NOTE: All 38 voices are verified working — every one was synthesised and quality checked in the audit.\n- NOTE: The model occasionally runs away and produces overlong audio. A length bound and a quality check catch it, and X-Quality-Check reports the result. On a stream, a degenerate generation falls back to a non-streaming retry automatically.\n- NOTE: Repeated short phrases should come from the filler cache instead: 1ms rather than ~2.5s. See GET /v1/fillers.","tags":["api"],"x-status":"alpha","x-curl-example":"curl -X POST https://api.latency.cam/v1/audio/speech \\\n  -H \"Authorization: Bearer $LATENCY_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"input\": \"Aapka EMI 15250 rupay ka hai, due date 4 tarikh hai.\",\n    \"language\": \"hi\",\n    \"script\": \"roman\",\n    \"format\": \"mulaw_8000\",\n    \"stream\": true\n  }' --output reply.raw","responses":{"200":{"description":"Raw audio in the requested format. Response headers carry X-Model-Used, X-Generate-Ms, X-Quality-Check and X-Cache."},"401":{"description":"Missing or invalid API key."},"503":{"description":"GPU is stopped. Check GET /api/status and turn it on at https://latency.cam.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"gpu_offline"},"message":{"type":"string"},"turn_on":{"type":"string","example":"https://latency.cam"}}}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"input":{"type":"string","description":"Text to speak. Keep replies under ~15 words on a phone call."},"language":{"type":"string","description":"Language code, e.g. hi, ta, te, bn, en. See GET /v1/models."},"script":{"type":"string","description":"For Hindi, Roman input (\"aapka order aa gaya\") is noticeably better than Devanagari. Other languages use native script.","default":"native","enum":["roman","native"]},"voice":{"type":"string","description":"Exact voice string from GET /v1/models, e.g. \"Telugu (Male)\". Omit and the female voice for that language is used."},"model":{"type":"string","description":"Only svara-tts-v1 exists today. Omit it.","default":"svara-tts-v1"},"emotion":{"type":"string","description":"Emotion tag. Also settable inline by putting the tag at the END of input."},"format":{"type":"string","description":"mulaw_8000 for telephony — the downsampling is done here.","default":"pcm_24000","enum":["pcm_16000","pcm_24000","mulaw_8000","wav"]},"stream":{"type":"boolean","description":"Chunked audio. Required for live calls.","default":false},"temperature":{"type":"number","description":"Lower is more consistent. 0.4 is good for fixed phrases.","default":0.6},"normalize":{"type":"boolean","description":"Rewrite digits as spoken words first. Leave it on.","default":true}},"required":["input","language"]}}}}}},"/v1/audio/transcriptions":{"post":{"operationId":"v1_audio_transcriptions","summary":"Speech to text","description":"Transcribe caller audio. Accepts wav, mp3, flac, ogg and raw mulaw or PCM. Optionally detects the language first.\n- NOTE: You do not need to pass a language. The default model has a unified multi-script vocabulary, so it writes what it hears and the script it writes in IS the language — detection is a by-product of transcription and costs nothing.\n- NOTE: detection_source tells you where the language came from: \"transcriber\" means the model worked it out, which is the accurate path.\n- NOTE: script and candidate_languages exist because the ambiguity is real, not hidden: Hindi and Marathi share Devanagari, so a Devanagari transcript returns both. Pass `language` to break the tie.\n- NOTE: For Hindi or Telugu specifically, whisper-hindi-large-v2 and whisper-telugu-large-v2 are about twice as accurate (Hindi WER 0.047 vs 0.104) but roughly 15x slower. Use them for transcription you are not waiting on.\n- NOTE: Those two are single-language models. Send them anything else and you get nonsense — measured WER ~1.0 on the wrong language.\n- NOTE: The optional large models load on demand and may evict each other, because vLLM permanently holds ~12.6GB of the 23GB GPU. The first call to one of them pays the load time.\n- NOTE: Audio under 1 second transcribes poorly. 2 seconds or more is much better.","tags":["api"],"x-status":"alpha","x-curl-example":"curl -X POST https://api.latency.cam/v1/audio/transcriptions \\\n  -H \"Authorization: Bearer $LATENCY_API_KEY\" \\\n  -F file=@call.wav \\\n  -F language=te","responses":{"200":{"description":"{ text, model_used, inference_ms, engine_ms, language, requested_language, detected_language, script, candidate_languages, detection_source, language_probability, duration_s }"},"401":{"description":"Missing or invalid API key."},"503":{"description":"GPU is stopped. Check GET /api/status and turn it on at https://latency.cam.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"gpu_offline"},"message":{"type":"string"},"turn_on":{"type":"string","example":"https://latency.cam"}}}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"file":{"type":"string","description":"Audio file. Multipart form upload."},"language":{"type":"string","description":"Optional. With the default model you do NOT need this — it works the language out itself. Used only as a tie-break between languages that share a script (Hindi vs Marathi)."},"model":{"type":"string","description":"See GET /v1/models. The default detects the language itself and is the fastest.","default":"vaanicall"},"sample_rate":{"type":"string","description":"Required only for raw headerless audio."},"detect_language":{"type":"boolean","description":"Run language ID first and report it alongside the transcript.","default":false}},"required":["file"]}}}}}},"/v1/translate":{"post":{"operationId":"v1_translate","summary":"Translate text across 23 languages","description":"IndicTrans2 distilled 200M, running on CPU so it never competes with the GPU that serves your calls. English to any Indian language and back, plus Indic to Indic.\n- NOTE: Indic to Indic pivots through English, which is an extra hop and shows as pivoted_through_english: true. The dedicated one-hop model is gated on Hugging Face.\n- NOTE: Text only. To translate speech: POST /v1/audio/transcriptions, then this, then POST /v1/audio/speech.\n- NOTE: Inference is serialised per worker process on purpose. Running two translations in one process deadlocked inside torch; queueing is both correct and faster. queued_ms tells you how long you waited for a slot.\n- NOTE: Nothing is rejected under load. There is no queue-depth limit and no 429.","tags":["api"],"x-status":"alpha","x-curl-example":"curl -X POST https://api.latency.cam/v1/translate \\\n  -H \"Authorization: Bearer $LATENCY_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"text\": \"Your order will arrive tomorrow between 10 am and 12 pm.\",\n    \"source_language\": \"en\",\n    \"target_language\": \"ta\"\n  }'","responses":{"200":{"description":"{ translations[], source_language, target_language, source_tag, target_tag, pivoted_through_english, model, latency_ms, queued_ms, inference_ms, backend }"},"401":{"description":"Missing or invalid API key."},"503":{"description":"GPU is stopped. Check GET /api/status and turn it on at https://latency.cam.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"gpu_offline"},"message":{"type":"string"},"turn_on":{"type":"string","example":"https://latency.cam"}}}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string","description":"One string, or an array to translate as a batch. Batching is much faster per item."},"source_language":{"type":"string","description":"e.g. en, hi, ta, te. See GET /v1/translate/languages."},"target_language":{"type":"string","description":"e.g. hi, ta, te, en."},"num_beams":{"type":"string","description":"1 is about twice as fast with slightly worse output.","default":5},"backend":{"type":"string","description":"indictrans2 is more faithful and does not paraphrase. llm falls back to Bedrock and needs no local model.","default":"indictrans2","enum":["indictrans2","llm"]},"llm_model":{"type":"string","description":"Only used when backend=llm.","default":"nova-lite"}},"required":["text","source_language","target_language"]}}}}}},"/v1/translate/languages":{"get":{"operationId":"v1_translate_languages","summary":"Which languages translation supports","description":"The 23 codes accepted by /v1/translate, and which directions are direct versus pivoted.","tags":["api"],"x-status":"alpha","x-curl-example":"curl https://api.latency.cam/v1/translate/languages -H \"Authorization: Bearer $LATENCY_API_KEY\"","responses":{"200":{"description":"{ languages[], count, directions[], backends{} }"},"401":{"description":"Missing or invalid API key."},"503":{"description":"GPU is stopped. Check GET /api/status and turn it on at https://latency.cam.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"gpu_offline"},"message":{"type":"string"},"turn_on":{"type":"string","example":"https://latency.cam"}}}}}}},"parameters":[]}},"/v1/lid":{"post":{"operationId":"v1_lid","summary":"Which Indian language is this","description":"Spoken language identification across 42 Indian languages, using Vaani-LID. Use it when you do not know what the caller will speak.\n- NOTE: Telugu against Tamil is reliable. Hindi against Urdu is not, because they are near-identical spoken.\n- NOTE: Under 1 second of audio returns a warning field; treat the result as a guess.\n- NOTE: The model was trained on 16kHz. It still works on 8kHz telephony audio but accuracy drops.","tags":["api"],"x-status":"alpha","x-curl-example":"curl -X POST https://api.latency.cam/v1/lid \\\n  -H \"Authorization: Bearer $LATENCY_API_KEY\" \\\n  -F file=@caller.wav \\\n  -F 'restrict_to=[\"ta\",\"te\",\"kn\",\"ml\"]'","responses":{"200":{"description":"{ candidates: [{ language, confidence }], duration_s, warning? }"},"401":{"description":"Missing or invalid API key."},"503":{"description":"GPU is stopped. Check GET /api/status and turn it on at https://latency.cam.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"gpu_offline"},"message":{"type":"string"},"turn_on":{"type":"string","example":"https://latency.cam"}}}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"file":{"type":"string","description":"Audio. 2 seconds or more is much more reliable than 1."},"top_k":{"type":"string","description":"How many candidates to return.","default":3},"restrict_to":{"type":"string","description":"JSON array or comma list of candidate codes. This is the single biggest accuracy lever — narrow it whenever you can."},"sample_rate":{"type":"string","description":"Required for raw headerless audio."}},"required":["file"]}}}}}},"/v1/chat/completions":{"post":{"operationId":"v1_chat_completions","summary":"LLM, OpenAI compatible","description":"Point any OpenAI SDK at https://api.latency.cam/v1 and it works. Backed by Amazon Bedrock.\n- NOTE: claude-haiku-4-5 needs the Anthropic use-case form submitted for this AWS account in the Bedrock console. Until then Bedrock returns ResourceNotFoundException and this endpoint reports it verbatim.\n- NOTE: stream is accepted but not yet implemented; the reply arrives whole.\n- NOTE: Nova handles Indian languages well enough for call replies, and you can always ask it to reply in the caller language in your system prompt.","tags":["api"],"x-status":"alpha","x-curl-example":"curl -X POST https://api.latency.cam/v1/chat/completions \\\n  -H \"Authorization: Bearer $LATENCY_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"model\": \"nova-lite\",\n    \"max_tokens\": 60,\n    \"messages\": [\n      {\"role\": \"system\", \"content\": \"Reply in Hindi, under 12 words.\"},\n      {\"role\": \"user\", \"content\": \"Mera order kab aayega?\"}\n    ]\n  }'","responses":{"200":{"description":"An OpenAI chat.completion object, plus latency_ms."},"401":{"description":"Missing or invalid API key."},"503":{"description":"GPU is stopped. Check GET /api/status and turn it on at https://latency.cam.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"gpu_offline"},"message":{"type":"string"},"turn_on":{"type":"string","example":"https://latency.cam"}}}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"model":{"type":"string","description":"nova-lite is the fastest and the right default for calls.","default":"nova-lite","enum":["nova-lite","nova-pro","claude-haiku-4-5"]},"messages":{"type":"string","description":"Standard OpenAI message list. system, user and assistant roles."},"max_tokens":{"type":"string","description":"Keep it low. Long replies sound robotic on a call.","default":150},"temperature":{"type":"number","description":"Standard.","default":0.7}},"required":["messages"]}}}}}},"/v1/normalize":{"post":{"operationId":"v1_normalize","summary":"Make numbers speakable","description":"Rewrites digits, currency and dates into words with Indian grouping, so TTS says them correctly. Called automatically by /v1/audio/speech; exposed separately so you can inspect or override it.\n- NOTE: Use style=digits for account and reference numbers. \"spoken\" would turn 4021 into \"four thousand twenty one\", which is wrong for an account number.\n- NOTE: coverage tells you what fraction of the numerals it recognised. Below 1.0 means something was left alone.","tags":["api"],"x-status":"alpha","x-curl-example":"curl -X POST https://api.latency.cam/v1/normalize \\\n  -H \"Authorization: Bearer $LATENCY_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"text\": \"Rs 15,250 due on 4/11\", \"language\": \"hi\", \"script\": \"roman\"}'","responses":{"200":{"description":"{ text, coverage, replacements[] }"},"401":{"description":"Missing or invalid API key."},"503":{"description":"GPU is stopped. Check GET /api/status and turn it on at https://latency.cam.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"gpu_offline"},"message":{"type":"string"},"turn_on":{"type":"string","example":"https://latency.cam"}}}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string","description":"Text containing numerals."},"language":{"type":"string","description":"Target language.","default":"hi"},"style":{"type":"string","description":"spoken for quantities (\"pandrah hazaar\"), digits for account numbers read one by one, grouped for phone numbers read in pairs.","default":"spoken","enum":["spoken","digits","grouped"]},"script":{"type":"string","description":"Output script.","default":"native","enum":["roman","native"]}},"required":["text"]}}}}}},"/v1/fillers":{"get":{"operationId":"v1_fillers","summary":"Pre-rendered phrases, served in about 1ms","description":"Short acknowledgements rendered ahead of time and cached on disk. Play one the instant the caller stops speaking, then stream the real reply behind it. This does not reduce measured latency at all — it removes the silence the caller would otherwise sit through, which is the thing that actually makes a bot feel broken.\n- NOTE: Categories: ack, thinking, confirm, greeting.\n- NOTE: GET /v1/fillers/pick/{language}?category=ack picks one for you, with a fallback chain so you never get silence.\n- NOTE: GET /v1/fillers/{id}?format=mulaw_8000 returns the audio.\n- NOTE: POST /v1/fillers/generate renders any that are missing.\n- NOTE: /v1/audio/speech also checks this cache first: if your text matches a cached phrase you get it in 2ms with X-Cache: HIT.","tags":["api"],"x-status":"alpha","x-curl-example":"curl https://api.latency.cam/v1/fillers -H \"Authorization: Bearer $LATENCY_API_KEY\"\n\n# pick one and play it the moment the caller stops talking\ncurl \"https://api.latency.cam/v1/fillers/pick/hi?category=ack\" \\\n  -H \"Authorization: Bearer $LATENCY_API_KEY\"","responses":{"200":{"description":"{ fillers[], categories, languages, _how_to_use }"},"401":{"description":"Missing or invalid API key."},"503":{"description":"GPU is stopped. Check GET /api/status and turn it on at https://latency.cam.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"gpu_offline"},"message":{"type":"string"},"turn_on":{"type":"string","example":"https://latency.cam"}}}}}}},"parameters":[]}},"/v1/models":{"get":{"operationId":"v1_models","summary":"Everything available, with the caveats","description":"The full catalog: STT models with measured error rates, the TTS model with all 38 voices and valid emotion tags, and the LID model.\n- NOTE: For STT entries, a \"languages\" list with one item means the model ONLY decodes that language.\n- NOTE: realtime_suitable marks the one STT model fast enough for a live call.\n- NOTE: gated: true means the weights need an HF_TOKEN whose account accepted the licence. indicconformer-600m is the only gated entry.","tags":["api"],"x-status":"alpha","x-curl-example":"curl https://api.latency.cam/v1/models -H \"Authorization: Bearer $LATENCY_API_KEY\"","responses":{"200":{"description":"{ stt[], tts[], lid[], _read_this_first }"},"401":{"description":"Missing or invalid API key."},"503":{"description":"GPU is stopped. Check GET /api/status and turn it on at https://latency.cam.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"gpu_offline"},"message":{"type":"string"},"turn_on":{"type":"string","example":"https://latency.cam"}}}}}}},"parameters":[]}},"/health":{"get":{"operationId":"health","summary":"Readiness, no key needed","description":"Whether the service is up, which models are loaded, how much VRAM is free, and whether auth is enabled. The one endpoint that does not need a key, so you can poll it while the box boots.\n- NOTE: After a cold start, models take about 40 seconds to warm. Until then requests still work but the first one is slow.\n- NOTE: auth reports \"OPEN - no API keys configured\" if no keys are set. If you ever see that in production, fix it.","tags":["api"],"x-status":"alpha","x-curl-example":"curl https://api.latency.cam/health","responses":{"200":{"description":"{ ok, build, uptime_seconds, auth, gpu{}, models{} }"},"401":{"description":"Missing or invalid API key."},"503":{"description":"GPU is stopped. Check GET /api/status and turn it on at https://latency.cam.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"gpu_offline"},"message":{"type":"string"},"turn_on":{"type":"string","example":"https://latency.cam"}}}}}}},"parameters":[]}},"/v1/clone":{"post":{"operationId":"v1_clone","summary":"Voice cloning — not implemented","description":"Returns 501. This is the one place where \"no restrictions\" does not extend to the open internet, and it is also genuinely not built yet. Cloning needs svara-tts-voiceclone-beta, a 6.6GB model, and the GPU currently has about 6.8GB free because vLLM holds the rest to keep call latency low. Fitting it means either a second GPU or replacing the default TTS model with the beta one.\n- NOTE: Use the 38 preset voices from GET /v1/models instead. They cover 19 languages in both genders.\n- NOTE: Every TTS licence in this stack forbids cloning a voice without the speaker's consent, so the consent flag stays even once this is built.","tags":["api"],"x-status":"planned","x-curl-example":"# Returns 501. Preset voices work today:\ncurl https://api.latency.cam/v1/models -H \"Authorization: Bearer $LATENCY_API_KEY\" | jq '.tts[0].voices'","responses":{"200":{"description":"501 with an explanation. 403 first if consent_confirmed is not true."},"401":{"description":"Missing or invalid API key."},"503":{"description":"GPU is stopped. Check GET /api/status and turn it on at https://latency.cam.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"gpu_offline"},"message":{"type":"string"},"turn_on":{"type":"string","example":"https://latency.cam"}}}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"file":{"type":"string","description":"Reference audio of the speaker."},"name":{"type":"string","description":"A label for the voice."},"consent_confirmed":{"type":"boolean","description":"Must be true. You must have the speaker's explicit permission."}},"required":["file","name","consent_confirmed"]}}}}}}},"x-model-count":17,"x-language-count":26,"x-machine-readable-docs":{"catalog":"https://latency.cam/api/models","llms":"https://latency.cam/llms.txt","llmsFull":"https://latency.cam/llms-full.txt"}}