The API that creates your PDFs

Your program describes the document it wants in JSON, posts it in one call, and the finished PDF comes back in the answer.

Summary

Think of ordering a cake. You write down what you want on it, hand that description over the counter, and the cake comes back over the same counter a moment later. Nothing else happens, and you never see the kitchen.

The description here is a piece of text your program writes. It says what goes on the page: this sentence in this place, this table under it, this logo in the corner, this QR code at the foot. Anything the page needs and the text cannot hold — a photograph, a letterhead template — travels along beside it.

The description is written in a form every machine agrees on, called JSON. It is ordinary text, made of names and values, and it reads the same whatever your program itself is written in.

What comes back is the finished document, ready to send to a customer or to a printing works. It comes back in the answer to the call itself, straight away.

The answer is the document. Not a link to fetch it later, not a ticket number to ask about again: the bytes of the finished PDF come back in the answer to that one call.

Nothing is installed to call this address: your program already knows how to send something across the network, and that is the whole of what is being asked of it.

There are twenty-four things you may put on a page: paragraphs, tables, photographs, pages taken from another document, the five kinds of barcode and square code, plain shapes, the fields of a form to fill in, and clickable links. Each of them has a page of its own here, with the shortest example that draws it.

Beside them, sixty keys settle the document around what is drawn: the list of what goes on the pages, and beside it the size of the sheet, the fonts, what the file says about itself, the way a reader finds their way through it, the colours, and how it all comes out. Each of those has a page of its own as well, so this section is eighty-four pages: one for each thing you may put on a page, and one for each key.

Two pieces, and why there are two

The server creates no PDF file itself. It reads the description, checks it, and then calls the library one instruction at a time: set this paragraph, start this table, place this picture here. The library is what writes the file.

They are two because they do two different jobs. The library knows how to draw and knows nothing of the network. The server knows the network — who is calling, how large a request may be, how many pages an account is allowed — and knows nothing of drawing.

The library works with fonts already loaded and ready to draw with, while a description arriving over the network carries only its name. Turning that name into a font the library can use is exactly the server's job, and it is done before the first letter lands on a page.

This is why the library can be bought and used on its own, called straight from a program written in Rust or in Python, with no server and no description travelling anywhere. And it is why a document comes out the same either way, page for page: the same library writes it.

Creating a PDF keeps a processor busy from beginning to end, so each request is handled by a process that has nothing else to do, and the server goes on answering everybody else meanwhile.

The round trip, drawn

  1. Your own program runs on your own machines
  2. The server on the address you call
  3. The library inside the server

What was noticed while the PDF was created

Beside the finished document, the answer says what was noticed while the PDF was created. It is a remark, not a refusal: the pages are the pages you asked for, and a remark leaves them exactly as they are.

The answer always says how many remarks there were, and a nought is an answer of its own: nothing was noticed while the PDF was created.

Two things are remarked on today. The first is something your description sent along that no page ever used — a font, a photograph, a colour, a layer. It travelled and it went into the file, so the file is heavier than the pages needed it to be.

The second is a font that carries a licence note from whoever drew it, saying how it may be passed on. The document is written with that font inside it, and the remark repeats what the licence note says, so that you can compare it with the licence you bought that font under. That licence is yours and stays with you: it is the one thing the file cannot carry.

Ten remarks at most come back in the answer, while the count says how many there really were. There is no way to fetch the rest: the server writes every one of them down in its own log.

Technically

One POST /render of multipart/form-data: the document described as JSON in the json_data part, the files it names in the parts beside it, and either 200 application/pdf back or a numbered refusal in plain text.

The transport

One endpoint, one method: a POST with a multipart/form-data body. The part named json_data holds the whole description as JSON. Every other part is a file the description addresses by that part's name — a PDF used as a template or imported page by page, an image, an ICC profile, an XMP packet, the XML of an invoice. There is no upload step and no asset identifier to keep: a part lasts only for the call that carried it.

Authentication is an API key carried in the Authorization header and checked by this site, which resolves it to a client name and forwards that name in a header of its own. A success answers 200 with application/pdf and the count of pages. A refusal answers a status code, one sentence of plain text, and x-hqf-refusal: one word from a closed list naming the kind of refusal, which is what a program reads. There is no envelope to unwrap and no job to poll.

