Get your API key
AI invoice generator: build one with Claude and PDF4.dev in 10 minutes

AI invoice generator: build one with Claude and PDF4.dev in 10 minutes

Generate professional PDF invoices with AI using Claude and PDF4.dev. Full tutorial with Node.js code, Handlebars templates, and MCP integration examples.

11 min read

AI invoice generators have gone from novelty to production pattern in 18 months. The combination of an LLM (for understanding and structuring unstructured input) and a PDF rendering API (for pixel-perfect output) produces invoices in under 5 seconds from a plain-text prompt. This guide builds one end-to-end.

What is an AI invoice generator?

An AI invoice generator is a pipeline that combines three components: a large language model that extracts and structures invoice data from free-text or semi-structured input, an HTML template that defines the visual layout, and a PDF rendering API that merges the two and returns a binary PDF. Each component does what it does best: the LLM handles language, the template handles design, and the API handles rendering.

The alternative, form-based invoice generators, require the user to fill every field manually. An AI generator accepts natural language like "invoice Acme Corp $2400 for 4 days of API consulting, net 30" and populates all fields automatically.

Architecture overview

ComponentRoleExample
LLM APIParse input, return structured JSONClaude 3.5 Haiku, GPT-4o-mini
HTML templateVisual layout with variablesHandlebars, stored in PDF4.dev
PDF rendering APIMerge template + data, return PDFPDF4.dev /api/v1/render
DeliveryStore or send the PDFS3/R2, Resend, file download

This separation of concerns is the core design principle. Changing the template doesn't require touching the LLM prompt. Adding a new data field means updating the JSON schema and the template, not the rendering layer.

Step 1: design the invoice HTML template

