The API that draws your documents
Your program describes the document it wants in JSON, posts it in one call, and the finished PDF comes back in the answer. This page says how, in plain words first, then for the developer writing that call, then key by key.
In plain words
Written for anybody. You can stop at the end of it and still know what this is and what it is for.
Think of ordering a cake. You write on a slip of paper what you want on it, hand the slip 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 slip of paper 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 square code at the foot. Anything the page needs and the text cannot hold — a photograph, a sheet of headed paper — travels along beside it.
What comes back is the finished document, ready to send to a customer or to a printing works. It comes back in the same breath, not in an hour and not in a mailbox.
There are twenty-one things you may put on a page: paragraphs, tables, photographs, pages taken from another document, the five kinds of square and striped code, plain shapes, the boxes 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.
If you are reading this to decide whether it fits, the answer is on that list: everything you can ask for is written down, and nothing else is needed to start.
For the developer writing the call
Written for whoever writes the calling code: the transport, the model, the coordinate system and the failure modes.
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 named binary 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 lives for the call it travelled in.
Authentication is a bearer key checked by the reverse proxy, which resolves it to a client name and forwards that name in a header of its own. A success answers 200 with application/pdf, plus the parse time and the drawing time in milliseconds. A refusal answers a status code and one sentence of plain text. There is no envelope to unwrap and no job to poll.
| The call that draws a document | POST /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 named binary the description addresses |
| How the key travels | Authorization: Bearer <key> |
| What the proxy forwards to the renderer | x-hqf-client: <name> |
| What a success carries | 200 application/pdf |
| How long reading the description took | x-hqf-parse-ms |
| How long the drawing took | x-hqf-render-ms |
| What an error carries | text/plain |
The model
A request is a flat document: 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. Nothing is positional and nothing is implicit — 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 the drawing
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 a viewer 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.
Failure modes, and what they cost
Validation is total and happens before anything is drawn: the server declines 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.
Unknown keys are ignored, which makes a request written for a later version safe to send at an earlier one. A key written twice in the same object is declined 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 declined by name as well.
200 |
The PDF is the body, and the two timing headers stand beside it. |
|---|---|
400 |
The description named something the drawing could not use: a font no declaration carries, a coordinate that will not parse, a template that is not a PDF, a link to a page beyond the last. The body is one sentence saying which. |
402 |
Something a licence key covers. The body names what was asked for, and a key turns it on. |
413 |
The body, or the document it would produce, runs past a ceiling the service was configured with. The body names that ceiling. |
500 |
A failure on the service's side rather than in the request. |
The reference: every key of a request
The format itself. Every figure was read off the sources of the render server and holds for the version in production.
The smallest request that draws something
One declared face, 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
Copy whichever of these matches your stack. Each posts the description and one file it draws on, and writes the answer straight to disk.
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.
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 answer is the PDF itself, so write the body straight to a file. The two timing headers are there whenever the drawing succeeded.
import json
import pathlib
import requests
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,
}
],
}
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-render-ms"], "ms drawing")
From JavaScript
A form body and the standard fetch, so the same lines run in a browser and in a server-side runtime. A refusal arrives as a sentence of plain text.
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", letterheadBlob, "letterhead.pdf");
const answer = await fetch("https://your-server/render", {
method: "POST",
headers: { Authorization: `Bearer ${key}` },
body,
});
if (!answer.ok) {
throw new Error(await answer.text());
}
const pdf = new Uint8Array(await answer.arrayBuffer());
The keys of a request
Thirty-three 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.
| 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 × 842
|
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. |
fonts |
array |
[]
|
Typefaces carried in the request as base64, each under a name the items call for. |
type1_fonts |
array |
[]
|
Type 1 typefaces, carried the same way. |
standard_fonts |
array |
[]
|
One of the fourteen faces every reader already carries, under a name of your choosing. |
font_variants |
array |
[]
|
A declared face with ligatures, small capitals, oldstyle figures or kerning switched on. |
type3_fonts |
array |
[]
|
A face whose letters are drawings rather than outlines. |
font_chains |
array |
[]
|
Two or more faces tried in order, so a letter 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 a reader 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 run of pages and carrying named values. |
metadata |
object |
—
|
Title, author, subject, producer, creation date, trapping state, and schemas of your own. |
archive |
string |
—
|
The archival standard the file claims: `pdfa3b` or `pdfa4`. |
invoice |
object |
—
|
The invoice XML carried inside the document, with its profile, its version, its relationship and its date. |
reading |
object |
—
|
How a reader 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. |
output_intent |
object |
sRGB
|
The colour profile the file's colours are to be read against. |
images |
array |
[]
|
Metadata attached to one supplied picture, either stated or carried as a packet. |
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 an opacity for fills, an opacity for strokes and a blend mode. |
layers |
array |
[]
|
Named layers a reader 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. |
The twenty-one things an item can be
An item is an object carrying a type. Each type has a page of its own: what it is, what a developer needs to know about it, every key it takes with its default, and a whole request showing it.
Text
-
A block of text placed on the page
textSet text inside a chosen rectangle, with its font, size and colour, and give one word in the middle of a sentence a style of its own. -
Text in paragraphs, with indents and tab stops
flowLay several paragraphs in a frame, with a first-line indent, space between them, bulleted lists and neat columns of figures. -
A stamp laid across the page
stampLay one word across an area, at the size that fills the box: draft, paid, copy, cancelled, on the diagonal or flat.
Tables
-
A table that carries on from page to page
tableRows and columns flowing over as many pages as needed, with a repeated header, rules, coloured backgrounds and merged cells.
Pictures and imported pages
-
A picture placed in a rectangle
imagePlace a photo or a logo supplied with the request in a chosen frame, stretched to the edges or kept at its own proportions. -
A page of a supplied PDF, placed on the page
pdf_pageTake one page of a PDF sent with the request and place it in a frame, at its own proportions, among the other items.
Barcodes and square codes
-
A barcode of vertical bars
barcodeDraw a barcode a scanner reads: Code 128 for text, and EAN-13, UPC-A or EAN-8 for a retail article number. -
A square QR code, read with a phone
qrDraw a QR code carrying a web address or a piece of text, read by any phone, with its white margin all around. -
An Aztec code, with no white margin around it
aztecDraw an Aztec code, the one on train tickets: square, needing no margin, with the share of safety you choose. -
A Data Matrix code, very small and very sturdy
data_matrixDraw a Data Matrix code, the one on parcels and small parts: as much content as a QR code in far less room. -
A PDF417 code, carrying a whole form
pdf417Draw a PDF417 code of stacked bars, the one on boarding passes: it carries a whole form, not merely a number.
Shapes
-
A free path: lines, curves and filled shapes
pathDraw a shape point by point, with straight segments and curves, filled with a colour or a gradient, and outlined with a stroke. -
A rectangle, filled or outlined
rectPlace a rectangle: a coloured band behind a heading, a frame around an area, or both, with the stroke you choose. -
A straight line between two points
linePull a line from one point to another, at the thickness, colour and dash you want: a divider, an underline, a ruled row.
Form fields
-
A box the reader types into
text_fieldAdd a box to fill in: a name, an address, a comment over several lines, a hidden password, or a number one box per digit. -
A box the reader ticks
check_boxAdd 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_fieldOffer 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_groupPlace 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_fieldKeep a spot on the page where an electronic signature will settle, with its name and its frame. -
A button the reader presses
push_buttonPlace a button that acts instead of answering: open an address, go to a page, send the form or reset it.
Navigation
-
A patch of page a reader clicks
linkMake a patch of page clickable: it opens a web address, or takes the reader to another page of the same document.
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.
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 caller 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 a reader 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 box. 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 declined, and the message names the key.
- A choice with no payload is written as a bare string —
"center","pdfa4". A choice carrying a payload is a single-key object naming the variant —{"points": 12},{"page": 3}. - An absent optional key and an explicit
nullare read the same way, which is what lets a serialiser emit either. - The same description, posted twice, yields the same bytes twice: the file identifier is a digest of what was written, so two runs can be compared as files.
The figures the service holds itself to
| The body of a request | 512 MiB |
|---|---|
| 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 |
| Correction level of a PDF417 code | 0 – 8 |
| Faces in a chain of fonts | 2 or more |
| Pages of the middle template | 1 |
| Characters in the name of a stored font | 64 |
| Pages in one document | what your plan carries |
Where to go next
How a program asks for its pages Everything the library puts on a page Everything the service keeps, in full
Glossary
- byte
- The unit a file's weight is counted in, the way a parcel is counted in grams. A thousand of them make a kilobyte, and a million make a megabyte — the size of one photograph taken with a telephone. A one-page PDF here weighs between six thousand and a hundred and twenty thousand, so a hundred of them fit in the space of five photographs.
- typographic point
- The unit printing measures with, worth a little over a third of a millimetre: seventy-two of them make an inch. The paper, the margins and the height of the lettering are all counted in them, and so is every measurement written inside a PDF file.
- standard
- A rule argued out in committee, published under a number anybody may buy and read, and identical for every firm claiming it. A claim to follow one can therefore be checked against the text. A way of working that merely spread because it worked is a habit of the trade: useful, widespread, and answerable to no text at all.
- Factur-X
- An invoice that is a page for a person and a data file for a machine, in one document. The page looks like any invoice; inside, the same amounts are attached in a form accounting software reads without anybody retyping them. French law requires this exchange between companies.
- the identity card of a file
- The block inside a file that says what the file is: its title, who made it, when, and which rules it follows. Search engines and archives read it; a reader never sees it. Its technical name is XMP.
- colour profile
- A file that says what a colour actually looks like. Without one, the same red is one red on your screen and another on a press. A document meant for print carries the profile its colours were chosen against.
- output intent
- The colour profile written into a document to say which press or which screen its colours were chosen for. A printer reads it to know what the numbers in the file are meant to look like, and the rules on documents kept for the long term ask for one.
- font
- The drawing of every letter, digit and mark a document writes, held in a file of its own. A PDF carries the fonts it is set in inside itself, which is why it opens looking the same on a machine that has never had them. Leave them out and a reader puts another font in their place, and the layout moves.
- gradient
- A colour that changes across the space it fills, with no step anywhere between one end and the other: a header that fades out, a bar with some depth to it. It is described once, in a handful of numbers, and painted wherever it is wanted, so it stays sharp at any size and weighs almost nothing.
- request
- One call to the service: you send what the document should say, and get the document back. Your bill counts these calls, one for each document. The number of pages inside a document is never counted.
- template
- A PDF file made once and reused as the background of new pages: your letterhead, a plain sheet for the pages that follow, the page of terms you always attach at the end. The file is laid down exactly as it stands, to the millimetre, and the new text is written on top.
- JSON
- A way of writing structured information as plain text, made of named values, lists and numbers. Every programming language reads and writes it, which is why it is what a program uses to describe the document it wants.
- server
- A machine that waits for calls and answers them, running day and night with nobody sitting at it. The one that draws documents here holds the engine and hands back the finished pages; the one your website runs on is a different machine doing the same kind of job.
- status code
- The three-digit number an answer opens with, saying how the call went. Two hundred means it worked. Anything in the four hundreds means the call needs changing; anything in the five hundreds means the machine answering has something to put right.
- multipart form
- A way of packing several things into one call: each gets a name and travels in its own part, and they can be text or files alike. It is what a web page uses to upload a photograph, and what carries a document's description alongside the pictures it draws.
- layer
- A named group of things drawn on a page that a reader can switch on and off, like tracing paper laid over a plan. A drawing can hold its measurements on one and its notes on another, and a layer can be set to show on screen and stay off the printer.
- watermark
- Wording printed across the page itself, saying the document was made with a free account. It cannot be removed from the file; a paid tier simply never puts it there.