Python PDF parser in five lines
The pdfparse-ai package for Python wraps the PdfParse API with typed results, automatic job polling, retries and an async client. Install it, set one environment variable and parse a PDF, a scan or an image into JSON, Markdown and CSV.
Get the Python call for your own file
Drop a document and the result page shows the parsed output next to the exact Python call that produces it, ready to copy into your project.
What one call returns
Page one rendered with every block outlined, next to the JSON your code receives.
- Reading text layer
- Detecting layout
- Extracting tables
- Building JSON
Headings, paragraphs, tables and totals detected
Portsmouth, NH 03801
| Description | Qty | Unit | Amount |
|---|---|---|---|
| Oak countertop, 2.4 m | 2 | 640.00 | 1,280.00 |
| Cabinet doors, shaker | 12 | 85.00 | 1,020.00 |
| On site fitting | 1 | 450.00 | 450.00 |
Quote the invoice number with your transfer.
Goods remain ours until paid in full.
Pages 2 and later are parsed after you create an account.
{ "document": { "pages": 2, "parsed_pages": 1, "language": "en" }, "pages": [ { "number": 1, "blocks": [ { "type": "heading", "text": "Invoice", "bbox": [48, 52, 132, 84] }, { "type": "paragraph", "text": "Harbor Street Joinery, 14 Dock Lane, Portsmouth" }, { "type": "key_value", "pairs": { "Invoice no.": "INV-2026-0418", "Issued": "Oct 14, 2026", "Due": "Nov 13, 2026" } }, { "type": "table", "columns": ["Description", "Qty", "Unit", "Amount"], "rows": [ ["Oak countertop, 2.4 m", 2, 640.00, 1280.00], ["Cabinet doors, shaker", 12, 85.00, 1020.00], ["On site fitting", 1, 450.00, 450.00] ] }, { "type": "total", "label": "Total due", "value": 2983.75, "currency": "USD", "confidence": 0.98 } ] } ], "fields": { "invoice_number": { "value": "INV-2026-0418", "confidence": 0.99, "page": 1 }, "total": { "value": 2983.75, "confidence": 0.98, "page": 1 }, "due_date": { "value": "2026-11-13", "confidence": 0.97, "page": 1 } } }
Downloads from the demo include a watermark field and cover page one only.
Page one is parsed in your browser, so your file never reaches our servers.
One demo per day from this network. Create an account to parse more pages.
$ curl https://api.pdfparse.ai/v1/parse \ -H "Authorization: Bearer $PDFPARSE_API_KEY" \ -F file[email protected] \ -F outputs=json,markdown,csv
Install the package
The SDK is published on PyPI as pdfparse-ai and imported as pdfparse_ai. Keep your API key out of source control by reading it from the PDFPARSE_API_KEY environment variable.
pip install pdfparse-ai
export PDFPARSE_API_KEY="pp_live_your_key"
Parse a PDF in five lines
This is the whole program. The client uploads the file, waits for the result and returns an object with every output you requested.
The result mirrors the API response. result.pages holds blocks with type, text, bounding box and confidence. result.tables holds each table with its page, rows, columns and a CSV string. result.usage.pages tells you how many pages the call counted against your allowance.
import os
from pdfparse_ai import PdfParse
client = PdfParse(api_key=os.environ["PDFPARSE_API_KEY"])
result = client.parse("invoice.pdf", outputs=["json", "markdown"])
print(result.markdown)
Paths, file objects and URLs
The first argument accepts a path or an open binary file. For documents already stored online, pass url instead and the API fetches the file itself.
with open("scan.tiff", "rb") as f:
result = client.parse(f, outputs=["json"], ocr="always")
result = client.parse(url="https://files.example.com/report.pdf", outputs=["markdown"])
Work with pages and tables
Use the pages option to parse only a range, then walk the blocks or save each table as its own CSV file.
Pages left out of the range are neither processed nor counted. On Starter a single file can hold up to 100 pages, on Growth 300, on Scale 1,000 and on Enterprise 2,000.
result = client.parse("annual-report.pdf", outputs=["json", "csv"], pages="1-5")
for page in result.pages:
headings = [b.text for b in page.blocks if b.type == "heading"]
print(page.number, headings)
for i, table in enumerate(result.tables):
with open(f"table_{table.page}_{i}.csv", "w") as out:
out.write(table.csv)
Extract your own fields with a schema
Create a schema once, keep its id in your settings and pass it on every call. Field types are string, number, date, money, boolean and array. Custom schemas are available on Growth and above.
Route fields by confidence. Store values you trust and queue the rest for a person, keeping the source page with every stored value.
schema = client.schemas.create(
name="supplier_invoice",
fields=[
{"name": "invoice_number", "type": "string", "description": "Invoice number printed near the top"},
{"name": "due_date", "type": "date", "description": "Payment due date"},
{"name": "total", "type": "money", "description": "Total amount payable including tax"},
{"name": "line_items", "type": "array", "description": "Each billed item with description, quantity and amount"},
],
)
result = client.parse("invoice.pdf", outputs=["json"], schema_id=schema.id)
for name, field in result.fields.items():
print(name, field.value, field.confidence, field.page)
Markdown and chunks for retrieval
Language model pipelines usually want two things from a PDF. Clean Markdown for the whole document, and smaller pieces that fit a context window and can be cited. Ask for section chunks on Growth and above, and every chunk arrives with the pages it spans and the headings above it.
Store the source string with each embedding so answers can link back to the page a reader should open.
result = client.parse(
"handbook.pdf",
outputs=["markdown", "json"],
chunking={"strategy": "section", "max_tokens": 800},
)
records = [
{
"text": c.text,
"source": f"handbook.pdf p.{c.page_start}-{c.page_end}",
"section": " > ".join(c.section_path),
}
for c in result.chunks
]
Large files and the async client
By default client.parse waits for the result, polling the job for you when the API answers with a job id. Pass wait=False to get the job back immediately, or use AsyncPdfParse inside asyncio code. Parallel calls beyond what your account runs at once wait in the queue.
import asyncio, os
from pdfparse_ai import AsyncPdfParse
async def main(paths):
client = AsyncPdfParse(api_key=os.environ["PDFPARSE_API_KEY"])
limit = asyncio.Semaphore(5)
async def one(path):
async with limit:
return await client.parse(path, outputs=["json"])
return await asyncio.gather(*(one(p) for p in paths))
results = asyncio.run(main(["a.pdf", "b.pdf", "c.pdf"]))
Send hundreds of files as one batch
On Growth and above, a batch accepts up to 500 files and returns one id for the whole set. Pass an idempotency key so a retried request never creates a second batch.
For long batches, register a webhook and react to parse.completed and parse.failed events instead of polling.
batch = client.batches.create(
files=["2026-09/inv-001.pdf", "2026-09/inv-002.pdf"],
outputs=["json", "csv"],
schema_id=schema.id,
idempotency_key="month-end-2026-09",
)
status = client.batches.get(batch.id)
print(status.status)
Handle errors explicitly
The SDK retries network failures and rate limit responses with backoff. Everything else raises a typed exception that maps to an HTTP status from the API.
A page limit error means parsing paused at your allowance. Nothing is charged automatically, and the dashboard offers an Upgrade button.
from pdfparse_ai.errors import (
AuthenticationError, PageLimitError, FileTooLargeError,
UnsupportedFileError, RateLimitError, PdfParseError,
)
try:
result = client.parse("contract.pdf", outputs=["markdown"])
except AuthenticationError:
raise SystemExit("Check PDFPARSE_API_KEY")
except PageLimitError:
notify_ops("Monthly page allowance reached, upgrade in billing settings")
except FileTooLargeError:
result = client.parse("contract.pdf", outputs=["markdown"], pages="1-100")
except UnsupportedFileError:
log.warning("Only PDF, PNG, JPG and TIFF are accepted")
except RateLimitError:
requeue_later()
except PdfParseError as exc:
log.error("Parse failed with status %s", exc.status)
FAQ
Does the Python PDF parser run locally?
The pdfparse-ai package is a client. It sends the file to the PdfParse API over TLS and returns the parsed result, so you need no local OCR engine or system libraries.
Can I use it with Django, FastAPI or Celery?
Yes. Use PdfParse in synchronous code such as Django views and Celery tasks, and AsyncPdfParse in asyncio frameworks such as FastAPI.
How do I get Markdown for a language model?
Request outputs=["markdown"] and read result.markdown. Headings, lists and tables are preserved, and on Growth and above you can also ask for section based chunks with page numbers.
What happens if a job takes longer than my request timeout?
Call client.parse with wait=False to get the job id right away, then check it later with client.jobs.get or receive a webhook when it finishes.
Get an API key and run the five lines
Choose a plan, copy your key into PDFPARSE_API_KEY and parse your first file.