pdfik
Official Python SDK for PDFik — the asynchronous URL/HTML-to-PDF API.
Submit a public URL or raw HTML, get a job id back, and receive an HMAC-signed webhook (or poll) when the PDF is ready. Rendering runs on sandboxed headless Chromium, so modern CSS, web fonts and JavaScript-heavy pages come out the way they look in the browser.
Features
- Type Safety: Type hints for all options and response objects.
- Sync & Async: Exposes both
PdfikClientandAsyncPdfikClientusing the modernhttpxengine. - Auto Retry: Automatic exponential backoff for
5xxand429(Rate Limit) errors powered bytenacity. - DX Affordances: Built-in polling logic (
wait_for_job) and file download helpers.
Installation
pip install pdfik
Quick Start
Convert public URL to PDF (Sync)
from pdfik import PdfikClient, PdfOptions, MarginOptions
# Initialize the client
client = PdfikClient(api_key="sk_live_...")
# 1. Submit the URL to be rendered
job = client.url_to_pdf(
"https://example.com",
options=PdfOptions(
format="A4",
landscape=False,
print_background=True,
margin=MarginOptions(top="10mm", bottom="10mm")
)
)
print(f"Job created: {job.job_id}. Waiting for rendering...")
# 2. Poll until the job completes
result = client.wait_for_job(job.job_id)
print(f"Job finished! Pages: {result.pages_count}")
# 3. Download the PDF bytes
pdf_bytes = client.download_pdf(job.job_id)
with open("output.pdf", "wb") as f:
f.write(pdf_bytes)
print("PDF saved to output.pdf")
# Close connection pool
client.close()
Convert raw HTML to PDF (Async)
import asyncio
from pdfik import AsyncPdfikClient, PdfOptions
async def main():
async with AsyncPdfikClient(api_key="sk_live_...") as client:
job = await client.html_to_pdf(
"<h1>Hello World</h1><p>Sent from PDFik Python SDK</p>",
options=PdfOptions(format="Letter")
)
result = await client.wait_for_job(job.job_id)
print(f"Job finished! Status: {result.status}")
pdf_bytes = await client.download_pdf(job.job_id)
asyncio.run(main())
Get the direct download URL
Get the direct API download URL for the generated PDF (requires the "X-API-Key" header to download):
file_info = client.get_file_url(job.job_id)
print(f"Direct Download URL: {file_info.download_url}")
Test mode
Pass test=True to run a job through the full pipeline (statuses, webhook, download) without real rendering — the download returns a small sample PDF. Test jobs are free (no PDF or byte quota is debited) and rate-limited instead: 60 test calls per minute and 2,000 per day per account. Job status and webhook payloads always carry test: true|false, so you can safely exercise your integration end to end:
job = client.url_to_pdf("https://example.com", test=True)
result = client.wait_for_job(job.job_id)
print(result.test) # True
print(result.expires_at) # e.g. "2026-08-05T12:00:00Z"
pdf_bytes = client.download_pdf(job.job_id) # sample PDF
Factur-X e-invoices
Turn a UN/CEFACT Cross-Industry-Invoice XML into a Factur-X / ZUGFeRD hybrid: a human-readable PDF/A-3 with the XML embedded as factur-x.xml. The XML is validated against the official XSD of the declared profile before any quota is spent; the output is validated with veraPDF and Mustangproject. Schema-valid does not mean tax-compliant — the invoice content is the caller's responsibility. Available on every plan;
from pdfik import PdfikClient
client = PdfikClient(api_key="sk_live_...")
# The visual invoice is built from a block template (saved in the dashboard,
# passed inline as `template=`, or the built-in default when both are omitted).
job = client.einvoice_to_pdf(
cii_xml, # your CII XML string, UTF-8, up to 1 MB
profile="en16931", # minimum | basicwl | basic | en16931 | extended
template_id="c0ffee00-...", # optional; mutually exclusive with template=
)
result = client.wait_for_job(job.job_id)
pdf_bytes = client.download_pdf(job.job_id)
Alternatively, render your own page as the visual half — pass einvoice= to url_to_pdf / html_to_pdf and the output is normalized to PDF/A-3 with the XML embedded (not combinable with user_password or compression, which break PDF/A):
from pdfik import EInvoiceOptions
job = client.html_to_pdf(
invoice_html,
einvoice=EInvoiceOptions(xml=cii_xml, profile="en16931"),
)
Profiles minimum and basicwl carry accompanying data only and are not a legally sufficient e-invoice. Error codes: einvoice-xml-invalid, einvoice-options-conflict, einvoice-template-not-found, payload-too-large-for-queue.
Limits, retention and error codes
- Generated volume per month (byte quota, counts rendered output only — downloads are free): Free 0.5 GB, Starter 10 GB, Pro 50 GB, Business 300 GB. Business plans can purchase additional +1 GB blocks from the dashboard. Exceeding the quota returns
429(quota-bytes-exceeded). - File size: up to 150 MB per PDF on every plan — contact support if you need more. A larger render fails the job with
FILE_TOO_LARGE(file-too-large). - Retention: each file is available for download for 24 hours after the job finishes; the exact deadline is the
expires_atfield in the job status and webhook payload. After that the download returns410(file-expired). - Downloads: at most 3 download attempts per file (counted when the stream starts); after that the API returns
429(download-attempts-exhausted) — re-render the file or contact support. Only 1 download stream may be active at a time; a concurrent request returns429(download-busy) — wait ~10 seconds and retry, it does not burn an attempt.
Handling Webhooks
If you specify webhook_url in the options of url_to_pdf or html_to_pdf, PDFik will send an HTTP POST request to your server when the rendering job is finished.
You should verify the authenticity of the webhook by validating the signature. We provide a static helper verify_webhook_signature in PdfikClient for this purpose.
During a secret rotation both the new and the previous secret verify for the overlap window you pick in the dashboard (1 hour to 5 days, or none at all), so check the new one first and fall back to the old one while you roll your receivers out.
Custom delivery headers (e.g. a Cloudflare Access service token) are configured once on the dashboard Webhooks page and attached to every delivery — nothing to pass per request.
Example (FastAPI):
from fastapi import FastAPI, Request, HTTPException
from pdfik import PdfikClient
app = FastAPI()
# Get your webhook secret from the PDFik Dashboard
WEBHOOK_SECRET = "whsec_..."
@app.post("/webhook")
async def handle_webhook(request: Request):
# Retrieve headers
headers = dict(request.headers)
# Retrieve the raw payload bytes
raw_body = await request.body()
# Verify signature
is_valid = PdfikClient.verify_webhook_signature(raw_body, headers, WEBHOOK_SECRET)
if not is_valid:
raise HTTPException(status_code=400, detail="Invalid signature")
# Process event
event = await request.json()
print(f"Received webhook event: {event.get('event_type')} for job: {event.get('job_id')}")
return {"received": True}
License
MIT License.
Metadata
Release files for pdfik 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pdfik-0.3.0.tar.gz | 13.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pdfik-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 25.6 kB
Release files / pdfik-0.3.0.tar.gz
| Download URL | pdfik-0.3.0.tar.gz |
|---|---|
| Size | 13.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1c5312929eaa806b4b41544a064edaae75f8e4bb36bb2d50c6dc42b64857f5fc
|
|
BLAKE2b-256 checksum How to use checksums |
eb74fe3b4ec6a290a9229b732bd047e19af00e501d08cfa8c940dbafa42ddf9a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.10
|
Release files / pdfik-0.3.0-py3-none-any.whl
| Download URL | pdfik-0.3.0-py3-none-any.whl |
|---|---|
| Size | 11.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6813e2b529ddd94f591f160664a4f550651c4fa0c2c184917e401d643ba77d00
|
|
BLAKE2b-256 checksum How to use checksums |
701a099e7b56c5f12dab9030874cf3a4681ad06ff975fde22278e64077a930bb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.10
|