Skip to main content

Overview

Bulk generation allows you to create hundreds or thousands of personalized videos efficiently using templates and automation.
Use Case: Perfect for personalized marketing campaigns, sales outreach, customer onboarding, and more.

Prerequisites

Before bulk generating videos:
You need:
  • A template with defined variables
  • A list of recipients with their data
  • A webhook endpoint to receive completion notifications
  • Sufficient API credits/quota

Basic Bulk Generation

Step 1: Prepare Your Data

// recipients.json
const recipients = [
  {
    name: "Sarah Johnson",
    company: "TechCorp",
    role: "CEO",
    metric: "30% increase in productivity"
  },
  {
    name: "Mike Chen",
    company: "StartupXYZ",
    role: "CTO",
    metric: "50% faster deployment"
  },
  {
    name: "Emma Davis",
    company: "BigCorp Inc",
    role: "VP of Sales",
    metric: "$2M in additional revenue"
  }
  // ... more recipients
];

Step 2: Simple Bulk Generator

async function bulkGenerate(templateId, recipients) {
  const results = [];
  
  for (const recipient of recipients) {
    try {
      const response = await fetch('https://api.slidevid.ai/v1/project/create', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'x-api-key': process.env.SLIDEVID_API_KEY
        },
        body: JSON.stringify({
          type: 'class',
          templateId: templateId,
          title: `Video for ${recipient.name}`,
          scenes: [{
            script: template.scenes[0].script,
            variables: Object.entries(recipient).map(([key, value]) => ({
              key,
              value: String(value)
            }))
          }],
          webhook: `https://yoursite.com/webhook?recipient=${recipient.email}`
        })
      });
      
      const result = await response.json();
      
      if (result.success) {
        results.push({
          recipient: recipient.name,
          projectId: result.data.projectId,
          status: 'queued'
        });
        console.log(`✅ Queued video for ${recipient.name}`);
      } else {
        throw new Error(result.message);
      }
      
      // Rate limiting: wait 1 second between requests
      await new Promise(r => setTimeout(r, 1000));
      
    } catch (error) {
      console.error(`❌ Failed for ${recipient.name}:`, error.message);
      results.push({
        recipient: recipient.name,
        status: 'failed',
        error: error.message
      });
    }
  }
  
  return results;
}

// Execute
const results = await bulkGenerate('template_123', recipients);
console.log(`Generated ${results.filter(r => r.status === 'queued').length}/${recipients.length} videos`);

Advanced Bulk Generation

With Rate Limiting & Retries

class BulkVideoGenerator {
  constructor(apiKey, options = {}) {
    this.apiKey = apiKey;
    this.baseUrl = 'https://api.slidevid.ai/v1';
    this.concurrency = options.concurrency || 5;
    this.retryAttempts = options.retryAttempts || 3;
    this.retryDelay = options.retryDelay || 5000;
  }
  
  async generateOne(templateId, recipient, attempt = 1) {
    try {
      const response = await fetch(`${this.baseUrl}/project/create`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'x-api-key': this.apiKey
        },
        body: JSON.stringify({
          type: 'class',
          templateId,
          title: `Video for ${recipient.name}`,
          scenes: [{
            variables: Object.entries(recipient).map(([key, value]) => ({
              key,
              value: String(value)
            }))
          }],
          webhook: recipient.webhook
        })
      });
      
      const result = await response.json();
      
      if (!response.ok) {
        // Retry on rate limit
        if (response.status === 429 && attempt < this.retryAttempts) {
          const retryAfter = response.headers.get('Retry-After') || this.retryDelay / 1000;
          console.log(`Rate limited. Retrying in ${retryAfter}s...`);
          await new Promise(r => setTimeout(r, retryAfter * 1000));
          return this.generateOne(templateId, recipient, attempt + 1);
        }
        
        throw new Error(result.message);
      }
      
