How to Generate 1,000+ Personalized Videos with API Automation (2026 Update)
Personalized videos convert at 3x the rate of generic ones (Tavus, 2025). This developer guide shows how to build a bulk video generation pipeline using REST APIs — 1,000 videos in under 20 minutes at $0.10–0.50 each.
Mark D.
Founder

Picture this: your e-commerce platform has 5,000 products and you need a personalized demo video for each one. By tomorrow.
Traditional agency production runs $1,000–7,000 per minute of footage (Synthesia, 2025). At that rate, you'd need years and a budget most companies don't have. A template-based video API? You can queue all 5,000 renders before lunch.
That shift is real: 58% of marketers are already using AI tools to script, edit, or personalize video (Wyzowl, 2025), and production costs have fallen from ~$4,500 to ~$400 per minute between 2023 and 2026 (Loopex Digital, 2026). This guide covers the developer side of that shift — how to build a production-grade pipeline that generates thousands of personalized videos automatically.
No fluff. Just code, architecture decisions, and results that hold up at scale.
Key Takeaways
- Personalized video campaigns convert at 3x the rate of generic equivalents (Tavus, 2025)
- Template-based API rendering runs $0.10–0.50 per video vs. $1,000–7,000/min for agency production (Synthesia, 2025)
- 58% of marketers now use AI tools for video creation — the tooling exists, the question is whether you've built the pipeline
- The batch pipeline in this guide handles 1,000 videos in 15–20 minutes of queue time with proper batching and rate limiting
Why Does Personalized Video Convert?
The business case is stronger than most teams realize, and the numbers have held steady across multiple studies.
Personalized video campaigns convert at 3x the rate of non-personalized ones, and produce 16x higher click-to-open rates in email campaigns (Tavus, 2025). For SaaS brands specifically, personalized video emails hit a 19.4% click-to-open rate — roughly double the industry average for any email type. The problem has never been the ROI case. It's been production capacity.
Template-based APIs close that gap. At $0.10–0.50 per render at volume (Plainly Videos, 2026), the math works for campaigns of almost any size. A run of 1,000 30-second product demos costs $100–500 in credits, not $500,000 in agency fees.
| Method | Cost per Video | Time per Video | Scalability |
|---|---|---|---|
| Agency production | $500–7,000 | Days to weeks | Manual, one-off |
| Template API (this guide) | $0.10–0.50 | 1–3 min render | 10K–100K/month |
| AI generation (Sora, Runway) | $0.40–2.50/clip | 30s–5 min | Limited control |

