Skip to content

Speak

Turn text + a face image + a voice into a lip-synced MP4 of that face speaking the text in that voice. One synchronous, blocking call chains the two stages for you:

  1. Voice clone (TTS) — synthesize text in the cloned voice → audio.
  2. Lip-sync (video) — drive the face image with that audio → an MP4.

Try it →

Generate a video

POST /api/v1/speak

Required scope: generations:write

Request body

Supply the media one of two ways — flat fields win if both are present.

(a) OpenAI-style content[] (recommended)

{
"workspace_id": "019...",
"text": "Hi! Thanks for trying out the avatar API.",
"content": [
{ "type": "image_url", "image_url": { "url": "https://example.com/face.jpg" } },
{ "type": "audio_url", "audio_url": { "url": "https://example.com/voice.mp3" } }
]
}

(b) Flat fields (legacy, still supported)

{
"workspace_id": "019...",
"text": "The words to speak.",
"image": "https://example.com/face.jpg",
"voice_ref": "https://example.com/voice.mp3"
}
FieldRequiredDescription
textYesThe words the avatar should say.
workspace_idConditionalWorkspace to attribute the generation to. Required if the token is not workspace-scoped.
contentYes*OpenAI content parts: image_url, audio_url, or input_audio (inline base64). *Required unless you pass the flat image + voice_ref.
imageYes*Face image as an http(s) URL or a data:image/...;base64,… URI.
voice_refYes*Voice reference to clone (3–10 s ideal), as a URL or data URI.
voice_textNoTranscript of voice_ref. Omit it and the server auto-transcribes (Whisper) + caches. Supplying it skips transcription.
immutable_url_for_cachingNoIf true, treats your media URLs as immutable cache keys → skips re-download/re-transcription/re-encode on repeats. Only set when the URL content never changes.
end_frameNoOptional last-frame face (URL or data URI); the clip ends on this pose. Defaults to image.
promptNoScene/style prompt for the video model.
num_stepNoTTS denoising steps. Default 16.
guidance_scaleNoTTS guidance. Default 2.0.
first_frame_strengthNoHow strongly the first frame is pinned to the source face. Default 0.5.
last_frame_strengthNoHow strongly the last frame is pinned. Default 0.0 (frees the mouth for natural lip-sync).
widthNoOutput width (px). Default 352.
heightNoOutput height (px). Default 480 (portrait).
frame_rateNoOutput fps. Default 24.

Media formats. image and voice_ref accept an http(s) URL (the server downloads it) or a data:<mime>;base64,… URI. Audio: wav, mp3, m4a, flac, ogg. Images: jpg, png, webp, etc.

You don’t need a transcript of the voice reference. The server transcribes it with Whisper and caches the result. Pass voice_text only to skip that step.

Response 200

Content-Type: video/mp4 — the body is the raw MP4. Timing/metadata is in headers:

HeaderMeaning
X-Clone-MsTTS (voice-clone) time, ms
X-Gen-MsVideo-generation time, ms
X-Total-MsTotal server time, ms
X-DurationVideo duration, seconds
X-Box-IdWhich GPU box served it

Errors

StatuserrorMeaning
400invalid_jsonBody was not valid JSON.
400missing_fieldsMissing text, image, or voice ref (the required field explains).
400workspace_id_requiredNo workspace_id and the token is not workspace-scoped.
401Missing/invalid token.
403forbiddenToken lacks generations:write (or workspace membership).
429at_capacityAll GPU cells busy. Honor Retry-After and retry.
502render_failedUpstream TTS/video failure; retry, report if persistent.
503no_box_availableNo healthy GPU box. Retry shortly.

Example

Terminal window
curl -sS -X POST https://api.casola.ai/api/v1/speak \
-H "Authorization: Bearer avatar_abc12345.def...your-key" \
-H "content-type: application/json" \
-d '{
"workspace_id": "019...",
"text": "Hello from the avatar API.",
"content": [
{ "type": "image_url", "image_url": { "url": "https://cdn.example.com/face.jpg" } },
{ "type": "audio_url", "audio_url": { "url": "https://cdn.example.com/voice.mp3" } }
],
"immutable_url_for_caching": true
}' --output reply.mp4 -D headers.txt
grep -i '^x-' headers.txt # timing

Notes

  • Warm latency is roughly 2–5 s per clip (≈ X-Clone-Ms + X-Gen-Ms).
  • Repeat avatars are faster. Reusing the same face + voice hits the transcript, cloned-voice, and face-latent caches. For best repeat performance, host the face/voice at stable URLs and set immutable_url_for_caching: true.
  • Resolution & frames. Default 352×480 portrait at 24 fps; duration is derived from the synthesized speech (you don’t set frame count directly).
  • Capacity. Each GPU box has a fixed number of cells; at capacity you get a 429 with Retry-After. Back off and retry. For an interactive, multi-turn stream instead of one-shot clips, use a live session.