      return {
        success: true,
        recipient: recipient.name,
        projectId: result.data.projectId
      };
      
    } catch (error) {
      if (attempt < this.retryAttempts) {
        console.log(`Retry ${attempt}/${this.retryAttempts} for ${recipient.name}`);
        await new Promise(r => setTimeout(r, this.retryDelay));
        return this.generateOne(templateId, recipient, attempt + 1);
      }
      
      return {
        success: false,
        recipient: recipient.name,
        error: error.message
      };
    }
  }
  
  async generateBulk(templateId, recipients, onProgress) {
    const results = [];
    const batches = [];
    
    // Split into batches based on concurrency
    for (let i = 0; i < recipients.length; i += this.concurrency) {
      batches.push(recipients.slice(i, i + this.concurrency));
    }
    
    let completed = 0;
    
    for (const batch of batches) {
      const batchResults = await Promise.all(
        batch.map(recipient => this.generateOne(templateId, recipient))
      );
      
      results.push(...batchResults);
      completed += batch.length;
      
      if (onProgress) {
        onProgress({
          completed,
          total: recipients.length,
          percentage: Math.round((completed / recipients.length) * 100)
        });
      }
      
      // Wait between batches to respect rate limits
      if (batches.indexOf(batch) < batches.length - 1) {
        await new Promise(r => setTimeout(r, 2000));
      }
    }
    
    return results;
  }
  
  getStats(results) {
    return {
      total: results.length,
      successful: results.filter(r => r.success).length,
      failed: results.filter(r => !r.success).length,
      successRate: Math.round((results.filter(r => r.success).length / results.length) * 100)
    };
  }
}

// Usage
const generator = new BulkVideoGenerator(process.env.SLIDEVID_API_KEY, {
  concurrency: 5,
  retryAttempts: 3,
  retryDelay: 5000
});

const results = await generator.generateBulk(
  'template_123',
  recipients,
  (progress) => {
    console.log(`Progress: ${progress.completed}/${progress.total} (${progress.percentage}%)`);
  }
);

const stats = generator.getStats(results);
console.log('Generation complete:', stats);

Processing CSV Files

Read and Process CSV

import fs from 'fs';
import csv from 'csv-parser';

async function processCSV(filePath, templateId) {
  const recipients = [];
  
  return new Promise((resolve, reject) => {
    fs.createReadStream(filePath)
      .pipe(csv())
      .on('data', (row) => {
        // Validate required fields
        if (row.name && row.email) {
          recipients.push({
            name: row.name,
            email: row.email,
            company: row.company || 'Unknown',
            role: row.role || 'Professional',
            // Add more fields as needed
            webhook: `https://yoursite.com/webhook?email=${row.email}`
          });
        }
      })
      .on('end', async () => {
        console.log(`Loaded ${recipients.length} recipients from CSV`);
        
        const generator = new BulkVideoGenerator(process.env.SLIDEVID_API_KEY);
        const results = await generator.generateBulk(templateId, recipients);
        
        resolve(results);
      })
      .on('error', reject);
  });
}

// Usage
const results = await processCSV('./recipients.csv', 'template_123');

Database Integration

Generate from Database Records

import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

async function generateFromDatabase(templateId, filters = {}) {
  // Fetch recipients from database
  const contacts = await prisma.contact.findMany({
    where: {
      status: 'active',
      hasConsent: true,
      ...filters
    },
    select: {
      id: true,
      name: true,
      email: true,
      company: true,
      customFields: true
    }
  });
  
  console.log(`Found ${contacts.length} contacts in database`);
  
  const generator = new BulkVideoGenerator(process.env.SLIDEVID_API_KEY);
  
  const results = await generator.generateBulk(
    templateId,
    contacts.map(c => ({
      ...c,
      ...c.customFields,
      webhook: `https://yoursite.com/webhook?contactId=${c.id}`
    })),
    async (progress) => {
      // Update progress in database
      await prisma.bulkJob.update({
        where: { id: jobId },
        data: {
          progress: progress.percentage,
          completed: progress.completed
        }
      });
    }
  );
  
  // Save results to database
  for (const result of results) {
    if (result.success) {
      await prisma.video.create({
        data: {
          projectId: result.projectId,
          contactId: result.recipient.id,
          status: 'processing'
        }
      });
    }
  }
  
  return results;
}

const results = await generateFromDatabase('template_123', {
  segment: 'enterprise'
});

Webhook Management for Bulk Jobs

Track Completions

// In-memory tracking (use Redis/Database for production)
const bulkJobs = new Map();

// Start bulk job
app.post('/api/bulk/start', async (req, res) => {
  const { templateId, recipients } = req.body;
  const jobId = `job_${Date.now()}`;
  
  bulkJobs.set(jobId, {
    id: jobId,
    total: recipients.length,
    completed: 0,
    videos: [],
    startedAt: new Date()
  });
  
  // Start generation in background
  generateBulkAsync(jobId, templateId, recipients);
  
  res.json({ jobId, status: 'started' });
});

