pdftagger — auto-tag PDFs for accessibility from Python
Official Python SDK for the PDF Tagger API. Upload a PDF, submit a tagging job, and download a PDF/UA-tagged, accessible PDF — with correct reading order, headings, tables, and alt text. Tag one PDF, or up to 100 at once as a batch.
pip install pdftagger
How to auto-tag PDFs with the PDF Tagger SDK
Get an API key from the Developers page, then:
from pdftagger import PdfTaggerClient
client = PdfTaggerClient(api_key="sk_live_...")
# Upload, submit, and wait for the tagged PDF — one call.
job = client.tag_pdf("report.pdf", ocr=True)
# Save the accessible, tagged PDF.
client.download_result(job, "report.tagged.pdf")
That's the whole flow. tag_pdf() uploads the file, submits the job, polls until it
finishes, and returns the completed job. Each job costs one credit per page, drawn from
the same balance as the web app.
Options
job = client.tag_pdf(
"scan.pdf",
ocr=True, # recover text from image-only / scanned pages
embed_fonts=True, # embed a non-embedded font where it is safe to do so
idempotency_key="order-1234", # safe to retry without double-charging
)
Don't want to block? Submit and poll yourself:
job = client.tag_pdf("report.pdf", wait=False) # returns immediately
job = client.wait_for_job(job) # ...poll later
Many PDFs at once: batches
Tag up to 100 PDFs as one batch. tag_pdfs() uploads every file, submits them in one
request, and waits until every job has finished:
batch = client.tag_pdfs(["a.pdf", "b.pdf", "c.pdf"], name="Q3 reports", ocr=True)
print(batch.counts) # BatchCounts(total=3, processing=0, completed=3, failed=0)
for job in batch.failed_jobs: # a failed job does not raise for a batch
print(job.filename, job.error_code, job.error_message)
client.download_batch_results(batch, "tagged/") # saves each completed PDF
A batch is all or nothing: if any file is not a PDF, has more than 750 pages, or the
credits do not cover every page, nothing is charged and the call raises.
BadRequestError.errors lists each bad file by its position:
from pdftagger import BadRequestError
try:
client.submit_batch([upload_a.key, upload_b.key])
except BadRequestError as e:
for problem in e.errors: # {"index": 1, "type": "too_many_pages", "message": "..."}
print(problem["index"], problem["message"])
Drive it yourself with submit_batch() (upload keys or BatchItems), get_batch() and
wait_for_batch(). Each job in a batch is a normal job, so get_job(),
get_job_result() and download_result() work on it.
Verifying webhooks
Register a webhook endpoint on the Developers page to be notified when a job finishes. Verify every delivery before trusting it — pass the raw request body:
from pdftagger import verify_signature
from pdftagger.webhooks import SIGNATURE_HEADER
event = verify_signature(
payload=raw_body_bytes, # the exact bytes you received
header=request.headers[SIGNATURE_HEADER],
secret="whsec_...", # the endpoint secret
)
if event.type == "job.completed":
url = client.get_job_result(event.job.id).result_url
elif event.type == "batch.completed":
batch = client.get_batch(event.batch.id) # every job in the batch has finished
verify_signature recomputes the HMAC-SHA256 signature over the raw body, compares it in
constant time, and rejects a stale timestamp (replay). It raises
WebhookVerificationError on any failure. A delivery can arrive more than once, so
dedupe on event.id.
Low-level API
Every endpoint is available directly if you want to drive the flow yourself:
client.get_account() # account + credit balance
upload = client.create_upload("report.pdf")
client.upload_file(upload, "report.pdf")
job = client.submit_job(key=upload.key, ocr=True)
job = client.get_job(job.id) # processing / completed / failed
result = client.get_job_result(job.id) # short-lived download URL
Errors
All errors derive from PdfTaggerError.
| Exception | When |
|---|---|
AuthenticationError |
401 — key missing, invalid, or revoked |
BadRequestError |
400 — invalid request (for a batch, .errors lists each bad file) |
InsufficientCreditsError |
402 — not enough credits (a batch charges nothing) |
NotFoundError |
404 — no such job/resource for this account |
ConflictError |
409 — idempotency conflict, or result not ready |
RateLimitError |
429 — rate limited (.retry_after seconds) |
JobFailed |
the job finished with a failed status (credits are refunded) |
JobTimeout |
the job, or the batch, did not finish within the poll timeout |
WebhookVerificationError |
a webhook signature could not be verified |
Requirements
- Python 3.9+
httpx(installed automatically)
Links
- API reference: https://pdf-tagger.com/api/reference
- Create an API key: https://pdf-tagger.com/developers
License
MIT
Metadata
Release files for pdftagger 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pdftagger-0.1.1.tar.gz | 20.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pdftagger-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 37.1 kB
Release files / pdftagger-0.1.1.tar.gz
| Download URL | pdftagger-0.1.1.tar.gz |
|---|---|
| Size | 20.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6746c67338fe719bcae0ae40a1228ad0b64c9069903948372d63463d07c0489f
|
|
BLAKE2b-256 checksum How to use checksums |
81a13859c594e6b4cfd775480fe4a5373a6d51c5ee4ec8abb129cfa92efb66e3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency logRelease files / pdftagger-0.1.1-py3-none-any.whl
| Download URL | pdftagger-0.1.1-py3-none-any.whl |
|---|---|
| Size | 16.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
18532a95bf872e05df20f7076da2b010e10d8ac53ef2e406acaa0e4039803363
|
|
BLAKE2b-256 checksum How to use checksums |
f2a7cfdd713b9cb661b1b41d8da7c4a3e1e27a247952b82a12c8ba2238422d97
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency log