Skip to content
MCPBytes
DocsExtract text from a PDF

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
Response202 Accepted
{
  "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"
}
Get the resultread.sh
# 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"
Response200 OK · trimmed
{
  "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.

Input sources
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.

Options
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.

Output files
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:

result
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 Code

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/pdf
Codex (CLI, IDE extension, ChatGPT desktop)

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_KEY
Cursor

~/.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

~/.cursor/mcp.json
{
  "mcpServers": {
    "mcpbytes": {
      "url": "https://api.mcpbytes.com/mcp/pdf",
      "headers": {
        "Authorization": "Bearer ${env:MCPBYTES_API_KEY}"
      }
    }
  }
}
11 more clients in the docs →

Arguments

The tool's options are flat arguments. Besides them:

extract_pdf arguments
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

MCP tools
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:

mcpbytes.py
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 0

Pricing

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

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.

Job errors
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.