The service we run adds one refusal of its own, which a server of your own never gives: when a month's documents are all used up, it answers 429, with Retry-After carrying the date the month turns. A server of your own counts no month.

What a call and its answer are made of
The call that creates a document, on a server of your own POST /render
The service we run, which takes a call of its own POST https://hqf-pdf.com/api/v1/render
The call that says the service is up GET /health
The shape the body is sent in multipart/form-data
The part carrying the description json_data
Every other part a file the description addresses by name
How the API key travels Authorization: Bearer <key>
What this site forwards to the server x-hqf-client: <name>
What a success carries 200 application/pdf
How many pages the finished document has x-hqf-pages
How many things were noticed while the PDF was created x-hqf-warnings
One remark, repeated once per remark carried x-hqf-warning
How many findings separate the document from the standards asked for x-hqf-findings
One finding, repeated once per finding carried x-hqf-finding
What an error carries text/plain
The word a refusal names its sort by x-hqf-refusal
The version of the service that answered x-hqf-version

The call the service we run takes, and every answer it gives

The body, part by part

The body is a form in several parts, and each part has a name.

  • json_data carries the description of the document, the sixty keys of it. It is the one part whose name is fixed.
  • Every other part is a file the request draws on, under the name the description calls it by: a PDF laid under the pages, named by templates, a picture named by an image item, an ICC profile named by output_intent or by an icc space, the XML named by invoice, a metadata packet named by an image. What a part is follows from the JSON that names it, never from the part itself.
  • The body is kept in memory whole, and its size is capped at 536 870 912 bytes — 512 MiB — short of a setting on the server saying otherwise.
  • The service we run caps the body of a call of its own at 536 870 912 bytes, or 512 MiB, and answers 413 naming body_too_large past that. The two ceilings are two settings of their own, so a server you run can be set lower or higher than the one above.

The header this site sets

The server authenticates nobody: this site takes the call, checks the API key and names the client it belongs to, in the x-hqf-client header. The server reads that name and creates the PDF under what the account carries: its fonts, its ceiling of pages, its entitlements. A request naming a client the server does not know, or naming none, is rendered under the default settings. The header is worth trusting because nothing but this site can set it: the server listens on the loopback and nothing outside this machine reaches it, and this site writes the header afresh on every request it forwards.

What a success hands back

  • The status code 200, and the bytes of the PDF as the body.
  • Content-Type: application/pdf.
  • x-hqf-pages, how many pages the finished document has.
  • x-hqf-warnings, how many things the engine remarked on while writing the document, and one x-hqf-warning per remark carried.
  • x-hqf-findings, how many findings stand between the finished document and the standards check asked it to be measured against, and one x-hqf-finding per finding carried. Both stand on an answer that carries a document, whether or not a standard was asked for.
  • A server of your own repeats the header once per value. The service we run sends the name once with its values separated by commas; the rule for splitting them is on that service's page.

What a render remarks on

A remark is something the engine noticed while writing a document it went on to write: the bytes handed back are the bytes a render with nothing to remark on would have handed back, and the status code stays 200. x-hqf-warnings carries how many there were and stands on every rendered answer, 0 included; an answer carrying no such header comes from a server older than the header itself.

x-hqf-warning is then repeated once per remark carried, in the order the engine made them. Ten of them at most, running to a thousand bytes together, whichever of the two is reached first. The remark that runs past the bytes left is cut short and ends on %E2%80%A6, an ellipsis, and the remarks after it stand in the server's log alone — which keeps every one of them in full, at INFO, under the name of the client the render was for. The count states the whole number either way, so a customer reading x-hqf-warnings: 50 beside ten headers knows exactly where they stand.

