Skip to main content

Batch Document Generation

Generate hundreds or thousands of documents in a single API call using RenderDoc batch generation.

When to Use Batch Generation

Batch generation is ideal for:

  • Monthly invoicing - Generate invoices for all customers at once
  • Report distribution - Create personalized reports for each team member
  • Certificate generation - Produce certificates for all event attendees
  • Contract processing - Generate contracts for multiple parties

Basic Batch Request

import { RenderDoc } from '@renderdoc/sdk';

const client = new RenderDoc({ apiKey: process.env.RENDERDOC_API_KEY });

const result = await client.documents.generateBatch({
templateId: 'invoice-template',
format: 'pdf',
documents: [
{
variables: {
invoiceNumber: 'INV-001',
customerName: 'Acme Corporation',
amount: 1500.00
},
filename: 'invoice-acme'
},
{
variables: {
invoiceNumber: 'INV-002',
customerName: 'Beta Industries',
amount: 2300.00
},
filename: 'invoice-beta'
},
{
variables: {
invoiceNumber: 'INV-003',
customerName: 'Gamma Systems',
amount: 890.00
},
filename: 'invoice-gamma'
}
]
});

console.log('Batch ID:', result.batchId);
console.log('Total documents:', result.totalDocuments);

Batch Response

{
"batchId": "batch_xyz789abc",
"status": "processing",
"totalDocuments": 3,
"completedDocuments": 0,
"failedDocuments": 0,
"createdAt": "2025-01-15T10:00:00Z"
}

Checking Batch Status

Poll the batch status endpoint to monitor progress:

async function waitForBatch(client, batchId) {
console.log(`Waiting for batch ${batchId}...`);

while (true) {
const status = await client.documents.getBatch(batchId);

console.log(`Progress: ${status.completedDocuments}/${status.totalDocuments}`);

if (status.status === 'completed') {
console.log('Batch completed!');
return status;
}

if (status.status === 'failed') {
throw new Error(`Batch failed: ${status.error}`);
}

// Wait 2 seconds before checking again
await new Promise(resolve => setTimeout(resolve, 2000));
}
}

const batchResult = await waitForBatch(client, result.batchId);

// Download all documents
for (const doc of batchResult.documents) {
if (doc.status === 'completed') {
console.log(`Download: ${doc.downloadUrl}`);
} else {
console.error(`Failed: ${doc.jobId} - ${doc.error}`);
}
}

For production, use webhooks instead of polling:

1. Set Up Webhook

const webhook = await client.webhooks.create({
url: 'https://your-server.com/webhooks/renderdoc',
events: ['batch.completed', 'document.generated', 'document.failed']
});

console.log('Webhook secret:', webhook.secret);

2. Submit Batch

const result = await client.documents.generateBatch({
templateId: 'invoice-template',
format: 'pdf',
documents: invoiceData,
metadata: {
batchName: 'January 2025 Invoices',
triggeredBy: 'monthly-billing-job'
}
});

console.log('Batch submitted:', result.batchId);
// Don't wait - webhook will notify when complete

3. Handle Webhook Events

import express from 'express';
import { verifyWebhookSignature } from '@renderdoc/sdk';

const app = express();

app.post('/webhooks/renderdoc', express.raw({ type: 'application/json' }), (req, res) => {
try {
const event = verifyWebhookSignature({
payload: req.body.toString(),
signature: req.headers['x-renderdoc-signature'],
secret: process.env.WEBHOOK_SECRET
});

switch (event.type) {
case 'batch.completed':
console.log(`Batch ${event.data.batchId} completed!`);
console.log(`Generated: ${event.data.completedDocuments}`);
console.log(`Failed: ${event.data.failedDocuments}`);
processBatchResults(event.data);
break;

case 'document.generated':
console.log(`Document ready: ${event.data.downloadUrl}`);
break;

case 'document.failed':
console.error(`Document failed: ${event.data.error}`);
break;
}

res.status(200).send('OK');
} catch (error) {
console.error('Webhook error:', error);
res.status(400).send('Invalid signature');
}
});

Real-World Example: Monthly Invoice Generation

import { RenderDoc } from '@renderdoc/sdk';
import { getCustomersWithPendingInvoices, saveInvoiceUrl } from './database';

async function generateMonthlyInvoices() {
const client = new RenderDoc({ apiKey: process.env.RENDERDOC_API_KEY });

// Get all customers that need invoices
const customers = await getCustomersWithPendingInvoices();

console.log(`Generating ${customers.length} invoices...`);

// Prepare batch documents
const documents = customers.map(customer => ({
variables: {
invoiceNumber: `INV-${Date.now()}-${customer.id}`,
customerName: customer.name,
customerEmail: customer.email,
address: customer.address,
lineItems: customer.pendingCharges,
subtotal: customer.subtotal,
tax: customer.tax,
total: customer.total,
dueDate: getNextMonthDate()
},
filename: `invoice-${customer.id}-${getMonthYear()}`,
metadata: {
customerId: customer.id,
billingPeriod: getMonthYear()
}
}));

// Submit batch
const batch = await client.documents.generateBatch({
templateId: 'monthly-invoice',
format: 'pdf',
documents
});

console.log(`Batch submitted: ${batch.batchId}`);

// Wait for completion
const result = await waitForBatch(client, batch.batchId);

// Save download URLs to database
for (const doc of result.documents) {
if (doc.status === 'completed') {
await saveInvoiceUrl(doc.metadata.customerId, doc.downloadUrl);
}
}

console.log(`Generated ${result.completedDocuments} invoices`);
console.log(`Failed: ${result.failedDocuments}`);
}

Batch Limits

PlanMax Documents per BatchConcurrent Batches
Free101
Starter1003
Growth50010
Scale1000Unlimited

Error Handling

Some documents may fail while others succeed:

const result = await waitForBatch(client, batchId);

const successful = result.documents.filter(d => d.status === 'completed');
const failed = result.documents.filter(d => d.status === 'failed');

console.log(`Successful: ${successful.length}`);
console.log(`Failed: ${failed.length}`);

// Handle failures
for (const doc of failed) {
console.error(`Document ${doc.jobId} failed: ${doc.error}`);

// Retry logic
if (doc.error.includes('temporary')) {
await retryDocument(doc);
}
}

Best Practices

1. Use Meaningful Filenames

documents: customers.map(c => ({
variables: { ... },
filename: `invoice-${c.name.toLowerCase().replace(/\s+/g, '-')}-${month}`
}))

2. Include Metadata for Tracking

documents: customers.map(c => ({
variables: { ... },
metadata: {
customerId: c.id,
orderId: c.orderId,
type: 'monthly-invoice'
}
}))

3. Process in Chunks for Large Batches

const BATCH_SIZE = 500;

async function processLargeBatch(allDocuments) {
const batches = [];

for (let i = 0; i < allDocuments.length; i += BATCH_SIZE) {
const chunk = allDocuments.slice(i, i + BATCH_SIZE);

const batch = await client.documents.generateBatch({
templateId: 'invoice-template',
format: 'pdf',
documents: chunk
});

batches.push(batch.batchId);
}

// Wait for all batches
const results = await Promise.all(
batches.map(id => waitForBatch(client, id))
);

return results;
}

Next Steps