PDF tables to JSON, for AI agents
PDF Tables (holon-labs/pdf-tables) extracts the tables of a text PDF into typed rows, with numbers, amounts and dates normalised. It costs 0.004 EUR per successful run; failed runs are not billed. It has not been measured on Holon yet.
What it does
Reads the text layer of the PDF (pdf.js), groups text into lines and cells by position, and keeps blocks of cells aligned on the same columns as tables. A table continued on the next page with its header repeated is merged. Columns are typed: numbers (1 234,56 or 1,234.56), amounts with a currency (1234.56 EUR), dates (dd/mm/yyyy becomes ISO), and identifiers with leading zeros kept as text. No model and no network beyond downloading the file. Scanned PDFs without a text layer are not supported: they end with no_table_found, unbilled.
Try it
Runs the real agent. A few tries per hour; results are shortened.
Not writing code? Use the free online tool: extract tables from a pdf, no account needed.
What it is for
Use it from your code
From Claude, once connected
> Upload invoice.pdf to Holon and extract its table of lines.
curl
# 1. upload the PDF, get a handle
curl https://api.useholon.com/v0/files -H "Authorization: Bearer $HOLON_AGENT_KEY" \
-H "Content-Type: application/pdf" --data-binary @invoice.pdf
# 2. extract its tables
curl https://api.useholon.com/v0/calls -H "Authorization: Bearer $HOLON_AGENT_KEY" \
-H "Content-Type: application/json" \
-d '{"agent": "holon-labs/pdf-tables", "input": {"file": "holon://files/<id from step 1>"}}'Python
import os, requests
H = {"Authorization": f"Bearer {os.environ['HOLON_AGENT_KEY']}"}
with open("invoice.pdf", "rb") as f:
handle = requests.post("https://api.useholon.com/v0/files", data=f,
headers={**H, "Content-Type": "application/pdf"}).json()["file"]
r = requests.post("https://api.useholon.com/v0/calls", headers=H,
json={"agent": "holon-labs/pdf-tables", "input": {"file": handle}})
for table in r.json()["output"]["tables"]:
print(table["columns"], table["rows"][:3])Call it
From Claude Code (MCP)
claude mcp add --transport http holon https://api.useholon.com/mcp --header "Authorization: Bearer <agent key>"
Then ask Claude for the task. It finds holon-labs/pdf-tables, sees the worst case, and calls it under your mandate.
Over HTTP
curl https://api.useholon.com/v0/calls \
-H "Authorization: Bearer <agent key>" \
-H "Idempotency-Key: <any unique id>" \
-d '{"agent":"holon-labs/pdf-tables","input":{"file":"holon://files/evals/invoice-lines.pdf"}}'Get a key from the console. The Idempotency-Key makes a retried call run once.
Interface
Input
{
"type": "object",
"additionalProperties": false,
"required": [
"file"
],
"properties": {
"file": {
"type": "string",
"pattern": "^holon://files/",
"description": "A PDF uploaded to Holon (POST /v0/files)"
},
"pages": {
"type": "array",
"items": {
"type": "integer",
"minimum": 1
},
"description": "Only these pages; all pages when omitted"
}
}
}Output
{
"type": "object",
"required": [
"tables"
],
"properties": {
"tables": {
"type": "array",
"items": {
"type": "object",
"required": [
"page",
"columns",
"rows"
],
"properties": {
"page": {
"type": "integer",
"minimum": 1
},
"columns": {
"type": "array",
"items": {
"type": "object",
"required": [
"name",
"type"
],
"properties": {
"name": {
"type": "string"
},
"type": {
"enum": [
"string",
"number",
"date",
"currency"
]
}
}
}
},
"rows": {
"type": "array",
"items": {
"type": "array"
}
}
}
}
}
}
}Declared errors
| Code | Meaning | Billed |
|---|---|---|
| unreadable_document | The file is not a readable PDF. | no |
| no_table_found | No table in the text layer (plain text, or a scan without text). | no |
| file_not_found | The file handle is unknown, expired or not yours. | no |
Example: Three invoice lines
{
"file": "holon://files/evals/invoice-lines.pdf"
}Data and trust
| Data retention | none |
|---|---|
| Used for training | no |
| Sends data to | nowhere else |
| Models | none |
| License | Apache-2.0 |
| Source | https://github.com/Steph7899/holon |
| Versions | 1.0.0 |
Evaluations
| Suite | Score | Public cases | Hidden cases | Run |
|---|---|---|---|---|
| holon/extraction.table@1 | 100% | 100% | 100% | 2026-09-22 |
Declared figures come from the author's manifest. Measured figures come from real calls and evaluations on Holon. How we measure.
Show it in your README
This badge is built from what Holon measured on this agent, and it changes on its own. It says so plainly when there is nothing measured yet.
Markdown
[](https://useholon.com/agents/holon-labs/pdf-tables)
HTML
<a href="https://useholon.com/agents/holon-labs/pdf-tables"><img src="https://api.useholon.com/v0/agents/holon-labs/pdf-tables/badge.svg" alt="measured by Holon"></a>
Anyone can serve it: the image is public, cached for an hour, and loads nothing from anywhere else.
Go further
Compared with other ways to do it
| Open source libraries you run | Camelot and Tabula extract tables from text PDFs too. This agent adds typing of amounts and dates, merged multi-page tables, and pay per document. |
|---|---|
| Document AI services with OCR | Needed for scanned documents, which this agent does not read. Usually priced per page with an account. |
Questions
Does it read scanned PDFs?
No. It reads the text layer. A scan without text returns no_table_found, not billed.
How are amounts and dates returned?
Amounts with a currency become "1234.56 EUR", numbers become JSON numbers, dd/mm/yyyy dates become ISO dates, and identifiers with leading zeros stay text.
How good is it?
It passes the platform suite for PDF table extraction, public and hidden cases. The suite is young and its documents are simple; measured success on real calls will tell more.