Developer API · version 2026-09-24
Talking videos, programmatically
One HTTP request turns a photo and a script (or your own audio) into a lip-synced MP4 — the same pipeline behind every TalkPix studio. Renders are paid with your account credits, reserved before anything starts and refunded if a render fails.
Photo + script → MP4
Script mode uses 30 AI voices in 10 languages; audio mode lip-syncs to your own file.
Async by design
Start a render, get an id, then poll or receive a signed webhook when it is done.
Pay per rendered second
1 credit/s at 720p, 2 at 1080p, from the same wallet as the studio.
No surprises
Credits reserved before the provider is called, automatic refund on failure, commercial use included.
Quickstart
- Create a key on Account → API. It is shown once — store it as a secret.
- Make sure the account holds credits (the studio's packs work; nothing renders on an empty wallet).
- POST a public photo URL and a script:
curl https://www.talkpix.ai/api/v1/videos \
-H "Authorization: Bearer tpx_live_…" \
-H "Content-Type: application/json" \
-d '{
"image_url": "https://example.com/portrait.jpg",
"script": "Hi! This video was made with one API call.",
"voice": "zephyr",
"language": "en-US",
"webhook_url": "https://example.com/hooks/talkpix"
}'{
"id": "6c1f…",
"object": "video",
"status": "processing",
"video_url": null,
"resolution": "720p",
"mode": "script",
"credits_reserved": 4,
"credits_charged": null,
"created_at": "2026-09-24T10:00:00.000Z",
"balance": 96
}Then poll GET /videos/:id every few seconds until status is completed and video_url is set — or let the webhook tell you. A typical 10-second video takes about a minute.
curl https://www.talkpix.ai/api/v1/videos/6c1f… \
-H "Authorization: Bearer tpx_live_…"Authentication
Every request carries a key in the Authorization header (or X-API-Key). Keys start with tpx_live_, are stored hashed on our side, and can be revoked at any time from your account. Never ship a key in a browser or a mobile app — call the API from your server.
Authorization: Bearer tpx_live_…Base URL: https://www.talkpix.ai/api/v1. All bodies and responses are JSON. Every response carries X-RateLimit-Remaining and X-TalkPix-Api-Version.
Endpoints
| POST/videos | Start a talking-photo render |
| GET/videos/:id | Retrieve one render (status, video_url) |
| GET/videos | List renders started with your keys |
| POST/estimate | Credits a script will reserve — free |
| POST/uploads | Direct-upload URL for a private photo or audio file |
| GET/voices | Voices, languages and direction presets |
| GET/me | Account behind the key and its credit balance |
Create a video
POST /videos. Give the face as a public HTTPS URL or as a key from Uploads. Then either a script for an AI voice, or your own audio.
| Field | Type | Description |
|---|---|---|
| image_url | string | Public HTTPS URL of a JPG, PNG or WebP portrait (≤ 10 MB). One clear face, front-facing works best. Exactly one of image_url / image_key. |
| image_key | string | Key returned by POST /uploads. |
| script | string | What the person says. Script mode. ~14 characters ≈ 1 second; up to 300 seconds at 720p (120 at 1080p). |
| voice | string | Required with script. A voice id or its alias, e.g. "zephyr". See Voices. |
| language | string | Required with script. "en-US", "en-GB", "es", "fr", "de", "it", "pt-BR", "ja", "ko", "hi". |
| voice_prompt | string | Optional speaking direction for script mode (≤ 200 chars), e.g. “Speak with high energy.” |
| video_prompt | string | Optional look-and-motion direction (≤ 200 chars), e.g. “The person is talking with a warm smile.” Defaults to steady eye contact. |
| captions | boolean | Burn even-paced captions from the script into the MP4. Script mode only. |
| audio_url | string | Public HTTPS URL of MP3, WAV, M4A, AAC, OGG, WebM or FLAC (≤ 25 MB). Audio mode: the person lip-syncs to this file; script, voice and voice_prompt do not apply. Length is measured from the file. |
| audio_key | string | Key returned by POST /uploads with kind: "audio". |
| resolution | "720p" | "1080p" | Default 720p. 1080p costs 2× per second and is capped at 120 seconds. |
| webhook_url | string | Public HTTPS endpoint that receives a signed POST when the render completes or fails. See Webhooks. |
| metadata | object | Up to 20 string values (keys ≤ 40, values ≤ 500 chars), echoed back on the video object and in webhooks — your order id, user id, etc. |
Audio mode example — your own recording, the same call:
{
"image_key": "inputs/…/api-….png",
"audio_url": "https://example.com/voiceover.mp3",
"resolution": "1080p",
"metadata": { "order_id": "A-1042" }
}The video object
| Field | Type | Description |
|---|---|---|
| status | string | queued → processing → completed | failed. |
| video_url | string | null | The MP4, set once completed. Hosted by us for 30 days — download it into your own storage if you need it longer. |
| duration_seconds | number | null | Rendered length once completed. |
| credits_reserved | number | Reserved when the render started, from the script or audio length. |
| credits_charged | number | null | The final charge after the render finished (rounded up to the second). 0 on failure. |
| credits_refunded | boolean | True when a failed render's reservation has been returned. |
| error | object | null | On failure: { code, message }. code is safety when our content filter rejected the photo or text. |
| webhook_delivered_at | string | null | When your endpoint acknowledged the completion event. |
| metadata | object | Exactly what you sent. |
Retrieve & list
GET /videos/:id returns the video object. It also reconciles with the renderer on every call, so if our webhook from the model is late the status still moves forward when you poll. Polling every 5 seconds is fine.
GET /videos lists renders started with your keys, newest first. Query: limit (1–100, default 20), status, and starting_after (an id) for the next page.
{
"object": "list",
"data": [ { "id": "…", "object": "video", … } ],
"has_more": true,
"next_starting_after": "…"
}Uploads
When the photo or audio is not on a public URL, ask for a direct-upload URL, PUT the bytes to it, then pass the returned key to POST /videos. The URL is valid for 5 minutes; files are limited to 25 MB.
curl https://www.talkpix.ai/api/v1/uploads \
-H "Authorization: Bearer tpx_live_…" \
-H "Content-Type: application/json" \
-d '{ "kind": "image", "content_type": "image/png" }'
{ "object": "upload", "key": "inputs/…/api-….png",
"upload_url": "https://…", "method": "PUT",
"headers": { "Content-Type": "image/png" }, "expires_in": 300 }curl -X PUT "<upload_url>" \
-H "Content-Type: image/png" \
--data-binary @portrait.png{ "image_key": "inputs/…/api-….png", "script": "…", "voice": "puck", "language": "en-US" }Voices & languages
GET /voices returns the 30 voices with a sample clip each, the 10 languages, and the ready-made voice_prompt / video_prompt strings the studios use. Use a voice's id or its alias (the bare name, any casing).
{
"voices": [
{ "id": "Zephyr (Female)", "alias": "zephyr", "sex": "female",
"sample_url": "https://www.talkpix.ai/assets/voice-samples/Zephyr.mp3" },
{ "id": "Puck (Male)", "alias": "puck", "sex": "male", … }
],
"languages": [ { "id": "English (US)", "alias": "en-US" }, … ],
"voice_prompts": [ { "id": "excited", "prompt": "Speak with high energy and excitement." }, … ],
"video_prompts": [ { "id": "smile", "prompt": "The person is talking with a warm, friendly smile." }, … ]
}POST /estimate with { "script", "resolution" } returns the credits a script will reserve. It is free and does not start anything.
Webhooks
Pass webhook_url when creating a video and we POST one event when it reaches a final state: video.completed or video.failed. Respond with any 2xx within 10 seconds. We retry twice on network errors and 5xx, then record the failure on the video object — keep polling as your fallback, and treat events as at-least-once.
{
"id": "evt_…_completed",
"object": "event",
"type": "video.completed",
"api_version": "2026-09-24",
"created_at": "2026-09-24T10:01:12.000Z",
"data": { "id": "…", "object": "video", "status": "completed", "video_url": "https://…", … }
}Every delivery is signed with the key's webhook signing secret (shown next to each key on Account → API): TalkPix-Signature: t=<unix seconds>,v1=<hex> where v1 is HMAC-SHA256 of `${t}.${rawBody}`. Verify before trusting the payload, and reject timestamps older than a few minutes.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyTalkPixSignature(secret, header, rawBody, toleranceSeconds = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => p.trim().split("=")));
const t = Number(parts.t);
if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return expected.length === parts.v1?.length &&
timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(parts.v1, "hex"));
}Errors
Errors use one envelope. type is stable and safe to branch on; message is for humans; param names the offending field when there is one.
{ "error": { "type": "insufficient_credits", "message": "This render needs 12 credits (12s at 720p); the account has 4. …" } }| 400 | invalid_request_error | A field is missing or invalid; `param` names it. Also returned when the text fails our content check. |
| 401 | authentication_error | Missing, malformed, or revoked key. |
| 402 | insufficient_credits | The wallet cannot cover the render. Nothing was started; add credits and retry. |
| 404 | not_found_error | No render with that id on this account. |
| 413 / 415 | invalid_request_error | The remote file is too large or not an accepted type. |
| 422 | invalid_request_error | A URL could not be fetched, or the audio duration could not be read. |
| 429 | rate_limit_error | Over 120 requests/min on one key, or 20 renders already in progress. |
| 500 | provider_error | The render could not be started upstream. Credits were returned automatically. |
| 503 | unavailable_error | The API is not available on this deployment yet. |
Pricing & limits
- API renders spend the same credits as the studio: 1 credit per rendered second at 720p, 2 at 1080p, minimum 3 seconds. Buy credits on Pricing or Account → Add credits; they never expire.
- Estimated from your script. The final charge follows the video's real length, rounded up to the next second — the difference is refunded or charged when it finishes.
- Credits are reserved before the render starts and returned automatically if it fails. A request the wallet cannot cover is refused with 402 and starts nothing.
- Length: up to 300 seconds at 720p, 120 seconds at 1080p. Photos ≤ 10 MB, audio ≤ 25 MB.
- 120 requests per minute per key; at most 20 renders in progress per account. Need more? Email support@talkpix.ai with your expected volume.
- The same content rules as the studio apply: no sexual, violent, or deceptive content, and no real people without their permission. Rejected renders are refunded.
- Commercial use is allowed when you hold the rights to the photo, script and audio you send. Delivered videos are kept for 30 days, then removed.
- API use is part of the service under our Terms (section 1, Developer API): you are responsible for every request made with your keys, and for having permission for any of your own customers’ content you send. See the Privacy Policy for how that data is handled.
Not a developer? The full product works in the browser — start in the studio.