Skip to main content
All posts
AI templates HTML API automation

In an AI Pipeline, Generate the Template — Not Every PDF

Let the model write one HTML template, then fill it with data for every record. The layout stops drifting, each document costs a cent, and nothing the model wrote can run.

PodPDF Team October 13, 2026 7 min read

A language model will happily write you an invoice in HTML. Ask it again tomorrow and it writes another one: the same information, a slightly different layout, the logo a little smaller, the table borders gone. For one document that is fine. For the four hundred invoices you send on the first of the month it is a problem, because nobody can tell you what the four hundredth will look like until it arrives.

The fix is to move the model one step earlier. Let it write the template once. Then fill that template with data, four hundred times, with no model in the loop.

Two jobs that look like one

Producing a document from data is really two jobs:

Designing it. Where the logo sits, how the table is laid out, what the footer says. Judgement, taste, and a job a model is genuinely good at — you describe the document and it writes the markup.

Filling it. Putting this customer’s name, these line items and this total into that design. Mechanical, and a job a model should never do, because it has to be identical every time and correct to the penny.

Asking a model to do both on every record means paying tokens and seconds to re-derive a design you already approved, and accepting that it may come out different. Splitting them means you approve the design once and the rest is arithmetic.

What that looks like with PodPDF

An HTML template is your own markup and CSS with a tag wherever a value goes. The model writes it; you save it once; every render after that is one API call.

<style>
  body { font-family: Inter, sans-serif; }
  h1 { color: var(--pp-accent); }
  td { padding: 2mm; border-bottom: 0.2mm solid #ddd; }
</style>

<h1>Invoice {{invoice_number}}</h1>
<p>Bill to {{customer.name}}</p>

<table>
  {{#each items}}
  <tr><td>{{@number}}</td><td>{{description}}</td><td>{{amount}}</td></tr>
  {{else}}
  <tr><td>Nothing to bill</td></tr>
  {{/each}}
</table>

{{#if notes}}<p>{{notes}}</p>{{/if}}

Three tags cover most documents: {{field}} prints a value, {{#each list}} repeats a block per row, and {{#if field}} shows a section only when there is something to show. That is a small enough surface that a model gets it right from one prompt, and a small enough surface that you can read the result and know what it will print.

Then every document is the same request, whatever produced the data:

curl -X POST https://api.podpdf.com/templates/$TEMPLATE_ID/render \
  -H "X-API-Key: $PODPDF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "data": {
          "invoice_number": "INV-1042",
          "customer": { "name": "Acme Ltd" },
          "items": [
            { "description": "Design work", "amount": 850 }
          ],
          "notes": "Thanks for your business."
        },
        "store": true
      }'

Same JSON, same PDF. Every time, at $0.01 a document, in well under a second.

Where the model still earns its keep

Taking the model out of the rendering loop does not take it out of the pipeline. It moves to the two places where the work is actually ambiguous:

Before. Turning the mess into fields. A forwarded email, a scanned order, a Slack thread — ask the model for JSON matching the template’s fields, and you have a structured record. This is extraction, which models are reliable at, and the output is small enough to validate before you spend anything on a PDF.

Around. The parts of a document that really are different each time: a covering note, a summary paragraph, a reason for a credit. Generate those as fields and let them flow into the same template as {{summary}} or {{notes}}.

What stays out of the model’s hands is the layout and the numbers. Totals are yours to calculate and send — an HTML template prints what it is given rather than adding up line items — which is exactly where you want a spreadsheet or a few lines of code rather than a probability distribution.

The safety part, which is the interesting part

Running markup a model wrote, filled with data a stranger submitted, sounds like a bad evening. It is survivable here because the renderer assumes both are hostile:

  • Scripts are switched off before the document loads, so nothing in the markup runs.
  • The browser has no network at all. No external stylesheets, no web fonts, no remote images, no callbacks. A template cannot phone home, and a page that tries to navigate away stays put.
  • Data is always printed as text. If a customer’s “company name” is <script>… or {{admin_notes}}, it appears in the PDF as those characters. Values are escaped, and tags in your data are never treated as tags.
  • Links are restricted to web, email and phone addresses. Anything else is dropped before printing.
  • Fonts are bundled — Inter, Roboto, Merriweather, Roboto Mono, Great Vibes — so a document that renders today renders the same in a year, with no font CDN in the path.

That last point matters more than it looks in an AI pipeline. The usual worry with model-written HTML is that you cannot read every line of it; the usual worry with customer data is prompt injection reaching something that executes. Here the markup cannot reach the network and the data cannot become markup, so the worst case is a document that looks wrong — which you can see.

When you should let the model write the whole thing

Templates are for documents you produce repeatedly. For a genuine one-off — a bespoke report, a letter whose structure depends on what happened — skip the template and send the markup straight to /quickjob with input_type: "html". The same locked-down renderer applies. The rule of thumb: if you will produce this shape of document more than twice, it is a template.

Doing it from an assistant

If the pipeline is a chat rather than a cron job, the PodPDF MCP server gives an assistant the same endpoints as tools: render_template to fill a saved template, generate_pdf for one-off markup, and get_download_link to fetch the result. An assistant can draft the template with you, then render real documents from it in the same conversation — with the template saved in your account afterwards, rather than living in a chat log.

What to watch

  • Totals are not calculated. Send every value the template prints.
  • Limits: 200 KB of HTML per template, 1 MB of data per render, 50,000 repeated rows, and a finished document up to 12 MB.
  • No barcodes or QR codes in HTML templates yet. Use a visual template for those, or send the code as an image field.
  • Templates keep their ID when you edit them, so improving the design does not break the pipeline that renders it. Duplicate it first if you want to try something riskier.

The short version

Ask the model for the template, not the document. You approve a design once, you can read it, and you can diff it. After that, every PDF is the same deterministic call with different JSON — fast enough for a bulk run, cheap enough not to think about, and identical enough that the four hundredth invoice looks like the first.

HTML templates are in beta; the tags and the editor may still change. Start one under Templates → New HTML template in the dashboard, or read the guide.

AI templates HTML API automation
Back to blog

PodPDF is built by XAD Labs, which builds custom PDF processing pipelines at scale.

Start generating PDFs today

$0.005 per PDF on a monthly plan, or $0.01 from one-off credit packs.