HTML in,
PDF out.
One POST. Sub-second warm renders from a real Chromium, with the print options you actually need — margins, headers, footers, page ranges, wait conditions.
100 renders a month, free, no card. Then $9 for 2,000.
curl -X POST https://api.pdfcraft.dev/v1/render \
-H "Authorization: Bearer sk_live_dS0nS7pl4Xe2ViUb-4TeKqRQA1khKqOq" \
-H "Content-Type: application/json" \
-d '{"html":"<h1>hello</h1>"}' \
--output hello.pdfPublic on purpose, capped at 5 renders an hour per IP so you can try before signing up.
Warm Chromium, always
One browser per process, held open, handing out a fresh isolated context per render. A warm render is roughly ten times faster than a cold launch — which is what every serverless PDF service pays on every single request.
You are billed when the browser ran
Successes bill. A document that threw bills, because Chromium did the work. Timeouts, bad keys, malformed bodies and our own failures are free. The error reference marks every code, so the published rule and the charged rule are the same line of data.
Your documents are not stored
A synchronous render never touches disk — it exists in memory for the length of the render and then the context is destroyed. Output PDFs live behind a signed link for 24 hours, then a sweep job deletes them. Render records keep metadata only.
Run the curl
You now have a PDF on disk. That is the whole product — everything else is options.
Get your own key
Enter your email, click the link we send, copy the key off your dashboard. No password to choose.
Create an accountInstall the SDK
Zero dependencies, TypeScript types included. Or keep using fetch — it is one endpoint.
npm install @pdfcraft-dev/pdf
import { Renderer } from "@pdfcraft-dev/pdf";
const r = new Renderer(process.env.PDFCRAFT_API_KEY!);
const pdf = await r.render({ html: "<h1>hi</h1>" }); // Buffer
const { url } = await r.renderToUrl({ url: "https://example.com" });/v1/renderRender now. Returns application/pdf, or JSON with a signed URL when you send output: "url"./v1/render/asyncQueue it and get a signed callback at your callback_url. Same body plus that one field./v1/renders/:idPoll one render./v1/usageRenders used, plan limit, when the period resets.POST a PDF and get its tables and labelled fields back as JSON, each with a bounding box saying where on the page it came from. A table broken across fourteen pages comes back as one table.
No OCR and no model — extraction is geometric, so the same document always gives the same answer, and a scan is an honest 422 rather than a confident guess. Read the extraction docs.
curl -X POST https://api.pdfcraft.dev/v1/extract \
-H "Authorization: Bearer $PDFCRAFT_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"file\": \"$(base64 -w0 invoice.pdf)\"}"
# { "fields": { "Invoice Number": {
# "value": "INV-2026-0417", "page": 1,
# "bbox": [115, 55, 176, 64] } },
# "tables": [ … ], "usage": { "pages": 1 } }Send a url instead of html, and pass the headers or cookies your own app would send. Wait for the selector that means the page is actually ready, rather than guessing with a delay.
curl -X POST https://api.pdfcraft.dev/v1/render \
-H "Authorization: Bearer $PDFCRAFT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://app.example.com/invoices/1042",
"headers": { "Authorization": "Bearer YOUR_APP_TOKEN" },
"cookies": [{ "name": "session", "value": "…" }],
"options": { "waitFor": { "selector": "#ready", "networkIdle": true } },
"output": "url"
}'Your webhook secret is on the dashboard and is rotatable. It signs render callbacks only — it is not your billing webhook secret.
import { createHmac, timingSafeEqual } from "node:crypto";
const expected = createHmac("sha256", process.env.PDFCRAFT_WEBHOOK_SECRET!)
.update(rawBody)
.digest("hex");
const ok = timingSafeEqual(
Buffer.from(expected),
Buffer.from(req.headers["x-signature"] as string),
);Requested by developers
Every request below came from someone building on the API. Vote to move something up; I build from the top of this list.
Start with 100 renders a month.
No card, no trial clock, no sales call. When you outgrow it, $9 buys 2,000.