New:Microsoft Teams Notifications Are Now Available in Socket.Learn more
Get Started

@puppetry.com/sdk

Package Overview
Dependencies
Maintainers
1
Versions
2
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@puppetry.com/sdk

Official TypeScript SDK for the Puppetry API — create talking head videos from photos using AI

latest
npmnpm
Version
0.1.1
Version published
Maintainers
1
Created
Source

@puppetry.com/sdk

Official TypeScript SDK for the Puppetry API — create talking head videos from photos using AI.

Installation

npm install @puppetry.com/sdk

Quick Start

import { Puppetry } from "@puppetry.com/sdk";

const client = new Puppetry({ apiKey: "pk_..." });

// Create a video from text
const job = await client.videos.createFromText({
  text: "Welcome to our product demo!",
  image_url: "https://example.com/photo.jpg",
  voice: "puppetry-af_heart",
  idempotencyKey: "demo-video-1",
});

// Wait for completion (polls automatically, honoring the create Retry-After hint)
const video = await job.waitForCompletion();
console.log(video.url);

Features

  • Text-to-Video: Provide text → get a talking head video with AI voice
  • Audio-to-Video: Provide audio → lip-sync any portrait
  • 500+ Voices: Choose from built-in voices or clone your own
  • 29 Languages: Arabic, Chinese, English, French, German, Hindi, and more
  • Progress Events: Stream job events through status polling that follows API retry hints
  • Voice Cloning: Upload 6 seconds of audio to create a custom voice
  • TypeScript-first: Full type safety, JSDoc, IntelliSense support

API Reference

Create a Client

const client = new Puppetry({
  apiKey: "pk_...", // Required — get yours at puppetry.com/dashboard/settings/api-keys
  baseUrl: "https://www.puppetry.com/api/v1", // Optional
  timeout: 30000, // Optional, ms
  maxRetries: 2, // Optional, retries GET 429/5xx and idempotent POST 429/5xx
  retryDelayMs: 500, // Optional, exponential backoff base delay
  onRetry: (event) => console.warn(event.code, event.retryAfter), // Optional
});

The SDK only retries side-effecting POST calls when you pass an idempotencyKey. Use one for video creates and signed upload URL requests so a transient 429/5xx cannot create duplicate jobs or charges. onRetry runs before the SDK sleeps for a safe/idempotent retry and includes Developer API job, operation, request, retry-after, and status URL metadata when the server returns it.

Videos

// From text (text-to-speech + lip sync)
const job = await client.videos.createFromText({
  text: "Hello, world!",
  image_url: "https://example.com/photo.jpg",
  voice: "puppetry-af_heart",
  language: "en",
  expressiveness: 1.0,
  idempotencyKey: "text-video-demo-1",
});
console.log(job.source); // "text"
console.log(`First poll at ${job.nextPollAt ?? "the Retry-After time"}`);

Agents and launch checks can preflight video credits and active slots before reserving uploads or queueing a job:

const readiness = await client.videos.getReadiness();
if (!readiness.can_create) {
  console.log(readiness.reason, readiness.retryAfter);
  for (const blocker of readiness.blockers ?? []) {
    console.log(blocker.code, blocker.retryable);
  }
}

// Throws PuppetryRateLimitError with retryAfter for active-slot blockers, or
// PuppetryError(402) when the API key is out of video credits.
await client.videos.ensureReady();

readiness.blockers preserves the API's machine-readable recovery list. Agents can treat insufficient_video_credits as a buy-credits path and concurrent_job_limit_reached as a backoff path using retryAfter or retry_after_seconds.

For hosted audio uploads that will immediately feed videos.createFromAudio(), pass requireVideoReadiness: true so Puppetry fails before reserving upload quota if video credits or active slots are currently blocked.

