Batch Document Generation
Generate hundreds or thousands of documents in a single API call using Rynko 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 { Rynko } from '@rynko/sdk';
const client = new Rynko({ apiKey: process.env.RYNKO_API_KEY });
// Queue batch generation (async operation)
const batch = await client.documents.generateBatch({
templateId: 'invoice-template',
format: 'pdf',
documents: [
{ invoiceNumber: 'INV-001', customerName: 'Acme Corporation', amount: 1500.00 },
{ invoiceNumber: 'INV-002', customerName: 'Beta Industries', amount: 2300.00 },
{ invoiceNumber: 'INV-003', customerName: 'Gamma Systems', amount: 890.00 },
]
});
console.log('Batch ID:', batch.batchId);
console.log('Total jobs:', batch.totalJobs);
console.log('Estimated wait:', batch.estimatedWaitSeconds, 'seconds');
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.errorMessage}`);
}
// 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.errorMessage}`);
}
}
Using Webhooks (Recommended)
For production, use webhooks instead of polling:
1. Set Up Webhook
const webhook = await client.webhooks.create({
url: 'https://your-server.com/webhooks/rynko',
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 '@rynko/sdk';
const app = express();
app.post('/webhooks/rynko', express.raw({ type: 'application/json' }), (req, res) => {
try {
const event = verifyWebhookSignature({
payload: req.body.toString(),
signature: req.headers['x-rynko-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.errorMessage}`);
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 { Rynko } from '@rynko/sdk';
import { getCustomersWithPendingInvoices, saveInvoiceUrl } from './database';
async function generateMonthlyInvoices() {
const client = new Rynko({ apiKey: process.env.RYNKO_API_KEY });
// Get all customers that need invoices
const customers = await getCustomersWithPendingInvoices();
console.log(`Generating ${customers.length} invoices...`);
// Prepare batch documents - each object contains the template variables
const documents = customers.map(customer => ({
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()
}));
// Submit batch (async operation)
const batch = await client.documents.generateBatch({
templateId: 'monthly-invoice',
format: 'pdf',
documents,
metadata: {
billingPeriod: getMonthYear()
}
});
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
| Plan | Max Documents per Batch | Concurrent Batches |
|---|---|---|
| Free | 10 | 1 |
| Starter | 100 | 3 |
| Growth | 500 | 10 |
| Scale | 1000 | Unlimited |
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.errorMessage}`);
// Retry logic
if (doc.errorMessage.includes('temporary')) {
await retryDocument(doc);
}
}
Best Practices
1. Use Meaningful Filenames
You can include custom filenames in the batch-level metadata for tracking:
const batch = await client.documents.generateBatch({
templateId: 'invoice-template',
format: 'pdf',
documents: customers.map(c => ({
invoiceNumber: c.invoiceNumber,
customerName: c.name,
// ... other variables
})),
metadata: {
batchName: `invoices-${month}`,
generatedAt: new Date().toISOString()
}
});
2. Include Metadata for Tracking
Use batch-level metadata to track the generation context:
const batch = await client.documents.generateBatch({
templateId: 'invoice-template',
format: 'pdf',
documents: invoiceVariables,
metadata: {
type: 'monthly-invoice',
billingPeriod: getMonthYear(),
triggeredBy: 'billing-cron-job'
}
});
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
- Set up webhooks for production batch processing
- Generate Excel reports for data exports
- API Reference for full batch API details