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.
Quickstart
Grab a key from the dashboard, then POST some HTML. The response body is the raw PDF.
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.pdfA 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.
X-API-Key: rk_live_k3yX9aR2mB7nQ4vL8Kq2Render a PDF
Renders content (HTML or Markdown) into a PDF (Content-Type: application/pdf).
Body parameters
| Field | Type | Description |
|---|---|---|
input_type required | string | html or markdown. |
content required | string | The HTML or Markdown source to render. Must be non-empty. |
options | object | page_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
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:
HTTP/2 200
content-type: application/pdf
x-request-id: 1a01b4ca-ad32-461b-9119-1a32a07802a1Validate a document
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 -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
/v1/validate (or ?strict=true) to find anything unsupported before you render.| Supported | Not 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": "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.
| Limit | Value | On exceed |
|---|---|---|
| Monthly conversions (Free) | 5,000 / month | 402 |
| Request rate (per account) | 10 req/s, burst 60 | 429 |
| Max input size | 2 MB | 400 |
| Render timeout | 5 s | 500 |
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.