Skip to main content

Async Workflows

How to use the async API and recommended patterns.

What the async API is

A subset of the Report Flow API endpoints support asynchronous processing:

OperationSyncAsync
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 { url, fileId } = await startPDFGeneration(req.body);

// Respond immediately
res.json({
message: 'Report generation started',
downloadUrl: url,
fileId
});

// 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

  1. Pick a sensible concurrency level: balance server load and throughput.
  2. Use exponential backoff: lengthen polling intervals progressively.
  3. Set timeouts: don't wait forever.
  4. Log errors: keep track of failed items.
  5. Show progress: keep users informed.

Next steps