What Are You Actually Building?
By the end of this guide, you'll have a working bulk video pipeline — the same architecture used by teams running thousands of renders per day.
Covered in this guide:
- Template-driven video generation using JSON configurations and the correct
isDynamicoverlay system - Bulk processing for 1,000+ videos with batching and rate limits
- Webhook automation for real-time completion callbacks with signature verification
- Error handling and exponential backoff for production reliability
- Credit-aware cost optimization
Tech stack:
- Video API: Renderly
- Runtime: Node.js (Python equivalents noted inline)
- Queue: Redis + BullMQ for background processing at scale
- Monitoring: Webhooks + structured logging
Step 1: Setting Up Your API Foundation
Getting connected to Renderly's API takes about 5 minutes. One endpoint, standard Bearer auth, JSON bodies throughout. The base URL is renderly.video/api/v1.
const RENDERLY_API_KEY = process.env.RENDERLY_API_KEY;
const API_BASE_URL = 'https://renderly.video/api/v1';
async function createRender(templateId, replacements) {
const response = await fetch(`${API_BASE_URL}/renders`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${RENDERLY_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ templateId, replacements })
});
if (!response.ok) {
const err = await response.json();
throw new Error(`API ${response.status}: ${err.message}`);
}
return response.json(); // { id, status: 'pending', ... }
}The replacements object maps to your template's dynamic overlays. That structure is explained in Step 2 — it's different from what most video API tutorials show, and getting it wrong is the most common mistake developers make here.
Step 2: How Renderly's Template System Actually Works
Most tutorials show {{ variable }} placeholder syntax for video templates. Renderly doesn't work that way — and for good reason. The system is overlay-based.
Each overlay element in your template has an isDynamic flag. When isDynamic: true, that overlay's content becomes a named variable. Your API request passes a replacements object keyed by overlay name — the rendering engine finds each dynamic overlay, matches it by name, and swaps in your value. For text and shape overlays it replaces content; for image, video, and audio overlays it replaces src.
A template configuration looks like this:
{
"replacements": {
"product_name": "Default Product",
"product_image": "https://cdn.example.com/default.jpg",
"price": "$0.00"
},
"overlays": [
{
"type": "text",
"name": "product_name",
"content": "Default Product",
"isDynamic": true,
"position": { "x": 100, "y": 200 }
},
{
"type": "image",
"name": "product_image",
"src": "https://cdn.example.com/default.jpg",
"isDynamic": true
},
{
"type": "text",
"name": "price",
"content": "$0.00",
"isDynamic": true
},
{
"type": "text",
"content": "Powered by Acme Co.",
"isDynamic": false
}
]
}The last overlay — the brand watermark — has isDynamic: false, so it renders identically across every video in your batch. Rendering a variation is a single API call:
const job = await createRender('product-demo-v1', {
product_name: 'Premium Noise-Canceling Headphones',
product_image: 'https://cdn.example.com/headphones-sku-42.jpg',
price: '$149'
});
// Returns { id: 'job_xyz', status: 'pending' }One template, unlimited variations. No extra setup per video.
Step 3: Generating Videos in Bulk
Bulk generation is about throughput and stability. Sequential calls without batching will either exhaust memory on large datasets or hit rate limits before you're halfway through.
async function generateInBatches(products, batchSize = 50) {
const results = { succeeded: [], failed: [] };
for (let i = 0; i < products.length; i += batchSize) {
const batch = products.slice(i, i + batchSize);
const batchNum = Math.floor(i / batchSize) + 1;
const totalBatches = Math.ceil(products.length / batchSize);
console.log(`Batch ${batchNum}/${totalBatches} — ${batch.length} videos`);
const settled = await Promise.allSettled(
batch.map(product => createRender('product-demo-v1', {
product_name: product.name,
product_image: product.imageUrl,
price: product.price
}))
);
for (const result of settled) {
if (result.status === 'fulfilled') {
results.succeeded.push(result.value);
} else {
results.failed.push({ reason: result.reason.message });
}
}
if (results.failed.length > 0) {
console.warn(` ${results.failed.length} failures in this batch`);
}
// Pause between batches to stay within rate limits
if (i + batchSize < products.length) {
await new Promise(resolve => setTimeout(resolve, 2000));
}
}
return results;
}Use Promise.allSettled rather than Promise.all. One failed render shouldn't abort 999 successful ones — allSettled lets you collect both results and failures, then decide what to retry.
Template-based APIs handle 10,000–100,000+ renders per month at this architecture level (Plainly Videos, 2026). Throughput is rarely the bottleneck. Your queue handling and error strategy matter more than submission speed.
Step 4: Tracking Progress — Polling vs. Webhooks
Two options exist for monitoring render completion. Both work; the right choice depends on your volume.
Option 1: Polling (Fine for Small Batches)
async function waitForRender(jobId, maxWaitMs = 300_000) {
const deadline = Date.now() + maxWaitMs;
while (Date.now() < deadline) {
const res = await fetch(`${API_BASE_URL}/renders/${jobId}`, {
headers: { 'Authorization': `Bearer ${RENDERLY_API_KEY}` }
});
const job = await res.json();
if (job.status === 'completed') return job;
if (job.status === 'failed') throw new Error(`Render failed: ${job.error}`);
await new Promise(r => setTimeout(r, 5000));
}
throw new Error('Render timed out after 5 minutes');
}Option 2: Webhooks (Right Call for Production)
Webhooks eliminate polling loops entirely. Register an endpoint and Renderly fires a POST on every render.completed or render.failed event. Don't skip signature verification — it's one createHmac call and the only thing standing between your endpoint and spoofed events:
import crypto from 'crypto';
app.post('/webhook/render-complete', express.json(), (req, res) => {
const signature = req.headers['x-renderly-signature'];
const expected = 'sha256=' + crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(JSON.stringify(req.body))
.digest('hex');
if (signature !== expected) {
return res.status(401).json({ error: 'Invalid signature' });
}
const { jobId, status, outputUrl, error } = req.body;
if (status === 'completed') {
// Persist outputUrl, notify downstream systems, send to S3, etc.
console.log(`Render ${jobId} complete: ${outputUrl}`);
} else if (status === 'failed') {
console.error(`Render ${jobId} failed: ${error}`);
// Queue for retry or flag for review
}
res.json({ received: true });
});For a deeper look at the webhook system and how to integrate it with Zapier or Make.com, see The Complete Guide to Automating Video Creation in 2026.
Step 5: The Full 1,000-Video Script
Here's a production-ready script that loads data, generates in batches, handles failures, and reports results:
// generate-bulk-videos.js
import 'dotenv/config';
const RENDERLY_API_KEY = process.env.RENDERLY_API_KEY;
const API_BASE_URL = 'https://renderly.video/api/v1';
const TEMPLATE_ID = 'product-demo-v1';
const BATCH_SIZE = 50;
async function createRender(replacements) {
const res = await fetch(`${API_BASE_URL}/renders`, {
method: 'POST',
headers: {
Authorization: `Bearer ${RENDERLY_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ templateId: TEMPLATE_ID, replacements }),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
async function createRenderWithRetry(replacements, attempt = 0) {
try {
return await createRender(replacements);
} catch (err) {
if (attempt >= 3) throw err;
// Exponential backoff: 1s, 2s, 4s
const delay = Math.min(1000 * 2 ** attempt, 30_000);
await new Promise(r => setTimeout(r, delay));
return createRenderWithRetry(replacements, attempt + 1);
}
}
async function run() {
// Replace this with your actual data source (database query, CSV, API call)
const products = Array.from({ length: 1000 }, (_, i) => ({
name: `Product ${i + 1}`,
imageUrl: `https://cdn.example.com/products/product-${i + 1}.jpg`,
price: `$${(10 + i * 0.5).toFixed(2)}`,
}));
console.log(`Queuing ${products.length} renders...`);
let succeeded = 0;
let failed = 0;
for (let i = 0; i < products.length; i += BATCH_SIZE) {
const batch = products.slice(i, i + BATCH_SIZE);
const batchNum = Math.floor(i / BATCH_SIZE) + 1;
const totalBatches = Math.ceil(products.length / BATCH_SIZE);
console.log(`Batch ${batchNum}/${totalBatches}`);
const settled = await Promise.allSettled(
batch.map(p => createRenderWithRetry({
product_name: p.name,
product_image: p.imageUrl,
price: p.price,
}))
);
succeeded += settled.filter(r => r.status === 'fulfilled').length;
failed += settled.filter(r => r.status === 'rejected').length;
if (i + BATCH_SIZE < products.length) {
await new Promise(r => setTimeout(r, 2000));
}
}
console.log(`\nDone: ${succeeded} queued, ${failed} failed`);
console.log(`Estimated render time: ~${Math.ceil(products.length / 60)} minutes`);
}
run().catch(console.error);Production Tips for Scale
Credit-Aware Template Selection
On Renderly, 1 credit = 1 minute of 1080p video. Picking the right template duration for each job type keeps costs predictable:
function pickTemplate(product) {
// Simple image swap: use the 15-second template
if (!product.hasVideo && !product.hasCustomAudio) {
return 'product-demo-15s'; // ~0.25 credits
}
// Rich demo with video and voiceover: 30-second template
return 'product-demo-30s'; // ~0.5 credits
}Rate Limiting Reference
const RATE_LIMITS = {
concurrentPerBatch: 50, // renders per concurrent Promise.allSettled call
msBetweenBatches: 2000, // pause between batches
maxRetriesPerRender: 3, // before marking a job as permanently failed
};Error Response Codes to Handle Explicitly
- 401 — invalid or expired API key
- 402 — credit balance exhausted; check balance before large batch runs
- 404 — template ID not found; verify template exists in your account
- 429 — rate limited; the retry backoff above handles this automatically
Realistic Performance Numbers
These figures come from production pipelines, not API marketing copy:
| Scale | Queue Time | Render Completion |
|---|---|---|
| 100 videos | ~2 min | ~10–15 min |
| 1,000 videos | ~15–20 min | ~1–2 hours |
| 10,000 videos | ~2–3 hours | ~12–24 hours |
Queueing (submitting the API calls) is fast. Actual rendering happens server-side — Renderly processes jobs concurrently, so wall-clock time depends more on template complexity and resolution than on your submission speed.
From what we've seen in production, throughput improves significantly when templates are lean: fewer overlays, compressed media assets, and shorter durations. If you're generating 10,000 videos and hitting time limits, look at the template before the API configuration.