Those two figures are what keeps the answer deliverable. A front server caps how large the headers of a response may be, and a document declaring fifty resources it never draws would produce fifty headers: the whole answer would be turned away before reaching the customer, and a document written without fault would arrive as a failure of the service.

  • A resource the request declared and no page drew with — a font, an image, a drawing, a colour space, a graphics state, a gradient, a pattern or a layer. It is written into the file and put at every page's disposal, and no content stream names it. The remark reads the image Im2 is never drawn with.
  • A font embedded against what its own fsType flag permits: a flag forbidding embedding, one asking to go in whole where the glyphs drawn went in, or one allowing only the pictures the font carries where outlines went in. The remark names the font as the font names itself and the name the request selects it by — the font Foo (F1) must not be embedded without the owner's permission. Whether the embedding is lawful turns on the licence the font was bought under, which is yours to read.
  • Every value is percent-encoded and decodes the way a URL decodes: every byte outside printable ASCII stands as %XX and the percent sign itself as %25, so Café arrives as Caf%C3%A9. A header carries ASCII alone, while a resource is named by whoever wrote the request and a font names itself in its own licence — so a remark holds whatever those two hold. A remark cut short is cut between two characters, so what arrives decodes, and what it decodes to is text.

The version that answered, and the health probe

GET /health answers a 200 and a small JSON object: version, the number of the build that answered, licensed, whether a licence covers the day, and faces, how many fonts the general library carries. That is what a supervisor or a load balancer reads to know the service is answering, and what a deployment reads to know that the build it installed is the one now answering.

The same number stands on every answer the service sends, in the x-hqf-version header: on a created document, on a refusal and on the probe alike. A customer writes it into their own log beside the call they made, so a document that came back wrong is tied to the build that created it; and after a deployment it is read to tell an instance that was replaced from one left behind. An answer carrying no such header comes from a build older than the header itself. The number is the service's own: the engine carries one of its own, on the page of the library, and this site carries a third, at the foot of every page.

The model

A request has one top level: a list of items, and beside it the resources those items name. Fonts, colour spaces, gradients, graphics states, layers and drawings are each declared once under a name, and an item refers to that name. A resource is found by its name, wherever it is declared, and everything that counts is written — a name an item calls for and no declaration carries is refused, with the name in the message.

Pages are created by what is drawn on them, not declared up front. An item states which pages it lands on; the default, an empty array, is every page, which is how a footer or a watermark is written once. A table that overflows its box carries on into the next box and creates the pages it needs, and the items keyed to every page follow it there.

Geometry is in typographic points with the origin at the bottom left, as the format itself has it. Any coordinate may be a quoted arithmetic expression over the page dimensions and the current page number, so a right margin is "{page_width} - 56" rather than a number your program had to work out. An item may name itself and another may hang off its edge, which is what keeps a layout right when the height of a block is only known after it is set.

What a request carries beside what it creates

The same request states the document's metadata, the archival standard it claims, the output intent its colours are read against, the compression of its streams, the outline and the named destinations, the article threads, the page labels, the reading preferences reading software honours, and the invoice XML a Factur-X document carries. One call produces a finished, standards-claiming file: there is no second pass and no post-processing tool.

The answers, and what they say

Validation is total and happens before anything is drawn: the server refuses a request rather than producing a document that quietly lost something. A refusal is one sentence naming the key or the name at fault, so it is worth logging verbatim.

Beside it stands x-hqf-refusal, one word from a closed list: client_name, call_number, form, body_too_large, description, drawing, too_many_pages, needs_a_licence, server. It is what a program branches on, the sentence being written for a person. A word not on this list comes from a server newer than this page.

Unknown keys are ignored almost everywhere, which makes a request written for a later version safe to send at an earlier one. The one exception is the object form of pages, the rule built from every, from, to and but: there a key the rule does not have is refused by name, since a rule stating nothing covers the whole document and a misspelt key would silently do the same. A key written twice in the same object is refused by name. Combinations that would contradict each other — a matrix together with a rotation, a link with both a short target and a full action, a table cell holding two things — are refused by name as well.

A page the document does not have is refused wherever a key names one — a bookmark, a named destination, the patch of an article thread — and the message names that page in the numbering the request used: ask for page nine of a five-page document and the message says page nine.

This site waits 30 seconds for the document and no longer. Past that the call comes back under the status 503 and one sentence saying the service did not answer. How long a document of your own takes is published nowhere: measure it on your own pages, on the machine that will run them.

