Talking animations
Make your mascot say a line in its own voice, moving as it talks. You get MP4 and transparent WebM and HEVC with sound, the voice as MP3 and a word-timed transcript.
Ko welcoming a new user, from this line:
[waves hello, cheerful] Welcome in! [leans in, whispering] Psst... the big orange button does everything. [points down, proud] That one!1. Give the mascot a voice
A mascot has one voice; a variant uses the Original's until it gets its own. Get ideas, hear three samples, keep one. Every account can add voices.
Ideas come from the mascot's references and description; refresh=true gives new ones.
curl https://api.masko.ai/v1/mascots/MASCOT_ID/voice/suggestions \
-H "Authorization: Bearer $MASKO_API_KEY"{
"data": {
"ideas": [
{
"title": "Cozy and warm",
"description": "A round, cozy pond creature with a big belly. Male voice, warm and round, medium-low, smiling while he talks, relaxed and kind, easy natural pace. American accent."
},
{
"title": "Excited little sidekick",
"description": "An excited little cartoon sidekick. Young male voice, fast and bouncy, high energy, can barely contain his excitement, slightly breathless and giggly. American accent."
}
]
}
}Send a description and a direction such as "Older" or "A little grumpy" to get the rewritten text, the changes and new directions. Without direction, only directions come back.
{
"data": {
"description": "A round, cozy pond creature with a big belly. Male voice, warm and round, medium-high, smiling while he talks, relaxed and kind, easy natural pace. American accent.",
"changes": [{ "from": "medium-low", "to": "medium-high" }],
"directions": ["Younger", "More excited", "Slower", "Drier", "Sleepier"]
}
}Three voices from one description reading the same line, for 1 credit. sample_line is optional.
curl -X POST https://api.masko.ai/v1/mascots/MASCOT_ID/voice/samples \
-H "Authorization: Bearer $MASKO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"description": "An excited little cartoon sidekick. Young male voice, fast and bouncy, high energy, slightly breathless and giggly. American accent."
}'{
"data": {
"samples": [
{ "id": "5b1c2d8e-8a0f-4c55-9f7e-1d2a3b4c5d61", "url": "https://storage.googleapis.com/...", "duration": 9.6 },
{ "id": "6c2d3e9f-9b10-4d66-a08f-2e3b4c5d6e72", "url": "https://storage.googleapis.com/...", "duration": 8.3 },
{ "id": "7d3e4f0a-ac21-4e77-b190-3f4c5d6e7f83", "url": "https://storage.googleapis.com/...", "duration": 9.3 }
],
"sample_line": "Hi! I'm Gubby. I live in your app, and I love showing people around.",
"cost_credits": 1,
"expires_at": "2026-09-30T09:12:00Z"
}
}Keep a sample within 24 hours; it replaces the current voice. name is optional.
curl -X PUT https://api.masko.ai/v1/mascots/MASCOT_ID/voice \
-H "Authorization: Bearer $MASKO_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "sample_id": "7d3e4f0a-ac21-4e77-b190-3f4c5d6e7f83" }'{
"data": {
"object": "voice",
"name": "Gubby's voice",
"description": "An excited little cartoon sidekick. Young male voice, fast and bouncy, high energy, slightly breathless and giggly. American accent.",
"sample_url": "https://storage.googleapis.com/...",
"source": "original",
"created_at": "2026-09-29T09:14:00Z"
}
}GET /v1/mascots/{id}/voicereturns the voice ornull.sourceisoriginal,variantorcreator(a Marketplace mascot using its creator's voice).- A new Original voice also applies to every variant without its own.
- Variants use the same calls under
/v1/mascots/{id}/variants/{variantId}/voice;DELETEthere restores the Original's voice.
2. Write the line
Send speech.script with bracketed movements where each starts. Bracketed words are never spoken; a feeling after a comma steers the voice.
[waves hello, excited] Hi! I'm Gubby. [points to the right] The docs are right here!- A movement starts on the words after it. With no words after it, it leads into the next one, or plays in the closing silence.
- Movements happen in place, so leaning in never changes the framing.
- The voice only says what is written. To hear a laugh, write it:
[throws its head back, laughing] Ha ha!. - Or send
speech.textwith an optionalspeech.direction, such as "excited, points at the docs at the end". Masko writes the movements and returns the script. speech.languagetakes an ISO 639-1 code such asfr; without it, the language is detected from the words.
Length follows the speech, up to 5 minutes. Longer than 15 seconds, the voice is recorded once and cut at real pauses into 5 to 15 second takes; each starts and ends on the start image, so the mascot returns to that pose at cuts and pauses can grow slightly.
3. Estimate for free
Add dry_run: true to get the script and estimate without charging or generating.
curl -X POST https://api.masko.ai/v1/mascots/MASCOT_ID/generate \
-H "Authorization: Bearer $MASKO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "animation",
"item_id": "ITEM_ID",
"speech": { "text": "Hi! I'\''m Gubby. Here'\''s how the Masko API works.", "direction": "excited about what he does" },
"dry_run": true
}'{
"data": {
"dry_run": true,
"script": "[waves hello, excited] Hi! I'm Gubby. [opens an arm to the right] Here's how the Masko API works.",
"estimate": { "speech_seconds": 4.3, "duration": 6, "max_duration": 6, "credits": 12, "too_long": false }
}
}4. Generate
Send an animation with speech. It starts from item_id or source_image_asset_id and ends on end_image_asset_id, or its start image; variant_id also picks that variant's voice.
curl -X POST https://api.masko.ai/v1/mascots/MASCOT_ID/generate \
-H "Authorization: Bearer $MASKO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "animation",
"item_id": "ITEM_ID",
"end_image_asset_id": "END_IMAGE_ID",
"speech": {
"script": "[waves hello, excited] Hi! I'\''m Gubby. [opens an arm to the right] Here'\''s how the Masko API works."
}
}'{
"data": {
"job_id": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f",
"status": "pending",
"type": "animation",
"animation_model": "standard",
"item_id": "4b5c6d7e-8f90-4a1b-9c2d-3e4f5a6b7c8d",
"item_name": "Hi, I'm Gubby",
"estimated_cost": 12,
"estimate": { "speech_seconds": 4.3, "duration": 6, "max_duration": 6, "credits": 12, "too_long": false },
"script": "[waves hello, excited] Hi! I'm Gubby. [opens an arm to the right] Here's how the Masko API works.",
"asset_ids": {
"video": "5d6e7f80-91a2-4b3c-8d4e-5f6a7b8c9d0e",
"webm": "6e7f8091-a2b3-4c4d-9e5f-6a7b8c9d0e1f",
"hevc": "7f8091a2-b3c4-4d5e-8f6a-7b8c9d0e1f20",
"audio": "8091a2b3-c4d5-4e6f-9a7b-8c9d0e1f2031",
"transcript": "91a2b3c4-d5e6-4f7a-8b8c-9d0e1f203142"
},
"poll_url": "/api/v1/jobs/3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f?api_version=2026-09-26"
}
}It costs 2 credits per second. The request charges for estimate.max_duration, the longest the line could need, and refunds unused seconds once the voice is recorded; the job's cost_credits shows the final price.
Poll https://api.masko.ai/v1/jobs/JOB_ID?api_version=2026-09-26 (jobs and polling) and retry safely with the same Idempotency-Key.
The transcript
Fetch the transcript JSON from the file_url of GET /v1/assets/{id}. Times are seconds from the first video frame, silence included, and lines end at ., ! or ? for captions.
{
"text": "Hi! I'm Gubby. Here's how the Masko API works.",
"language": "en",
"duration": 6,
"words": [
{ "text": "Hi!", "start": 0.26, "end": 0.6 },
{ "text": "I'm", "start": 0.92, "end": 1.04 },
{ "text": "Gubby.", "start": 1.16, "end": 1.5 },
{ "text": "Here's", "start": 2.04, "end": 2.24 },
{ "text": "how", "start": 2.26, "end": 2.38 },
{ "text": "the", "start": 2.42, "end": 2.5 },
{ "text": "Masko", "start": 2.54, "end": 2.9 },
{ "text": "API", "start": 3.0, "end": 3.46 },
{ "text": "works.", "start": 3.56, "end": 3.9 }
],
"lines": [
{ "text": "Hi! I'm Gubby.", "start": 0.26, "end": 1.5 },
{ "text": "Here's how the Masko API works.", "start": 2.04, "end": 3.9 }
],
"movements": [
{ "text": "waves hello, excited", "start": 0.26, "end": 2.04 },
{ "text": "opens an arm to the right", "start": 2.04, "end": 3.9 }
]
}The audio asset is the voice alone as MP3; video assets report has_audio: true in their metadata.
What a line can do
Tubey, a robot whose screen is his face, shows one ability per clip (his real lines also asked for a pixel mouth).
Whisper, shout and laugh.
[raises one hand beside his screen, whispering] I can whisper a little secret... [throws both fists up, excited] or shout the big news! [shakes with laughter, laughing] Ha ha!Several languages in one line.
[waves, happy] I speak your language too. [waves with both hands, excited] Bonjour ! ¡Hola! Ciao! [puts a hand on his chest, proud] And my voice was made from my look.Act with the mascot's own features, such as Tubey's screen.
[his screen shows a big pixel heart, happy] I love this feature. [his screen shows a question mark, curious] Any questions? [his screen shows two smiling eyes, calm] Just ask.Limits
speechneeds a start image:item_idorsource_image_asset_id.- The line sets the length, so
speechrefusesduration,loop,reverse,auto_reverse,sizes,animation_prompt,image_promptand the image crops. Talking uses Standard;animation_model: "premium"is refused. - A mascot without a voice returns
409withdetails.reason: "voice_required", before any charge. - A talk longer than 5 minutes returns
400; split it into two animations. - A minute of talk is about 12 MB in WebM and 30 MB in HEVC at 720 px; see file sizes.
- Talking animations cannot be reversed or edited, since the voice would not follow. For a new take, resend the script.
generate-batchdoes not acceptspeech; send each talking animation to/generate.
