The render pipeline
POST /v1/render fills a template from JSON and runs a list of steps on the result. After this page you can build one request that fills an invoice, converts it, sets its metadata and encrypts it, and you will know what comes back.
curl -F template=@invoice.docx -F "data=<invoice.json" \ -F steps=convert,metadata,encrypt \ -F 'metadata={"Title":"Invoice INV-2041"}' \ -F userPassword=open-sesame \ http://localhost:9027/v1/render -o invoice.pdfhead -c4 invoice.pdf | grep -q %PDFThe body is a multipart/form-data form. Tag syntax is in template syntax.
Send data as text or as a file
Section titled “Send data as text or as a file”These two do the same thing:
# As a text field. The quotes stop your shell reading the <.curl -F template=@invoice.docx -F "data=<invoice.json" \ -F steps=convert -F to=html \ http://localhost:9027/v1/render -o filled.htmlgrep -q "Amani" filled.html
# As a file part, with any file name, before or after the template.curl -F data=@invoice.json -F template=@invoice.docx \ -F steps=convert -F to=html \ http://localhost:9027/v1/render -o filled.htmlgrep -q "Amani" filled.htmlctx works the same way. What the service does with these parts:
- A
dataorctxpart that is not valid JSON is a400that names the file. - Sending the same field twice, such as text and a file, is a
400. - A file named exactly
data.jsonorctx.jsonis read as that field, whichever field it arrives in. - Once those are set aside, the first file part in the form is the template, whatever its field name.
With no data and no steps, a document returns as it came in
Section titled “With no data and no steps, a document returns as it came in”A form with an Office file or PDF, no data and no steps is a no-op. The response is the same bytes you sent, with a 200.
curl -F template=@report.docx \ http://localhost:9027/v1/render -D headers.txt -o same.docxhead -c2 same.docx | grep -q PKgrep -qi "report.docx" headers.txtTags in an Office file are filled only when data is a non-empty object. So steps=convert with no data converts the file with its tags as written. Text templates (.txt, .md, .html, .htm, .csv, .xml, .json, .svg) always render, so one with tags and no data gets 422.
A file in any other format with data gets 501 not_supported. With no data it passes through.
Form fields
Section titled “Form fields”| Field | Kind | What it does |
|---|---|---|
template |
File | The template. The first file part in the form takes this role, whatever its field name |
templateId |
Text | The id of a stored template. Sending it with a template file part is a 400 |
templateHash |
Text | The hex SHA-256 of a template this caller sent recently. See sending a hash |
data |
Text, JSON object | What the template sees as its root. Anything other than an object is a 400 |
ctx |
Text, JSON | Visible in the template as ctx. data wins if it has its own ctx key |
steps |
Text | Comma-separated step names, in order |
outputFilename |
Text | The result’s file name, without an extension |
files |
Files | Further PDFs for the merge step. Every file part after the template lands here, except those under the fields below |
fonts |
Files | Fonts for this request alone. See fonts |
watermark, stamp |
Files | Image or PDF marks for those steps |
underlay |
File | The PDF the underlay step lays the document over |
embeds |
Files | Attachments for the embed step |
facturxXml |
File | The invoice XML for the factur-x step |
flatten |
Text | With a PDF template and data: fix the filled values into the page |
stripAnnotations |
Text | With a PDF template and data: false keeps the annotations that held tags. Default is to remove them |
downloadFrom |
Text, JSON array | Fetches files from URLs instead of uploading them, as [{"url": "..."}]. At most 10 entries. Private addresses are refused unless RUSTICDOC_DOWNLOAD_FROM_ALLOW_PRIVATE=true |
The conversion options in Convert documents, such as landscape and nativePageRanges, apply to every LibreOffice step.
A PDF sent with data is a form to fill. See PDF templates.
Each step works on the document or documents in flight, in the order steps lists them. Names are not case-sensitive.
| Step | Fields | What it does |
|---|---|---|
convert |
to, default pdf: pdf, docx, xlsx, pptx, odt, ods, odp, html, txt |
Converts with LibreOffice |
pdfa |
pdfa, default PDF/A-2b: PDF/A-1b, PDF/A-2b, PDF/A-3b, PDF/A-4 |
Exports as PDF/A with LibreOffice |
pdfua |
None | Exports as PDF/UA with LibreOffice |
merge |
files |
Appends the further PDFs, in the order sent. After a split, merges the parts again |
split |
splitMode (pages or intervals) and splitSpan, both required; splitUnify |
Splits into several documents |
watermark |
watermarkSource, watermarkExpression, watermarkOptions, watermarkPages, watermark |
Draws text, an image or a PDF under the page content |
stamp |
stampSource, stampExpression, stampOptions, stampPages, stamp |
Draws text, an image or a PDF over the page content |
underlay |
underlay, one PDF |
Lays each page over the matching page of the underlay PDF, such as letterhead or a pre-printed form. See below |
rotate |
rotateAngle, default 90: 90, 180, 270, -90; rotatePages, default all |
Rotates pages |
flatten |
None | Fixes forms and annotations into the page content |
optimize |
None | Recompresses streams and drops unused objects |
bookmarks |
bookmarks, a JSON array of {"title", "page", "children"} |
Replaces the outline |
metadata |
metadata, a JSON object such as {"Title": "Invoice"} |
Writes metadata keys and keeps the others |
embed |
embeds; embedsMetadata, a JSON object mapping a file name to {"mimeType": "..."} |
Attaches files |
factur-x |
facturxXml; facturxConformanceLevel, default EN16931: MINIMUM, BASIC_WL, BASIC, EN16931, EXTENDED |
Embeds Factur-X invoice XML |
encrypt |
userPassword, ownerPassword; at least one. A missing owner password takes the user password |
Encrypts the PDF |
sign |
reason, location, contactInfo, signerName, level, certify, signingKey, and for a visible signature page, x, y, width and an image under signatureImage, all optional |
Signs the PDF cryptographically, as /v1/pdf/sign with signature=certificate does: invisibly, or with the image or a box of text at x and y. See signing with a certificate |
Every step from merge down needs a PDF, so one of convert, pdfa or pdfua comes first when the template is not a PDF. Otherwise the response is a 400 that says 'letter.txt' is not a PDF.
pdfa and pdfua run LibreOffice on whatever is in flight. steps=pdfa on an Office file converts it directly. steps=convert,pdfa converts to PDF, then opens that PDF in LibreOffice and exports it again, so prefer steps=pdfa.
Watermark and stamp fields
Section titled “Watermark and stamp fields”watermarkSource is text, image or pdf. text takes its text from watermarkExpression. image and pdf take a file uploaded under the watermark field. A stamp has the same fields with the stamp prefix.
watermarkPages is a page selection such as 1-3,5,7-. watermarkOptions is a JSON object whose values are all strings. {"op": 0.5} is a 400, and {"op": "0.5"} works.
| Option | Default | Meaning |
|---|---|---|
pos |
c |
tl, tc, tr, c, bl, bc, br. t is tc and b is bc |
op |
1 |
Opacity, 0 to 1 |
rot |
0 |
Rotation in degrees |
sc |
0.5 |
Scale relative to the page, 0.01 to 1 |
fontsize |
24 |
Font size of a text mark |
fillc |
#808080 |
Colour of a text mark, as #rrggbb |
curl -F template=@report.docx -F steps=convert,stamp \ -F stampSource=text -F stampExpression=PAID \ -F 'stampOptions={"pos":"tr","op":"0.5","fontsize":"12"}' -F stampPages=1 \ http://localhost:9027/v1/render -o paid.pdfhead -c4 paid.pdf | grep -q %PDFRepeat the Source, Expression, Options and Pages fields for several marks, up to 5 of each kind. Name the step once: each name in steps applies every mark of that kind, so steps=watermark,watermark with two sources draws four.
Underlay
Section titled “Underlay”underlay puts a base PDF under the document in flight, page by page, as /v1/pdf/overlay does with the base sent first. So one request can fill an Office template, convert it and lay it on your letterhead:
curl -F template=@letter.txt -F "data=<letter.json" -F underlay=@a.pdf \ -F steps=convert,underlay http://localhost:9027/v1/render -o on-letterhead.pdfhead -c4 on-letterhead.pdf | grep -q %PDF- Page 1 of the document is drawn over page 1 of the base, page 2 over page 2, and so on. The result has the base’s page sizes.
- Base pages past the end of the document stay as they are. Document pages past the end of the base are added at the end, with nothing under them. For letterhead on every page, send a base with at least as many pages.
- The
underlayfile part is never taken as the template or as a file to merge, wherever it comes in the form. - The step needs exactly one
underlayPDF, and a PDF in flight, soconvertcomes first for an Office or text template. Otherwise the response is a400.
How the fields are read
Section titled “How the fields are read”- Fields of a step you did not name have no effect, but they are still checked.
-F rotateAngle=45is a400even withoutrotateinsteps. - The order of
stepsdecides the order of work. The fields only give each step its options. encryptgoes last. A later PDF step gets a400that says the file is encrypted.signmust be the last step, because any later step would change the signed bytes, and it cannot be combined withencryptin either order. Both are refused with a400before any work starts. Without a signing key configured, a request withsigngets a503, as does alevelthat needs a timestamp authority the service does not have; a caller not allowed the key gets403. After asplit, each part is signed.
curl -F template=@invoice.docx -F data=@data.json -F steps=convert,sign \ -F reason=Issued http://localhost:9027/v1/render -o invoice.pdf- A request runs at most 50 steps.
Several documents in flight
Section titled “Several documents in flight”split turns one document into several, and every later step runs on each. merge joins them again. If several remain at the end, the response is a ZIP.
curl -F template=@a.pdf -F files=@b.pdf \ -F steps=merge,split,rotate \ -F splitMode=intervals -F splitSpan=1 -F rotateAngle=180 \ -F outputFilename=parts \ http://localhost:9027/v1/render -D headers.txt -o parts.ziphead -c2 parts.zip | grep -q PKgrep -qi 'filename="parts.zip"' headers.txtHere the ZIP holds parts_1.pdf and parts_2.pdf, numbered from 1. One request may have at most 1,000 documents in flight.
merge takes PDFs only. It does not convert the further files, so convert them first with /v1/convert or list them already as PDFs.
To render one template once per element of an array, use batch rendering.
What comes back
Section titled “What comes back”| Status | Body |
|---|---|
200 |
The file. content-type follows its extension. Several documents come as application/zip |
204 |
Nothing, because the request named a webhook and the result goes there |
4xx, 5xx |
JSON, as in errors |
The file is named after outputFilename, or else after the template, with the extension of what came out. invoice.docx through convert is invoice.pdf.
Response headers
Section titled “Response headers”| Header | On | Meaning |
|---|---|---|
x-request-id |
Every response | The id of this request, also in the logs and on webhook deliveries. Send your own X-Request-Id to set it |
content-disposition |
Files | attachment; filename="..." |
x-fonts-used |
PDF results of up to 32 MB | The fonts named in the PDF, such as LiberationSans, LiberationSans-Bold. Check it to catch a font that fell back. See fonts |
server-timing |
Files | Milliseconds per stage, such as template;dur=18, convert;dur=67 |
x-rusticdoc-version |
Authenticated routes, and every route when no credentials are configured | The service version |
x-content-type-options |
Every response | nosniff |
curl -F template=@invoice.docx -F "data=<invoice.json" -F steps=convert \ -H "X-Request-Id: order-2041" \ http://localhost:9027/v1/render -D headers.txt -o invoice.pdfgrep -qi "^x-request-id: order-2041" headers.txtgrep -qi "^x-fonts-used:" headers.txtSending a hash
Section titled “Sending a hash”Each template sent as a file is kept in this replica’s memory for a while, under the caller’s identity. A later request from the same caller can send templateHash in place of the file. The hash is the hex SHA-256 of the file, which /v1/templates/validate returns as template_hash.
A miss answers 404 template_not_cached, and the client sends the file again. Nothing guarantees a hit: the cache is per replica, holds templates up to 16 MB and empties on restart. To keep a template, store it.