Common Pitfalls
Template Design Mistakes
- Missing fallback values in
replacements— if a key is absent from your API call, the overlay renders with its defaultcontentorsrcfrom the template config. Always populate defaults in your template JSON. - Inconsistent image dimensions — if
srcvalues point to images with varying aspect ratios, you'll get cropping. Normalize image dimensions before passing them toisDynamicimage overlays. - Overly complex templates add render time at scale. Keep overlay counts lean for bulk jobs; reserve rich multi-layer templates for premium renders.
API Integration Issues
- Not handling 402 responses — credit exhaustion returns a 402, not a generic 500. Check for it explicitly and surface it clearly rather than letting it masquerade as a server error.
- Skipping webhook signature validation — covered in Step 4. Don't skip it.
- No structured logging — at 1,000+ renders,
console.logisn't enough. Log job IDs at every stage so you can reconstruct what happened when a batch partially fails.
Scale Problems
- Sequential awaits instead of
Promise.allSettled— this is the most common performance mistake. Sequential calls turn a 20-minute batch run into a 3-hour one. - No retry tracking — store failed job IDs to a separate list or database table for reprocessing. Logging and forgetting means those renders are permanently lost.
- Database connection exhaustion — if you're persisting job state, use a connection pool. Long-running batch scripts will exhaust single-connection setups.
For a full breakdown of API pricing across competing providers, see Best Video APIs for Developers: Compared and Video API vs Traditional Production: Cost Comparison.
Frequently Asked Questions
How many videos can a template-based video API generate per hour? Template-based APIs handle 10,000–100,000+ renders per month (Plainly Videos, 2026), with typical throughput of 50–150 concurrent renders per batch. For most business use cases, the API isn't the bottleneck — your queue architecture is.
What's the real cost difference between API and agency production? Agency production runs $1,000–7,000 per minute of footage (Synthesia, 2025). Template-based APIs run $0.10–0.50 per render at volume. For 30-second videos at 1,000 units, that's a 95–99% cost reduction.
Does personalized video actually convert better? Consistently, yes. Personalized video campaigns convert at 3x the rate of non-personalized equivalents and generate 16x higher click-to-open rates in email (Tavus, 2025). The ROI case has held up across multiple independent studies.
When should I use AI-generated video instead of templates? When you genuinely need novel footage — a scene, landscape, or character that doesn't exist yet. For variations, personalization, and scale use cases (product demos, outreach videos, onboarding clips), template assembly wins on consistency, cost, and brand control.
What happens when a render fails mid-batch?
Using Promise.allSettled, failures don't abort the batch — they're collected separately. Store failed job IDs with the failure reason, then reprocess them as a second pass. Most failures are transient (network timeouts, brief API hiccups) and succeed on retry.
The pipeline above handles 1,000 personalized videos in about 15–20 minutes of queue time. The architecture scales linearly — 10,000 works the same way with more batches.
The conversion case is solid: 3x the rate of generic content (Tavus, 2025). The infrastructure is a weekend project. Start with 10 videos, verify your template renders correctly end-to-end, then scale.
Related Articles

The Complete Guide to Automating Video Creation in 2026
Automate video creation in 2026 with templates, AI, and APIs. Production costs dropped from $4,500 to $400 per minute - here's the complete playbook.
April 6, 2026

4 Best Video APIs for Developers in 2026 (Compared)
An honest comparison of the top programmatic video generation APIs for developers. We break down Renderly, Shotstack, Creatomate, and JSON2Video on pricing, features, DX, and scalability.
February 9, 2026

Video API vs Traditional Video Production: Cost Comparison for Businesses
Discover why enterprise companies are saving up to 90% on video production costs by switching from traditional agencies to API-driven video generation platforms.
October 12, 2025