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
  }
}
FieldRequiredDescription
textYesPlain text or supported service markup. Limit: 10,000 characters.
voiceNoVoice ID from /api/v1/tts/voices. Defaults to the configured production voice.
languageNoLanguage hint. Use en-AU unless directed otherwise.
backendNoOperational override. Omit for normal production traffic.
optionsNoPer-request voice controls supported by the Camlin Voice path.

Voice Options

OptionRangeEffect
length_scale0.6..1.6Pace. Lower is faster; higher is slower.
noise_scale0.1..1.2Voice variation. Lower is steadier.
noise_w_scale0.1..1.4Pronunciation and timing variation. Lower is more repeatable.
sentence_silence0..2Pause in seconds after sentence boundaries.
volume0.2..2.0Output volume multiplier.

Headers

HeaderRequiredDescription
Content-Type: application/jsonYesJSON request body.
x-camlin-tts-voiceNoOverrides voice for this request.
x-camlin-tts-languageNoOverrides language for this request.
x-camlin-tts-backendNoOperational 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] }
  ]
}
FieldTypeDescription
audio_urlstringRelative URL for generated WAV bytes. Resolve against CAMLIN_SPEECH_URL.
audio_duration_snumberClip length in seconds.
blend_dataarrayLip-sync frames using { audioOffset, blendshapes }.
blendDataarrayCamel-case mirror of blend_data for browser clients.
backendstringOperational 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.wav

Generated 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

StatusMeaningTypical Fix
400Empty text, unsupported voice, unsupported override, or out-of-range option.Validate text length, voice ID, and option ranges.
404Audio token not found.Re-synthesize or fetch the audio sooner.
503Requested route unavailable or not configured.Retry with default routing or use queued fallback.
500Synthesis 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.

See also