Tool reference · Documents
Extract text from a PDF
Extract the text of a PDF, page by page, in chunks an agent can read.
- MCP tool
- extract_pdf
- MCP endpoint
- /mcp/pdf
- REST
- POST /v1/tools/extract_pdf/jobs
- Price
- 1 credit per 25 pages
Overview
Send a PDF and MCPBytes returns its text with a marker before every page. Agents get the beginning inline and read on in chunks, so a long document never floods the context. pages.json locates every page in the text and carries the document's metadata.
- Input format
- .pdf (not password-protected)
- Pages
- Up to 500 per job; continue with start_page
- Outputs
- text.txt with page markers, pages.json with page offsets and metadata
- Reading
- First bytes inline over MCP; the rest in chunks of up to 100,000 bytes
- Scanned pages
- No OCR: pages without a text layer come back empty
Quickstart
Send the file as the request body with the options in the query string, or refer to it in a JSON body (upload_id or url) with an options object. The response is the new job; poll it until its status is final, then download the files in result.files.
# The PDF is the request body
curl "https://api.mcpbytes.com/v1/tools/extract_pdf/jobs?filename=paper.pdf" \
-H "Authorization: Bearer $MCPBYTES_API_KEY" \
--data-binary @paper.pdf{
"id": "j_7c2m9x4f1q8w3e6r5t0y2u7i9o",
"tool": "extract_pdf",
"status": "queued",
"input": {
"name": "paper.pdf",
"bytes": 1204331,
"source": "body"
},
"options": {
"start_page": 1,
"max_pages": 500
},
"error": null,
"result": null,
"compute_ms": null,
"credits_charged": null,
"created_at": "2026-09-19T12:00:00.000Z",
"started_at": null,
"finished_at": null,
"outputs_expire_at": "2026-09-20T12:00:00.000Z"
}# JOB_ID is the id from the create response
curl https://api.mcpbytes.com/v1/jobs/$JOB_ID \
-H "Authorization: Bearer $MCPBYTES_API_KEY"
# Read the text, 20,000 bytes at a time
curl "https://api.mcpbytes.com/v1/jobs/$JOB_ID/files/text.txt?offset=0" \
-H "Authorization: Bearer $MCPBYTES_API_KEY"{
"id": "j_7c2m9x4f1q8w3e6r5t0y2u7i9o",
"tool": "extract_pdf",
"status": "succeeded",
"result": {
"files": [
{
"name": "pages.json",
"bytes": 1842,
"sha256": "…",
"url": "https://api.mcpbytes.com/blob/dl/j_7c2m9x4f1q8w3e6r5t0y2u7i9o/pages.json?t=…"
},
{
"name": "text.txt",
"bytes": 48903,
"sha256": "…",
"url": "https://api.mcpbytes.com/blob/dl/j_7c2m9x4f1q8w3e6r5t0y2u7i9o/text.txt?t=…"
}
],
"pages": 12,
"first_page": 1,
"last_page": 12,
"empty_pages": 0,
"chars": 48211,
"notes": []
},
"compute_ms": 1310,
"credits_charged": 1,
"finished_at": "2026-09-19T12:00:03.000Z",
"outputs_expire_at": "2026-09-20T12:00:00.000Z"
}A job can be canceled while it is queued: POST /v1/jobs/{id}/cancel.
More on statuses, downloads and retention in Jobs & files.
Input
One file per job, in one of these ways. The formats it accepts are listed in the overview.
- File body
- REST only: the file itself as the request body, with ?filename= and the options in the query string.
- upload_id
- Create an upload URL (POST /v1/uploads, or create_upload over MCP), PUT the file to it, then pass the upload_id. Upload URLs expire after an hour.
- url, filename
- A public https URL. Add filename when the URL does not end in the file's extension.
# 1. Create an upload URL (valid for one hour)
curl https://api.mcpbytes.com/v1/uploads \
-H "Authorization: Bearer $MCPBYTES_API_KEY" \
-H "Content-Type: application/json" \
-d '{"filename": "paper.pdf"}'
# 2. PUT the file to the "url" from the response
curl -X PUT --data-binary @paper.pdf "$UPLOAD_URL"
# 3. Start the job with the "upload_id"
curl https://api.mcpbytes.com/v1/tools/extract_pdf/jobs \
-H "Authorization: Bearer $MCPBYTES_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"upload_id\": \"$UPLOAD_ID\",
\"options\": {
\"max_pages\": 20
}
}"To retry safely, send an Idempotency-Key header (or idempotency_key in a JSON body): a repeated key returns the original job instead of starting a new one.
Options
Put options in the query string when the file is the request body, in the options object of a JSON body, or pass them as arguments of the MCP tool. Leave an option out to use its default.
- start_page
integer ≥ 1First page to extract. Default 1.- max_pages
integer ≥ 1Extract at most this many pages. Capped by the plan: 500 on free, 5,000 on pro. Fewer pages can reduce the cost.
Outputs
A succeeded job lists its files in result.files, each with a download url.
- text.txt
- Every extracted page: a --- Page N --- line, the page's text, a blank line. UTF-8.
- pages.json
- The page count, the document's metadata, and for every extracted page its byte offset and length in text.txt.
Read text.txt in chunks with GET /v1/jobs/{id}/files/text.txt?offset=&limit= (or read_job_file over MCP): pass next_offset as the next offset until eof. A page's offset in pages.json takes you straight to it.
Result summary
Next to files, the job's result has a summary you can check without downloading anything:
- pages
- Pages in the document.
- first_page, last_page
- The range that was extracted.
- empty_pages
- Extracted pages without a text layer.
- chars
- Characters extracted.
- notes
- For example, which pages were not extracted and the start_page to continue from.
Tips
- Read text.txt in chunks: pass next_offset as the next offset until eof, and stop once you have what the task needs.
- To go straight to page N, read pages.json and use that page's offset and bytes as offset and limit.
- For a long document, extract only the range you need with start_page and max_pages. Fewer pages cost less.
- Text follows the page layout: tables and multi-column pages may need care when quoting.
- There is no OCR. Scanned pages come back empty; empty_pages counts them, and a note says when no text was found at all.
MCP
Your agent calls extract_pdf. Connect it to https://api.mcpbytes.com/mcp/pdf for this tool, or to https://api.mcpbytes.com/mcp for every tool.
Claude CodeOAuth or API key
The first form signs in through the browser when you run /mcp. In .mcp.json (project root), ${MCPBYTES_API_KEY} is read from the environment when Claude Code connects. Claude Code docs
claude mcp add --transport http mcpbytes \
https://api.mcpbytes.com/mcp/pdfCodex (CLI, IDE extension, ChatGPT desktop)OAuth or API key
Codex reads the key from the environment variable each time it connects. The same table can go in ~/.codex/config.toml by hand; codex mcp add has no --header flag. Codex docs
codex mcp add mcpbytes --url https://api.mcpbytes.com/mcp/pdf \
--bearer-token-env-var MCPBYTES_API_KEYCursorOAuth or API key
~/.cursor/mcp.json (all projects) or .cursor/mcp.json (one project). Remote servers take no "type". Without the header, the client signs you in with GitHub or an email link (OAuth). Cursor docs
{
"mcpServers": {
"mcpbytes": {
"url": "https://api.mcpbytes.com/mcp/pdf",
"headers": {
"Authorization": "Bearer ${env:MCPBYTES_API_KEY}"
}
}
}
}Arguments
The tool's options are flat arguments. Besides them:
- upload_id
- From create_upload, after the agent has PUT the file.
- url, filename
- A public https URL, and the file name when the URL does not end in the extension.
- wait_seconds
0–45, default 20How long the call waits for the result. If the job is still running, the agent calls get_job.- max_length
16–100,000, default 20,000Bytes of text.txt returned with the result; read the rest with read_job_file.- idempotency_key
OptionalReuse it with the same arguments when retrying after a network error.
What the agent sees
- extract_pdf
- Starts a job that extracts the text of a PDF, page by page, and returns the beginning of it; continue with read_job_file (text.txt). pages.json has per-page offsets and the document metadata. Scanned pages have no text (no OCR). Give upload_id (from create_upload) or a public https url. Waits up to wait_seconds for the result. Costs 1 credit per 25 pages (at least 1).
- create_upload
- Returns a one-hour URL to PUT a local file to; then call the tool with the upload_id.
- get_job
- Status and results of a job; waits up to wait_seconds for it to finish.
- cancel_job
- Cancel a queued job or a workflow whose can_cancel is true; repeated calls never delete results.
- list_jobs
- Your most recent jobs, newest first.
- read_job_file
- Reads a text output (.txt .json .md .csv) of a succeeded job in chunks.
Setup for every client, and how agents upload files and read results: Connect over MCP.
Agent skill
The MCPBytes skill teaches agents this tool, in references/extract_pdf.md. Agents without MCP run it through the REST API with the skill's script, which reads your key from MCPBYTES_API_KEY:
python scripts/mcpbytes.py run extract_pdf paper.pdf --out out/
python scripts/mcpbytes.py run extract_pdf \
https://example.com/a.pdf --filename a.pdf \
-o max_pages=20 --out out/
python scripts/mcpbytes.py read j_... text.txt --offset 0Pricing
1 credit per 25 pages. The price follows the input's size and is charged once the input has been measured; starting a job needs at least the smallest price. A job that fails costs nothing: its charge is refunded.
- Per 25 pages
- 1 credit
One credit balance pays for every tool, and a new account starts with free credits. See Credits & prices or buy credits.
Limits
- PDF size
- Up to 500 pages and 20 MB
- At a time
- 1 job
- Uploads
- Up to 50 MB
- Results
- 24 hours by default
GET /v1/me returns your limits and current usage; Limits & errors explains what happens when one is reached.
Errors
A failed job comes back with error.code, error.message and often error.hint. Codes are stable. Inputs that fail with a validation error will fail again unchanged.
- invalid_pdf
- The file is not a PDF, or it is too damaged to open.
- encrypted_pdf
- The PDF is password-protected. Remove the password and try again.
- invalid_options
- start_page is beyond the PDF's last page. Checked before any charge.
- unsupported_format
- The tool does not take this file type.
- insufficient_credits
- The job costs more than the balance, which is known once the input has been measured. Nothing was charged.
- timeout
- The job did not finish within its time limit.
- out_of_memory
- The file needs more memory than a job has.
Requests can also fail before a job starts, with an HTTP status: see HTTP errors.