Async Workflows
The async API hands back a requestId without waiting for generation to finish, and you collect the result afterwards via webhook or polling. Because it sidesteps the 120-second timeout on the sync endpoints, it suits bulk generation and background processing. This guide covers three patterns — parallel, queue-based, and batch — plus retries and partial failures.
What the async API is
A subset of the Re:port Flow API endpoints support asynchronous processing:
| Operation | Sync | Async |
|---|---|---|
| Single PDF | /file/sync/single | /file/async/single |
| Multiple PDFs | /file/sync/multiple | /file/async/multiple |
Why use async
1. Avoid timeouts
Sync endpoints time out at 120 seconds. Async endpoints have no such cap, so they handle long-running jobs gracefully.
// ❌ Sync: may exceed 120s on large payloads
const pdf = await generateSyncPDF(largeData);
// ✅ Async: no client-side wait
const { url } = await generateAsyncPDF(largeData);
2. Background processing
You can return a response to the user immediately and finish the work behind the scenes.
// Accept the user's request right away
app.post('/api/generate-report', async (req, res) => {
// Kick off PDF generation in the background
const { requestId, url, fileId } = await startPDFGeneration(req.body);
// Respond immediately
res.json({
message: 'Report generation started',
requestId,
fileId,
// `url` is an app-screen (output history) link. It requires a login and is
// not a direct download URL. Fetch the file from the download API using
// requestId / fileId (see the authentication guide for credentials).
outputsUrl: url
});
// Notify by email when finished (background)
notifyWhenComplete(fileId, req.user.email);
});
3. Parallel bulk generation
You can fan out and generate many PDFs in parallel.
// Generate 100 PDFs in parallel
const requests = invoices.map(invoice =>
generateAsyncPDF({
designId: 'invoice-template',
content: invoice
})
);
const results = await Promise.all(requests);
console.log(`Generated ${results.length} PDFs`);
Bulk-processing patterns
Pattern 1: Parallel execution (small/medium scale)
async function generateBulkPDFs(items, concurrency = 5) {
const chunks = chunkArray(items, concurrency);
const results = [];
for (const chunk of chunks) {
const promises = chunk.map(item =>
generateAsyncPDF({
designId: 'template-id',
content: item
})
);
const chunkResults = await Promise.all(promises);
results.push(...chunkResults);
console.log(`${results.length}/${items.length} done`);
}
return results;
}
function chunkArray(array, size) {
const chunks = [];
for (let i = 0; i < array.length; i += size) {
chunks.push(array.slice(i, i + size));
}
return chunks;
}
// Usage: 100 items, 5 in parallel
const invoices = [...]; // 100 items
const results = await generateBulkPDFs(invoices, 5);
Pattern 2: Queue-based (large scale)
import Queue from 'bull';
// Create a Redis-backed queue
const pdfQueue = new Queue('pdf-generation', {
redis: {
host: 'localhost',
port: 6379
}
});
// Worker
pdfQueue.process(async (job) => {
const { designId, content } = job.data;
// Call the PDF generation API
const result = await generateAsyncPDF({ designId, content });
// Update progress
await job.progress(100);
return result;
});
// Enqueue jobs
async function enqueuePDFGeneration(items) {
const jobs = items.map(item =>
pdfQueue.add({
designId: 'template-id',
content: item
}, {
attempts: 3,
backoff: {
type: 'exponential',
delay: 2000
}
})
);
return Promise.all(jobs);
}
// Listen for events
pdfQueue.on('completed', (job, result) => {
console.log(`Job ${job.id} completed:`, result);
});
pdfQueue.on('failed', (job, err) => {
console.error(`Job ${job.id} failed:`, err);
});
Pattern 3: Batch processing (very large scale)
// For 1000+ items, prefer the multi-file generation endpoint
async function generateMassiveBulk(items) {
const batchSize = 100; // align with the API limit
const batches = chunkArray(items, batchSize);
const results = [];
for (const [index, batch] of batches.entries()) {
console.log(`Processing batch ${index + 1}/${batches.length}…`);
// Multi-file async generation
const { url, files } = await generateMultiplePDFsAsync({
designId: 'template-id',
contents: batch.map(item => ({
fileName: `${item.id}.pdf`,
params: item
}))
});
results.push({ batchIndex: index, url, files });
// Throttle for rate limits
if (index < batches.length - 1) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
return results;
}
Error handling
Retry logic
async function generateWithRetry(params, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await generateAsyncPDF(params);
} catch (error) {
const isLastAttempt = attempt === maxRetries - 1;
if (isServerError(error) && !isLastAttempt) {
const delay = 1000 * Math.pow(2, attempt);
console.log(`Retry ${attempt + 1}/${maxRetries} (after ${delay}ms)`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
throw error;
}
}
}
function isServerError(error) {
return error.response?.status >= 500;
}
Partial-failure handling
async function generateWithFallback(items) {
const results = [];
const errors = [];
for (const item of items) {
try {
const result = await generateAsyncPDF({
designId: 'template-id',
content: item
});
results.push({ item, result, status: 'success' });
} catch (error) {
console.error(`Failed to generate item ${item.id}:`, error);
errors.push({ item, error, status: 'failed' });
}
}
return {
results,
errors,
summary: {
total: items.length,
success: results.length,
failed: errors.length
}
};
}
Python example
import asyncio
import aiohttp
from typing import List, Dict, Any
async def generate_pdf_async(session: aiohttp.ClientSession, params: Dict[str, Any]) -> Dict:
"""Generate a PDF asynchronously."""
url = "https://api.re-port-flow.com/v1/file/async/single"
headers = {
'appkey': params['app_key'],
'Content-Type': 'application/json'
}
data = {
'designId': params['design_id'],
'version': params['version'],
'content': params['content']
}
async with session.post(url, headers=headers, json=data) as response:
response.raise_for_status()
return await response.json()
async def generate_bulk_pdfs(
items: List[Dict],
app_key: str,
design_id: str,
version: int = 1,
concurrency: int = 5,
):
"""Generate multiple PDFs in parallel, sharing one ClientSession so connection pooling is effective."""
semaphore = asyncio.Semaphore(concurrency)
async with aiohttp.ClientSession() as session:
async def generate_with_limit(item):
async with semaphore:
return await generate_pdf_async(session, {
'app_key': app_key,
'design_id': design_id,
'version': version,
'content': item,
})
tasks = [generate_with_limit(item) for item in items]
return await asyncio.gather(*tasks)
# Usage
items = [...] # data to generate from
results = asyncio.run(generate_bulk_pdfs(
items,
app_key='your-app-key',
design_id='template-id',
version=1,
concurrency=10,
))
Best practices
- Pick a sensible concurrency level: balance server load and throughput.
- Use exponential backoff: lengthen polling intervals progressively.
- Set timeouts: don't wait forever.
- Log errors: keep track of failed items.
- Show progress: keep users informed.