Why Manual Invoice Creation Does Not Scale
Creating invoices manually works when you have five clients. It stops working at fifty. By five hundred, it's a full-time job that nobody wants.
The manual process looks something like this: open a template in Word or Google Docs, replace the client name, update line items, adjust the totals, export to PDF, rename the file, email it. Repeat for every client, every month.
This process is error-prone, time-consuming and completely unnecessary in 2026. Any invoice that follows a consistent format can be generated programmatically from a template.
How Template-Based Invoice Generation Works
The concept is straightforward:
- Design a template - Create your invoice layout once, with placeholders where dynamic data goes
- Define variables - Map each placeholder to a data field (client name, invoice number, line items, total)
- Call an API - Send your variable data to a generation endpoint, receive a formatted PDF
- Deliver - Email the PDF to your client or store it in your system
The template stays the same. Only the data changes.
Setting Up an Invoice Template
A well structured invoice template needs the following variable placeholders:
Header Variables
{{company_name}}- Your business name{{company_address}}- Your address block{{company_logo}}- Your logo (HTML content type for image embedding){{invoice_number}}- Unique identifier{{invoice_date}}- Issue date{{due_date}}- Payment deadline
Client Variables
{{client_name}}- Recipient name{{client_address}}- Recipient address{{client_email}}- For reference
Line Item Variables
{{line_items}}- An HTML table with all billable items (HTML content type for rich formatting){{subtotal}}- Pre-tax amount{{tax_rate}}- Applicable tax percentage{{tax_amount}}- Calculated tax{{total}}- Final amount due
Footer Variables
{{payment_instructions}}- Bank details or payment link{{notes}}- Optional terms or notes
Variable Content Types
Not all variables are plain text. Quaterio supports multiple content types for different rendering needs:
Text - Simple string replacement. HTML is escaped for safety. Use for names, numbers, dates.
HTML - Rendered as formatted HTML. Use for line item tables, styled addresses or any content that needs structure.
Markdown - Parsed to HTML, then rendered. Useful for notes or descriptions that benefit from basic formatting.
QR Code - Renders as an SVG QR code. Pass a payment URL or reference string, get a scannable code on the invoice.
Barcode - Renders as an SVG barcode (CODE128, EAN13 and other formats). Useful for invoice reference numbers that need machine-readable encoding.
Generating a Single Invoice via API
Once your template is designed in the Quaterio editor, generating a PDF is a single API call:
const response = await fetch('https://quaterio.com/api/v1/generate/template', {
method: 'POST',
headers: {
'Authorization': 'Bearer q_your_api_key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
templateId: 'inv-template-2026',
variables: {
company_name: 'Triple Down AB',
invoice_number: 'INV-2026-0547',
invoice_date: '2026-05-16',
due_date: '2026-06-16',
client_name: 'Acme Corporation',
client_address: '123 Business Avenue\nStockholm, Sweden',
line_items: `
<table>
<thead>
<tr><th>Description</th><th>Qty</th><th>Unit Price</th><th>Amount</th></tr>
</thead>
<tbody>
<tr><td>Consulting - May 2026</td><td>40</td><td>$150</td><td>$6,000</td></tr>
<tr><td>Development Sprint 12</td><td>1</td><td>$8,500</td><td>$8,500</td></tr>
</tbody>
</table>
`,
subtotal: '$14,500',
tax_rate: '25%',
tax_amount: '$3,625',
total: '$18,125',
payment_instructions: 'Bank: SEB\nAccount: 1234-5678-9012\nReference: INV-2026-0547',
},
}),
});
const { url } = await response.json();
console.log('Invoice PDF:', url);
The API returns a download URL for your generated PDF. The invoice inherits all formatting, pagination, headers and footers from your template design.
Batch Generation for Monthly Billing
Generating invoices one at a time works for ad-hoc needs. For monthly billing cycles where you need to generate dozens or hundreds of invoices, use the batch endpoint:
const invoiceData = [
{
client_name: 'Acme Corp',
invoice_number: 'INV-2026-0547',
total: '$18,125',
// ... other variables
},
{
client_name: 'Globex Inc',
invoice_number: 'INV-2026-0548',
total: '$4,200',
// ... other variables
},
// ... up to 1000 rows per batch
];
const response = await fetch('https://quaterio.com/api/v1/generate/batch', {
method: 'POST',
headers: {
'Authorization': 'Bearer q_your_api_key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
templateId: 'inv-template-2026',
rows: invoiceData,
concurrency: 10, // parallel generation (max 20)
}),
});
const results = await response.json();
// results: [{ success: true, url: "..." }, { success: true, url: "..." }, ...]
Each row in the array produces a separate PDF. The batch endpoint processes them with configurable concurrency, so you're not waiting sequentially for each generation to complete.
Integrating with Your Billing System
Here's a practical example of connecting invoice generation to a billing workflow:
import { createClient } from '@supabase/supabase-js';
async function generateMonthlyInvoices() {
// 1. Fetch all active subscriptions with billing due
const { data: subscriptions } = await supabase
.from('subscriptions')
.select('*, customers(*)')
.eq('status', 'active')
.lte('next_billing_date', new Date().toISOString());
// 2. Transform subscription data into invoice variables
const rows = subscriptions.map((sub) => ({
client_name: sub.customers.name,
client_address: sub.customers.address,
invoice_number: generateInvoiceNumber(),
invoice_date: new Date().toISOString().split('T')[0],
due_date: addDays(new Date(), 30).toISOString().split('T')[0],
line_items: buildLineItemsHtml(sub.line_items),
total: formatCurrency(sub.amount),
// ... remaining variables
}));
// 3. Generate all invoices in one batch call
const response = await fetch('https://quaterio.com/api/v1/generate/batch', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.QUARTERIO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
templateId: process.env.INVOICE_TEMPLATE_ID,
rows,
concurrency: 15,
}),
});
const results = await response.json();
// 4. Store PDF URLs and send to customers
for (let i = 0; i < results.length; i++) {
if (results[i].success) {
await sendInvoiceEmail(subscriptions[i].customers.email, results[i].url);
await recordInvoice(subscriptions[i].id, results[i].url);
}
}
}
Tips for Invoice Template Design
Keep it simple - Invoices should be scannable. Use clear hierarchy: company header, client details, line items table, totals, payment info.
Use HTML content type for tables - Line items are best passed as an HTML table variable. This gives you full control over columns, alignment and styling.
Include a QR code - Add a {{payment_qr}} variable with content type qrcode. Pass a payment URL (like a Stripe payment link) and your invoice becomes interactive.
Add a barcode for reference - A {{reference_barcode}} variable with content type barcode makes your invoice number machine-readable for automated payment matching.
Test with edge cases - Generate test invoices with very long client names, many line items (to test pagination) and large amounts to ensure your layout handles everything gracefully.
Pricing Considerations
Quaterio's API is available on Business plans and above. The batch endpoint supports up to 100 rows per call on Business tier and up to 1,000 on Enterprise. For most small-to-medium billing operations, the Business tier is sufficient.
If you're generating thousands of invoices monthly, the Enterprise tier removes rate limits and adds priority processing.
Get Started
- Sign up for Quaterio and create a Business account
- Design your invoice template in the visual editor
- Add template variables for all dynamic fields
- Generate your API key in the organization settings
- Integrate the generation endpoint into your billing workflow
The entire setup - from template design to first generated invoice - typically takes less than an hour.