Skip to content

Invoice PDF API: generating invoice PDFs from your own billing data

Two very different jobs get called "invoice PDF API": fetching the PDF a billing platform already made, and generating your own from an HTML template. Here is when to use each, with the Stripe and QuickBooks endpoints and the render call, plus how to keep a record every finance team eventually needs.

"Invoice PDF API" gets used for two jobs that barely overlap. One is fetching a PDF a billing platform has already generated for you, the Stripe or QuickBooks invoice, over its API. The other is generating your own invoice PDF from an HTML template you control, because the platform version does not match your brand or you bill outside any of them. They need different tools, and picking the wrong one is where teams waste a week. Here is the map, with the actual endpoints and the render call, plus the record keeping a finance team always ends up needing.

The short version

If your invoices already live in Stripe or QuickBooks, do not build a PDF renderer. Both expose the finished PDF over their API: Stripe returns an invoice_pdf URL on the invoice object, and QuickBooks Online has a dedicated PDF endpoint. Fetch it and store it. Build your own renderer only when you need a branded or custom invoice that no platform produces, when you bill from your own system, or when you need a document that matches your HTML invoice exactly. For that, render the HTML you already have in a real browser engine and keep a timestamped copy for the audit trail. Do not hand build PDFs with a drawing library unless you enjoy pain.

If the invoice is in Stripe: fetch, do not render

Stripe generates a PDF for every finalized invoice. You do not need to draw anything. Retrieve the invoice and read invoice_pdf, a short-lived URL to the PDF file, alongside hosted_invoice_url for the web version.

// Node, Stripe SDK
const invoice = await stripe.invoices.retrieve('in_1QabcABC');

// invoice.invoice_pdf     -> URL to the PDF Stripe generated
// invoice.hosted_invoice_url -> the hosted web copy

const pdf = await fetch(invoice.invoice_pdf);
const buffer = Buffer.from(await pdf.arrayBuffer());
// store buffer in your own bucket for your records

The catch worth knowing: invoice_pdf is only present once the invoice is finalized, and the URL is temporary, so download and store the bytes rather than saving the link. Stripe controls the layout, which is a feature if you are fine with the Stripe look and a limitation if finance wants your logo, your terms and your column order. If the standard Stripe invoice is acceptable, this is the whole job and you should stop here.

If the invoice is in QuickBooks: the PDF endpoint

QuickBooks Online exposes the invoice PDF directly. Send a GET to the invoice PDF path with an Accept: application/pdf header and you get the file back.

GET https://quickbooks.api.intuit.com/v3/company/{realmId}/invoice/{invoiceId}/pdf
Authorization: Bearer {oauth2_access_token}
Accept: application/pdf

The response body is the PDF itself. As with Stripe, the layout is QuickBooks' own, driven by the template settings in the account, so you get consistency and limited control. For teams whose books already live in QuickBooks, pulling the PDF from here keeps one source of truth and avoids a second layout to maintain. If you also need the invoice data as structured rows rather than a document, that is a different pipeline, and the finance side of it, chasing what is owed, is its own product category.

When you render your own: HTML in, PDF out

Fetching works only when a platform already made the invoice. Plenty of teams bill from their own system, need a branded document Stripe cannot produce, or want the PDF to match the HTML invoice they already show in the app. In that case you generate the PDF yourself, and the sane way to do it is to render the HTML you already have rather than draw the document by hand with a library like PDFKit.

You almost certainly already have the invoice as an HTML template: it is what your app shows on the invoice detail page. Render that same markup to PDF and the document matches the screen for free. The trap is using a renderer that does not run a real browser: dompdf, wkhtmltopdf and similar cannot handle modern CSS, so an invoice built with flexbox or grid comes out broken. Render in actual Chromium instead. From any stack that is one HTTP request.

// Render your existing HTML invoice to a PDF, Node
const res = await fetch('https://api.sitepdf.com/v1/render', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.SITEPDF_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    html: renderedInvoiceHtml,   // the same template your app shows
    format: 'Letter',
    margin: 'normal',
    archive: true,               // keep a dated copy of this invoice
  }),
});

const { pdf_url, archive } = await res.json();

Because the render happens in managed Chromium, your CSS, web fonts and any dynamic totals computed in the page all come through exactly as they look in the browser. The PDF generator API page covers this document generation path at volume, the convert HTML to PDF page has the general version, and the generate PDF invoices from HTML guide walks a full invoice template end to end.

Whichever way you get the PDF, keep a dated copy

An invoice is a financial record, and financial records get disputed, audited and requested years later. Whatever produced the PDF, you want to be able to answer "what exactly did we send, and when" without ambiguity. Storing the file is the minimum. Storing it with a trustworthy timestamp of the moment it was produced is what actually settles a dispute.

That is why the render call above uses archive=true. Sitepdf keeps a timestamped snapshot of the rendered document, retrievable later by id, so the invoice and its record live together rather than in a bucket you hope nobody touched. If you are fetching from Stripe or QuickBooks instead, replicate the same discipline: download the bytes immediately and store them with the issue date, do not rely on the platform link staying live. The record-keeping angle is covered in more depth in the website archiving for compliance post.

Which approach fits your case

Your situation Use this
Invoices live in Stripe and the Stripe layout is fine Fetch invoice_pdf from the invoice object
Invoices live in QuickBooks Online GET the invoice /pdf endpoint with Accept: application/pdf
You bill from your own system, or need a branded custom invoice Render your HTML invoice in managed Chromium via an API
The PDF must match the invoice page your app already shows Render that same HTML template, do not redraw it
You need to prove what was sent and when, later Store the bytes with a dated archive, whatever produced them

The decision is really "did a platform already make this PDF, or do I have to." If it did, fetch it and store it. If it did not, render the HTML you already own rather than hand building the document, and keep a dated copy either way. Generating the invoice is only half the lifecycle; once it is out the door, getting paid is the other half, and the accounts payable team on the receiving end often runs your invoice straight through accounts payable automation software that reads it and schedules the payment. Designing your invoice so it is clean and machine readable helps both sides.

For the mechanics of rendering, the URL to PDF API and PDF generator API pages have the endpoint details, and the seven tool comparison covers the hosted options with pricing. Invoices are rarely the only document a billing system produces, and once statements, receipts and dunning letters join them, the templating and volume side is covered on the document generation API page.

Written by the team building Sitepdf, an HTML to PDF API that archives every page it renders. The in-browser converter is free to try; early access locks the launch pricing.

§ Early access

Get on the early-access list

The API opens to the list first, in order. Early access locks the planned launch rates for 12 months. No card required, launching soon.

Render + archive, one API