Invoicing is one of those tasks that looks simple until you actually have to do it at scale. Generating one invoice for a one-off client is easy — open a template in Word, fill in the fields, save as PDF, send. But when you have dozens of clients, recurring engagements, and varying line items, the manual approach becomes unsustainable quickly.
Dedicated invoicing software is one option, but it comes with subscription costs, vendor lock-in, and yet another dashboard to manage. A better approach for developers: generate invoices programmatically from your own billing data, using the ReportForge invoice template. Your data stays in your systems, and invoices are generated on demand with a single API call.
What the Invoice Template Produces
The invoice template generates a complete, print-ready HTML invoice with:
- Invoice number, date, and client name/email rendered in a professional header
- Line items table with description, quantity, unit price, and amount columns
- Subtotal, tax calculation (if a
tax_ratecolumn is provided), and grand total - Payment terms section
- Print-optimized CSS — Ctrl+P from any browser gives a clean PDF
The output is self-contained HTML with all styles inline, so it renders correctly in email clients and can be saved as a standalone file.
CSV Schema for the Invoice Template
The invoice template expects your CSV to have one row per line item. Client information (name, email, invoice number) is read from the first row and applied to the invoice header.
| Column | Required | Description |
|---|---|---|
| description | Required | Line item description (e.g., "Web Development") |
| amount | Required | Line item total in decimal (e.g., 6000.00) |
| quantity | Optional | Units, hours, or quantity for the line item |
| unit_price | Optional | Price per unit (displayed alongside quantity) |
| invoice_number | Optional | Invoice ID — displayed in the header (read from row 1) |
| client_name | Optional | Client or company name — displayed in the header |
| client_email | Optional | Client email address — displayed in the header |
description,quantity,unit_price,amount,invoice_number,client_name,client_email Web Development,40,150.00,6000.00,INV-2026-042,Acme Corp,billing@acme.com UI Design,20,125.00,2500.00,INV-2026-042,Acme Corp,billing@acme.com Project Management,10,100.00,1000.00,INV-2026-042,Acme Corp,billing@acme.com Code Review,5,150.00,750.00,INV-2026-042,Acme Corp,billing@acme.com
Note: The invoice_number, client_name, and client_email values are read from the first data row and applied to the entire invoice. You do not need to repeat them on every line item — the API reads them once.
Node.js Integration Example
Here is a complete invoice generation function that takes a billing record from your database and produces a ready-to-send HTML invoice. This pattern fits naturally into a webhook handler or a billing event listener.
const API_URL = 'https://reportforge-api.vercel.app/api/csv-to-report'; /** * Generate an HTML invoice from a billing record. * @param {Object} billing - Your internal billing object * @returns {Promise<string>} Complete HTML invoice */ export async function generateInvoice(billing) { // Build CSV rows from line items const headers = ['description', 'quantity', 'unit_price', 'amount', 'invoice_number', 'client_name', 'client_email']; const rows = billing.lineItems.map((item, index) => { return [ `"${item.description}"`, item.quantity, item.unitPrice.toFixed(2), item.amount.toFixed(2), index === 0 ? billing.invoiceNumber : billing.invoiceNumber, // same on every row index === 0 ? `"${billing.clientName}"` : `"${billing.clientName}"`, index === 0 ? billing.clientEmail : billing.clientEmail, ].join(','); }); const csv = [headers.join(','), ...rows].join('\n'); const response = await fetch(API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': process.env.REPORTFORGE_API_KEY, }, body: JSON.stringify({ csv, template: 'invoice', title: `Invoice ${billing.invoiceNumber}`, }), }); if (!response.ok) { const err = await response.json(); throw new Error(`Invoice generation failed: ${err.error}`); } const { html } = await response.json(); return html; }
Wiring Into a Stripe Webhook
A common pattern is to generate and email an invoice automatically when a payment succeeds in Stripe. Here is how to connect the two:
import express from 'express'; import Stripe from 'stripe'; import { generateInvoice } from './generate-invoice.js'; import { sendInvoiceEmail } from './mailer.js'; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY); const app = express(); app.post('/webhooks/stripe', express.raw({ type: 'application/json' }), async (req, res) => { const sig = req.headers['stripe-signature']; let event; try { event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET); } catch (err) { return res.status(400).send(`Webhook Error: ${err.message}`); } if (event.type === 'payment_intent.succeeded') { const pi = event.data.object; // Build a billing record from your database using pi.metadata const billing = await getBillingRecord(pi.metadata.orderId); // Generate the invoice HTML const invoiceHtml = await generateInvoice(billing); // Email it to the client await sendInvoiceEmail({ to: billing.clientEmail, subject: `Invoice ${billing.invoiceNumber} — Payment Confirmed`, html: invoiceHtml, }); console.log(`Invoice ${billing.invoiceNumber} sent to ${billing.clientEmail}`); } res.json({ received: true }); });
Python Integration Example
Here is the same invoice generation logic in Python, using dataclasses to model your billing structure and requests for the API call.
from dataclasses import dataclass from typing import List import csv import io import os import requests @dataclass class LineItem: description: str quantity: float unit_price: float amount: float @dataclass class BillingRecord: invoice_number: str client_name: str client_email: str line_items: List[LineItem] def generate_invoice(billing: BillingRecord) -> str: """ Generate a professional HTML invoice from a BillingRecord. Returns the complete HTML string. """ fieldnames = ["description", "quantity", "unit_price", "amount", "invoice_number", "client_name", "client_email"] buf = io.StringIO() writer = csv.DictWriter(buf, fieldnames=fieldnames) writer.writeheader() for item in billing.line_items: writer.writerow({ "description": item.description, "quantity": item.quantity, "unit_price": f"{item.unit_price:.2f}", "amount": f"{item.amount:.2f}", "invoice_number": billing.invoice_number, "client_name": billing.client_name, "client_email": billing.client_email, }) resp = requests.post( "https://reportforge-api.vercel.app/api/csv-to-report", json={ "csv": buf.getvalue(), "template": "invoice", "title": f"Invoice {billing.invoice_number}", }, headers={"X-API-Key": os.environ["REPORTFORGE_API_KEY"]}, timeout=10, ) resp.raise_for_status() return resp.json()["html"] # Example usage billing = BillingRecord( invoice_number="INV-2026-042", client_name="Acme Corp", client_email="billing@acme.com", line_items=[ LineItem("Web Development", 40, 150.00, 6000.00), LineItem("UI Design", 20, 125.00, 2500.00), LineItem("Project Management", 10, 100.00, 1000.00), ], ) html = generate_invoice(billing) with open("invoice-INV-2026-042.html", "w") as f: f.write(html) print("Invoice saved.")
Numbering Invoices Automatically
Professional invoice numbering typically follows a pattern like INV-YYYY-NNN. Here is a simple sequence generator using a database counter that guarantees unique, incrementing invoice numbers even under concurrent load.
import { pool } from './db.js'; export async function nextInvoiceNumber() { const year = new Date().getFullYear(); // Atomic increment — safe under concurrent requests const { rows } = await pool.query( `INSERT INTO invoice_sequences (year, last_seq) VALUES ($1, 1) ON CONFLICT (year) DO UPDATE SET last_seq = invoice_sequences.last_seq + 1 RETURNING last_seq`, [year] ); const seq = String(rows[0].last_seq).padStart(3, '0'); return `INV-${year}-${seq}`; } // Returns: INV-2026-001, INV-2026-002, INV-2026-003...
Storing and Retrieving Generated Invoices
For compliance and audit purposes, you should persist generated invoices alongside your billing records. The HTML string can be stored in your database as a text field, or you can save it to object storage (S3, Vercel Blob, Cloudflare R2) and store the URL.
import { put } from '@vercel/blob'; import { pool } from './db.js'; export async function storeInvoice(invoiceNumber, html) { // Upload HTML to Vercel Blob storage const { url } = await put( `invoices/${invoiceNumber}.html`, html, { access: 'private', contentType: 'text/html' } ); // Save URL to database for retrieval await pool.query( 'UPDATE invoices SET html_url = $1, generated_at = NOW() WHERE invoice_number = $2', [url, invoiceNumber] ); return url; }
Rate Limits and Scaling
The free tier allows 5 reports per day, which is enough to build and test your integration. For production invoice systems:
- Starter plan ($9/month) — 100 invoices per day. Suitable for small businesses with daily billing.
- Business plan ($29/month) — Unlimited invoices per day. No input size limits. Right for high-volume billing pipelines.
For burst scenarios (e.g., month-end billing runs), consider batching your invoice generation requests with a small delay between calls to stay within rate limits on the Starter plan.
Summary
The ReportForge invoice template gives you a complete, professional invoice generation system without dedicated invoicing software. Your billing data lives in your own database, invoices are generated on demand via API, and the output is print-ready HTML that works in any browser and email client.
The Node.js and Python examples above cover the most common integration patterns — connect to your billing system, call the API, and deliver the invoice wherever it needs to go.
Start Generating Invoices Free
Try the invoice template right now — no API key or sign-up required.
Try It Live →