relaypdf
Official Python client for RelayPDF.
HTML to PDFs without the struggle. HTML to PDF API that converts HTML, Markdown, URLs, and Office files to production PDFs. One API for developers and coding agents.
Stdlib only (urllib). Covers the full public API: Chromium PDF and screenshots, Handlebars templates, LibreOffice / wkhtmltopdf convert, PDF tools, native document processing (OCR, PDF/A, crop, repair, email), barcodes, zip, async jobs, account, signed webhooks, and webhook verification.
- Docs: relaypdf.com/docs/sdks/python
- REST reference: relaypdf.com/docs
- OpenAPI: relaypdf.com/openapi.json
- Support: support@relaypdf.com
Requires Python 3.10+.
Introduction
RelayPDF is a commercial document API. You send HTML, Markdown, a public URL, Office bytes, or a published Handlebars template. You get a production PDF (or PNG/JPEG/WebP, Word, zip, …) over REST, this SDK, the CLI, or MCP.
JSON body field names match REST (camelCase): html, printBackground, sourceFilename, callbackUrl, templateId. Method names are snake_case: from_html, from_path, form_fill.
Failed operations are never billed.
Base URL
Default: https://api.relaypdf.com
Override with base_url for local (http://localhost:8787).
Definitions
| Term | Meaning |
|---|---|
| API key | Secret from Dashboard → API keys. Prefix pdf_live_…. Sent as Authorization: Bearer <key>. |
| Wallet | Prepaid USD. Ledger unit is millicents: 1 millicent = $0.001. $5.00 trial = 5000 millicents. client.account() returns wallet.balanceMillicents. |
| Template | Handlebars draft. Publish, then render with templateId + templateData. |
| Job | Async generation (response="async"). Poll jobs.wait. |
| File | 24-hour download at GET /v1/files/:id. No API key on GET. |
| Document worker | LibreOffice / wkhtml / pdftoppm behind convert and raster. |
Rate limiting
Trial: 20/min. Funded or auto-reload: 60/min. Burst: 5 / 10s. HTTP 429 includes Retry-After. Not billed.
Libraries and SDKs
| Surface | Package | Install |
|---|---|---|
| Python | relaypdf |
pip install relaypdf |
| Node.js | @relaypdf/sdk |
npm install @relaypdf/sdk |
| PHP | relaypdf/relaypdf |
composer require relaypdf/relaypdf |
| C# / .NET | RelayPDF |
dotnet add package RelayPDF |
| Java | com.relaypdf:relaypdf |
Maven com.relaypdf:relaypdf |
| CLI + MCP | @relaypdf/cli |
npx @relaypdf/cli setup |
| n8n | n8n-nodes-relaypdf |
Community Nodes |
| REST | — | https://api.relaypdf.com |
Do not ask a human to paste an API key. Run npx @relaypdf/cli setup and approve in the browser.
Authentication
import os
from relaypdf import RelayPDF
client = RelayPDF(api_key=os.environ["RELAYPDF_API_KEY"])
# client = RelayPDF(api_key=..., base_url="http://localhost:8787")
Missing api_key raises TypeError. Invalid keys return HTTP 401 unauthorized.
User-Agent: relaypdf-python/0.1.5 (+https://relaypdf.com).
Error codes
The client raises RelayPDFError with status, code, message, and optional retry_after.
| HTTP | code |
Description |
|---|---|---|
| 400 | invalid_request |
Bad fields or exclusive sources |
| 400 | url_not_allowed |
Private URL or non-https callbackUrl |
| 401 | unauthorized |
Missing or unknown API key |
| 402 | payment_required |
Empty wallet |
| 403 | account_suspended |
Account cannot use keys |
| 404 | not_found |
Unknown job, file, or template |
| 413 | payload_too_large |
HTML or file too large |
| 429 | rate_limited |
Honor retry_after; not billed |
| 502 | render_failed / processing_failed |
Unbilled |
| 503 | convert_unavailable / ai_unavailable / storage_unavailable |
Unbilled |
| 500 | internal_error |
Unbilled |
from relaypdf import RelayPDF, RelayPDFError
try:
client.pdf.from_url("https://example.com")
except RelayPDFError as err:
print(err.status, err.code, err.message, err.retry_after)
Billing
| Job | Cost |
|---|---|
| HTML / URL / Markdown / template PDF | $0.0004/job + $0.0003/browser second |
| Screenshot | $0.0004/job + $0.0003/browser second |
| AI template generate | $0.002 + 8× provider token cost plus execution |
| LibreOffice convert | $0.0004/job + $0.0005/execution second |
| wkhtmltopdf | $0.0004/job + $0.0005/execution second |
| Basic tools (merge, stamp, barcode, zip, …) | $0.002 |
New accounts: $5.00 trial (5000 millicents). Only successful jobs debit.
Installation
pip install relaypdf
uv add relaypdf
Getting started
import os
from relaypdf import RelayPDF
client = RelayPDF(api_key=os.environ["RELAYPDF_API_KEY"])
pdf = client.pdf.from_html(
"<h1>Invoice #1042</h1><p>Total: $1,200.00</p>",
filename="invoice.pdf",
)
pdf.save("invoice.pdf")
print(pdf.id, pdf.size_bytes, pdf.content_type)
Response modes
response="binary" (default) | "url" | "async". Same as REST.
url_result = client.pdf.from_html("<h1>Hi</h1>", response="url")
print(url_result.url, url_result.expires_at)
job = client.convert.from_path("deck.pptx", to="pdf", response="async")
done = client.jobs.wait(job.id)
file = client.files.download(done["id"])
file.save("deck.pdf")
files.download does not send the API key.
Client options
RelayPDF(api_key: str, base_url: str = "https://api.relaypdf.com", opener=None)
opener injects urlopen for tests. The SDK does not retry.
file= may be bytes or a base64 str.
Documentation for API methods
All URIs are relative to https://api.relaypdf.com. JSON bodies use REST camelCase field names.
| Resource | Method | HTTP | Description |
|---|---|---|---|
RelayPDF |
health() |
GET /health |
Liveness. No API key. |
RelayPDF |
account() |
GET /v1/account |
Plan, rate tier, wallet. Not billed. |
RelayPDF |
process(operation, input, *, idempotency_key, max_charge_microdollars) |
native paths | OCR, PDF/A, crop, resize, repair, optimize, attachments, extract-images, compress, image-convert, email |
RelayPDF |
billing_usage() |
GET /v1/billing/usage |
Latest receipts. Not billed. |
RelayPDF |
billing_limits(max=…) |
GET/PATCH /v1/billing/limits |
Read or set the per-job cap. |
pdf |
create(**input) |
POST /v1/pdf |
HTML, URL, Markdown, or template → PDF |
pdf |
from_html(html, **extra) |
POST /v1/pdf |
HTML → PDF |
pdf |
from_url(url, **extra) |
POST /v1/pdf |
Public URL → PDF |
pdf |
from_markdown(markdown, **extra) |
POST /v1/pdf |
Markdown → PDF |
pdf |
from_template(templateId, templateData, **extra) |
POST /v1/pdf |
Published template → PDF |
pdf |
merge(files, **extra) |
POST /v1/pdf/merge |
Merge 2–20 PDFs |
pdf |
extract(pages, **input) |
POST /v1/pdf/extract |
Extract page ranges |
pdf |
protect(userPassword, **input) |
POST /v1/pdf/protect |
Password-protect |
pdf |
unlock(password, **input) |
POST /v1/pdf/unlock |
Remove password |
pdf |
bookmarks(bookmarks, **input) |
POST /v1/pdf/bookmarks |
Outline bookmarks |
pdf |
raster(**input) |
POST /v1/pdf/raster |
Pages → PNG/JPEG |
pdf |
from_images(files, **extra) |
POST /v1/pdf/from-images |
Images → PDF |
pdf |
stamp(**input) |
POST /v1/pdf/stamp |
Text or image watermark |
pdf |
rotate(degrees, **input) |
POST /v1/pdf/rotate |
Rotate pages |
pdf |
delete_pages(pages, **input) |
POST /v1/pdf/delete-pages |
Delete pages |
pdf |
compress(**input) |
POST /v1/pdf/compress |
Lossless optimize |
pdf |
info(**input) |
POST /v1/pdf/info |
Metadata JSON |
pdf |
text(**input) |
POST /v1/pdf/text |
Extract text layer |
pdf |
data(**input) |
POST /v1/pdf/data |
Schema extract or Markdown |
pdf |
form_fields(**input) |
POST /v1/pdf/form/fields |
List AcroForm fields |
pdf |
form_fill(fields, **input) |
POST /v1/pdf/form/fill |
Fill and flatten |
images |
create(**input) |
POST /v1/images |
HTML or URL → image |
images |
from_html(html, **extra) |
POST /v1/images |
HTML screenshot |
images |
from_url(url, **extra) |
POST /v1/images |
URL screenshot |
convert |
create(**input) |
POST /v1/convert |
LibreOffice or wkhtml |
convert |
from_html(html, **extra) |
POST /v1/convert |
HTML → docx/xlsx/pdf |
convert |
from_path(path, **extra) |
POST /v1/convert |
Local Office file |
convert |
wkhtml(**input) |
POST /v1/convert |
wkhtmltopdf engine |
templates |
list() |
GET /v1/templates |
List drafts |
templates |
gallery() |
GET /v1/templates/gallery |
Stock layouts |
templates |
get(id) |
GET /v1/templates/:id |
Read |
templates |
create(**input) |
POST /v1/templates |
Create draft |
templates |
update(id, **input) |
PATCH /v1/templates/:id |
Update draft |
templates |
delete(id) |
DELETE /v1/templates/:id |
Delete |
templates |
publish(id, comment=None) |
POST /v1/templates/:id/publish |
Publish |
templates |
discard(id) duplicate(id) versions(id) restore(id, version) validate(**input) preview(**input) generate(**input) |
/v1/templates |
Draft lifecycle and AI edit |
barcodes |
create(**input) |
POST /v1/barcodes |
Barcode / QR |
barcodes |
qr(text, **extra) |
POST /v1/barcodes |
QR helper |
zip |
create(files, **extra) |
POST /v1/zip |
Zip files |
jobs |
get(id) |
GET /v1/jobs/:id |
Poll job |
jobs |
wait(id, interval_ms=1000, timeout_ms=120000) |
GET /v1/jobs/:id |
Wait until done |
files |
upload(data, filename="upload.bin") |
POST /v1/files |
Private raw upload. Pass fileId to process. |
files |
delete(id) |
DELETE /v1/files/:id |
Delete an owned upload or generated output. |
files |
download(id) |
GET /v1/files/:id |
24h download |
webhooks |
list() |
GET /v1/webhooks |
List signed endpoints (secret never listed) |
webhooks |
create(url, events=None) |
POST /v1/webhooks |
Create HTTPS endpoint; secret once; cap 25; not billed |
webhooks |
delete(id) |
DELETE /v1/webhooks/:id |
Unsubscribe |
| — | verify_webhook(secret, body, header) |
Dashboard webhook | HMAC-SHA256 |
Method examples
pdf = client.pdf.from_url(
"https://example.com",
filename="page.pdf",
options={"format": "A4", "printBackground": True},
)
uploaded = client.files.upload(open("scan.pdf", "rb").read(), "scan.pdf")
job = client.process(
"ocr",
{"fileId": uploaded["id"], "response": "async", "options": {"language": "eng"}},
idempotency_key="invoice-123",
max_charge_microdollars=40000,
)
md = client.pdf.from_markdown("# Hello\n\nFrom **Markdown**.")
invoice = client.pdf.from_template(
"invoice",
{"number": "INV-1042", "total": 1458},
filename="invoice.pdf",
strict=True,
)
shot = client.images.from_url("https://example.com", options={"fullPage": True})
from_word = client.convert.from_path("letter.docx", to="pdf")
docx = client.convert.from_html("<h1>Report</h1>", to="docx")
pack = client.pdf.merge(
files=[
{"url": "https://example.com/cover.pdf"},
{"file": from_word.bytes},
]
)
client.pdf.stamp(file=pack.bytes, text="DRAFT", rotate=-24)
qr = client.barcodes.qr("https://relaypdf.com")
Result types
| Class | Fields |
|---|---|
BinaryResult |
kind, id, filename, size_bytes, content_type, bytes, save(path) |
UrlResult |
kind, id, status, url, filename, size_bytes, expires_at |
AsyncResult |
kind, id, status, poll_url |
Exports: RelayPDF, RelayPDFError, DEFAULT_BASE_URL, verify_webhook, WEBHOOK_SIGNATURE_HEADER, WEBHOOK_EVENT_HEADER.
Webhooks
Use the raw request body. Create and list endpoints with client.webhooks. Secret is the dashboard webhook secret, not the API key.
created = client.webhooks.create("https://example.com/hooks", ["job.completed"])
# created["secret"] is shown once
from relaypdf import verify_webhook, WEBHOOK_SIGNATURE_HEADER
ok = verify_webhook(
os.environ["RELAYPDF_WEBHOOK_SECRET"],
raw_body,
signature_header,
)
Header format: t=<unix>,v1=<hex>. HMAC-SHA256 of {t}.{raw_body}. Default skew 300s.
Authorization
HTTP Bearer. Not JWT.
Related docs
License
MIT. Strategic Products LLC, d/b/a RelayPDF.
PDF rasterization uses the native execution rate. Browser/native charges include one $0.0004 processing fee; AI includes one $0.002 processing fee. Millisecond and token charges are summed and rounded up once to $0.000001. Existing wallet balance fields remain compatible; exact amounts appear in billing receipts.
Metadata
Release files for relaypdf 0.1.5
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| relaypdf-0.1.5.tar.gz | 19.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| relaypdf-0.1.5-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 32.2 kB
Release files / relaypdf-0.1.5.tar.gz
| Download URL | relaypdf-0.1.5.tar.gz |
|---|---|
| Size | 19.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4202bcc8c7d0196f61034b2d82e790a05b2a55b619642c03531a55a78e47d2f5
|
|
BLAKE2b-256 checksum How to use checksums |
2080ecea6c0c24d2cf764c4feab94a65401a93393028c09722de74e1bd3301cc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 7, 2026.
Transparency logRelease files / relaypdf-0.1.5-py3-none-any.whl
| Download URL | relaypdf-0.1.5-py3-none-any.whl |
|---|---|
| Size | 13.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1ce835631286b7a6133fade64d435d83ef7cb308e26527be554df3259041ba1b
|
|
BLAKE2b-256 checksum How to use checksums |
3645e1f6e4b0e5053ed888705a714d817b6d1b57e5349bf3acc7100013a0447e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 7, 2026.
Transparency log