Generate a PDF with one POST request
Send HTML or a URL, get a finished PDF back in the response body. No SDK required — any HTTP client works. The free tier covers 50 documents a month.
Quick start
Create a free account, confirm your email, and your key is waiting in the dashboard. Then one request:
# 1. create a free account and copy your key from the dashboard
# https://pdfgeny.com/account/signup/
# 2. render a document
curl -X POST https://pdfgeny.com/api/v1/render \
-H "Authorization: Bearer $PDFGENY_KEY" \
-H "Content-Type: application/json" \
-d '{"html":"<h1>Invoice 001</h1>"}' \
--output invoice.pdf
Authentication
Every request carries your key in a header. X-API-Key works as well if a bearer token is awkward in your client. Keys are shown once at creation and stored hashed — if you lose one, revoke it and create another in the dashboard.
Authorization: Bearer pg_your_key_here
POST /api/v1/render
Send exactly one of html or url. The response is the PDF itself with Content-Type: application/pdf.
| Field | Type | Default | Notes |
|---|---|---|---|
html | string | — | Markup to render. Up to 2 MB. |
url | string | — | Page to load. Must start with http:// or https://. |
format | string | A4 | A3, A4, A5, Letter, Legal, Tabloid. |
landscape | bool | false | Rotate the page. |
margin | string | 12mm | Any CSS length, applied to all four sides. |
scale | number | 1.0 | Between 0.1 and 2. |
print_background | bool | true | Keep background colours and images. |
filename | string | document.pdf | Sets Content-Disposition. |
Two response headers are worth reading: X-Render-Duration-Ms and X-Quota-Remaining.
Examples
Python
import requests
pdf = requests.post(
"https://pdfgeny.com/api/v1/render",
headers={"Authorization": f"Bearer {KEY}"},
json={"html": "<h1>Invoice 001</h1>", "format": "A4"},
timeout=60,
)
open("invoice.pdf", "wb").write(pdf.content)
Node.js
const res = await fetch("https://pdfgeny.com/api/v1/render", {
method: "POST",
headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ url: "https://example.com/report", format: "Letter" }),
});
await writeFile("report.pdf", Buffer.from(await res.arrayBuffer()));
PHP
$ch = curl_init("https://pdfgeny.com/api/v1/render");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer $key", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => json_encode(["html" => "<h1>Invoice</h1>"]),
]);
file_put_contents("invoice.pdf", curl_exec($ch));
Go
body, _ := json.Marshal(map[string]any{"html": "<h1>Invoice</h1>"})
req, _ := http.NewRequest("POST", "https://pdfgeny.com/api/v1/render", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+key)
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
Quotas and errors
Check your remaining balance at any time:
curl https://pdfgeny.com/api/v1/usage -H "Authorization: Bearer $PDFGENY_KEY"
{"plan":"free","quota":50,"used":3,"remaining":47,"watermark":true,"resets":"2026-10-01"}
Errors share one shape: {"error": {"code": "quota_exceeded", "message": "…", "docs": "…"}}. Codes: missing_api_key, invalid_api_key, invalid_json, invalid_input, payload_too_large, quota_exceeded, render_failed.
| Status | Meaning |
|---|---|
401 | Key missing, unknown, revoked, or the account email is not confirmed yet. |
400 | Malformed body, or neither/both of html and url. |
413 | HTML payload above 2 MB. |
422 | The page failed to render — bad markup, or a URL that never loaded. |
429 | Monthly quota reached. The response names the plan and the limit. |
Documents on the free plan carry a small footer line. Any paid plan removes it.