> For the documentation index, fetch https://sync.so/docs/llms.txt. Append .md to a page URL for Markdown. Documentation-search MCP: https://sync.so/docs/_mcp/server. # TypeScript SDK Guide > Complete guide to the sync. labs TypeScript SDK for AI lip sync. Installation, authentication, code examples, and best practices for lip sync generation with TypeScript and JavaScript. The Sync Labs TypeScript SDK (`@sync.so/sdk`) provides typed methods for Node.js 18+ applications. It wraps the full [Sync Labs API](/api-reference) surface for creating lip sync generations, polling status, and retrieving results from any TypeScript/JavaScript runtime. ## Installation Requires **Node.js 18+**. The SDK ships with both ESM and CommonJS builds, so it works with any module system. ```bash # npm npm i @sync.so/sdk # yarn yarn add @sync.so/sdk # pnpm pnpm add @sync.so/sdk ``` ## Authentication The SDK reads your API key from the `SYNC_API_KEY` environment variable automatically. ```bash export SYNC_API_KEY="your-api-key" ``` Create a key from the [API Keys](https://sync.so/settings/api-keys) page in your dashboard. You can also pass the key explicitly: ```typescript import { SyncClient } from "@sync.so/sdk"; // Reads SYNC_API_KEY from environment const sync = new SyncClient(); // Or pass it directly const sync = new SyncClient({ apiKey: "your-api-key" }); ``` > **Warning** > > Never expose your API key in client-side code. The SDK is designed for server-side use only. See [Framework Integration](#framework-integration) for details. ## Quick Start Create a lip sync generation, poll for completion, and get the output URL: **`quickstart.ts`** ```typescript quickstart.ts import { SyncClient, SyncError } from "@sync.so/sdk"; const sync = new SyncClient(); async function main() { // 1. Submit a generation const response = await sync.generations.create({ input: [ { type: "video", url: "https://assets.sync.so/docs/example-video.mp4" }, { type: "audio", url: "https://assets.sync.so/docs/example-audio.wav" }, ], model: "lipsync-2", options: { sync_mode: "cut_off" }, }); const jobId = response.id; console.log(`Job submitted: ${jobId}`); // 2. Poll until complete let generation = await sync.generations.get(jobId); while (!["COMPLETED", "FAILED", "REJECTED"].includes(generation.status)) { await new Promise((r) => setTimeout(r, 10000)); generation = await sync.generations.get(jobId); } // 3. Get output if (generation.status === "COMPLETED") { console.log(`Output: ${generation.outputUrl}`); } else { console.log(`Generation ${jobId} failed`); } } main(); ``` Run it with: ```bash npx tsx quickstart.ts ``` ## Core Operations ### Creating Generations #### Video + Audio The most common pattern. Provide a source video and an audio track -- Sync Labs generates lip movements matching the audio. ```typescript const response = await sync.generations.create({ input: [ { type: "video", url: "https://your-cdn.com/video.mp4" }, { type: "audio", url: "https://your-cdn.com/audio.wav" }, ], model: "lipsync-2", options: { sync_mode: "cut_off" }, outputFileName: "my-output", }); ``` #### Video + Text-to-Speech Use the built-in ElevenLabs integration to go from text to lip-synced video in a single call: ```typescript const response = await sync.generations.create({ input: [ { type: "video", url: "https://your-cdn.com/video.mp4", }, { type: "text", provider: { name: "elevenlabs", voiceId: "EXAVITQu4vr4xnSDxMaL", script: "Your script goes here. Max 5,000 characters per generation.", stability: 0.5, similarityBoost: 0.75, }, }, ], model: "lipsync-2", options: { sync_mode: "cut_off" }, }); ``` > **Note** > > Enable the ElevenLabs integration from your [Integrations settings](https://sync.so/settings/integrations) before using the TTS input type. ### Polling vs Webhooks The quick start above uses polling. For production systems, [webhooks](/api-reference/guides/webhooks) are more efficient -- you receive a POST notification when the job finishes instead of making repeated API calls. ```typescript // With webhooks, pass a URL when creating the generation const response = await sync.generations.create({ input: [ { type: "video", url: "https://your-cdn.com/video.mp4" }, { type: "audio", url: "https://your-cdn.com/audio.wav" }, ], model: "lipsync-2", webhookUrl: "https://your-app.com/api/sync-webhook", }); // No polling needed -- your webhook endpoint receives the result ``` ### Listing Generations Retrieve your recent generations: ```typescript const generations = await sync.generations.list(); for (const gen of generations) { console.log(`${gen.id} - ${gen.status}`); } ``` ### Getting Generation Details Fetch a specific generation by ID: ```typescript const generation = await sync.generations.get("generation-id"); console.log(`Status: ${generation.status}`); console.log(`Output: ${generation.outputUrl}`); console.log(`Model: ${generation.model}`); ``` ### Cost Estimation Call the public estimate endpoint directly with the model and billable duration in seconds. The response is a single object; `estimatedCredits` is null for dollar-billed organizations. ```typescript const response = await fetch("https://api.sync.so/v2/generations/estimate", { method: "POST", headers: { "x-api-key": process.env.SYNC_API_KEY!, "Content-Type": "application/json", }, body: JSON.stringify({ model: "lipsync-2", duration: 10, fps: 30 }), }); if (!response.ok) throw new Error(await response.text()); const estimate = await response.json(); console.log(`Estimated cost: $${estimate.estimatedGenerationCost}`); console.log(`Estimated frames: ${estimate.estimatedFrameCount}`); console.log(`Estimated credits: ${estimate.estimatedCredits}`); ``` ## Working with Files ### URL Inputs The simplest approach. Provide publicly accessible URLs for your video and audio files: ```typescript const response = await sync.generations.create({ input: [ { type: "video", url: "https://your-cdn.com/video.mp4" }, { type: "audio", url: "https://your-cdn.com/audio.wav" }, ], model: "lipsync-2", }); ``` ### Uploading Files via the Assets API If your files are not hosted at public URLs, use the Assets API to upload them first. Then reference the returned URLs in your generation request. ```typescript // 1. List your uploaded assets const assets = await sync.assets.list(); // 2. Use an asset URL in a generation const response = await sync.generations.create({ input: [ { type: "video", url: assets[0].url }, { type: "audio", url: "https://your-cdn.com/audio.wav" }, ], model: "lipsync-2", }); ``` For direct file uploads, use the [create with files](/api-reference/api/generate-api/create-with-files) endpoint. ## Error Handling The SDK throws `SyncError` for API errors. Wrap calls in try/catch to handle failures gracefully. ```typescript import { SyncClient, SyncError } from "@sync.so/sdk"; const sync = new SyncClient(); async function createGeneration() { try { const response = await sync.generations.create({ input: [ { type: "video", url: "https://your-cdn.com/video.mp4" }, { type: "audio", url: "https://your-cdn.com/audio.wav" }, ], model: "lipsync-2", }); return response.id; } catch (err) { if (err instanceof SyncError) { console.error(`API error ${err.statusCode}: ${JSON.stringify(err.body)}`); // Retry on transient errors (5xx) if (err.statusCode >= 500) { console.log("Retrying in 5 seconds..."); await new Promise((r) => setTimeout(r, 5000)); return createGeneration(); } } throw err; } } ``` Common error scenarios: | Status Code | Cause | Action | | :---------- | :------------------------------------------ | :------------------------------------ | | 401 | Invalid or missing API key | Check `SYNC_API_KEY` is set correctly | | 400 | Invalid input (bad URL, unsupported format) | Validate inputs before sending | | 429 | Rate limit exceeded | Back off and retry after delay | | 500+ | Transient server error | Retry with exponential backoff | See the [Error Handling](/developer-guides/error-handling) guide for the full list of error codes. ## TypeScript Types The SDK is fully typed. Import types directly for use in your own functions and interfaces: ```typescript import { SyncClient } from "@sync.so/sdk"; import type { Sync Labs } from "@sync.so/sdk"; const sync = new SyncClient(); async function processGeneration(jobId: string): Promise { const generation = await sync.generations.get(jobId); // generation is fully typed -- your editor provides autocomplete // for properties like .status, .outputUrl, .model, .id, etc. if (generation.status === "COMPLETED") { await downloadVideo(generation.outputUrl!); } } ``` The SDK exports types for all request and response objects, so you get autocomplete and compile-time checks across your entire codebase. ## Framework Integration The Sync Labs SDK is server-side only. Never import it in client-side code -- doing so would expose your API key. ### Next.js (App Router) Use the SDK in Server Components, Route Handlers, or Server Actions: **`app/api/generate/route.ts`** ```typescript app/api/generate/route.ts import { NextResponse } from "next/server"; import { SyncClient } from "@sync.so/sdk"; const sync = new SyncClient(); export async function POST(request: Request) { const { videoUrl, audioUrl } = await request.json(); const response = await sync.generations.create({ input: [ { type: "video", url: videoUrl }, { type: "audio", url: audioUrl }, ], model: "lipsync-2", webhookUrl: "https://your-app.com/api/sync-webhook", }); return NextResponse.json({ jobId: response.id }); } ``` ### Express **`server.ts`** ```typescript server.ts import express from "express"; import { SyncClient } from "@sync.so/sdk"; const app = express(); const sync = new SyncClient(); app.use(express.json()); app.post("/generate", async (req, res) => { const { videoUrl, audioUrl } = req.body; const response = await sync.generations.create({ input: [ { type: "video", url: videoUrl }, { type: "audio", url: audioUrl }, ], model: "lipsync-2", }); res.json({ jobId: response.id }); }); app.listen(3000); ``` > **Warning** > > Store your `SYNC_API_KEY` in environment variables or a secrets manager. Do not hardcode it in your source files. ## Frequently Asked Questions #### What Node.js version is required? The Sync Labs TypeScript SDK requires Node.js 18 or later. It ships with both ESM and CommonJS builds, so it works with any module system. The SDK is designed for server-side use only -- never import it in client-side code as this would expose your API key. #### Can I use the SDK in the browser? No. The SDK is server-side only. Using it in the browser would expose your API key to end users. Instead, create a server-side endpoint that calls the Sync Labs API and have your frontend communicate with your server. See the Framework Integration section for examples. #### How do I handle errors? The SDK throws SyncError for API errors. Wrap your calls in try/catch blocks and inspect the statusCode and body properties. For generation creation, send an [Idempotency-Key](/api-reference/guides/idempotency) on the first attempt and preserve it and the inputs across retries. Use backoff for explicit 429 rejections; do not blindly repeat an unkeyed create after a timeout or 5xx response. Check the Error Handling guide for the full list of error codes. > Complete guide to the sync. labs TypeScript SDK for AI lip sync. Installation, authentication, code examples, and best practices for lip sync generation with TypeScript and JavaScript.