Quickstart
Get a key from your dashboard, then:
curl https://api.pdfinspector.com/v1/convert \ -H "Authorization: Bearer $PDFINSPECTOR_API_KEY" \ -F file=@report.pdf
const form = new FormData();
form.append("file", pdfFile); // File or Blob
const res = await fetch("https://api.pdfinspector.com/v1/convert", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.PDFINSPECTOR_API_KEY}` },
body: form,
});
const data = await res.json();
if (!res.ok) throw new Error(data.error.message);
console.log(data.markdown);
import os, requests
res = requests.post(
"https://api.pdfinspector.com/v1/convert",
headers={"Authorization": f"Bearer {os.environ['PDFINSPECTOR_API_KEY']}"},
files={"file": open("report.pdf", "rb")},
)
res.raise_for_status()
print(res.json()["markdown"])
Have a URL instead of a file? Send JSON:
curl https://api.pdfinspector.com/v1/convert \
-H "Authorization: Bearer $PDFINSPECTOR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/report.pdf", "pages": "1-5"}'
Using the API without a key
Without a key you can make one test call a day per IP address, up to 10 text pages, without OCR. Scanned pages are listed in pages_needing_ocr. Everything else needs a key with credits.
curl https://api.pdfinspector.com/v1/convert \ -H "Accept: text/markdown" \ -F file=@report.pdf
Accept: text/markdown returns only the Markdown as plain text. Leave it out to get the full JSON.
Markdown URL shortcut
Put https://api.pdfinspector.com/md/ in front of any PDF link and you get plain Markdown back. It works with any tool that can fetch a URL, including an AI agent's built-in web fetch, with nothing to install:
https://api.pdfinspector.com/md/https://example.com/annual-report.pdf
Optional query parameters: pages and ocr. Anonymous limits apply unless you send your key in the Authorization header.
Authentication
Send your key as a bearer token: Authorization: Bearer <key>.
sk_live_…: secret key. Server-side only.pk_live_…: publishable key. Allowed in browsers. It only works from origins you list in the dashboard and stops at the monthly page cap you set.sk_test_…: test key. Returns realistic sample output and never uses credits.
The MCP server also supports OAuth sign-in, so Claude and Claude Code users don't need to paste a key.
Keys for agents
An agent can create a key with one API call. It doesn't need a browser, a form or a CAPTCHA.
curl https://api.pdfinspector.com/v1/keys \
-H "Content-Type: application/json" \
-d '{"email": "you@example.com", "name": "my-app", "agent": "claude-code"}'
{
"key": "sk_live_…", // shown once, store it as PDFINSPECTOR_API_KEY
"credits_remaining": 0,
"checkout_url": "https://pdfinspector.com/checkout/…",
"dashboard_url": "https://pdfinspector.com/dashboard"
}
- The key is returned right away with no credits. It works as soon as credits are bought through
checkout_url. - If you pass an
email, that person also gets the checkout link by email. - Agents: store the key in the project's secrets or
.envasPDFINSPECTOR_API_KEY, never in code, and show thecheckout_urlto your user. Agents can't pay. - Check limits any time with
GET /v1/usage.
POST /v1/convert
Convert a PDF to Markdown. Send either multipart/form-data with a file, or application/json with a url or file_base64.
| Parameter | Type | Description |
|---|---|---|
file | file | The PDF (multipart). Max 50 MB. |
url | string | Public URL of a PDF. Alternative to file. |
file_base64 | string | Base64-encoded PDF (JSON body). Alternative to file. |
pages | string | 1-indexed page selection, e.g. 1-3,7. Only these pages are processed and charged. |
ocr | string | auto (default): OCR only pages without real text. off: never OCR; scanned pages are listed in pages_needing_ocr. force: OCR every selected page. |
per_page | boolean | Also return a pages array with each page's Markdown and source. Default false. |
webhook_url | string | Run as a job and POST the result here. See long documents. |
Response
{
"pdf_type": "mixed", // "text_based" | "scanned" | "image_based" | "mixed"
"confidence": 0.97, // classification confidence, 0–1
"page_count": 12,
"markdown": "# Q3 Report\n\n## Revenue\n\n| Region | Q2 | Q3 |\n|---|---|---|\n…",
"pages_ocr": [11, 12], // pages whose text came from OCR
"pages_needing_ocr": [], // scanned pages NOT OCR'd (only with ocr: "off")
"pages_with_tables": [2, 3, 7],
"pages_with_columns": [4],
"has_encoding_issues": false,
"metadata": {
"title": "Q3 Report", "author": "Finance Team", "subject": null,
"keywords": null, "creator": "Microsoft Word", "producer": "macOS Quartz",
"creation_date": "D:20260901103000+02'00'", "mod_date": null
},
"pages": [ // only with per_page: true
{ "page": 1, "markdown": "# Q3 Report\n…", "source": "native" },
{ "page": 11, "markdown": "…", "source": "ocr" }
],
"processing_time_ms": 3140,
"usage": { "pages": 10, "ocr_pages": 2, "credits_used": 40, "credits_remaining": 19960 },
"notices": [] // hints, e.g. "2 scanned pages skipped: calls without a key don't include OCR…"
}
- Tables are Markdown tables inside
markdown. metadatadates are returned as written in the PDF (PDF date format).usage.pagescounts regular pages (1 credit each) andusage.ocr_pagescounts OCR pages (15 credits each). A page is never counted twice.
Scanned pages & OCR
With the default ocr: "auto", every page is checked first:
- Pages with real text are extracted directly. That takes milliseconds and costs 1 credit per page.
- Pages that are scanned, image-only or have broken font encoding go through OCR, including table structure. That costs 15 credits per page.
- Both are merged into one Markdown document in page order.
A PDF created on a computer usually has no OCR pages at all. Use ocr: "off" if you never want to spend OCR credits; scanned pages are then listed in pages_needing_ocr with no text. Not sure what you're dealing with? /v1/inspect tells you without converting anything.
POST /v1/inspect
Classify a PDF without converting it. It samples the file's content streams and usually answers in well under a second, even for a 300-page file. Same inputs as /v1/convert (file, url or file_base64). Uses no credits.
{
"pdf_type": "scanned",
"confidence": 0.99,
"page_count": 48,
"pages_needing_ocr": [1, 2, 3, …, 48],
"pages_with_tables": [],
"pages_with_columns": [],
"is_complex_layout": false,
"has_encoding_issues": false,
"metadata": { "title": null, "author": null, "producer": "Canon iR-ADV C5535" }
}
Use it to warn users before a large OCR job, estimate cost, or send scans somewhere else.
POST /v1/text-items Advanced
Every text run with its position and font. Use it for highlighting, redaction overlays, or your own layout logic. Works on pages with real text; OCR isn't applied here. Accepts pages.
{
"items": [
{ "text": "Revenue", "page": 2, "x": 72.0, "y": 690.4, "width": 58.2, "height": 14.0,
"rotation": 0, "font": "Helvetica-Bold", "font_size": 14, "is_bold": true, "is_italic": false }
]
}
Coordinates are PDF points from the lower-left corner of the visible page box, with y growing upward.
Long documents & webhooks
Up to 50 pages come back in the same request. Longer documents, or any request with webhook_url, return 202 with a job:
{ "id": "job_8f2k1c", "status": "processing" }
- Poll
GET /v1/jobs/{id}every few seconds untilstatusiscompletedorfailed, or - Wait for our POST to your
webhook_url. It carries aPDFInspector-Signatureheader (HMAC-SHA256 of the body with your webhook secret).
Job results are kept only until you fetch them: they're deleted after the first successful fetch or after 1 hour, whichever comes first.
Data retention
- PDFs and results exist only while your request is being processed and are deleted automatically right after. There is no permanent storage and no archive.
- File contents and extracted text are never written to our logs or error reports.
- Nothing is used for training.
- Long-document job results are deleted when fetched, or after 1 hour at the latest (see above).
- We keep only billing metadata: timestamp, key id, page counts, processing time.
Errors
Errors always look like this. The message is written so a person or an AI builder can fix the problem from it, and next_action tells an agent exactly what to call next:
{ "error": {
"code": "anonymous_limit",
"message": "You've used today's test call. Create a key with POST /v1/keys and ask your user to buy credits through the checkout link.",
"next_action": { "type": "create_key", "method": "POST", "url": "https://api.pdfinspector.com/v1/keys" },
"docs": "https://pdfinspector.com/docs/#keys" } }
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | Missing file/url or bad parameter. |
| 401 | invalid_key | Key missing, wrong, or revoked. |
| 402 | insufficient_credits | Not enough credits, or the key's spend cap was reached. next_action is buy_credits with a checkout link for the human. |
| 403 | origin_not_allowed | Publishable key used from a domain not on its list. |
| 413 | file_too_large | Over 50 MB or 500 pages. |
| 422 | unreadable_pdf | Corrupt file, not a PDF, or password-protected. |
| 429 | anonymous_limit | The daily test call without a key is used up. Create a key with POST /v1/keys. |
| 429 | rate_limited | Too many requests. Retry after the Retry-After header. |
Limits
- Max file size 50 MB, max 500 pages.
- Same-request response up to 50 pages; larger documents run as a job.
- No key: one test call a day per IP, up to 10 text pages, no OCR.
- Rate limit: 30 requests/min with credit packs, 60/min on Builder, higher on Scale.
- 1 page = 1 credit, 1 OCR page = 15 credits. Failed requests and
/v1/inspectare free. - Subscription credits reset monthly and don't roll over. Pack credits last 12 months and are used after monthly credits.
- Check your balance with
GET /v1/usage.
MCP server
Remote MCP over Streamable HTTP at https://pdfinspector.com/mcp. Without auth it allows the daily test call. Otherwise use OAuth (with dynamic client registration, so any MCP client can connect) or Authorization: Bearer sk_…. When credits run out, the tool result includes the checkout link for the user. Setup for each client is on the integrations page.
| Tool | Inputs | Returns |
|---|---|---|
pdf_to_markdown | url or file_base64, optional pages, ocr | Same JSON as /v1/convert |
inspect_pdf | url or file_base64 | Same JSON as /v1/inspect |
get_job | id | Status and result of a long-document job |
create_api_key | optional email, name | A new key with no credits, plus a checkout link (same as POST /v1/keys) |
get_usage | none | Plan and credit balance |