pdfops
Python client for the PDFops API: fill AcroForm PDFs, merge PDFs, inspect form fields, and generate invoices with one HTTP call. No native dependencies, no headless browser, nothing to install but this package.
Built for the moment pypdf / PyPDFForm stop cooperating — appearance streams that never render, NeedAppearances flags, encrypted government blanks, checkboxes that will not tick — and for runtimes where a PDF toolchain does not fit: Lambda, Cloud Functions, Airflow tasks, notebooks.
pip install pdfops
Zero dependencies (standard-library urllib), Python 3.9+, fully typed.
Quick start
from pdfops import PdfOps
client = PdfOps(api_key="pdfops_live_...") # or PdfOps() for the keyless trial (100 req/IP/month)
# 1. Discover the field names — you cannot fill fields you cannot name.
info = client.inspect("blank-w9.pdf")
print(info["count"], "fields")
fields = info["fillTemplate"] # paste-ready {name: ""} dict
# 2. Fill, optionally flattening so the result is no longer editable.
fields["Name"] = "Ada Lovelace"
fields["Agree"] = "true" # checkboxes take "true" / "false"
pdf_bytes = client.fill_form("blank-w9.pdf", fields, flatten=True)
open("w9-filled.pdf", "wb").write(pdf_bytes)
# 3. Merge, in order.
packet = client.merge(["cover.pdf", pdf_bytes, "terms.pdf"])
# 4. Invoice from data — deterministic, safe to re-render from a webhook.
invoice = client.invoice({
"from": "Acme Ltd",
"to": {"name": "Ada Lovelace", "lines": ["12 Analytical Way", "London"]},
"items": [{"description": "Consulting", "quantity": 3, "unit_price": 120.0}],
"invoice_number": "2026-0042",
"date": "2026-09-05",
"currency": "GBP",
})
PDF inputs may be bytes, a path (str / pathlib.Path), or a binary file object. Every PDF-producing call returns bytes.
API
| Method | Endpoint | Returns |
|---|---|---|
inspect(pdf) |
POST /api/inspect |
dict — count, hasXFA, fields[], fillTemplate |
fill_form(pdf, fields, *, flatten=False) |
POST /api/fill-form |
filled PDF bytes |
merge([pdf, pdf, ...]) |
POST /api/merge |
merged PDF bytes |
invoice(request) |
POST /api/invoice |
invoice PDF bytes |
usage() |
GET /api/usage |
dict — tier, limit, used, remaining, resets_at |
signup(email) |
POST /api/signup |
None — the key is emailed |
Full request/response schemas: pdfops.dev/docs · OpenAPI.
Errors
Every non-2xx response raises PdfOpsError carrying the API's stable error slug:
from pdfops import PdfOps, PdfOpsError
try:
client.fill_form("form.pdf", {"Nmae": "typo"})
except PdfOpsError as e:
print(e.status, e.code, e.details) # 422 unknown_field No field named Nmae
Common codes: unknown_field, exceeds_max_length, invalid_option, encrypted_pdf, rate_limited, invalid_api_key.
Configuration
PdfOps(api_key=...)or thePDFOPS_API_KEYenvironment variable. A free key (250 req/month, no card) is one field at pdfops.dev/pricing.client_tag="..."sets theX-Pdfops-Clientheader for anonymous usage attribution when you embed this client in another tool. Defaults to"py".timeout=60.0seconds per request;base_url=overrides the API origin for testing.transport=injects a(method, url, headers, body) -> (status, headers, body)callable — the test suite uses this; no network needed.
Why an API instead of a library
pdf-lib and pypdf are excellent when the PDF is well-behaved. PDFops exists for the other ones: it renders appearance streams server-side so every viewer shows the values, handles the encrypted-with-empty-password blanks governments publish, flattens deterministically, and is byte-for-byte reproducible — the same request yields the same file. The compatibility registry records which public forms fill cleanly with which toolchain.
Links
Fill a PDF in Python · Docs · MCP server for AI agents · TypeScript SDK · n8n node · hello@pdfops.dev
MIT © PDFops
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 pdfops-0.1.0.tar.gz.
File metadata
- Download URL: pdfops-0.1.0.tar.gz
- Upload date:
- Size: 9.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 |
514cd822ae88ff72aa1f34010ae77f09f0146b4256fb9dacdeb7e83c901516cb
|
|
| MD5 |
af90a3c53a6536fe5c9136968a940048
|
|
| BLAKE2b-256 |
156b3cc910b244eaf9a8e5292b64c4948afcad9541f1ca544c4f9bb3aa4581aa
|
Provenance
The following attestation bundles were made for pdfops-0.1.0.tar.gz:
Publisher:
publish.yml on pdfops/pdfops-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pdfops-0.1.0.tar.gz -
Subject digest:
514cd822ae88ff72aa1f34010ae77f09f0146b4256fb9dacdeb7e83c901516cb - Sigstore transparency entry: 2731120085
- Sigstore integration time:
-
Permalink:
pdfops/pdfops-python@c5223f7fd03a385407f971921349da9ee37ef404 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/pdfops
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c5223f7fd03a385407f971921349da9ee37ef404 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pdfops-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pdfops-0.1.0-py3-none-any.whl
- Upload date:
- Size: 8.0 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 |
b8bd90fac5e25b9f0ad60190cc1c55931daadcfadea5f3c333814106d03b64df
|
|
| MD5 |
d26e209ac539175e7a7e173bdf32405f
|
|
| BLAKE2b-256 |
19936be5ca99c144efb7ff7623831cab3f545cae46b70bce213b674b27a29f0d
|
Provenance
The following attestation bundles were made for pdfops-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on pdfops/pdfops-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pdfops-0.1.0-py3-none-any.whl -
Subject digest:
b8bd90fac5e25b9f0ad60190cc1c55931daadcfadea5f3c333814106d03b64df - Sigstore transparency entry: 2731120169
- Sigstore integration time:
-
Permalink:
pdfops/pdfops-python@c5223f7fd03a385407f971921349da9ee37ef404 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/pdfops
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c5223f7fd03a385407f971921349da9ee37ef404 -
Trigger Event:
push
-
Statement type: