Heart
English (US)Female- voice_id
puppetry-af_heart- language_code
en- preview_url
/api/voices/puppetry-preview?voice=af_heart&language=en
Puppetry Developer API
Puppetry Voice API access, hosted audio uploads, and credit-backed video jobs — callable from your code, your agent, or your terminal.
Included with the Studio plan. Keys are created in Studio settings and shown once.
$ curl -X POST https://www.puppetry.com/api/v1/videos/text
-d { "image_url": "https://…/portrait.jpg",
"text": "Welcome to the demo!", "voice_id": "puppetry-af_heart" }
202 {"status": "queued", "id": "api_video_8b1d…" }
$ curl https://www.puppetry.com/api/v1/videos/api_video_8b1d…
200 {"status": "processing", "progress": 0.62 }
200 {"status": "completed",
"video_url": "https://assets.puppetry.com/…/take.mp4" }
TypeScript SDK
npm i @puppetry.com/sdkMCP server — Claude Desktop, Codex CLI, Cursor
npx -y @puppetry.com/mcp-serverPlain HTTP
curl https://www.puppetry.com/openapi.json5-minute quickstart
Run Puppetry from a server runtime so your bearer token stays private. The SDK queues one idempotent job, follows the API's polling hints, and returns the completed video URL.
Create it in Studio settings and save it once as PUPPETRY_API_KEY.
Run npm i @puppetry.com/sdk in your Node.js 18+ app.
Use a stable idempotency key, then let the job helper poll at the server-provided pace.
Node.js or server runtime
import { Puppetry } from '@puppetry.com/sdk';
const client = new Puppetry({
apiKey: process.env.PUPPETRY_API_KEY!,
});
const job = await client.videos.createFromText({
image_url: 'https://example.com/portrait.jpg',
text: 'Welcome to the demo!',
voice: 'puppetry-af_heart',
idempotencyKey: 'welcome-video-v1',
});
const video = await job.waitForCompletion();
console.log(video.url);Browser handoff
Call your own server route from the browser. Never embed the bearer token in client code or prefix it with NEXT_PUBLIC_.
// This route lives on your server and keeps the API key private.
const response = await fetch('/api/talking-videos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
imageUrl: 'https://example.com/portrait.jpg',
text: 'Welcome to the demo!',
}),
});
if (!response.ok) throw new Error('Video creation failed');
const { video_url } = await response.json();Authentication
Create your key in Studio settings, copy it when it is shown, and store it in your server's secret manager. Every public Developer API endpoint uses the same HTTPS Authorization header.
Verify the key without spending a credit
curl -sS https://www.puppetry.com/api/v1/usage \
-H "Authorization: Bearer $PUPPETRY_API_KEY"The usage endpoint is read-only. A valid response confirms the key and reports video readiness before you queue work.
Never put the key in browser JavaScript, a NEXT_PUBLIC_ variable, source control, URLs, or logs.
Poll a video with the same key that created it. A different key cannot read that job by id.
A 401 means the key is missing, invalid, or revoked. A 403 means it lacks the endpoint's required scope. Do not retry the same credentials unchanged. Revoke an exposed key in Studio API key settings before creating its replacement.
Create is async: you get a job id back in milliseconds, poll until it completes, then download the video. Video job responses include retry hints, and an Idempotency-Key makes create retries safe. Saved puppets let you reuse a portrait without passing its URL into every job.
/api/v1/videos/textPhoto + script → talking-head video job/api/v1/videos/audioPhoto + your audio → lip-synced video job/api/v1/videos/{jobId}Poll a job until video_url is ready/api/v1/tts/puppetryText → hosted WAV speech/api/v1/voices/puppetryBrowse the voice catalog with previews/api/v1/uploads/audio-urlReserve a signed upload for your audio/api/v1/usageCheck credits and video readiness before you create/api/v1/puppetsList your saved portrait library/api/v1/puppetsSave a hosted portrait for later video jobs/api/v1/puppets/{puppetId}Read one saved portrait by id/api/v1/puppets/{puppetId}Delete one saved portrait by idRead-only API playground
Run a real GET request from this page. Your key stays in this tab’s memory and is sent only to the selected Puppetry endpoint. This playground never writes it to local or session storage.
GET /api/v1/usageRun a request to inspect its status, request id, retry hint, and JSON response.
Rate limits and error handling
Every JSON error has an error code and a human-readable message. Treat it as retryable only when the response says retryable: true or supplies an explicit retry hint. Reuse the same Idempotency-Key when retrying a write. The current beta is polling-first: it does not accept webhook or callback URLs, so complete jobs from your server using the returned status_url.
Retry-AfterX-RateLimit-LimitX-RateLimit-RemainingX-RateLimit-ResetX-Puppetry-Next-Poll-Atretryable, then Retry-After or next_poll_at.status_url. Do not create a replacement job while status is transient.retryable is false. Use retry_block_code and retry_blocked_reason to fix the request before trying again.400Invalid requestCorrect the request body or input. Retry only when the response includes an explicit retry hint.
401UnauthorizedSend a valid live API key as an Authorization bearer token.
402Credits requiredAdd video credits before submitting another credit-backed job.
403Scope missingUse a key that includes the scope required by the endpoint.
404Not foundVerify the resource id. Poll video jobs with the same API key that created them.
409Idempotency conflictIf retryable, wait and repeat the same request with the same Idempotency-Key. Otherwise resolve the key/input mismatch.
413Payload too largeReduce the request body or audio file to the maximum reported in the error details.
415Unsupported media typeSend the Content-Type required by the endpoint.
429Rate limitedWait for Retry-After or the rate-limit reset before sending the next request.
502Upstream unavailableHonor the retry hint. For status lookups, keep polling the same public job id.
503Temporarily unavailableHonor Retry-After or the absolute next-poll time, then retry.
504Status lookup timeoutKeep the accepted job id and retry its status URL after the supplied delay.
Included with Studio
Video jobs are credit-backed
GET /api/v1/usage before creating — no wasted callsCurrent beta limits: 1 active API key per account · 10 requests per minute · 2 concurrent API jobs · 10 API video credits per month · 100k Puppetry Voice characters per month · 2GB hosted API audio storage · 250 audio uploads per month · 50MB max per uploaded audio file
Every voice below is callable by id from the API. Preview them before you write a line of code.
Puppetry voices
The Developer API returns 45 Puppetry voices from GET /api/v1/voices/puppetry. Each preview_url uses a short localized sample, so Italian voices speak Italian, Japanese voices speak Japanese, and so on.
List voices
GET /api/v1/voices/puppetryResponse is object: "list" with data[] voice objects.
Voice object
{
"id": "puppetry-af_heart",
"object": "voice",
"name": "Heart",
"provider": "puppetry",
"language": "English",
"language_code": "en",
"gender": "Female",
"preview_url": "/api/voices/puppetry-preview?voice=af_heart&language=en"
}Generate speech
POST /api/v1/tts/puppetry{
"voice_id": "puppetry-af_heart",
"text": "This is a free voice preview in Puppetry.",
"speed": 1
}Showing 6 recommended starters
puppetry-af_hearten/api/voices/puppetry-preview?voice=af_heart&language=enpuppetry-am_adamen/api/voices/puppetry-preview?voice=am_adam&language=enpuppetry-bf_emmaen-gb/api/voices/puppetry-preview?voice=bf_emma&language=en-gbpuppetry-ef_doraes/api/voices/puppetry-preview?voice=ef_dora&language=espuppetry-if_sarait/api/voices/puppetry-preview?voice=if_sara&language=itpuppetry-zf_xiaobeizh/api/voices/puppetry-preview?voice=zf_xiaobei&language=zhThree calls from an API key to a finished talking-head video.
Get your API key