Every answer the service gives, by its number
200 The PDF is the body. Beside it stand the count of pages and what was noticed while the PDF was created: a count, and one header per remark carried.
400 The request must be corrected and sent again; the body is one sentence of plain text. Known cases: the JSON will not read; the `json_data` part is absent; a font named is nowhere declared; a coordinate will not parse; a link or a bookmark leads to a page the document has not, named in the numbering the request itself uses; a standard declared and a setting that standard puts aside contradict each other; a compression level falls outside 1 to 9; a template supplied is not a PDF that reads.
402 The request asks for something only a licence key covers, under `x-hqf-refusal: needs_a_licence`. The sentence names what was asked for and says a licence key carries it. Every case: text set in a font with no outline to draw the glyphs from — the fourteen standard fonts, a font whose glyphs are drawings, a type 1 program; a page imported from another document showing more than ten thousand words of its own; an imported page whose words cannot be counted at all; a claim of accessibility, or of archiving at the level that rests on saying what the marks on a page stand for; a destination leading to an element of the structure tree. The last three come of a free copy tracing every glyph as a shape: the document has no structure tree for any of them to rest on.
413 The body of the request runs past the size the server accepts, or the document runs past the ceiling of pages of the server or of the account. The sentence names the size or the ceiling.
500 Writing the PDF or the font store did not come off. That is on the service's side rather than in the request.

Request schema

Every key a request carries, with its type, whether it is required and what it falls back to when it is left out. Every figure was read off the sources of the server and holds for the version in production.

The smallest request that creates a PDF

One declared font, one item, one line of text. Save it, post it, and a one-page PDF comes back.

{
  "standard_fonts": [{ "name": "sans", "face": "helvetica" }],
  "items": [
    {
      "type": "text",
      "rect": { "llx": 56, "lly": 700, "urx": "{page_width} - 56", "ury": 780 },
      "content": ["Invoice 2026-014"],
      "font": "sans",
      "font_size": 24
    }
  ]
}

The same call, written three times

None of the three is a library to install: the server answers over the network, so any language able to post a form calls it. Here are three — a shell, Python and JavaScript.

Copy whichever of these matches your stack. Each posts the description and one file it draws on, and writes the answer straight to disk.

What the Rust library and the Python library create

From a shell

One part carries the description, one part per file it draws on. The name of a part is the name the description calls that file by.

# The service we run takes a call of its own:
# POST https://hqf-pdf.com/api/v1/render described on
# https://hqf-pdf.com/en/api/pdf-api-endpoint/
curl -X POST https://your-server/render \
  -H "Authorization: Bearer $HQF_PDF_KEY" \
  -F "json_data=@invoice.json;type=application/json" \
  -F "letterhead.pdf=@letterhead.pdf" \
  -o invoice.pdf

From Python

The API key comes from the environment, and the answer is the PDF itself, so write the body straight to a file. The count of pages stands on every answer that carries a document.

import json
import os
import pathlib

import requests

KEY = os.environ["HQF_PDF_KEY"]
LETTERHEAD = pathlib.Path("letterhead.pdf")

description = {
    "standard_fonts": [{"name": "sans", "face": "helvetica"}],
    "items": [
        {
            "type": "text",
            "rect": {"llx": 56, "lly": 700, "urx": "{page_width} - 56", "ury": 780},
            "content": ["Invoice 2026-014"],
            "font": "sans",
            "font_size": 24,
        }
    ],
}

# The service we run takes a call of its own: POST https://hqf-pdf.com/api/v1/render
# described on https://hqf-pdf.com/en/api/pdf-api-endpoint/
answer = requests.post(
    "https://your-server/render",
    headers={"Authorization": f"Bearer {KEY}"},
    files={
        "json_data": ("request.json", json.dumps(description), "application/json"),
        "letterhead.pdf": ("letterhead.pdf", LETTERHEAD.read_bytes()),
    },
    timeout=120,
)
answer.raise_for_status()
pathlib.Path("invoice.pdf").write_bytes(answer.content)
print(answer.headers["x-hqf-pages"], "pages")

