PDF4me Python SDK Getting Started
The pdf4me package on PyPI is the official async Python SDK for the PDF4me REST API. It ships 106 typed async functions across 18 modules covering conversion, optimization, merging, splitting, stamping, e-signing, OCR, form filling, extraction, barcodes, ZUGFeRD e-invoicing, and AI-based document extraction from invoices, receipts, contracts, bank statements, tax documents, and pay stubs.
Package facts
| Field | Value |
|---|---|
| Package name | pdf4me |
| Current version | 1.0.2 (published 2026-09-14) |
| License | MIT |
| Python support | >=3.11 (tested on 3.11, 3.12, 3.13, 3.14) |
| Development status | Production/Stable |
| Framework | AsyncIO |
| Source | github.com/pdf4me/pdf4me-clientapi-python |
| PyPI | pypi.org/project/pdf4me |
Under the hood the client is generated with Microsoft Kiota on top of httpx[http2], so every action is fully typed, returns typed results, and reuses one connection pool per client.
Prerequisites
- Python 3.11 or newer.
- A PDF4me account and API key.
pip(oruv,poetry, or any PyPI-compatible installer).
Install
- pip
- uv
- Poetry
python -m pip install pdf4me
uv add pdf4me
poetry add pdf4me
Installing pdf4me pulls in httpx[http2] and the Microsoft Kiota runtime packages (microsoft-kiota-abstractions, microsoft-kiota-http, and the JSON, form, text, and multipart serialization backends).
Authenticate
Set your API key in the environment:
- Bash / Zsh
- PowerShell
- Windows CMD
export PDF4ME_API_KEY="your-api-key"
$env:PDF4ME_API_KEY = "your-api-key"
set PDF4ME_API_KEY=your-api-key
The examples below read the key from that variable at runtime. You can also pass it directly to Pdf4meClient(api_key) if you prefer to load it from a secret manager, .env file, or another source.
Quick start
The smallest useful program: open a client, run one action, write the result to disk.
import asyncio
import os
from pathlib import Path
from pdf4me import Pdf4meClient
from pdf4me.optimize import optimize
async def main() -> None:
api_key = os.environ.get("PDF4ME_API_KEY")
if not api_key:
raise SystemExit("Set PDF4ME_API_KEY before running this example.")
source = Path("input.pdf")
async with Pdf4meClient(api_key) as client:
result = await optimize(
client, source.read_bytes(), doc_name=source.name
)
Path("optimized.pdf").write_bytes(result)
print("Saved optimized.pdf")
if __name__ == "__main__":
asyncio.run(main())
Three things to notice:
- One client, many calls. The
async with Pdf4meClient(...)context manages the underlyinghttpxconnection pool. Reuse the same client for every call insidemaininstead of opening one per action. - Actions are free functions, not client methods. Import each action from its module (
from pdf4me.optimize import optimize,from pdf4me.pdf import get_pdf_metadata) and pass the client as the first argument. This keeps the surface flat and lets you tree-shake unused modules in your editor. - Bytes in, bytes out. Actions that produce a file return
bytes. Actions that produce metadata return typed dataclasses. There is no on-disk staging, so you can pipe results between actions without touching the filesystem.
Two more common calls
Stamp a PDF:
from pdf4me.edit import StampAlignX, stamp
stamped = await stamp(client, data, text="DRAFT", align_x=StampAlignX.Center)
Path("stamped.pdf").write_bytes(stamped)
Read PDF metadata:
from pdf4me.pdf import get_pdf_metadata
metadata = await get_pdf_metadata(client, data, doc_name="input.pdf")
print(metadata.page_count)
Every value passed as an enum (like StampAlignX.Center) is a typed constant, so your IDE auto-completes the legal values and mypy or pyright catches typos at edit time.
What is available in the box (106 actions across 18 modules)
| Module | Actions | Import example |
|---|---|---|
pdf4me.ai_document_extraction | 14 | from pdf4me.ai_document_extraction import extract_invoice_data |
pdf4me.barcode | 7 | from pdf4me.barcode import create_barcode, read_barcodes |
pdf4me.convert | 13 | from pdf4me.convert import html_to_pdf, url_to_pdf, word_to_pdf |
pdf4me.edit | 7 | from pdf4me.edit import stamp, add_page_number, add_html_header_footer |
pdf4me.excel | 1 | from pdf4me.excel import excel_to_pdf |
pdf4me.extract | 8 | from pdf4me.extract import extract_text, extract_attachments |
pdf4me.find_search | 2 | from pdf4me.find_search import find_and_replace_text |
pdf4me.forms | 2 | from pdf4me.forms import fill_pdf_form, get_form_fields |
pdf4me.generate | 6 | from pdf4me.generate import generate_document_single |
pdf4me.image | 13 | from pdf4me.image import resize_image, add_watermark_to_image |
pdf4me.merge_split | 5 | from pdf4me.merge_split import merge_pdfs, split_pdf_by_barcode |
pdf4me.optimize | 1 | from pdf4me.optimize import optimize |
pdf4me.organize | 5 | from pdf4me.organize import rotate_pdf, extract_pages |
pdf4me.pdf | 16 | from pdf4me.pdf import get_pdf_metadata, sign_pdf |
pdf4me.pdf4me | 1 | from pdf4me.pdf4me import ping |
pdf4me.security | 2 | from pdf4me.security import protect_document, unlock_pdf |
pdf4me.word | 2 | from pdf4me.word import add_tracked_changes |
pdf4me.zugferd | 1 | from pdf4me.zugferd import create_zugferd_invoice |
The import paths above are illustrative of the module surface. See the PDF4me REST API reference for the complete parameter list for every action, and the source repository's pdf4me/ package tree for the exact symbol names your version exports.
Where to go next
Troubleshooting
ModuleNotFoundError: No module named 'pdf4me'. The package is installed in a different interpreter than the one running your script. Confirm with python -m pip show pdf4me inside the same virtual environment. If you use pyenv or conda, activate the environment first.
SystemExit: Set PDF4ME_API_KEY before running this example. The environment variable is not visible to your process. Restart your shell after export, or pass the key directly with Pdf4meClient("your-api-key"). On Windows, remember that set inside CMD does not persist across sessions; use setx for a permanent value.
httpx.HTTPStatusError: 401 Unauthorized. The API key is present but rejected. Regenerate it in the PDF4me dashboard and try again.
Slow first call after a period of inactivity. HTTP/2 keep-alive resets on idle. Keep the Pdf4meClient open for the lifetime of a batch job rather than opening a new one per file.
asyncio.run() cannot be called from a running event loop. You are inside Jupyter or a framework that already runs an event loop. Use await main() at the top level of a notebook cell, or wrap your code in the framework's own async entrypoint.