API Reference
Convert HTML and URLs to pixel-perfect PDFs with a simple REST API.
Quickstart
Three steps to your first PDF: sign up, verify your email, and grab an API key from /app/keys. Then:
curl -X POST https://api.docbrew.io/pdf/render \
-H "Authorization: Bearer pdfl_live_..." \
-H "Content-Type: application/json" \
-d '{"html": "<h1>Hello, PDF.</h1>"}' \
--output hello.pdfThat's it. The response is a binary PDF you can stream, save, or pipe.
Authentication
All production calls require an API key sent as a Bearer token in the Authorization header. Keys are created in your dashboard and shown in plaintext once — store them somewhere safe.
Authorization: Bearer pdfl_live_abc123...Requests without a key fall through to the public Playground flow (Cloudflare Turnstile, per-IP rate limit) — fine for testing in a browser, not viable for server integrations. Invalid keys return 401.
Endpoints
Base URL: https://api.docbrew.io
POST /pdf/render
Renders a raw HTML string to PDF.
{
"html": "<!DOCTYPE html><html>...</html>",
"options": {
"format": "A4",
"landscape": false,
"margin": { "top": "10mm", "right": "10mm", "bottom": "10mm", "left": "10mm" }
}
}html is required. options is optional — see Options. Responds with application/pdf bytes on success.
POST /pdf/render-url
Fetches a URL server-side and renders the resulting page. Waits for network idle before snapshotting; 30-second timeout.
{
"url": "https://example.com/invoice/1234",
"options": { "format": "Letter", "landscape": true }
}GET /health
Unauthenticated liveness probe. Returns {"status":"ok","service":"node-api"}.
Options
Passed as the options object in the request body.
| Field | Type | Default | Notes |
|---|---|---|---|
| format | string | "A4" | A3, A4, A5, Letter, Legal, Tabloid… |
| landscape | bool | false | Rotates the page. |
| margin | object | 10mm all | top / right / bottom / left — CSS units. |
Response headers
On successful keyed calls you'll get quota info in headers:
X-Plan: free
X-Plan-Renders-Used: 12
X-Plan-Renders-Limit: 50Use these to surface usage in your own dashboards and pre-empt 429s.
Errors
Errors return JSON with an error field and a meaningful HTTP status.
| Status | Meaning |
|---|---|
| 400 | Missing or invalid html / url. |
| 401 | Invalid or revoked API key. |
| 403 | Email not verified, or Turnstile failed (browser flow). |
| 408 / 504 | Render timed out — simplify content or lower the target URL timeout. |
| 429 | Plan render limit reached. Body includes renders_used / renders_limit / plan. |
| 500 | Chrome crashed or unreachable — retry. |
Examples
Copy-paste starters in a few languages.
curl
curl -X POST https://api.docbrew.io/pdf/render-url \
-H "Authorization: Bearer $DOCBREW_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com"}' \
--output page.pdfNode.js (fetch)
import fs from "node:fs/promises";
const res = await fetch("https://api.docbrew.io/pdf/render", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.DOCBREW_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
html: "<h1>Invoice</h1>",
options: { format: "A4" },
}),
});
if (!res.ok) throw new Error(`DocBrew ${res.status}: ${await res.text()}`);
await fs.writeFile("invoice.pdf", Buffer.from(await res.arrayBuffer()));Python (requests)
import os, requests
r = requests.post(
"https://api.docbrew.io/pdf/render",
headers={"Authorization": f"Bearer {os.environ['DOCBREW_KEY']}"},
json={"html": "<h1>Invoice</h1>", "options": {"format": "A4"}},
timeout=30,
)
r.raise_for_status()
with open("invoice.pdf", "wb") as f:
f.write(r.content)PHP
<?php
$res = Http::withToken(env('DOCBREW_KEY'))
->post('https://api.docbrew.io/pdf/render-url', [
'url' => 'https://example.com',
]);
file_put_contents('page.pdf', $res->body());Rate limits & quota
Each account has a monthly render budget tied to its plan. Current plans:free(200 renders/cycle) andpro(15,000 renders/cycle — manage in your dashboard).
Unauthenticated (Playground / Turnstile) traffic is additionally rate-limited per IP. Keyed traffic uses the plan quota instead.