From JavaScript

A form body and the standard fetch, run by Node. The API key comes from the environment and the template from a file beside the program, and a refusal arrives as a sentence of plain text.

import { readFile, writeFile } from "node:fs/promises";

const key = process.env.HQF_PDF_KEY;
const letterhead = new Blob([await readFile("letterhead.pdf")]);

const description = {
  standard_fonts: [{ name: "sans", face: "helvetica" }],
  items: [
    {
      type: "text",
      rect: { llx: 56, lly: 700, urx: "{page_width} - 56", ury: 780 },
      content: ["Invoice 2026-014"],
      font: "sans",
      font_size: 24,
    },
  ],
};

const body = new FormData();
const asJson = JSON.stringify(description);
body.append("json_data", new Blob([asJson], { type: "application/json" }));
body.append("letterhead.pdf", letterhead, "letterhead.pdf");

// The service we run takes a call of its own:
// POST https://hqf-pdf.com/api/v1/render
// described on https://hqf-pdf.com/en/api/pdf-api-endpoint/
const answer = await fetch("https://your-server/render", {
  method: "POST",
  headers: { Authorization: `Bearer ${key}` },
  body,
});
if (!answer.ok) {
  throw new Error(await answer.text());
}
await writeFile("invoice.pdf", new Uint8Array(await answer.arrayBuffer()));

The keys of a request

Sixty keys stand at the top level of a request, and one of them is required. Every other key defaults to the value in the column beside it, so a request states what it needs and nothing else. Each key leads to its own page.

