Skip to main content

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

FieldValue
Package namepdf4me
Current version1.0.2 (published 2026-09-14)
LicenseMIT
Python support>=3.11 (tested on 3.11, 3.12, 3.13, 3.14)
Development statusProduction/Stable
FrameworkAsyncIO
Sourcegithub.com/pdf4me/pdf4me-clientapi-python
PyPIpypi.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 (or uv, poetry, or any PyPI-compatible installer).

Install

python -m pip install 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:

export 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:

  1. One client, many calls. The async with Pdf4meClient(...) context manages the underlying httpx connection pool. Reuse the same client for every call inside main instead of opening one per action.
  2. 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.
  3. 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)

ModuleActionsImport example
pdf4me.ai_document_extraction14from pdf4me.ai_document_extraction import extract_invoice_data
pdf4me.barcode7from pdf4me.barcode import create_barcode, read_barcodes
pdf4me.convert13from pdf4me.convert import html_to_pdf, url_to_pdf, word_to_pdf
pdf4me.edit7from pdf4me.edit import stamp, add_page_number, add_html_header_footer
pdf4me.excel1from pdf4me.excel import excel_to_pdf
pdf4me.extract8from pdf4me.extract import extract_text, extract_attachments
pdf4me.find_search2from pdf4me.find_search import find_and_replace_text
pdf4me.forms2from pdf4me.forms import fill_pdf_form, get_form_fields
pdf4me.generate6from pdf4me.generate import generate_document_single
pdf4me.image13from pdf4me.image import resize_image, add_watermark_to_image
pdf4me.merge_split5from pdf4me.merge_split import merge_pdfs, split_pdf_by_barcode
pdf4me.optimize1from pdf4me.optimize import optimize
pdf4me.organize5from pdf4me.organize import rotate_pdf, extract_pages
pdf4me.pdf16from pdf4me.pdf import get_pdf_metadata, sign_pdf
pdf4me.pdf4me1from pdf4me.pdf4me import ping
pdf4me.security2from pdf4me.security import protect_document, unlock_pdf
pdf4me.word2from pdf4me.word import add_tracked_changes
pdf4me.zugferd1from 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.