Generating PDFs

Anvil allows you to dynamically generate new PDFs using JSON data you provide via the /api/v1/generate-pdf REST endpoint. Useful for agreements, invoices, disclosures, or any other text-heavy documents.

Generate Invoice PDF Example


  • Number of pages are dynamically generated to fit your content
  • Documents are generated with standard 8.5" x 11" pages
  • Supports markdown formatting, including tables, images, headings, etc.


First you will need an API key. You can find your API key on the Organization Settings -> API Settings page. We provide a node API client that wraps authentication.

For more information on generating an API key and handling API authentication, check out the API getting started article.

Postman Collection

Quickly see the PDF Generation endpoint in action with our PDF Generation Postman Collection.

Generating a PDF

POST to /api/v1/generate-pdf with the data you want to embed in the PDF. Anvil will respond with the raw binary data for the filled PDF.

// Optional - set the title encoded into the PDF document
"title": "Example Invoice",
// Optional - display a logo on the right corner of the first page
"logo": {
"src": "",
"maxWidth": 200,
"maxHeight": 200,
// Optional - set the font size / text color of the PDF
"fontSize": 16,
"textColor": "#171717",
// Optional - show or hide the timestamp shown at the bottom of the PDF
// defaults to true
"includeTimestamp": true,
// Required - the data to actually fill the PDF
"data": [{
"label": "Name",
"content": "Sally Smith",
}, {
"content": "Lorem **ipsum** dolor sit _amet_...",
"fontSize": 12,
"textColor": "#616161",
}, {
"table": {
"rows": [
["Description", "Quantity", "Price"],
["4x Large Wigets", "4", "$40.00"],
["10x Medium Sized Widgets in dark blue", "10", "$100.00"],
["6x Small Widgets in white", "6", "$60.00"],
"columnOptions": [
{ "align": "left", "width": "60%" },
{ "align": "center", "width": "100px" },
{ "align": "right" },
"firstRowHeaders": true,
"rowGridlines": true,
"columnGridlines": true,
"verticalAlign": "center",
// => Raw PDF bytes

An example with curl:

curl \
-H 'Content-Type: application/json' \
-d '{ "title": "Hello", "data": [ { "label": "Hello World", "content": "I like turtles" } ] }' \ > test.pdf

Using the Node Client

For convenience, we provide a node API client. It handles authentication, PDF generation, and errors to help with debugging.

import fs from 'fs'
import Anvil from '@anvilco/anvil'
const apiKey = '7j2JuUWmN4fGjBxsCltWaybHOEy3UEtt'
const exampleData = {
"title": "My PDF Title",
"data": [{
"label": "Hello World!"
const anvilClient = new Anvil({ apiKey })
const {
} = await anvilClient.generatePDF(exampleData)
console.log(statusCode) // => 200
// Data will be the filled PDF raw bytes
fs.writeFileSync('output.pdf', data, { encoding: null })

Encrypting Data Payloads

You can encrypt the data sent in to fill the PDF. Setup an RSA keypair, then encrypt the string value of the data property with your public key. You can use our node encryption library.

// Encrypt with your RSA key
"data": 'an encrypted JSON string'
// Other metadata
"title": "Some Title",
// => Raw PDF bytes

By default, the Anvil logo will be displayed on the right corner of the first page. You can replace this with your own logo via the logo property:


logo: {
// (required) Supports http(s) and data URIs
src: ''
// (optional) The max size to display the logo
// * Default is 200px
// * Maximum is 500px
maxWidth: 200,
maxHeight: 200,
title: 'Logo Example',
data: [{
content: 'Hello _World_!'

Supported Format of data

data is specified as an array of objects. The objects have a handful of supported keys. Objects can contain multiple keys.

title: '...',
data: [
label: 'A label',
content: 'Some content',
fontSize: 12,
textColor: '#171717',
// ... more data to output


Generates a bolded label

data: [{
label: 'A label',


Generates a larger bolded heading intended to break up sections of a document.

data: [{
heading: 'Some Heading',


Generates a block of text. Supports multiline text and markdown formatting, including headings, bold, italic, bullets, tables, blockquotes, links, images, etc.

data: [{
content: 'Some content\n\nAnother line of content with [a link](',


While the content key supports markdown tables, they can be difficult to generate, so we provide a specific table key.

data: [{
table: {
// (required) The data!
rows: [
['Description', 'Quantity', 'Price']
['A widget', '3', '$3']
['Some other widget', '10', '$10']
// (optional) firstRowHeaders defaults to true
// set to false for no header row on the table
firstRowHeaders: true,
// (optional) rowGridlines / columnGridlines defaults to false
// set to true to display gridlines in-between rows or columns
rowGridlines: true,
columnGridlines: false,
// (optional) verticalAlign defaults to 'top'
// adjust vertical alignment of table text
// accepts 'top', 'center', or 'bottom'
verticalAlign: 'center',
// (optional) columnOptions - An array of columnOption objects.
// You do not need to specify all columns. Accepts an
// empty object indicating no overrides on the
// specified column.
// Supported keys for columnOption:
// align (optional) - adjust horizontal alginment of table text
// accepts 'left', 'center', or 'right'; defaults to 'left'
// width (optional) - adjust the width of the column
// accepts width in pixels or as percentage of the table width
columnOptions: [
{ align: 'left' }, // the default
{ align: 'center', width: '100px' },
{ align: 'right', width: '20%' },

fontSize / textColor

Applies the specified font size or text color to that section of the PDF.

data: [{
content: 'Lorem ipsum',
fontSize: 20, // must be an int within 5 to 30
textColor: '#006ec2', // must be a 6 digit hex code

Handling Errors

Should you run into any errors, the response status will be >= 400, and the body will be a JSON payload.

// empty request body!
// => 400

Rate Limits

This API enforces separate rate limits for Development and Production API keys.

  • Production: 200 requests over 5 seconds
  • Development: 10 requests over 5 seconds

Each response will contain a few headers that keep track of usage:

HTTP Status: 200
X-RateLimit-Limit: 200
X-RateLimit-Remaining: 196
X-RateLimit-Reset: 148086765

When exceeded, the API will respond with a 429 status code plus a Retry-After header indicating how many seconds to wait:

HTTP Status: 429
Retry-After: 5

Get started today

Start filling, generating, and signing PDFs from your app. Every account comes with free access to the Developer API.