4 min read

Generating PDFs for Invoices and Reports

Use an AI tool to turn structured data into professional PDF documents.

Dw

Dwizi Team

Editorial

Generating PDFs for Invoices and Reports

If you are building a B2B agent, eventually you will hit the "Paper Ceiling."

Your agent can negotiate a deal, schedule a meeting, and answer support questions. But when the client says, "Great, send me an invoice," the agent hits a wall. It can send text. It can send Markdown. But it cannot send a PDF.

And in the business world, if it's not a PDF, it's not real.

The Problem: The Format Gap

LLMs produce streams of tokens (text). They do not produce binary files. They cannot layout a page, handle page breaks, or embed fonts.

To cross the Paper Ceiling, we need a translator. We need a tool that takes the LLM's structured data (JSON) and paints it onto a canvas (PDF).

The Solution: HTML as the Intermediate Language

The easiest way to generate a PDF is to generate HTML first. LLMs are excellent at writing HTML. We can use a service (like PDFShift, DocRaptor, or a headless Chrome wrapper) to convert that HTML into a PDF.

The Implementation

We will build a tool called generate_invoice_pdf.

/**
 * Generates a PDF invoice URL.
 * 
 * Description for LLM: "Create a PDF invoice for a client based on a list of items."
 */

// 1. Structured Input
// We don't ask the LLM to write the HTML directly in the input arguments
// because it might hallucinate broken tags.
// Instead, we ask for DATA. We will wrap the data in HTML ourselves.
type InvoiceItem = {
  description: string;
  amount: number;
};

type Input = {
  clientName: string;
  invoiceNumber: string;
  items: InvoiceItem[];
};

export default async function generateInvoice(args: Input) {
  const apiKey = Deno.env.get("PDF_API_KEY");
  if (!apiKey) throw new Error("Missing PDF_API_KEY");
  
  const { clientName, items, invoiceNumber } = args;
  
  // 2. The Logic Layer
  // We calculate totals here. Why? Because LLMs are bad at math (see our other article).
  // We trust the LLM to list the items, but we do the summing.
  const total = items.reduce((sum, item) => sum + item.amount, 0);

  // 3. The Templating
  // We inject the data into a clean, professional HTML template.
  // This ensures every invoice looks on-brand, no matter what the AI does.
  const html = `
    <html>
      <body style="font-family: sans-serif; padding: 40px;">
        <h1 style="color: #333;">Invoice #${invoiceNumber}</h1>
        <p><strong>Bill To:</strong> ${clientName}</p>
        <table style="width: 100%; border-collapse: collapse; margin-top: 20px;">
          <tr style="background: #eee;">
            <th style="padding: 10px; text-align: left;">Item</th>
            <th style="padding: 10px; text-align: right;">Cost</th>
          </tr>
          ${items.map(i => `
            <tr>
              <td style="padding: 10px; border-bottom: 1px solid #ddd;">${i.description}</td>
              <td style="padding: 10px; border-bottom: 1px solid #ddd; text-align: right;">$${i.amount.toFixed(2)}</td>
            </tr>
          `).join("")}
        </table>
        <h2 style="text-align: right; margin-top: 20px;">Total: $${total.toFixed(2)}</h2>
      </body>
    </html>
  `;

  // 4. The Conversion
  // We send the HTML to our PDF service.
  const res = await fetch("https://api.pdfservice.com/convert", {
    method: "POST",
    headers: { 
      "Authorization": `Basic ${apiKey}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({ source: html })
  });

  const data = await res.json();

  // 5. The Handoff
  // We return a URL. The agent can now say:
  // "Here is your invoice: [Link]"
  return { 
    success: true, 
    pdfUrl: data.url, 
    expiresIn: "24h"
  };
}

The Execution Story

User: "Send an invoice to Acme for the 'Q1 Strategy Session'. It was $5,000."

Agent Action:

  1. Recognizes the intent: generate_invoice.
  2. Generates an invoice number (e.g., "INV-2024-001").
  3. Calls the tool:
    {
      "clientName": "Acme Corp",
      "invoiceNumber": "INV-2024-001",
      "items": [
        { "description": "Q1 Strategy Session", "amount": 5000 }
      ]
    }
    
  4. The tool calculates the total, builds the HTML, converts it, and returns https://files.dwizi.com/inv-2024-001.pdf.

Agent Response: "I've generated the invoice for Acme. You can download it here: [Link]. Should I email it to them?"

Now the agent isn't just chatting about work. It's doing the paperwork.

Subscribe to Dwizi Blog

Get stories on the future of work, autonomous agents, and the infrastructure that powers them. No fluff.

We respect your inbox. Unsubscribe at any time.

Read Next

The Junior Dev (GitHub)

Automating the first 15 minutes of every bug report. How to build an agent that triages issues.

Read article