Every JSON key a request carries at its top level
JSON key Type Default What it is for
items array required Everything drawn on the pages, in the order it is drawn. This is the one key a request cannot leave out.
page object 595.276 × 841.89 The sheet every page is cut to, in points, unless a page names its own.
page_sizes array [] A sheet of its own for one numbered page.
page_boxes array [] The crop, bleed, trim and art boxes of one page, which is what a printing works reads before anything else.
page_turns array [] Which way up a page is presented to whoever opens the file.
page_tab_orders array [] The order the keyboard walks the fields of one page.
page_shows array [] How one page arrives, and how long it stands, when reading software shows the document full screen.
supplied_pdfs object How the PDFs supplied alongside the request are read: how far one of their streams is allowed to unpack.
structure object What the document says it is made of, in reading order, so a program reads it as a person would rather than as the ink went down.
accessible string The accessibility standard the document claims, checked against what it actually carries.
simple_fonts array [] A font reached by one-byte codes fitted to a stated text: a lighter page, and word spacing that has a code to reach.
attachments array [] The attachments the document carries beside its pages.
collection object How reading software lays the attachments out as a portfolio: the columns, the order the rows stand in, and the file opened first.
page_files array [] Which of the attachments one page claims as its own.
page_metadata array [] The identity card one page carries, beside the document's own.
tilings array [] A cell repeated to fill a shape: hatching, a weave, a background.
fonts array [] Fonts carried in the request as base64, each under a name the items call for, with the characters they keep and what they draw for one they have no glyph for.
type1_fonts array [] Type 1 fonts, carried the same way.
standard_fonts array [] One of the fourteen fonts all reading software already carries, under a name of your choosing.
font_variants array [] A declared font with ligatures, small capitals, oldstyle figures or kerning switched on.
type3_fonts array [] A font whose glyphs are drawings rather than outlines.
font_chains array [] Two or more fonts tried in order, so a glyph the first lacks is taken from the next.
templates object The supplied PDFs laid under the first page, under the middle pages and under the last.
renumber array [] Where page numbering restarts, in which style, behind which prefix.
bookmarks array [] The outline reading software opens beside the page, nested as deep as you like.
destinations array [] Named places in the document a link or an action may aim at.
articles array [] Reading threads that carry a reader from one column to the next.
document_parts object The tree of parts a PDF 2.0 document declares, each claiming a range of pages and carrying named values.
private_data object What a program leaves in the document for itself, filed under its own name. No reading software shows it.
page_private_data array [] The same, left on one numbered page rather than on the document.
metadata object Title, author, subject, keywords, the program it was written in, producer, creation and modification dates, trapping state, and schemas of your own.
information_dictionary object Whether the file keeps its table of entries beside its metadata packet, or says what it is in the packet alone.
archive string The archival standard the file claims: `pdfa3b` or `pdfa4`.
protection object The passwords the document is locked under, and what reading software is asked to allow once it is open.
invoice object The invoice XML carried inside the document, with its profile, its version, its relationship and its date.
reading object How reading software is asked to open the file: which panel, which layout, which page, which zoom, and what to send a printer.
base_uri string The address a relative link is resolved against.
version string the engine's own The PDF version the file declares: `1.4` through `1.7`, or `2.0`.
compression object Whether streams are deflated, at which level, and whether small objects travel packed together.
first_page_first object Whether the file is written first page first, so reading software shows page one while the rest is still arriving.
output_intent object sRGB The colour profile the file's colours are to be read against.
images array [] What one supplied picture says about itself, and how it is read: its masks, its see-through samples, its orientation and its profile.
color_spaces array [] Separations, indexed palettes, Lab, calibrated grey and RGB, and profile-based spaces, each under a name a colour may call for.
shadings array [] Axial and radial gradients, each under a name a fill may call for.
graphics_states array [] Named states carrying the opacities, the blend mode, a mask, and what a press asks for: the overprinting, the intent, the transfer curve and the halftone screen.
layers array [] Named layers reading software can switch on and off, and which say whether they print.
drawings array [] Named vector drawings, which is what a push button shows as its icon.
transparency object The blending space the page group works in, and whether it is isolated or knocked out.
page_units array How many points one unit of a page is, for a sheet larger than the 14 400 points a PDF measures in.
page_review_marks array The shapes a page lays over what it draws: a note, a stamp, a shape drawn by hand.
page_triggers array What a page sets off when reading software arrives at it, and when it leaves it.
print string The print standard the document claims, and is refused for breaking.
check array The standards the finished document is measured against, the answer coming back beside the file.
page_spaces array What a page's bare numbers mean, colour space by colour space, and how its marks are composited.
page_separations array Which plate of a separated sheet a page is, and the ink it is printed in.
page_output_intents array The device one page's colours are stated for, where the cover and the inside are not printed on the same paper.
layer_rules array A name items are drawn on that stands for several layers at once, and the rule reading software applies to decide whether to show them.
layer_configurations array The named sets of layer states reading software offers beside the one the document opens under.
layer_families array Layers reading software shows one of at a time, so showing one of them turns the others off.
carried_facts array [] Named lists of data of your own, each hung on a run of ink by the item that names it. No reading software shows them.

The eighty-four pages of this section

One page per item and per key a request can carry, filed under the question it answers. Each says what the thing is, what it does technically, the schema of the request it takes, and shows a whole request using it.

What a request creates

Twenty-four things go on a page, and each has a page of its own here: what it is, what it does technically, the schema of the request it takes, and a whole request showing it.

Text
Tables
Pictures and imported pages
Barcodes and square codes
Shapes
Form fields
  • A field the reader types into text_field Add a field to fill in: a name, an address, a comment over several lines, a hidden password, or a number one square per digit.
  • A box the reader ticks check_box Add a tick box: accepting terms, choosing an option, with the mark you pick and the value the form reports.
  • A list the reader picks from choice_field Offer a list of answers: an open list, a drop-down menu, or a menu the reader may also type an answer into.
  • Several buttons, one answer radio_group Place buttons of which only one stays chosen at a time, each in its own spot, all belonging to the same question.
  • The place kept for a signature signature_field Keep a spot on the page where an electronic signature will settle, with its name and its frame.
  • A button the reader presses push_button Place a button that acts instead of answering: open an address, go to a page, send the form or reset it.
Navigation

What a request settles

Sixty keys stand at the top level of a request and settle the document around what is drawn: the sheet, the fonts, the identity of the file, the way a reader moves through it, the colour, and the rendering itself.

The sheet, and what is laid under it
The fonts
The document as a whole
Finding one's way through it
Colour and how it is shown
What is drawn, and how it comes out

The shapes every key is built from