If an idempotent retry replays an already completed job, the SDK normalizes the terminal URL onto url, videoUrl, resultUrl, downloadUrl, and outputUrl while preserving raw API fields such as download_url and idempotent_replay. Video job responses also preserve and normalize the create source across source, request_source, and requestSource, so agents can distinguish text jobs from audio/lip-sync jobs during status polling or idempotent replay. The seven-day lookup expiry is normalized across expires_at and expiresAt. Retryable video job responses expose both relative retry seconds and absolute poll timestamps as next_poll_at / nextPollAt. SDK waitForCompletion() and videos.stream() polling honors both forms, so agents can follow the server-paced poll schedule without custom sleep logic. Video create, replay, and status responses also normalize the public operation handle across operation_id / operationId, using the response operation-id headers when the body omits those aliases. waitForCompletion() also treats retryable status-poll errors as part of the same job lifecycle: it uses the error's preserved job identity, status URL, and retry timing to keep polling the original job until the caller timeout expires. The polling URL is normalized across status_url / statusUrl; when a queued create or replay response only exposes the URL in Location / Content-Location, the SDK fills the same aliases from those headers. Job helpers also derive their polling target from that canonical status URL when it is present, so idempotent replays and sparse header-only responses keep polling the accepted video job.

// From audio (lip sync only)
const job2 = await client.videos.createFromAudio({
  audio_url: "https://example.com/speech.mp3",
  image_url: "https://example.com/photo.jpg",
  idempotencyKey: "audio-video-demo-1",
});

// Get job status
const status = await client.jobs.get(job.id);

// Wait for completion (with polling)
const completed = await job.waitForCompletion({
  intervalMs: 2000, // Poll every 2s (default)
  timeoutMs: 300000, // 5 min timeout (default)
  // initialDelayMs defaults to the job's Retry-After hint when present
  onPoll: (latest) => {
    console.log(latest.status, latest.progress ?? 0);
  },
});

// The explicit resource helper is still available when you only have an ID.
const completedAgain = await client.jobs.waitForCompletion(job.id, {
  initialDelayMs: (job.retryAfter ?? 0) * 1000,
});

// Stream progress events through polling
for await (const event of client.videos.stream(job.id)) {
  console.log(`${event.type}: ${event.progress}%`);
  if (event.type === "completed") {
    console.log(`Video ready: ${event.video_url}`);
  }
}

Voices

// List available voices
const { data: voices } = await client.voices.list({
  language: "en",
  gender: "female",
  limit: 20,
});

// List built-in Puppetry voices for /tts/puppetry
const puppetryVoices = await client.voices.listPuppetry();
console.log(puppetryVoices.default_voice_id);
console.log(puppetryVoices.data[0]?.preview_text);

Text to Speech

const voices = await client.voices.listPuppetry();

const audio = await client.tts.createPuppetry({
  voice_id: voices.default_voice_id,
  text: "Create a voiceover before turning it into video.",
  speed: 1.0,
});

console.log(audio.audio_url);

Uploads

const upload = await client.uploads.createAudioUrl({
  contentType: "audio/mpeg",
  fileSize: 12345,
  idempotencyKey: "upload-demo-1",
});

await fetch(upload.uploadUrl, {
  method: upload.method,
  headers: upload.headers,
  body: audioBytes,
});

console.log(upload.readUrl);

// Or let the SDK request the signed URL and upload bytes in one call.
const uploaded = await client.uploads.uploadAudio({
  contentType: "audio/mpeg",
  sizeBytes: audioBytes.byteLength,
  body: audioBytes,
  idempotencyKey: "upload-demo-1",
});

const job = await client.videos.createFromAudio({
  audio_url: uploaded.readUrl,
  image_url: "https://example.com/photo.jpg",
});

The raw API fields (upload_url, read_url, expiry fields, and limits) stay available too; the SDK also exposes camelCase aliases for TypeScript and agent callers, including idempotentReplay when a retried upload reservation is replayed.

Puppets

// Create a reusable puppet (portrait)
const puppet = await client.puppets.create({
  imageUrl: "https://example.com/portrait.jpg",
  name: "Marketing Avatar",
});

// List puppets
const { data: puppets } = await client.puppets.list({ limit: 10 });

// Delete a puppet when it should no longer appear in your reusable library
await client.puppets.delete(puppet.id);

Usage

const usage = await client.usage.get();
console.log(
  `${usage.videoCredits?.balance ?? usage.creditsRemaining} video credits remaining`,
);
console.log(usage.videoGeneration?.blockers ?? []);
console.log(
  `${usage.videoCredits?.netUsed ?? usage.creditsUsed} video credits used this month`,
);

