Camlin Speech API
Text-to-Speech
Create Australian English speech clips with a stable audio URL and optional lip-sync frame stream for avatar playback.
Endpoints
POST/api/v1/tts/synthesize
GET/api/v1/tts/audio/{token}.wav
GET/api/v1/tts/voices
GET/api/v1/tts/healthz
Synthesize Speech
json
{
"text": "Thanks for calling Camlin. How can I help today?",
"voice": "piper-en-au",
"language": "en-AU",
"backend": "gpu",
"options": {
"length_scale": 1.0,
"noise_scale": 0.667,
"noise_w_scale": 0.8,
"sentence_silence": 0.25,
"volume": 1.0
}
}| Field | Required | Description |
|---|---|---|
text | Yes | Plain text or supported service markup. Limit: 10,000 characters. |
voice | No | Voice ID from /api/v1/tts/voices. Defaults to the configured production voice. |
language | No | Language hint. Use en-AU unless directed otherwise. |
backend | No | Operational override. Omit for normal production traffic. |
options | No | Per-request voice controls supported by the Camlin Voice path. |
Voice Options
| Option | Range | Effect |
|---|---|---|
length_scale | 0.6..1.6 | Pace. Lower is faster; higher is slower. |
noise_scale | 0.1..1.2 | Voice variation. Lower is steadier. |
noise_w_scale | 0.1..1.4 | Pronunciation and timing variation. Lower is more repeatable. |
sentence_silence | 0..2 | Pause in seconds after sentence boundaries. |
volume | 0.2..2.0 | Output volume multiplier. |
Headers
| Header | Required | Description |
|---|---|---|
Content-Type: application/json | Yes | JSON request body. |
x-camlin-tts-voice | No | Overrides voice for this request. |
x-camlin-tts-language | No | Overrides language for this request. |
x-camlin-tts-backend | No | Operational override. Omit for normal production traffic. |
Header values win over body values when both are supplied.
Response Schema
json
{
"text": "Thanks for calling Camlin. How can I help today?",
"voice": "piper-en-au",
"backend": "gpu",
"audio_url": "/api/v1/tts/audio/abcd1234.wav",
"audio_format": "wav",
"audio_sample_rate": 16000,
"audio_duration_s": 3.41,
"blend_data": [
{ "audioOffset": 0.0, "blendshapes": [0.0, 0.02, 0.01] }
],
"blendData": [
{ "audioOffset": 0.0, "blendshapes": [0.0, 0.02, 0.01] }
]
}| Field | Type | Description |
|---|---|---|
audio_url | string | Relative URL for generated WAV bytes. Resolve against CAMLIN_SPEECH_URL. |
audio_duration_s | number | Clip length in seconds. |
blend_data | array | Lip-sync frames using { audioOffset, blendshapes }. |
blendData | array | Camel-case mirror of blend_data for browser clients. |
backend | string | Operational route used by the service. Do not branch user logic on it. |
Fetch Audio
bash
curl "$CAMLIN_SPEECH_URL/api/v1/tts/audio/abcd1234.wav" --output out.wavGenerated audio tokens are short-lived and should be fetched immediately by the caller if long-term storage is required.
List Voices
bash
curl "$CAMLIN_SPEECH_URL/api/v1/tts/voices"json
{
"voices": [
{ "id": "piper-en-au", "backend": "piper", "locale": "en-AU", "gender": "male", "label": "Keith" },
{ "id": "camlin-jeremy-au-male-piper-v1", "backend": "piper", "locale": "en-AU", "gender": "male", "label": "Jeremy" }
]
}Use id as the request voice. Labels are display text and may change.
Errors
| Status | Meaning | Typical Fix |
|---|---|---|
400 | Empty text, unsupported voice, unsupported override, or out-of-range option. | Validate text length, voice ID, and option ranges. |
404 | Audio token not found. | Re-synthesize or fetch the audio sooner. |
503 | Requested route unavailable or not configured. | Retry with default routing or use queued fallback. |
500 | Synthesis failed unexpectedly. | Retry once, then capture request context for support. |
Implementation Recipe
javascript
export async function synthesizeSpeech(text, { voice = "piper-en-au" } = {}) {
const res = await fetch(`${process.env.CAMLIN_SPEECH_URL}/api/v1/tts/synthesize`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
text,
voice,
language: "en-AU",
options: { length_scale: 1.0, sentence_silence: 0.2 },
}),
});
if (!res.ok) throw new Error(`tts failed: HTTP ${res.status}`);
const clip = await res.json();
const wavRes = await fetch(`${process.env.CAMLIN_SPEECH_URL}${clip.audio_url}`);
const wavBytes = await wavRes.arrayBuffer();
return { clip, wavBytes, lipSync: clip.blendData ?? clip.blend_data ?? [] };
}Lip-Sync Contract
json
{ "audioOffset": 16.667, "blendshapes": [0.0, 0.03, 0.01] }audioOffset is milliseconds from the start of the WAV. blendshapes is a fixed-order numeric activation array for avatar playback. Treat the array as opaque unless Camlin provides the channel map for your renderer.
Operational Notes
- Production callers should omit backend overrides and let the service route to the fastest healthy path with CPU fallback.
- Legacy and experimental voice routes remain available for controlled internal testing but are not part of the public demo surface.
- Public implementations should use the response contract above rather than relying on provider-specific behavior.