A handful of objects recur throughout the format. They are written once here, and every page that uses one names it by these words.

The objects the JSON keys are built from
rect Four coordinates — `llx`, `lly`, `urx`, `ury`. The origin sits at the bottom left of the sheet and the vertical axis goes up, which is the convention of the format itself.
coordinate A number, or a quoted expression over `+ - * / ( )` and the variables `{page_width}`, `{page_height}`, `{current_page}`, `{total_pages}`, `{llx}`, `{lly}`, `{urx}`, `{ury}`. An expression nests up to a hundred deep, which is how an item sits against the page width without the customer computing anything.
color One number for grey, three for red, green and blue, four for the four printing inks, all from 0 to 1. Also `{"gray": n}`, and `{"space": "name", "components": …}` for a space the request declared.
stroke A `width`, and optionally a `color`, a `dash` pattern with its phase, a `cap` and a `join`.
pages The one-based pages an item is drawn on. The default, an empty array, draws it on every page, which is how a footer is written once.
layer The declared layer an item belongs to, which is what reading software switches on and off.
id / relative_to An item names itself with `id`; another places itself against that item's `top` or `bottom` edge with an `offset`, so a paragraph follows a table whose height nobody knew in advance.
transform / rotate Six numbers for a matrix, or a number of degrees counter-clockwise about the centre of the rectangle. An item states one or the other.

How a request is read

  • A key the server does not know is ignored, so a request written for a later version renders at an earlier one.
  • A key written twice in the same object is refused, and the message names the key.
  • A choice that has no value of its own is written as a bare string — "center", "pdfa4". A choice that has one is a single-key object naming the variant — {"points": 12}, {"page": 3}.
  • An absent optional key and an explicit null are read the same way, which is what lets a serialiser emit either.
  • The same description, posted twice, yields the same bytes twice, outside the two places where the schema says otherwise: a run asking for the moment of rendering reads the clock as the page is written, and a protection block stating no key of its own is locked under thirty-two bytes read off the machine. Everywhere else the file identifier is a digest of what was written, so two renders can be compared as files.

The figures the service holds itself to

Every figure, and what it is a ceiling on
The body of a request, on a server of your own 536 870 912 (512 MiB)
The body of a request to the service we run 536 870 912 (512 MiB)
How long the service we run waits for a document 30 seconds
How deep an expression nests 100
Entries in an indexed palette 256
Compression level 1 – 9
Error correction of an Aztec code 0 – 90 %
Columns of a PDF417 code 1 – 30
Error correction level of a PDF417 code 0 – 8
Fonts in a chain 2 or more
Remarks an answer carries 10
Bytes those remarks run to together 1 000
Pages of the middle template 1
How far one stream of a supplied PDF unpacks 1 073 741 824
Characters in the name of a stored font 64
Pages in one document what your plan includes

The questions people ask first

Is anything of my document kept?

The pages are not. The finished PDF goes back in the answer to your call, and it is never written to a disk along the way.

The description you sent, and any file you sent with it, are kept only for as long as the render takes, and only in the machine's own memory. Neither of them is written to a disk, and both are gone the moment the pages leave.

Two things are written down, and only two. The first is fonts. If your account is allowed to send fonts of its own, then a font the render really used is kept in a folder belonging to that account, so that you never have to send it a second time — from then on your description simply names it. A font that was sent but drawn with nowhere is not kept, and a render that failed keeps nothing at all. An account that draws with the fonts already on the server keeps nothing either.

The second is a line raised when the server breaks on its own side, so that somebody here is woken. It names the sort of failure, the account, the number your answer quoted and the moment, and nothing your description chose. It waits in a small file until this site has taken it, and leaves it as soon as it has.

Nothing about the bill travels from the server. This site counts the render off the answer handed back to it and writes one line of its own: the name of the account, one identifier standing for that single render, the moment it happened, and how many pages came out. Nothing of what is written on those pages goes with it.

The server keeps a log, as any program on a machine does. It writes a line for every document that comes out, holding the name of the account, how many pages came out and how long it took. It writes another when a render mentions something it then never used — a font, a picture, a colour nothing selected — carrying the name of the account and the remark. The pages come back either way: it is a remark, not a refusal.