// Agent integrations can use the quota alias for a clearer tool name.
const quota = await client.quota.get();
console.log(`${quota.creditsRemaining} video credits remaining`);

Agent Tool Manifest

import {
  Puppetry,
  PUPPETRY_AGENT_TOOLS,
  callPuppetryAgentTool,
} from "@puppetry.com/sdk";

console.log(PUPPETRY_AGENT_TOOLS.map((tool) => tool.name));
// puppetry_create_video_from_text, puppetry_create_video_from_audio,
// puppetry_create_video_from_text_and_wait,
// puppetry_create_video_from_audio_and_wait, puppetry_create_audio_upload_url,
// puppetry_lipsync, puppetry_list_voices, puppetry_get_job_status,
// puppetry_wait_for_video, puppetry_get_video_readiness, puppetry_get_quota

const client = new Puppetry({ apiKey: process.env.PUPPETRY_API_KEY! });
const result = await callPuppetryAgentTool(
  client,
  "puppetry_create_video_from_text",
  {
    prompt: "Agent-generated launch update.",
    photo_url: "https://example.com/avatar.jpg",
    voice: "puppetry-af_heart",
    preflightReadiness: true,
  },
);

console.log(result.id);
console.log(result.readiness_checked, result.readiness?.credits_remaining);

await callPuppetryAgentTool(client, "puppetry_get_job_status", {
  taskId: result.taskId ?? result.id,
});

Direct video agent tools accept preflightReadiness / preflight_readiness when the caller wants to check video credits and active slots before queueing a credit-bearing job. Successful direct-create results include readiness_checked / readinessChecked and, when preflight ran, the normalized readiness object that was checked before the create POST. The create-and-wait agent tools perform that preflight by default and expose preflightReadiness: false only for callers that already checked readiness. Both result shapes include snake_case and camelCase readiness aliases so agents can read the same evidence regardless of their naming convention.

MCP stdio server

{
  "mcpServers": {
    "puppetry": {
      "command": "npx",
      "args": ["-y", "@puppetry.com/sdk"],
      "env": { "PUPPETRY_API_KEY": "pk_live_xxx" }
    }
  }
}

The puppetry-mcp bin serves the same tool manifest over MCP stdio and dispatches calls through the SDK. By default it uses https://www.puppetry.com/api/v1, the live Developer API route family. Set PUPPETRY_BASE_URL only when pointing agents at a preview or staging API host. When the live API returns a Puppetry error, MCP JSON-RPC errors include structured error.data with the API status/statusCode, Puppetry code, message, optional details, and optional retry_after_seconds/ retryAfterSeconds/retryAfter seconds so agents can back off instead of retrying blindly. Retry hints are normalized to seconds whether the API sends a numeric or HTTP-date Retry-After header, and fall back to top-level retry_after_seconds / retryAfterSeconds / retryAfter body aliases or matching details.* aliases if an intermediary strips the header. Video job responses also include next_poll_at / nextPollAt when the API can provide an absolute retry time, plus pollable video identity fields such as object, id, job_id / jobId, source, request_source / requestSource, status_url / statusUrl, and expires_at / expiresAt when the error belongs to a video job.

Error Handling

import { PuppetryError, PuppetryAuthError, PuppetryRateLimitError, PuppetryNetworkError } from '@puppetry.com/sdk';

try {
  await client.videos.createFromText({ ... });
} catch (err) {
  if (err instanceof PuppetryAuthError) {
    console.error('Invalid API key');
  } else if (err instanceof PuppetryRateLimitError) {
    console.error(`Rate limited. Retry after ${err.retryAfter}s`);
  } else if (err instanceof PuppetryNetworkError) {
    console.error(`Network error: ${err.message}`);
  } else if (err instanceof PuppetryError) {
    if (err.retryAfter) {
      console.error(`Try again after ${err.retryAfter}s`);
    }
    console.error(`API error ${err.status}: ${err.message}`);
  }
}

Requirements

  • Node.js 18+ (uses native fetch)
  • For Node.js 16, pass a fetch implementation: new Puppetry({ apiKey: '...', fetch: nodeFetch })

License

MIT

Keywords

puppetry

FAQs

Package last updated on 12 Jul 2026

Related posts