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, 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.3 (+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.015 |
| Screenshot | $0.015 |
| AI template generate | $0.05 |
| LibreOffice convert | $0.04 |
| wkhtmltopdf | $0.025 |
| Tools (merge, stamp, raster, barcode, zip, …) | $0.005 |
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. |
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 |
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 |
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 |
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 |
Node has additional template helpers (discard, duplicate, versions, restore, validate, preview, generate). Those REST paths are available with raw HTTP if you need them before a Python minor.
Method examples
pdf = client.pdf.from_url(
"https://example.com",
filename="page.pdf",
options={"format": "A4", "printBackground": True},
)
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.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file relaypdf-0.1.3.tar.gz.
File metadata
- Download URL: relaypdf-0.1.3.tar.gz
- Upload date:
- Size: 17.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3446ed2189841c148fc97c6742ee5b7c80fb2b565983e00eef397fca93a3de72
|
|
| MD5 |
e23cc0b5fddba57c997b82dd9aeaf552
|
|
| BLAKE2b-256 |
d4706fef28494c823b6e4dd3cebe19debc3760e676db245236e0c41cbcd69be4
|
Provenance
The following attestation bundles were made for relaypdf-0.1.3.tar.gz:
Publisher:
publish-python.yml on timspell1/PDF
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
relaypdf-0.1.3.tar.gz -
Subject digest:
3446ed2189841c148fc97c6742ee5b7c80fb2b565983e00eef397fca93a3de72 - Sigstore transparency entry: 2582091009
- Sigstore integration time:
-
Permalink:
timspell1/PDF@6dd2f0eec99c5b9f1f763fe5701d4d5ce2708a97 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/timspell1
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-python.yml@6dd2f0eec99c5b9f1f763fe5701d4d5ce2708a97 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file relaypdf-0.1.3-py3-none-any.whl.
File metadata
- Download URL: relaypdf-0.1.3-py3-none-any.whl
- Upload date:
- Size: 11.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
87616957a43f02ab8ce348dc10ded06e065ba1ac5c249b9041fd0b26f5f36b89
|
|
| MD5 |
be3c16dbbf67ad32ee44f5da56e7f8fc
|
|
| BLAKE2b-256 |
fb8c1c29cc3894ba40b74cbc929213002ff2175b3118b6a4c1ade736e23c69d7
|
Provenance
The following attestation bundles were made for relaypdf-0.1.3-py3-none-any.whl:
Publisher:
publish-python.yml on timspell1/PDF
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
relaypdf-0.1.3-py3-none-any.whl -
Subject digest:
87616957a43f02ab8ce348dc10ded06e065ba1ac5c249b9041fd0b26f5f36b89 - Sigstore transparency entry: 2582091020
- Sigstore integration time:
-
Permalink:
timspell1/PDF@6dd2f0eec99c5b9f1f763fe5701d4d5ce2708a97 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/timspell1
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-python.yml@6dd2f0eec99c5b9f1f763fe5701d4d5ce2708a97 -
Trigger Event:
workflow_dispatch
-
Statement type: