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:

bash
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.pdf

That'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.

http
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.

json
{
  "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.

json
{
  "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.

FieldTypeDefaultNotes
formatstring"A4"A3, A4, A5, Letter, Legal, Tabloid…
landscapeboolfalseRotates the page.
marginobject10mm alltop / right / bottom / left — CSS units.

Response headers

On successful keyed calls you'll get quota info in headers:

http
X-Plan: free
X-Plan-Renders-Used: 12
X-Plan-Renders-Limit: 50

Use 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.

StatusMeaning
400Missing or invalid html / url.
401Invalid or revoked API key.
403Email not verified, or Turnstile failed (browser flow).
408 / 504Render timed out — simplify content or lower the target URL timeout.
429Plan render limit reached. Body includes renders_used / renders_limit / plan.
500Chrome crashed or unreachable — retry.

Examples

Copy-paste starters in a few languages.

curl

bash
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.pdf

Node.js (fetch)

javascript
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)

python
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
<?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.

Ready to start?

Sign up and grab a key in 30 seconds. No credit card required.

Create your account →