API Reference

Featherpress API

Turn HTML or Markdown into a pixel-perfect PDF with a single HTTP request. No headless Chrome — a purpose-built renderer, edge-ready.

The API has one base URL and a tiny surface area: JSON in, a PDF (or JSON) out. Every request is authenticated with an API key.

BASEhttps://api.featherpress.dev
New here? Jump to the Quickstart to render your first PDF.

Quickstart

Grab a key from the dashboard, then POST some HTML. The response body is the raw PDF.

curl
curl -X POST "https://api.featherpress.dev/v1/render" \
  -H "X-API-Key: rk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"input_type": "html", "content": "<h1>Hello, world!</h1>"}' \
  --output hello.pdf

A 200 streams back application/pdf. That is the whole loop.

Authentication

Pass your key in the X-API-Key header on every request. Keys look like rk_live_… and are shown in full only once, at creation time. Only a SHA-256 hash is stored.

Authorization
X-API-Key: rk_live_k3yX9aR2mB7nQ4vL8Kq2
!
Never ship a key to the browser. Render server-side or from an edge function and keep keys in environment variables. Revoke a key any time from the dashboard.

Render a PDF

POST/v1/render

Renders content (HTML or Markdown) into a PDF (Content-Type: application/pdf).

Body parameters

FieldTypeDescription
input_type
required
stringhtml or markdown.
content
required
stringThe HTML or Markdown source to render. Must be non-empty.
options
objectpage_size (a3, a4, a5, letter, legal), margin (points, or {top,right,bottom,left}), landscape (boolean), header, footer.

Add ?strict=true to reject HTML that uses unsupported constructs with a 422 (the validator findings, below) instead of rendering it.

Request

Node.js
import { writeFile } from "node:fs/promises";

const res = await fetch("https://api.featherpress.dev/v1/render", {
  method: "POST",
  headers: {
    "X-API-Key": "rk_live_your_api_key",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    input_type: "html",
    content: "<h1>Hello, world!</h1>",
  }),
});

if (!res.ok) throw new Error(`Render failed: ${res.status}`);
await writeFile("hello.pdf", Buffer.from(await res.arrayBuffer()));

Response

On success the body is the binary PDF. Every response carries an x-request-id you can quote when reporting an issue:

Response headers
HTTP/2 200
content-type: application/pdf
x-request-id: 1a01b4ca-ad32-461b-9119-1a32a07802a1

Validate a document

POST/v1/validate

Statically lints content for the engine's CSS-subset blind spots without rendering it — and without spending a conversion. Returns { ok, findings }; each finding has a rule, severity (error or warn), message, and suggestion.

curl
curl -X POST "https://api.featherpress.dev/v1/validate" \
  -H "X-API-Key: rk_live_…" \
  -H "Content-Type: application/json" \
  -d '{"content": "<div style=\"background: var(--brand)\">Hi</div>"}'

# 200 OK
{
  "ok": false,
  "findings": [
    {
      "rule": "css-var-unsupported",
      "severity": "error",
      "message": "CSS custom property var() is not resolved; the property is silently dropped.",
      "suggestion": "Inline the literal value instead of var(--x).",
      "snippet": "background: var(--brand)"
    }
  ]
}

Supported CSS

i
Important caveat: because there is no full browser engine behind the API, only a subset of CSS is supported. This keeps rendering fast and predictable. Use /v1/validate (or ?strict=true) to find anything unsupported before you render.
SupportedNot supported (yet)
Box model, flexbox, basic grid, typography, colors, backgrounds, borders, tables, page breaks (break-before/after), web fonts via @font-face, images (PNG/JPEG/SVG).JavaScript execution, position: sticky, CSS animations/transitions, filters & backdrop-filter, complex clip-path, external stylesheets fetched at render time.

Errors

Errors return a JSON body with a human-readable error message and the numeric code:

error.json
{
  "error": "API key is missing or invalid.",
  "code": 401
}
200Success — the body is the PDF (or JSON for /v1/validate).
400Invalid request — malformed JSON or empty content.
401Missing, unknown, or revoked API key.
402Insufficient credits / quota exhausted.
422strict=true and the document uses unsupported constructs.
429Per-account rate limit exceeded — back off and retry.
500Internal error — safe to retry with backoff.

Limits

The Free plan includes 5,000 conversions per month. Each successful /v1/render counts as one conversion; /v1/validate is free and never counts. When your monthly quota is exhausted the API responds 402.

LimitValueOn exceed
Monthly conversions (Free)5,000 / month402
Request rate (per account)10 req/s, burst 60429
Max input size2 MB400
Render timeout5 s500

Track live usage and latency in your dashboard. No SDK is required — call the HTTP API from any language (see integration examples). First-party client libraries are planned.