Quickstart
Create an account, confirm your email, create an API key in the dashboard, then send the request below from your backend.
- Sign up and confirm your email, then create an API key in the dashboard.
- POST your HTML to /v1/documents/render and read documentId from the JSON response.
- GET /v1/documents/:documentId/download with the same key and follow the redirect to the signed URL.
curl -X POST https://serverlesspdf.com/v1/documents/render \
-H "Authorization: Bearer <YOUR_API_KEY>" \
-H "Content-Type: application/json" \
-d '{"html": "<h1>Hello PDF!</h1>"}'
# → {"documentId": "<id>", "downloadUrl": "/v1/documents/<id>/download", ...}
curl -L https://serverlesspdf.com/v1/documents/<id>/download \
-H "Authorization: Bearer <YOUR_API_KEY>" -o out.pdfdownloadUrl is relative — prepend https://serverlesspdf.com. On success the requestId lives in the x-request-id response header.
const res = await fetch("https://serverlesspdf.com/v1/documents/render", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SERVERLESSPDF_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ html: "<h1>Hello PDF!</h1>", options: { pageSize: "A4" } }),
});
const { documentId, downloadUrl } = await res.json();
const pdf = await fetch(`https://serverlesspdf.com${downloadUrl}`, {
headers: { "Authorization": `Bearer ${process.env.SERVERLESSPDF_API_KEY}` },
});import json, os, urllib.request
KEY = os.environ["SERVERLESSPDF_API_KEY"]
BASE = "https://serverlesspdf.com"
def call(path, payload=None):
req = urllib.request.Request(
BASE + path,
data=json.dumps(payload).encode() if payload is not None else None,
headers={"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"},
method="POST" if payload is not None else "GET",
)
with urllib.request.urlopen(req) as res:
return json.load(res), res.headers.get("x-request-id")
body, _ = call("/v1/documents/render", {"html": "<h1>Hello PDF!</h1>"})
pdf, _ = call(body["downloadUrl"]) # follows the signed-URL redirect
print(body["documentId"], body["pageCount"])POST /v1/documents/render
POSTPrimary PDF generation method. Accepts HTML, CSS and render options. Returns a ready document or a link to it.
Authorization: Bearer <api_key>
Content-Type: application/json
{
"html": "<!doctype html><html>...</html>",
"options": {
"pageSize": "A4",
"landscape": false,
"printBackground": true,
"margin": {
"top": "12mm",
"right": "12mm",
"bottom": "12mm",
"left": "12mm"
}
}
}- html (required, string up to 10 MB) — the full ready-made HTML document.
- options.pageSize (optional, A4 | LETTER) — page size, default A4.
- options.landscape (optional, boolean) — landscape orientation.
- options.printBackground (optional, boolean) — print CSS backgrounds.
- options.margin.top | right | bottom | left (optional, string) — CSS sizes, e.g. 12mm.
On success the API returns the document id and a download path you can call with the same API key.
{
"documentId": "<id>",
"status": "success",
"downloadUrl": "/v1/documents/<id>/download",
"sizeBytes": 12345,
"pageCount": 1,
"checksum": "<sha256>"
}GET /v1/documents/:documentId/download
GETFetch a generated PDF document by ID. On success the route returns a redirect to a short-lived signed URL for secure downloads.
When an Authorization header is present, only the API key is used; an invalid key returns AUTH_FAILED 401 without falling back to the session.
On success the route answers with a 302 redirect to a short-lived signed URL. Download the PDF by following the redirect.
curl -L https://serverlesspdf.com/v1/documents/<id>/download \
-H "Authorization: Bearer <YOUR_API_KEY>" -o out.pdf
# → 302 Found
# → Location: https://storage/v1/object/sign/documents/<id>.pdf?token=<short-lived>
# → followed automatically with -L; without -L read the Location header
- Session cookie (browser)
- Bearer API key (backend integrations)
Error format
All errors use the same structured response shape:
- 400 VALIDATION_ERROR / INVALID_HTML: fix the payload and retry.
- 401 AUTH_FAILED: missing or invalid API key.
- 403 INSUFFICIENT_SCOPE / QUOTA_EXCEEDED: the key lacks the required scope or the quota window is exhausted.
- 404 DOCUMENT_NOT_FOUND: unknown id or a document from another workspace.
- 413 HTML_TOO_LARGE: shrink the HTML payload and retry.
- 429 RATE_LIMITED / CONCURRENT_LIMIT_EXCEEDED: back off and respect the Retry-After header.
- 500 RENDER_ERROR / 502 STORAGE_UPLOAD_FAILED / 504 RENDER_TIMEOUT: retry with simpler HTML and keep requestId for support.
{
"error": {
"code": "QUOTA_EXCEEDED",
"message": "Monthly limit reached",
"requestId": "req_123abc"
}
}
# 429 responses include a Retry-After header (seconds): Retry-After: 60The message names the offending field, so agents and SDKs can repair the payload without asking:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid render payload: \"options.pageSize\" — Invalid option: expected one of \"A4\"|\"LETTER\".",
"requestId": "req_456def"
}
}