Render with the Renders API
The Renders API turns a published template version, or HTML your application builds, into a PDF. Each request creates a render with its own ID that you can wait for, download or cancel, and that can notify you through webhooks. To create many at once, see batches. To render a template by name and get the file straight back, or to get an Excel file, use the Reports API instead.
Before you start
Section titled “Before you start”- An API key with
render:createandrender:read, plustemplate:readto look up template versions; see the HTTP API. - A published template, unless you render your own HTML; see Build a template.
- curl or a client library, set up as in the quickstart.
1. Render a specific template version
Section titled “1. Render a specific template version”A version ID keeps your PDFs on that version until you change the ID, even after the template is
published again. Look the ID up once and put it in your configuration. The clients don’t look up
versions, so use curl; the current version’s ID is publishedVersionId:
curl "$PDF_URL/v1/templates?search=Invoice" -H "x-api-key: $API_KEY"Then render that version with your data:
curl "$PDF_URL/v1/renders" \ -H "x-api-key: $API_KEY" \ -H "content-type: application/json" \ -d '{ "templateVersionId": "c7e0b9a4-1f2d-4c3b-9a8e-5d6f7a8b9c0d", "data": { "invoice": { "number": "INV-2041" }, "customer": { "name": "Marta Tamm" } }, "waitMs": 10000 }'using Typefold.Client;
var client = new TypefoldClient( new Uri(Environment.GetEnvironmentVariable("PDF_URL")!), Environment.GetEnvironmentVariable("API_KEY")!);
byte[] pdf = await client.RenderAsync(new TemplateVersionRenderRequest{ TemplateVersionId = "c7e0b9a4-1f2d-4c3b-9a8e-5d6f7a8b9c0d", Data = new { invoice = new { number = "INV-2041" }, customer = new { name = "Marta Tamm" }, },});
await File.WriteAllBytesAsync("invoice.pdf", pdf);import { writeFile } from "node:fs/promises";import { TypefoldClient } from "@typefold/client";
const client = new TypefoldClient({ baseUrl: process.env.PDF_URL!, apiKey: process.env.API_KEY!,});
const pdf = await client.render({ templateVersionId: "c7e0b9a4-1f2d-4c3b-9a8e-5d6f7a8b9c0d", data: { invoice: { number: "INV-2041" }, customer: { name: "Marta Tamm" } },});
await writeFile("invoice.pdf", pdf);import { writeFile } from "node:fs/promises";import { TypefoldClient } from "@typefold/client";
const client = new TypefoldClient({ baseUrl: process.env.PDF_URL, apiKey: process.env.API_KEY,});
const pdf = await client.render({ templateVersionId: "c7e0b9a4-1f2d-4c3b-9a8e-5d6f7a8b9c0d", data: { invoice: { number: "INV-2041" }, customer: { name: "Marta Tamm" } },});
await writeFile("invoice.pdf", pdf);import osfrom pathlib import Path
from typefold import TypefoldClient, TemplateVersionRenderRequest
client = TypefoldClient(os.environ["PDF_URL"], os.environ["API_KEY"])
pdf = client.render( TemplateVersionRenderRequest( template_version_id="c7e0b9a4-1f2d-4c3b-9a8e-5d6f7a8b9c0d", data={"invoice": {"number": "INV-2041"}, "customer": {"name": "Marta Tamm"}}, ))
Path("invoice.pdf").write_bytes(pdf)With curl, waitMs keeps the request open; see wait in the request. The
clients wait, download the PDF and throw if the render fails.
datais required. Send{}if the template needs none.- Paper, fonts, images and translations come from the version, so there is no
settingsfield. locale, such asde-DE, picks which of the template’s translations to use. Without it, the template’s own language is used.- Any version can be rendered, not only the latest. Once the template is deleted, renders of any
of its versions answer
404 NOT_FOUND. metadata,encryption,signatureandattachmentschange the PDF’s properties, passwords, signature and embedded files for this render only; see PDF standards and security.destinationalso uploads the PDF to a bucket of yours; see storage destinations.
2. Or render your own HTML
Section titled “2. Or render your own HTML”Send HTML your code builds as a document, with the paper and margins in settings:
curl "$PDF_URL/v1/renders" \ -H "x-api-key: $API_KEY" \ -H "content-type: application/json" \ -d '{ "document": { "kind": "html", "html": "<h1>Invoice INV-2041</h1><p>Total due: 360.00 EUR</p>", "css": ["h1 { color: #1d3557; }"] }, "settings": { "pageSize": { "widthMm": 210, "heightMm": 297 }, "margins": { "topMm": 20, "rightMm": 15, "bottomMm": 20, "leftMm": 15 } }, "waitMs": 10000 }'using Typefold.Client;
var client = new TypefoldClient( new Uri(Environment.GetEnvironmentVariable("PDF_URL")!), Environment.GetEnvironmentVariable("API_KEY")!);
byte[] pdf = await client.RenderAsync(new DirectRenderRequest{ Document = new HtmlDocument { Html = "<h1>Invoice INV-2041</h1><p>Total due: 360.00 EUR</p>", Css = new[] { "h1 { color: #1d3557; }" }, }, Settings = new RenderSettings { PageSize = PageSize.A4, Margins = new Margins { TopMm = 20, RightMm = 15, BottomMm = 20, LeftMm = 15 }, },});
await File.WriteAllBytesAsync("invoice.pdf", pdf);import { writeFile } from "node:fs/promises";import { PageSize, TypefoldClient } from "@typefold/client";
const client = new TypefoldClient({ baseUrl: process.env.PDF_URL!, apiKey: process.env.API_KEY!,});
const pdf = await client.render({ document: { html: "<h1>Invoice INV-2041</h1><p>Total due: 360.00 EUR</p>", css: ["h1 { color: #1d3557; }"], }, settings: { pageSize: PageSize.A4, margins: { topMm: 20, rightMm: 15, bottomMm: 20, leftMm: 15 }, },});
await writeFile("invoice.pdf", pdf);import { writeFile } from "node:fs/promises";import { PageSize, TypefoldClient } from "@typefold/client";
const client = new TypefoldClient({ baseUrl: process.env.PDF_URL, apiKey: process.env.API_KEY,});
const pdf = await client.render({ document: { html: "<h1>Invoice INV-2041</h1><p>Total due: 360.00 EUR</p>", css: ["h1 { color: #1d3557; }"], }, settings: { pageSize: PageSize.A4, margins: { topMm: 20, rightMm: 15, bottomMm: 20, leftMm: 15 }, },});
await writeFile("invoice.pdf", pdf);import osfrom pathlib import Path
from typefold import ( DirectRenderRequest, HtmlDocument, Margins, PageSize, TypefoldClient, RenderSettings,)
client = TypefoldClient(os.environ["PDF_URL"], os.environ["API_KEY"])
pdf = client.render( DirectRenderRequest( document=HtmlDocument( html="<h1>Invoice INV-2041</h1><p>Total due: 360.00 EUR</p>", css=["h1 { color: #1d3557; }"], ), settings=RenderSettings( page_size=PageSize.A4, margins=Margins(top_mm=20, right_mm=15, bottom_mm=20, left_mm=15), ), ))
Path("invoice.pdf").write_bytes(pdf)- Your HTML is used as sent: it is not a Handlebars template, so
{{name}}stays text, and JavaScript in it never runs. htmlis up to 5,000,000 characters.cssholds up to 20 stylesheets, applied in order;<style>elements in the HTML work too. See supported CSS.settingstakes the same fields as a template’spage.json.pageSizeandmarginsare required.operationsis refused with400 BAD_REQUEST, because only templates add pages from other templates.metadata,encryptionandsignatureare used as written, since there is no data to fill{{path}}placeholders.- The request also takes
metadata,encryption,signature,attachmentsanddestination, as in step 1. - Instead of HTML,
"kind": "node"with anodefield takes a tree ofcontainer,textandimageobjects. Its fields are in the API reference.
Use fonts and images in your HTML
Section titled “Use fonts and images in your HTML”The renderer doesn’t load files from the web. Upload each font, image or stylesheet once (scope
asset:manage; see the HTTP API), list its ID in assetIds (up to 100), and use
the ID where HTML or CSS takes a URL:
{ "document": { "kind": "html", "html": "<img src=\"3f2b6c1d-logo\" width=\"120\"><h1>Invoice INV-2041</h1>", "css": [ "@font-face { font-family: 'Brand'; src: url('9a7e5d3c-brand'); } body { font-family: 'Brand'; }" ], "assetIds": ["3f2b6c1d-logo", "9a7e5d3c-brand"] }, "settings": { "pageSize": { "widthMm": 210, "heightMm": 297 }, "margins": { "topMm": 20, "rightMm": 15, "bottomMm": 20, "leftMm": 15 } }}| Asset | File types | Use it as |
|---|---|---|
| Image | PNG, JPEG, SVG | <img src="ASSET_ID"> or CSS url("ASSET_ID") |
| Font | TTF, OTF, WOFF2 | @font-face { src: url("ASSET_ID"); } |
| Stylesheet | CSS | Applied automatically, before css |
- An unknown, deleted or duplicate asset ID answers
400 ASSET_INVALID, and no render is created. - Changing or deleting an asset later does not change renders that already exist.
- Small images can be inlined as
data:URLs instead.
3. Get the finished file
Section titled “3. Get the finished file”A render ends succeeded, failed or cancelled; see render states. The
clients’ render method does this step for you. Use the calls below to create a render now and
collect the PDF later.
Wait in the request
Section titled “Wait in the request”waitMs (0 to 10,000, default 0) keeps POST /v1/renders open until the render ends or the
time runs out:
| Response | When | Body |
|---|---|---|
200 |
The render ended in time. | The render: succeeded, failed or cancelled. |
202 |
Time ran out, or waitMs was 0. |
The render, still queued or processing. |
200 means ended, not succeeded: check state. Set your HTTP client’s timeout above waitMs.
Poll or get a webhook
Section titled “Poll or get a webhook”Read the render every few seconds until it has ended, or let a webhook tell you:
curl "$PDF_URL/v1/renders/0b6f0f8e-3c55-4c1e-a3a7-8d1a6f2c9e41" \ -H "x-api-key: $API_KEY"using Typefold.Client;
var client = new TypefoldClient( new Uri(Environment.GetEnvironmentVariable("PDF_URL")!), Environment.GetEnvironmentVariable("API_KEY")!);
Render render = await client.GetRenderAsync("0b6f0f8e-3c55-4c1e-a3a7-8d1a6f2c9e41");
Console.WriteLine(render.State);import { TypefoldClient } from "@typefold/client";
const client = new TypefoldClient({ baseUrl: process.env.PDF_URL!, apiKey: process.env.API_KEY!,});
const render = await client.getRender("0b6f0f8e-3c55-4c1e-a3a7-8d1a6f2c9e41");
console.log(render.state);import { TypefoldClient } from "@typefold/client";
const client = new TypefoldClient({ baseUrl: process.env.PDF_URL, apiKey: process.env.API_KEY,});
const render = await client.getRender("0b6f0f8e-3c55-4c1e-a3a7-8d1a6f2c9e41");
console.log(render.state);import os
from typefold import TypefoldClient
client = TypefoldClient(os.environ["PDF_URL"], os.environ["API_KEY"])
render = client.get_render("0b6f0f8e-3c55-4c1e-a3a7-8d1a6f2c9e41")
print(render.state)Download the PDF
Section titled “Download the PDF”A succeeded render has a result with downloadUrl, pageCount, outputBytes and
durationMs, and destination when it was uploaded to your bucket. downloadUrl works without the API key, so you can hand it to a browser, for five
minutes (expiresAt):
curl -o invoice.pdf "PASTE-THE-downloadUrl-HERE"using Typefold.Client;
var client = new TypefoldClient( new Uri(Environment.GetEnvironmentVariable("PDF_URL")!), Environment.GetEnvironmentVariable("API_KEY")!);
Render render = await client.GetRenderAsync("0b6f0f8e-3c55-4c1e-a3a7-8d1a6f2c9e41");byte[] pdf = await client.DownloadAsync(render);
await File.WriteAllBytesAsync("invoice.pdf", pdf);import { writeFile } from "node:fs/promises";import { TypefoldClient } from "@typefold/client";
const client = new TypefoldClient({ baseUrl: process.env.PDF_URL!, apiKey: process.env.API_KEY!,});
const render = await client.getRender("0b6f0f8e-3c55-4c1e-a3a7-8d1a6f2c9e41");const pdf = await client.download(render);
await writeFile("invoice.pdf", pdf);import { writeFile } from "node:fs/promises";import { TypefoldClient } from "@typefold/client";
const client = new TypefoldClient({ baseUrl: process.env.PDF_URL, apiKey: process.env.API_KEY,});
const render = await client.getRender("0b6f0f8e-3c55-4c1e-a3a7-8d1a6f2c9e41");const pdf = await client.download(render);
await writeFile("invoice.pdf", pdf);import osfrom pathlib import Path
from typefold import TypefoldClient
client = TypefoldClient(os.environ["PDF_URL"], os.environ["API_KEY"])
render = client.get_render("0b6f0f8e-3c55-4c1e-a3a7-8d1a6f2c9e41")pdf = client.download(render)
Path("invoice.pdf").write_bytes(pdf)Don’t store downloadUrl: every read of the render gives a fresh one, and the clients fetch it
for you when it has expired. Keep your own copy of PDFs you need, because renders are
deleted after 30 days. The other result fields are in the
API reference.
Cancel a render
Section titled “Cancel a render”A render that hasn’t ended can be cancelled (scope render:cancel):
curl -X POST "$PDF_URL/v1/renders/0b6f0f8e-3c55-4c1e-a3a7-8d1a6f2c9e41/cancel" \ -H "x-api-key: $API_KEY"using Typefold.Client;
var client = new TypefoldClient( new Uri(Environment.GetEnvironmentVariable("PDF_URL")!), Environment.GetEnvironmentVariable("API_KEY")!);
await client.CancelRenderAsync("0b6f0f8e-3c55-4c1e-a3a7-8d1a6f2c9e41");import { TypefoldClient } from "@typefold/client";
const client = new TypefoldClient({ baseUrl: process.env.PDF_URL!, apiKey: process.env.API_KEY!,});
await client.cancelRender("0b6f0f8e-3c55-4c1e-a3a7-8d1a6f2c9e41");import { TypefoldClient } from "@typefold/client";
const client = new TypefoldClient({ baseUrl: process.env.PDF_URL, apiKey: process.env.API_KEY,});
await client.cancelRender("0b6f0f8e-3c55-4c1e-a3a7-8d1a6f2c9e41");import os
from typefold import TypefoldClient
client = TypefoldClient(os.environ["PDF_URL"], os.environ["API_KEY"])
client.cancel_render("0b6f0f8e-3c55-4c1e-a3a7-8d1a6f2c9e41")- A
queuedrender never starts. Aprocessingrender becomescancelledat once, and no PDF is kept. - A render that has already ended answers
409 CONFLICT; an unknown ID answers404 NOT_FOUND.
Find earlier renders
Section titled “Find earlier renders”In Studio, Render jobs lists the workspace’s renders, newest first, with a Status filter. Open one to see its PDF and Diagnostics, then Download PDF or Cancel render.
Through the API, GET /v1/renders (scope render:read) lists them the same way, paged with
limit and cursor. Add ?state=failed, or any other state, to see only those:
curl "$PDF_URL/v1/renders?state=failed" -H "x-api-key: $API_KEY"List items have no result, error or download link; read the single render for those.
Render states
Section titled “Render states”| State | Ended | Meaning |
|---|---|---|
queued |
No | Accepted and waiting to start. |
processing |
No | Rendering, or waiting to try again after a temporary problem. |
succeeded |
Yes | The PDF is stored. The render has a result. |
failed |
Yes | No PDF could be made. The render has an error. |
cancelled |
Yes | You cancelled it before it ended. It has neither result nor error. |
A render that has ended never changes state again.
Handle failures
Section titled “Handle failures”A render that fails is not an HTTP error: the request succeeded, and the render’s state becomes
failed with an error that says why. A render.failed webhook event
carries only its code.
The error object
Section titled “The error object”{ "id": "5e2a7c1b-9d0f-4b8e-a6c3-2f1e0d9c8b7a", "state": "failed", "createdAt": "2026-10-08T09:20:11.002Z", "updatedAt": "2026-10-08T09:20:13.480Z", "error": { "code": "LIMIT_EXCEEDED", "message": "Reduce the document length to fit the page limit.", "retryable": false, "diagnostics": [ { "code": "max_pages", "message": "Reduce the document length to fit the page limit." } ] }}| Field | Description |
|---|---|
code |
ASSET_INVALID, UNSUPPORTED_FEATURE, LIMIT_EXCEEDED or RENDER_FAILED. |
message |
What to change. |
retryable |
Whether creating the same render again later might work. See retries. |
diagnostics |
Optional. Each problem, with a code, a message and sometimes a path. |
details |
Optional. The same problems as { path?, message }, as in other API errors. |
What each code and diagnostic means, and what to do about it, is under render failures in the HTTP API. Other optional fields are for support requests; don’t branch on them.
Retries
Section titled “Retries”Temporary problems are retried for you while the render stays processing. So when you see
failed:
retryable: false: the problem is in the request (the document, its files or settings), and the same request will fail again. Fix it first.retryable: true: the service had a temporary problem on every attempt. Creating the render again later may work. If it keeps happening, tell whoever runs your installation.
To render again, create a new render with a new Idempotency-Key: the old key returns the
same failed render.
Idempotency keys
Section titled “Idempotency keys”If creating a render times out or the connection drops, send it again with the same
Idempotency-Key header (idempotencyKey in the .NET and TypeScript clients, idempotency_key in
Python). You get the render the first request created instead of a second one. Use a value that
names the document, such as invoice-2026-0042.
- A key is 1 to 255 characters, per workspace.
- A repeat returns the first render in its current state, and waits for it if it has
waitMs. Repeats don’t count against your render allowances. - The key belongs to the method, path and body. Field order in the JSON doesn’t matter, but any
other change does, including a different
waitMs: reusing a key for a different request answers409 CONFLICT. - Two identical requests sent at the same moment still create only one render.
- A key is remembered as long as its render exists. After retention deletes the render, the key can be used again.
POST /v1/rendersand the Reports API’s PDF requests share one set of keys. Batches have their own.
Render an image
Section titled “Render an image”Set format to png, jpeg or webp to get one page of the document as an image instead of
the PDF, for a thumbnail, a preview or a social card.
curl "$PDF_URL/v1/renders" \ -H "x-api-key: $API_KEY" \ -H "content-type: application/json" \ -d '{ "templateVersionId": "c7e0b9a4-1f2d-4c3b-9a8e-5d6f7a8b9c0d", "data": { "invoice": { "number": "INV-2041" }, "customer": { "name": "Marta Tamm" } }, "format": "png", "image": { "page": 1, "width": 400 }, "waitMs": 10000 }'using Typefold.Client;
var client = new TypefoldClient( new Uri(Environment.GetEnvironmentVariable("PDF_URL")!), Environment.GetEnvironmentVariable("API_KEY")!);
byte[] png = await client.RenderAsync(new TemplateVersionRenderRequest{ TemplateVersionId = "c7e0b9a4-1f2d-4c3b-9a8e-5d6f7a8b9c0d", Data = new { invoice = new { number = "INV-2041" }, customer = new { name = "Marta Tamm" }, }, Format = RenderFormat.Png, Image = new ImageOptions { Page = 1, Width = 400 },});
await File.WriteAllBytesAsync("invoice-thumbnail.png", png);import { writeFile } from "node:fs/promises";import { TypefoldClient } from "@typefold/client";
const client = new TypefoldClient({ baseUrl: process.env.PDF_URL!, apiKey: process.env.API_KEY!,});
const png = await client.render({ templateVersionId: "c7e0b9a4-1f2d-4c3b-9a8e-5d6f7a8b9c0d", data: { invoice: { number: "INV-2041" }, customer: { name: "Marta Tamm" } }, format: "png", image: { page: 1, width: 400 },});
await writeFile("invoice-thumbnail.png", png);import { writeFile } from "node:fs/promises";import { TypefoldClient } from "@typefold/client";
const client = new TypefoldClient({ baseUrl: process.env.PDF_URL, apiKey: process.env.API_KEY,});
const png = await client.render({ templateVersionId: "c7e0b9a4-1f2d-4c3b-9a8e-5d6f7a8b9c0d", data: { invoice: { number: "INV-2041" }, customer: { name: "Marta Tamm" } }, format: "png", image: { page: 1, width: 400 },});
await writeFile("invoice-thumbnail.png", png);import osfrom pathlib import Path
from typefold import ImageOptions, RenderFormat, TypefoldClient, TemplateVersionRenderRequest
client = TypefoldClient(os.environ["PDF_URL"], os.environ["API_KEY"])
png = client.render( TemplateVersionRenderRequest( template_version_id="c7e0b9a4-1f2d-4c3b-9a8e-5d6f7a8b9c0d", data={"invoice": {"number": "INV-2041"}, "customer": {"name": "Marta Tamm"}}, format=RenderFormat.PNG, image=ImageOptions(page=1, width=400), ))
Path("invoice-thumbnail.png").write_bytes(png)image field |
Default |
|---|---|
page |
1. The page to draw, counted from 1 in the finished PDF, after pageRanges. |
width |
The page’s width in CSS pixels, at most 4096. From 16 to 4096; the height keeps the page’s shape. |
quality |
90. JPEG only, from 1 to 100. |
- The image shows the page as the PDF does, with its header and footer, page numbers and watermarks. A test render draws “TEST” on it too.
- For a social card (an Open Graph image), make the template’s page 1200 × 630 px (317.5 × 166.7 mm) with no margins. The image is then 1200 × 630 pixels.
webpimages are lossless.jpegis smallest for photos.- An image counts as one document, like a PDF. The render’s
result.mediaTypeis the image’s type,pageCountis1, and the download link serves the image. encryption,signature,attachmentsandcheckConformanceare for PDFs, so with an image they answer400 BAD_REQUEST, and so dosettings.encryption,signature,pdfaandpdfuaon your own HTML. A template’s own passwords, signature, attachments, PDF/A and PDF/UA are left out.- A page the document doesn’t have fails the render with the diagnostic
image_page_missing. An image taller than 8192 pixels fails withLIMIT_EXCEEDEDandimage_too_large; ask for a smallerwidth. - Batch items take
formatandimagetoo, but a batch with images can’t be downloaded as one PDF. The Reports API returns the image in the response.
Watermarks
Section titled “Watermarks”Add watermark to a render request to draw text such as DRAFT, COPY or PAID across every
page, in front of the content.
curl "$PDF_URL/v1/renders" \ -H "x-api-key: $API_KEY" \ -H "content-type: application/json" \ -d '{ "templateVersionId": "c7e0b9a4-1f2d-4c3b-9a8e-5d6f7a8b9c0d", "data": { "invoice": { "number": "INV-2041" }, "customer": { "name": "Marta Tamm" } }, "watermark": { "text": "DRAFT", "color": "#c00000", "opacity": 0.2 }, "waitMs": 10000 }'using Typefold.Client;
var client = new TypefoldClient( new Uri(Environment.GetEnvironmentVariable("PDF_URL")!), Environment.GetEnvironmentVariable("API_KEY")!);
byte[] pdf = await client.RenderAsync(new TemplateVersionRenderRequest{ TemplateVersionId = "c7e0b9a4-1f2d-4c3b-9a8e-5d6f7a8b9c0d", Data = new { invoice = new { number = "INV-2041" }, customer = new { name = "Marta Tamm" }, }, Watermark = new PdfWatermark { Text = "DRAFT", Color = "#c00000", Opacity = 0.2 },});
await File.WriteAllBytesAsync("invoice-draft.pdf", pdf);import { writeFile } from "node:fs/promises";import { TypefoldClient } from "@typefold/client";
const client = new TypefoldClient({ baseUrl: process.env.PDF_URL!, apiKey: process.env.API_KEY!,});
const pdf = await client.render({ templateVersionId: "c7e0b9a4-1f2d-4c3b-9a8e-5d6f7a8b9c0d", data: { invoice: { number: "INV-2041" }, customer: { name: "Marta Tamm" } }, watermark: { text: "DRAFT", color: "#c00000", opacity: 0.2 },});
await writeFile("invoice-draft.pdf", pdf);import { writeFile } from "node:fs/promises";import { TypefoldClient } from "@typefold/client";
const client = new TypefoldClient({ baseUrl: process.env.PDF_URL, apiKey: process.env.API_KEY,});
const pdf = await client.render({ templateVersionId: "c7e0b9a4-1f2d-4c3b-9a8e-5d6f7a8b9c0d", data: { invoice: { number: "INV-2041" }, customer: { name: "Marta Tamm" } }, watermark: { text: "DRAFT", color: "#c00000", opacity: 0.2 },});
await writeFile("invoice-draft.pdf", pdf);import osfrom pathlib import Path
from typefold import PdfWatermark, TypefoldClient, TemplateVersionRenderRequest
client = TypefoldClient(os.environ["PDF_URL"], os.environ["API_KEY"])
pdf = client.render( TemplateVersionRenderRequest( template_version_id="c7e0b9a4-1f2d-4c3b-9a8e-5d6f7a8b9c0d", data={"invoice": {"number": "INV-2041"}, "customer": {"name": "Marta Tamm"}}, watermark=PdfWatermark(text="DRAFT", color="#c00000", opacity=0.2), ))
Path("invoice-draft.pdf").write_bytes(pdf)| Field | Default |
|---|---|
text |
Required. One line, up to 200 characters. |
opacity |
0.3. From 0 (invisible) to 1 (solid). |
rotation |
Along the page’s diagonal. Degrees counter-clockwise; 0 is horizontal. |
fontSize |
Fills 60% of the page along the text’s angle, at most 144 pt. In points. |
color |
#808080 (gray). A hex colour such as #c00000. |
- The text is centred on each page in a bold sans-serif font. It covers Latin, Greek and
Cyrillic letters; other characters fail the render with
UNSUPPORTED_FEATUREand the diagnosticwatermark_text_unsupported. - The watermark is not part of the document’s text: it can’t be selected or searched, and screen readers skip it. PDF/A and PDF/UA files stay valid.
- Every request shape takes
watermark, and so does the Reports API inpdf. Withtest, both watermarks are drawn. - For a logo or styled text, draw another template over every page instead; see Combine templates.
Test renders
Section titled “Test renders”Add "test": true to a render request to get a free test PDF or image. It is the same document
with a large, translucent “TEST” drawn diagonally across every page. Use it while you build and check your
integration.
curl "$PDF_URL/v1/renders" \ -H "x-api-key: $API_KEY" \ -H "content-type: application/json" \ -d '{ "templateVersionId": "c7e0b9a4-1f2d-4c3b-9a8e-5d6f7a8b9c0d", "data": { "invoice": { "number": "INV-2041" }, "customer": { "name": "Marta Tamm" } }, "test": true, "waitMs": 10000 }'using Typefold.Client;
var client = new TypefoldClient( new Uri(Environment.GetEnvironmentVariable("PDF_URL")!), Environment.GetEnvironmentVariable("API_KEY")!);
byte[] pdf = await client.RenderAsync(new TemplateVersionRenderRequest{ TemplateVersionId = "c7e0b9a4-1f2d-4c3b-9a8e-5d6f7a8b9c0d", Data = new { invoice = new { number = "INV-2041" }, customer = new { name = "Marta Tamm" }, }, Test = true,});
await File.WriteAllBytesAsync("invoice-test.pdf", pdf);import { writeFile } from "node:fs/promises";import { TypefoldClient } from "@typefold/client";
const client = new TypefoldClient({ baseUrl: process.env.PDF_URL!, apiKey: process.env.API_KEY!,});
const pdf = await client.render({ templateVersionId: "c7e0b9a4-1f2d-4c3b-9a8e-5d6f7a8b9c0d", data: { invoice: { number: "INV-2041" }, customer: { name: "Marta Tamm" } }, test: true,});
await writeFile("invoice-test.pdf", pdf);import { writeFile } from "node:fs/promises";import { TypefoldClient } from "@typefold/client";
const client = new TypefoldClient({ baseUrl: process.env.PDF_URL, apiKey: process.env.API_KEY,});
const pdf = await client.render({ templateVersionId: "c7e0b9a4-1f2d-4c3b-9a8e-5d6f7a8b9c0d", data: { invoice: { number: "INV-2041" }, customer: { name: "Marta Tamm" } }, test: true,});
await writeFile("invoice-test.pdf", pdf);import osfrom pathlib import Path
from typefold import TypefoldClient, TemplateVersionRenderRequest
client = TypefoldClient(os.environ["PDF_URL"], os.environ["API_KEY"])
pdf = client.render( TemplateVersionRenderRequest( template_version_id="c7e0b9a4-1f2d-4c3b-9a8e-5d6f7a8b9c0d", data={"invoice": {"number": "INV-2041"}, "customer": {"name": "Marta Tamm"}}, test=True, ))
Path("invoice-test.pdf").write_bytes(pdf)- Test renders don’t count against your workspace’s
render allowances. They have their own: 10 queued or
processing and 10 created per minute, per workspace, or your own allowance if lower. Over
either, the request answers
429 RATE_LIMITEDwithRetry-After. - Every request shape takes
test: published versions, your own HTML, previews and batch items. So does the Reports API for PDFs. - On a workspace with a monthly document allowance,
every PDF preview is a test render, with or without
test, and test renders have the workspace’s own allowances. - Otherwise a test render is an ordinary render: it has an ID, appears in lists, sends webhooks and counts in usage.
- A render’s
testfield, anddata.testin its webhook events, istruefor a test render andfalseotherwise.
Retention
Section titled “Retention”Renders that have ended, and their PDFs, are deleted 30 days after they were created. After that,
reading the render answers 404 NOT_FOUND, and it is gone from lists and batches. Renders still
queued or processing are never deleted. On a self-hosted installation, set the period with
RENDER_RETENTION_DAYS in .env; see configuration.
Render unpublished template content
Section titled “Render unpublished template content”A preview request renders template content you send, without saving or publishing anything,
and needs template:read as well. The result is an ordinary render you wait for and download.
The clients don’t send previews, so use curl:
curl "$PDF_URL/v1/renders" \ -H "x-api-key: $API_KEY" \ -H "content-type: application/json" \ -d '{ "preview": { "composition": { "root": { "type": "html", "html": "<h1>Invoice {{invoice.number}}</h1>" } }, "settings": { "pageSize": { "widthMm": 210, "heightMm": 297 }, "margins": { "topMm": 20, "rightMm": 15, "bottomMm": 20, "leftMm": 15 } } }, "data": { "invoice": { "number": "INV-2041" } }, "waitMs": 10000 }'previewtakes the same object as thecontentof a template’s draft (GET /v1/templates/{id}/draft).templateIdnames a template whose folder the draft’s workspace files are found from. Without it, paths start at the workspace root.datareplaces the draft’s sample data."checkConformance": truerenders a plain PDF and lists inresult.conformancewhich PDF/A levels and PDF/UA parts it meets; see PDF standards and security.
To get the file straight back instead, use POST /v1/templates/{id}/preview; see
templates. Every field is in the API reference.
Next steps
Section titled “Next steps”- Get notified with webhooks instead of polling.
- Save PDFs to your own bucket.
- Render many documents at once.
- Build a template: paper, header and footer, page numbers and page breaks.
- API reference: every field of
POST /v1/rendersand its limits.