// Webhook handler
app.post('/api/webhook', async (req, res) => {
  const { projectId, status, video } = req.body;
  const jobId = req.query.jobId;
  
  if (jobId && bulkJobs.has(jobId)) {
    const job = bulkJobs.get(jobId);
    
    job.completed++;
    if (status === 'completed') {
      job.videos.push({
        projectId,
        url: video.url,
        completedAt: new Date()
      });
    }
    
    // Check if job complete
    if (job.completed === job.total) {
      job.completedAt = new Date();
      job.duration = job.completedAt - job.startedAt;
      
      console.log(`Bulk job ${jobId} completed!`);
      console.log(`Generated ${job.videos.length}/${job.total} videos in ${job.duration}ms`);
      
      // Notify user
      await notifyJobComplete(jobId, job);
    }
    
    bulkJobs.set(jobId, job);
  }
  
  res.status(200).send('OK');
});

// Check job status
app.get('/api/bulk/:jobId', (req, res) => {
  const job = bulkJobs.get(req.params.jobId);
  
  if (!job) {
    return res.status(404).json({ error: 'Job not found' });
  }
  
  res.json({
    ...job,
    progress: Math.round((job.completed / job.total) * 100),
    status: job.completedAt ? 'completed' : 'processing'
  });
});

Performance Optimization

Tips for Large Batches

1

Use Concurrency

Process multiple videos in parallel (5-10 concurrent requests)
const concurrency = 5;
2

Implement Queuing

Use a job queue (Bull, BullMQ) for reliability
import Queue from 'bull';

const videoQueue = new Queue('videos', {
  redis: { host: 'localhost', port: 6379 }
});

videoQueue.process(async (job) => {
  return await generateVideo(job.data);
});
3

Cache Templates

Fetch template details once and reuse
const template = await getTemplate(templateId);
// Reuse for all recipients
4

Monitor Rate Limits

Track and respect API rate limits
const rateLimiter = new RateLimiter(100, '1h');

Error Handling Strategies

Partial Failure Recovery

async function generateWithRecovery(templateId, recipients) {
  const results = {
    successful: [],
    failed: [],
    pending: [...recipients]
  };
  
  // Save state to disk/database
  const saveState = () => {
    fs.writeFileSync('bulk-state.json', JSON.stringify(results));
  };
  
  for (const recipient of recipients) {
    try {
      const projectId = await generateOne(templateId, recipient);
      results.successful.push({ recipient, projectId });
      results.pending = results.pending.filter(r => r !== recipient);
      
    } catch (error) {
      results.failed.push({ recipient, error: error.message });
      results.pending = results.pending.filter(r => r !== recipient);
    }
    
    // Save progress
    saveState();
  }
  
  return results;
}

// Resume from saved state
function resumeBulkGeneration() {
  const state = JSON.parse(fs.readFileSync('bulk-state.json'));
  return generateWithRecovery(templateId, state.pending);
}

Best Practices

Start Small

Test with 5-10 videos before scaling to hundreds

Use Webhooks

Never poll for bulk jobs - always use webhooks

Monitor Progress

Track success rates and errors in real-time

Implement Retries

Handle transient failures with exponential backoff

Example: Complete Bulk Campaign

// complete-campaign.js
import { BulkVideoGenerator } from './bulk-generator';
import { sendEmail } from './email-service';

async function runCampaign(campaignId) {
  // 1. Load recipients from database
  const recipients = await db.recipients.findMany({
    where: { campaignId, status: 'pending' }
  });
  
  // 2. Get template
  const campaign = await db.campaign.findUnique({
    where: { id: campaignId },
    include: { template: true }
  });
  
  // 3. Generate videos
  const generator = new BulkVideoGenerator(process.env.SLIDEVID_API_KEY);
  
  const results = await generator.generateBulk(
    campaign.template.id,
    recipients.map(r => ({
      ...r,
      webhook: `https://mysite.com/webhook?recipientId=${r.id}&campaignId=${campaignId}`
    })),
    async (progress) => {
      // Update campaign progress
      await db.campaign.update({
        where: { id: campaignId },
        data: { progress: progress.percentage }
      });
    }
  );
  
  // 4. Log results
  const stats = generator.getStats(results);
  await db.campaign.update({
    where: { id: campaignId },
    data: {
      status: 'completed',
      stats: stats,
      completedAt: new Date()
    }
  });
  
  // 5. Notify admin
  await sendEmail({
    to: campaign.owner.email,
    subject: `Campaign ${campaign.name} completed`,
    text: `Generated ${stats.successful}/${stats.total} videos (${stats.successRate}% success rate)`
  });
  
  return results;
}

// Start campaign
runCampaign('campaign_123');

Next Steps