Videos
Talking-head videos
One vertical shot of your character presenting to camera — the workhorse format for updates, promos, and UGC-style ads. Up to 59.5 seconds, lipsync always on, ready to post.
How a job runs
POST /videos with mode: "talking_head" queues an async job and returns immediately. The job makes one 9:16 first frame of your character (from your video_idea, or uses the frame you supply), gets the speech (synthesized from your script, or your recording), lip-syncs it in windows of 5–14.5 s cut at natural pauses, and merges them into one vertical video. Captions are burned in if you asked for them. You poll the job until it hands you a video_url.
The request
| Field | Type | Description |
|---|---|---|
| mode | "talking_head" required | Or "scenes" — see Scene videos. |
| character_id | uuid required | Who is on camera. |
| voice_id + script | strings | Audio source A — the job speaks script (≤4000 characters) in your voice, verbatim. |
| audio_url | https url | Audio source B — your recording (or a POST /tts result). Provide exactly one source. Duration is measured server-side. A non-https URL is ignored with a FIELD_IGNORED warning. |
| video_idea | string ≤2000 | The scene to stage. Required unless you supply a frame (first_frame_url or first_frame_asset_id). |
| first_frame_url | https url | Animate exactly this still. Non-https URLs are ignored with a FIELD_IGNORED warning. |
| first_frame_asset_id | uuid | A first_frame asset you made with POST /images/first-frame (kind: "talking_head", the default). Not together with first_frame_url. A scenes frame here → 400 FIRST_FRAME_KIND_MISMATCH. |
| resolution | string | 480P | 768P (default) | 1080P; lowercase and 480p / 720p / 1080p are accepted. 720p is served as 768P with a RESOLUTION_MAPPED warning. |
| voice_settings | object | Script mode: speed (0.7–1.2), stability, similarity_boost, style (0–1). Out-of-range values are clamped. |
| language | string | Spoken language — ISO code or name ("ar", "spanish"). |
| use_dynamic_captions | boolean | Burn in dynamic captions — an add-on, 10 credits. See below. |
| captions_language | string | Optional caption language, e.g. "en", "ar". Omit to auto-detect. |
| captions_position | "top" | "center" | "bottom" | Where the caption band sits. Default "bottom". |
| topic / audience | strings | Accepted but no effect (FIELD_IGNORED); put the brief in video_idea / scene_prompt. |
| expected_credits | int ≥0 | The price you quoted. If the server quote is higher → 409 QUOTE_MISMATCH, nothing charged. |
| client_request_id | uuid | Idempotency key (or send the Idempotency-Key header). If both are sent they must match. |
fast, lipsync, narration_mode, audio_duration, first_frame_titleand other legacy tuning fields no longer do anything. They don't fail the request; each comes back as a FIELD_IGNORED warning.
The response
{
"workflow_id": "partner_h3t_8f2c…",
"status": "queued",
"credits_reserved": 125,
"credits_quoted": 125,
"job_type": "talking_head_video",
"resolution": "768P",
"price_table_version": "2026-09-30",
"warnings": [
{ "field": "fast", "code": "FIELD_IGNORED", "message": "…" }
],
"duplicate": false
}Retried with the same idempotency key and body? You get { workflow_id, status, duplicate: true, credits_reserved } for the original job and nothing is charged twice.
Dynamic captions
Captions remain an add-on. Set use_dynamic_captions: true for big, bold captions that reveal word by word, on a solid band, in Latin and Arabic scripts (pass captions_language: "ar" for Arabic). Flat 10 credits, quoted as a captions line. Choose captions_position top, center or bottom to keep them off your subject; when you build the opening still, leave that strip clear.
Limits
Under 1 s → 400 AUDIO_TOO_SHORT; 1–5 s is billed as 5 s. Over 59.5 s (a recording measured, or a script estimated) → 400 AUDIO_TOO_LONG at queue time, nothing charged. Scripts are capped at 4,000 characters. Rule of thumb: keep scripts under about 120 words.
What it costs
Generation is billed per second of output at the resolution's rate (480P 5/s, 768P 8/s, 1080P 16/s), plus a first frame when the job makes one, a small transcription line, the speech in script mode, and captions if chosen. Examples at the default 768P:
| Line | 15 s, your audio | …with your own frame | …with captions |
|---|---|---|---|
| generation | 120 | 120 | 120 |
| first_frame | 4 | — | 4 |
| transcription | 1 | 1 | 1 |
| captions | — | — | 10 |
| Total | 125 | 121 | 135 |
The smallest video — 5 s at 480P — is 30 credits. With voice_id + script the quote adds a tts line and is based on an estimated length with headroom; you are charged the measured price if it is lower (how billing settles).
Quote it, then queue it
curl -X POST https://platform.promoat.ai/api/partner/v1/videos/estimate \
-H "Authorization: Bearer $PROMOAT_KEY" \
-H "Content-Type: application/json" \
-d '{
"mode": "talking_head",
"character_id": "CHARACTER_ID",
"audio_url": "https://your-cdn.com/audio/15-second-take.mp3",
"video_idea": "standing in a bright modern kitchen, smiling at the camera",
"resolution": "768P"
}'
# → { "mode": "talking_head", "credits_required": 125, "credits_balance": 961,
# "sufficient_credits": true, "estimated_seconds": 15, "billed_seconds": 15,
# "resolution": "768P", "job_type": "talking_head_video",
# "price_table_version": "2026-09-30",
# "lines": [
# { "kind": "generation", "credits": 120, "quantity": 15, "unit": "output_second" },
# { "kind": "first_frame", "credits": 4 },
# { "kind": "transcription", "credits": 1 }
# ],
# "breakdown": { "render_credits": 121, "lipsync_credits": 0, "tts_credits": 0,
# "first_frame_credits": 4, "captions_credits": 0 },
# "warnings": [] }curl -X POST https://platform.promoat.ai/api/partner/v1/videos \
-H "Authorization: Bearer $PROMOAT_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-4812-video-1" \
-d '{ ...same body..., "expected_credits": 125 }'
# → { "workflow_id": "partner_h3t_…", "status": "queued", "credits_reserved": 125, … }curl https://platform.promoat.ai/api/partner/v1/jobs/partner_h3t_…/status \
-H "Authorization: Bearer $PROMOAT_KEY"
# → { "status": "running", "phase": "generating", "progress": 40, … }
# → { "status": "completed", "video_url": "https://…/final.mp4",
# "poster_url": "https://…/poster.jpg", "credits_consumed": 125, … }Up to 5 jobs can run at once (429 beyond that). Finished videos can be changed scene by scene — see Video edits.
Script mode with a ready-made frame
curl -X POST https://platform.promoat.ai/api/partner/v1/videos \
-H "Authorization: Bearer $PROMOAT_KEY" \
-H "Content-Type: application/json" \
-d '{
"mode": "talking_head",
"character_id": "CHARACTER_ID",
"voice_id": "VOICE_ID",
"script": "Hi! Three quick updates from the shop this week…",
"language": "en",
"voice_settings": { "speed": 1.05, "stability": 0.5 },
"first_frame_asset_id": "FRAME_ASSET_ID",
"resolution": "1080P",
"use_dynamic_captions": true,
"captions_position": "center",
"client_request_id": "6f1d3c1e-8a55-4c1f-9d55-2f0f8f0d2b11"
}'