A good invoice template is standard HTML and CSS. PDF4.dev renders it through Chromium, so any CSS that works in Chrome works here. Handlebars syntax ({{variable}}) marks dynamic fields.

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <style>
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body {
      font-family: 'Inter', sans-serif;
      font-size: 14px;
      color: #111;
      padding: 48px;
    }
    .header { display: flex; justify-content: space-between; margin-bottom: 40px; }
    .company-name { font-size: 24px; font-weight: 700; }
    .invoice-meta { text-align: right; }
    .invoice-number { font-size: 20px; font-weight: 600; color: #2563eb; }
    table { width: 100%; border-collapse: collapse; margin-top: 32px; }
    th { background: #f1f5f9; text-align: left; padding: 10px 12px; font-size: 12px; text-transform: uppercase; letter-spacing: 0.05em; }
    td { padding: 10px 12px; border-bottom: 1px solid #e2e8f0; }
    .total-row td { font-weight: 700; font-size: 16px; border-bottom: none; padding-top: 16px; }
    .due-date { color: #dc2626; font-weight: 600; }
  </style>
</head>
<body>
  <div class="header">
    <div>
      <div class="company-name">{{from.name}}</div>
      <div>{{from.address}}</div>
      <div>{{from.email}}</div>
    </div>
    <div class="invoice-meta">
      <div class="invoice-number">Invoice {{invoiceNumber}}</div>
      <div>Issued: {{issuedAt}}</div>
      <div class="due-date">Due: {{dueDate}}</div>
    </div>
  </div>
 
  <div>
    <strong>Bill to:</strong>
    <div>{{to.name}}</div>
    <div>{{to.email}}</div>
  </div>
 
  <table>
    <thead>
      <tr>
        <th>Description</th>
        <th>Qty</th>
        <th>Unit price</th>
        <th>Total</th>
      </tr>
    </thead>
    <tbody>
      {{#each items}}
      <tr>
        <td>{{description}}</td>
        <td>{{quantity}}</td>
        <td>${{unitPrice}}</td>
        <td>${{total}}</td>
      </tr>
      {{/each}}
    </tbody>
    <tfoot>
      <tr class="total-row">
        <td colspan="3">Total due</td>
        <td>${{totalAmount}}</td>
      </tr>
    </tfoot>
  </table>
 
  {{#if notes}}
  <p style="margin-top: 32px; color: #64748b; font-size: 12px;">{{notes}}</p>
  {{/if}}
</body>
</html>

Save this template in PDF4.dev's template manager. Note the variable names: from.name, to.name, items[].description, totalAmount. These must match exactly what the LLM returns.

Step 2: define the JSON schema for the LLM

The JSON schema drives Claude's structured output. Every field in the schema maps to a Handlebars variable in the template.

const INVOICE_SCHEMA = {
  type: "object",
  properties: {
    invoiceNumber: { type: "string", description: "Invoice number, e.g. INV-2026-001" },
    issuedAt: { type: "string", description: "Issue date in DD MMM YYYY format" },
    dueDate: { type: "string", description: "Due date in DD MMM YYYY format" },
    from: {
      type: "object",
      properties: {
        name: { type: "string" },
        address: { type: "string" },
        email: { type: "string" }
      },
      required: ["name", "email"]
    },
    to: {
      type: "object",
      properties: {
        name: { type: "string" },
        email: { type: "string" }
      },
      required: ["name", "email"]
    },
    items: {
      type: "array",
      items: {
        type: "object",
        properties: {
          description: { type: "string" },
          quantity: { type: "number" },
          unitPrice: { type: "number" },
          total: { type: "number" }
        },
        required: ["description", "quantity", "unitPrice", "total"]
      }
    },
    totalAmount: { type: "number" },
    notes: { type: "string" }
  },
  required: ["invoiceNumber", "issuedAt", "dueDate", "from", "to", "items", "totalAmount"]
};

Step 3: extract invoice data with Claude

Send the user's input to Claude with the schema. Claude's tool_use mode guarantees the response matches the expected structure.

import Anthropic from "@anthropic-ai/sdk";
 
const client = new Anthropic();
 
async function extractInvoiceData(userInput: string): Promise<InvoiceData> {
  const response = await client.messages.create({
    model: "claude-haiku-4-5",
    max_tokens: 1024,
    tools: [
      {
        name: "create_invoice",
        description: "Extract invoice data from the user's input and return structured JSON",
        input_schema: INVOICE_SCHEMA,
      },
    ],
    tool_choice: { type: "tool", name: "create_invoice" },
    messages: [
      {
        role: "user",
        content: `Today is ${new Date().toLocaleDateString("en-GB", { day: "2-digit", month: "short", year: "numeric" })}. My company is Acme Dev, [email protected], 12 Rue de la Paix, Paris.
 
Extract the invoice data from this input: ${userInput}`,
      },
    ],
  });
 
  const toolUse = response.content.find((block) => block.type === "tool_use");
  if (!toolUse || toolUse.type !== "tool_use") {
    throw new Error("Claude did not return structured invoice data");
  }
 
  return toolUse.input as InvoiceData;
}

Claude Haiku processes a typical invoice prompt in under 1 second and costs roughly $0.0003 per call. For high-volume use, that's less than $0.30 per 1000 invoices.

Step 4: render the invoice with PDF4.dev

Pass the structured JSON to PDF4.dev's API with your stored template ID.

async function renderInvoicePDF(data: InvoiceData): Promise<Buffer> {
  const response = await fetch("https://pdf4.dev/api/v1/render", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.PDF4DEV_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      template_id: process.env.PDF4DEV_TEMPLATE_ID,
      data,
      options: {
        format: "A4",
        margin: { top: "0", right: "0", bottom: "0", left: "0" },
      },
    }),
  });
 
  if (!response.ok) {
    throw new Error(`PDF rendering failed: ${response.statusText}`);
  }
 
  const buffer = await response.arrayBuffer();
  return Buffer.from(buffer);
}

PDF4.dev renders through a warm Chromium pool, so the typical render time for a one-page invoice is 200-400ms. Cold starts are avoided by the pool, which keeps browser instances alive between requests.

Putting it together: a complete Express endpoint

import express from "express";
import { extractInvoiceData } from "./claude";
import { renderInvoicePDF } from "./pdf4dev";
 
const app = express();
app.use(express.json());
 
app.post("/api/generate-invoice", async (req, res) => {
  const { prompt } = req.body;
 
  if (!prompt || typeof prompt !== "string") {
    return res.status(400).json({ error: "prompt is required" });
  }
 
  try {
    // 1. Extract structured data with Claude (~500ms)
    const invoiceData = await extractInvoiceData(prompt);
 
    // 2. Render PDF with PDF4.dev (~300ms)
    const pdfBuffer = await renderInvoicePDF(invoiceData);
 
    // 3. Return as download
    res.setHeader("Content-Type", "application/pdf");
    res.setHeader(
      "Content-Disposition",
      `attachment; filename="invoice-${invoiceData.invoiceNumber}.pdf"`
    );
    res.send(pdfBuffer);
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: "Invoice generation failed" });
  }
});
 
app.listen(3000, () => console.log("Server running on port 3000"));

Test it with a plain-text prompt:

curl -X POST http://localhost:3000/api/generate-invoice \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Invoice Brandlink Agency ([email protected]) for 3 days of backend consulting at $1800/day, payment due in 30 days"}' \
  --output invoice.pdf

Claude parses the client name, email, service description, quantity, unit price, and calculates the total. The resulting PDF downloads immediately.

Automating invoices from Stripe webhooks

The same pattern works for automated post-payment invoices. When a Stripe checkout.session.completed event fires, pass the session data to the LLM to format it, then render and email the PDF.

app.post("/webhooks/stripe", express.raw({ type: "application/json" }), async (req, res) => {
  const event = stripe.webhooks.constructEvent(
    req.body,
    req.headers["stripe-signature"]!,
    process.env.STRIPE_WEBHOOK_SECRET!
  );
 
  if (event.type === "checkout.session.completed") {
    const session = event.data.object;
 
    // Format Stripe data into invoice prompt
    const prompt = `
      Customer: ${session.customer_details?.name} (${session.customer_details?.email})
      Amount paid: $${(session.amount_total! / 100).toFixed(2)}
      Product: ${session.metadata?.product_name ?? "Software license"}
      Payment date: ${new Date(session.created * 1000).toLocaleDateString()}
    `;
 
    const invoiceData = await extractInvoiceData(prompt);
    const pdfBuffer = await renderInvoicePDF(invoiceData);
 
    // Email via Resend
    await resend.emails.send({
      from: "[email protected]",
      to: session.customer_details!.email!,
      subject: `Invoice ${invoiceData.invoiceNumber}`,
      html: "<p>Thank you for your purchase. Your invoice is attached.</p>",
      attachments: [{ filename: "invoice.pdf", content: pdfBuffer }],
    });
  }
 
  res.json({ received: true });
});

This pattern eliminates manual invoice creation after every sale. The LLM handles the formatting (net 30 language, date formatting, description writing), while PDF4.dev handles the rendering.

Using Claude Desktop with the PDF4.dev MCP server

For non-developer workflows, the PDF4.dev MCP server provides a zero-code path to invoice generation. Once connected to Claude Desktop, you can type invoice instructions in chat and receive a PDF file directly.

Setup takes under 5 minutes:

  1. Install the PDF4.dev MCP server from pdf4.dev/mcp
  2. Add your API key to the MCP config
  3. Connect to Claude Desktop in Settings > Developer > MCP servers

Then from Claude Desktop:

Generate an invoice for Virtuel Studio ([email protected]) for UX design work, 
8 hours at €150/hour, issued today, due April 15. Include a 10% discount.
Save it as virtuel-invoice.pdf.

Claude calls the PDF4.dev MCP tool, renders the invoice using your account's default template, and downloads the file to your machine. No server, no code.

Comparison: manual vs AI invoice generation

ApproachTime per invoiceError rateScales to 1000/monthSetup
Manual (form fill)3-5 minHuman typosNoNone
Template only (fixed form)30 secLowYesMedium
AI + PDF API (this guide)< 5 secVery lowYesMedium
MCP (Claude Desktop)10 secVery lowNoLow

The AI approach is best when the input source is unstructured: emails, chat messages, free-text descriptions. For structured data (Stripe, database), you can skip the LLM entirely and pass data directly to the PDF API.

Error handling and production considerations

A few things to harden before going to production:

LLM validation: always validate the LLM output against your schema before sending it to the PDF API. Claude is reliable with tool use, but malformed JSON (missing required fields, wrong types) will cause template rendering errors. Use Zod or a JSON Schema validator.

import { z } from "zod";
 
const InvoiceDataSchema = z.object({
  invoiceNumber: z.string(),
  issuedAt: z.string(),
  dueDate: z.string(),
  from: z.object({ name: z.string(), email: z.string() }),
  to: z.object({ name: z.string(), email: z.string() }),
  items: z.array(z.object({
    description: z.string(),
    quantity: z.number(),
    unitPrice: z.number(),
    total: z.number(),
  })),
  totalAmount: z.number(),
  notes: z.string().optional(),
});
 
const validated = InvoiceDataSchema.parse(rawLLMOutput);

PDF API timeouts: set a 30-second timeout on your PDF4.dev requests and retry once on timeout. Slow renders are rare but happen under load.

Data privacy: for invoices with customer PII, use Claude via Anthropic's API (data is not used for training) and avoid logging full invoice data to persistent storage unless required for audit.

Invoice numbering: generate invoice numbers server-side (e.g. with a database sequence), not from the LLM. LLMs will invent plausible-looking numbers but cannot guarantee uniqueness or sequential ordering.

What the HTML to PDF tool covers for one-off conversions

For ad-hoc conversions where you already have an HTML invoice and just need the PDF, the HTML to PDF tool at PDF4.dev handles it without any API setup. Paste or upload the HTML, download the PDF. No account required for small documents.

The API approach in this guide is for automated, recurring generation at scale.

Next steps

The full example code from this article is about 80 lines of TypeScript. Adding proper error handling, Zod validation, and email delivery brings it to roughly 150 lines, a complete invoicing backend from scratch.

Free tools mentioned:

Html To PdfTry it freeMerge PdfTry it free

Frequently asked questions

Can AI generate PDF invoices automatically?
Yes. You can combine a large language model (Claude, GPT-4, Gemini) with a PDF generation API like PDF4.dev to produce professional invoices from plain-text prompts. The LLM extracts structured data; the PDF API renders it.
What is the best AI invoice generator for developers?
For developers, the best approach is to combine an LLM API (Anthropic Claude, OpenAI) for data extraction with a PDF rendering API like PDF4.dev. This gives you full control over the template design and zero dependency on third-party invoice software.
How do I use Claude to generate an invoice PDF?
Prompt Claude with the invoice details and a JSON schema. Claude returns structured JSON. Pass that JSON to PDF4.dev's API with your Handlebars HTML template and receive a rendered PDF buffer in one HTTP call.
Can I use ChatGPT to create PDF invoices?
Yes. Use the OpenAI API with structured output (response_format: json_object) to extract invoice data from text, then send it to PDF4.dev or any HTML-to-PDF API to render the final document.
What does MCP have to do with invoice generation?
PDF4.dev ships an MCP server. When connected to Claude Desktop or Cursor, Claude can generate and download PDF invoices directly from chat without writing any code. Ideal for one-off invoices or low-code workflows.
How long does it take to generate an AI invoice?
The LLM call takes 1-3 seconds depending on the model and input length. The PDF rendering step adds 200-400ms with PDF4.dev's warm browser pool. Total round-trip is typically under 5 seconds.
Do I need a template to generate invoices with AI?
You need an HTML/CSS template for the visual layout. The AI handles the data extraction and variable population. PDF4.dev's Handlebars engine merges your template with the structured data at render time.
Can I automate invoice generation on Stripe payment events?
Yes. A common pattern is: Stripe webhook fires on payment, your Node.js server passes the charge object to an LLM to format it, then calls PDF4.dev to render the invoice PDF and email it to the customer.
Is it safe to send customer data to an LLM for invoice generation?
For production use, extract only the fields you need server-side without sending full customer records to the LLM. Alternatively, skip the LLM for structured Stripe/database data and only use AI for free-text inputs like contract descriptions.
What is the cheapest way to automate invoice generation with AI?
Use a fast, cheap model like Claude Haiku or GPT-4o-mini for data extraction (less than $0.001 per invoice), and PDF4.dev's free tier for rendering. The cost per invoice is negligible at low volume.

Start generating PDFs

Build PDF templates with a visual editor. Render them via API from any language in ~300ms.