If you run the server on your own machines, none of this reaches us at all. The reporting is switched on by naming an address to send it to, and a server that has not been given one sends nothing anywhere.

How is the server called, and in what form?

One address takes the work, and the request is posted to it as a form. One part of that form carries the description of the document, written as ordinary text. Every other part is a file the document draws on. What comes back is the PDF itself.

Anything able to send a form across the network can call it, whatever it is written in. The table above names the address, the shape and the answer exactly, and the short example below is a whole working request.

What does this bring over a free PDF library?

A great many documents are written every day with free PDF libraries, and several of them are very good at what they set out to do. What is sold here is a different thing: one piece that already covers the whole of the ground, and somebody who answers for it.

The ground is wide. Text that flows, tables that carry on over as many pages as they need, pictures, printing colours, barcodes, forms to fill in, files made to still open decades from now, files made to be read aloud to somebody who cannot see the page, and the room left for a seal. One thing to learn, one thing to keep up to date, and one place to write to when a page does not come out as you expected.

It is checked from outside. There is a free checker called veraPDF whose whole job is to open a file and say whether it follows the published rules, and every archival document shown on this site goes through it. We own no part of it, and it knows nothing about who wrote the file in front of it.

The same document is written three ways — a Rust program, a Python program, and a request to the server — and the three are re-run and compared. Where they come out the same to the byte, it is written down beside them; where they come out the same to the eye but not to the byte, that is written down too.

And there is a contract, a company, and a person to write to. Putting several free pieces together is a fine way to work, and it is also work that somebody has to do and go on doing: when two of them disagree, finding out why is yours. Here it is ours.

The library bought outright stays yours: paid once, installed as often as you like, and it does not stop working the day anything runs out.

Do I have to install anything?

On a subscription, no. You call an address across the network and the pages come back. Nothing goes on your machines and nothing has to be kept up to date.

If you buy the server, it is one program to start on your own machines, with a handful of settings: where its fonts live, where its licence key lives, and how many pages one document may run to. It listens on the machine itself and sits behind whatever front server you already run.

If you buy the library on its own, it goes inside your program like any other part of it, from Rust or from Python. No server, no network, nothing running beside it.

How am I billed?

By the document. Each render is reported once, with the number of pages it created, and a month of them is added up into one bill. A document counts as one, however many pages come out of it: the page count is written down beside the render and never charged for.

A report that could not be sent straight away is not lost: it waits and goes later. And a report that arrives twice is counted once, because each render carries an identifier of its own.

Which programming languages can call it?

All of them. The request is ordinary text sent across the network, so anything able to do that can ask for a document — which is every language in everyday use.

The library underneath is written in Rust and is called from Python just as readily, which is what a team buying the library on its own works with.

What does the free version do?

It creates documents, and it signs its work. Every page it produces comes out with a watermark, and every letter on those pages is traced as a shape instead of being written as text, so the pages look right and nothing can be lifted out of them.

A few things need a licence key. A free copy asked for one of them refuses, says so plainly, and names the licence key that covers it: setting text in one of the fourteen fonts all reading software already carries or in either of the two older kinds of font, and taking a page from another document that holds more than ten thousand words of its own. A free copy sets text only in a font that gives a glyph to trace: one your request carries with it, or one the service already holds.

Which rules are really checked, and by whom?

Files made to stay readable for years, and files made to be read aloud to somebody who cannot see the page, are put through veraPDF, the free checker named above, and it accepts them. It is run by the Open Preservation Foundation and the PDF Association, and we own no part of either.

Files meant for a printing press are held to four requirements before a single byte is written, and a file short of one of them is refused rather than written. The page on printing standards names the four.

How do I know the service is answering?

There is a second address whose only job is to say so. It creates nothing, costs nothing to ask, and answers for as long as the service is up.

Counting your documents never delays a render. The pages come back first, and the count goes on its own afterwards.

Where to go next

Everything the library puts on a page Everything the service keeps, in full What is held about you, and for how long Files made to still open decades from now Files a printing works runs without asking

See the prices See the examples