MarqPDF API

Compress, convert, OCR, merge and protect PDFs from your own code. Upload a file, run an operation on it, download the result: three HTTP calls.

18 operations, workflows, an agentCompress to a size, OCR in English and Arabic, convert, merge, split, protect; AI extraction and Q&A on your own model; saved chains of steps; an agent that plans them for you.
SDKs and AI agentsJavaScript and Python SDKs, and an MCP server so Claude, Cursor and other agents can work on PDFs.
100 free a monthFree accounts get 100 operations a month. A Week Pass, Pro or Team plan is unlimited.

Quick start

Create a key under Account → Developers, then:

export MARQPDF_API_KEY=mp_live_…

# 1. Upload
curl -s https://marqpdf.com/v1/files \
  -H "Authorization: Bearer $MARQPDF_API_KEY" \
  -F file=@contract.pdf
# {"object":"file","id":"file_8rX2…","name":"contract.pdf","pages":12,…}

# 2. Compress it to fit under 1 MB
curl -s https://marqpdf.com/v1/compress \
  -H "Authorization: Bearer $MARQPDF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"file": "file_8rX2…", "target_mb": 1}'
# {"object":"job","status":"succeeded",
#  "output":{"files":[{"id":"file_Qm7…","name":"contract-compressed.pdf",
#   "size":948211,"download_url":"https://marqpdf.com/v1/files/file_Qm7…/content?e=…&t=…"}],
#   "note":"…"}}

# 3. Download
curl -s -o small.pdf "https://marqpdf.com/v1/files/file_Qm7…/content" \
  -H "Authorization: Bearer $MARQPDF_API_KEY"

Operations take file ids and return new files, so steps chain: OCR a scan, then compress the searchable PDF, then protect it.

SDKs

Both take a local path, a URL, bytes or a file id wherever a file goes, wait for the job, and raise a typed error with the API's code if it fails. No dependencies.

JavaScript (Node 18+, Deno, Bun)

npm install @nucleushq/marqpdf
import { MarqPDF } from "@nucleushq/marqpdf";
const marq = new MarqPDF();                        // reads MARQPDF_API_KEY

const job = await marq.compress("contract.pdf", { target_mb: 1 });
await marq.download(job, "contract-small.pdf");

const scan = await marq.ocr("scan.pdf");           // steps chain on outputs
const doc = await marq.convert(scan.output.files[0], { to: "docx" });

Python (3.8+)

pip install https://marqpdf.com/sdk/nucleushq_marqpdf-0.3.0-py3-none-any.whl
from marqpdf import MarqPDF
marq = MarqPDF()                                   # reads MARQPDF_API_KEY

job = marq.compress("contract.pdf", target_mb=1)
marq.download(job, "contract-small.pdf")
print(marq.extract_text("https://example.com/statement.pdf")["output"]["text"])

Each SDK also has every operation (merge, split, protect, …), file and job calls, usage(), and a webhook signature check. For any other language, generate a client from the OpenAPI spec.

AI agents (MCP)

MarqPDF is an MCP server, so an AI agent can use it as a set of tools: compress_pdf, ocr_pdf, convert_file, merge_pdfs, extract_text and the rest. There are two ways to connect.

Files on your computer: the local server

For Claude Desktop, Claude Code, Cursor and other clients that run a command. Tools take file paths, and results are saved next to the originals (never over them unless you ask), so you can say "compress ~/Downloads/lease.pdf under 2 MB".

{
  "mcpServers": {
    "marqpdf": {
      "command": "npx",
      "args": ["-y", "@nucleushq/marqpdf-mcp"],
      "env": { "MARQPDF_API_KEY": "mp_live_…" }
    }
  }
}

That goes in claude_desktop_config.json (Claude Desktop) or ~/.cursor/mcp.json (Cursor). For Claude Code:

claude mcp add marqpdf -e MARQPDF_API_KEY=mp_live_… -- npx -y @nucleushq/marqpdf-mcp

Agents in the cloud: the remote server

Streamable HTTP at https://marqpdf.com/v1/mcp, with your API key as a bearer token. Files come in by URL (or base64 for small ones) and go out as one-hour download_url links.

claude mcp add --transport http marqpdf https://marqpdf.com/v1/mcp   --header "Authorization: Bearer mp_live_…"
// Cursor, VS Code and other clients that take a URL
{ "mcpServers": { "marqpdf": {
    "url": "https://marqpdf.com/v1/mcp",
    "headers": { "Authorization": "Bearer mp_live_…" } } } }

A tool call is an API call: the same files, limits and monthly operations.

AI: extract and ask

Two operations read a document with a language model. Both take a PDF or an image (a phone photo of an invoice works).

POST /v1/extract: fields as JSON

curl -s https://marqpdf.com/v1/extract \
  -H "Authorization: Bearer $MARQPDF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"file": "file_8rX2…",
       "fields": ["invoice_number", "supplier", {"name": "total", "type": "number"}, {"name": "due_date", "type": "date"}]}'
# "output": {"data": {"invoice_number": "INV-2041", "supplier": "Northwind Design Studio",
#                     "total": 5136, "due_date": "2025-04-13"},
#            "model": "gemini/gemini-2.5-flash", "files": [ …the same as a .json file… ]}

A field given as a plain name comes back as text. Field types are string (the default), number, integer, boolean, date (YYYY-MM-DD) and list. For nested data, pass a JSON Schema in schema instead. A field the document doesn't contain comes back null; values aren't guessed. Add instructions for anything else ("amounts in AED", "the buyer, not the seller").

POST /v1/ask: an answer from the document

curl -s https://marqpdf.com/v1/ask \
  -H "Authorization: Bearer $MARQPDF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"file": "file_lease…", "question": "When can the tenant end the lease early?"}'
# "output": {"text": "After 12 months, with 60 days' written notice (clause 14.2, page 6).", "model": "…"}

Answers come from the document only; if it doesn't say, the answer says so.

Workflows

A workflow is a named chain of operations you save once and run on any file. Each step works on the previous step's output; a step that only produces data (extract, ask) passes its inputs on.

curl -s https://marqpdf.com/v1/workflows \
  -H "Authorization: Bearer $MARQPDF_API_KEY" -H "Content-Type: application/json" \
  -d '{"name": "Scan to small searchable PDF",
       "steps": [{"operation": "ocr"},
                 {"operation": "compress", "options": {"target_mb": 1}},
                 {"operation": "protect", "options": {"password": "…"}}]}'
# {"object": "workflow", "id": "wf_9Kd…", …}

curl -s https://marqpdf.com/v1/workflows/wf_9Kd…/run \
  -H "Authorization: Bearer $MARQPDF_API_KEY" -H "Content-Type: application/json" \
  -d '{"file": "file_8rX2…"}'
# the job: output.files = the last step's files, output.steps = what each step did

Steps are checked when you save (up to 10 steps, 50 workflows an account), and the first step again against the real file when you run. Each step that succeeds counts as one operation. POST /v1/workflows/run with steps in the body runs a chain without saving it. You can also save workflows on the Developers screen, and agents can run them through MCP (list_workflows, run_workflow).

The agent

POST /v1/agent takes files and a goal in plain words. The agent, running on the model you pick for it, chooses MarqPDF operations, reads each result, and stops when the goal is met:

curl -s https://marqpdf.com/v1/agent \
  -H "Authorization: Bearer $MARQPDF_API_KEY" -H "Content-Type: application/json" \
  -d '{"files": ["file_jan…", "file_feb…", "file_mar…"],
       "goal": "Make these scanned statements searchable, merge them in date order and get the result under 2 MB",
       "model": "anthropic/claude-opus-5-5", "wait": 300}'
# output.text  = its summary
# output.files = the files it hands back
# output.steps = every operation it ran, with arguments and results

It only sees the files you give it and the files it makes; it can't reach anything else on your account. max_steps (1–15, default 8) caps the model turns. Each operation it runs counts as one; on MarqPDF's model the run also uses one AI operation.

Your own model

AI operations, and the AI edits in MarqPDF's editor, run on MarqPDF's model unless you choose another. Add your own key under Account → Developers → Models for any of:

ProviderModel ids, e.g.Reads PDFs directly
Anthropic (Claude)anthropic/claude-opus-5-5, anthropic/claude-sonnet-5-5Yes
OpenAIopenai/gpt-5.1Yes
Google Geminigemini/gemini-2.5-pro, gemini/gemini-2.5-flashYes
OpenRouteropenrouter/ + any of its model idsYes
Any OpenAI-compatible endpointcustom/ + the model your endpoint serves (a public https base URL)No: MarqPDF sends the text, OCR'd if it's a scan

Then pick a model for each step (extraction, questions, the agent, the editor) on the same screen, or name one per request with "model": "anthropic/claude-opus-5-5". GET /v1/models lists what your account can use. Keys are checked with the provider when you add them, encrypted at rest, used only for your requests, and never shown again.

On your own key, the provider bills you and MarqPDF doesn't cap AI calls (they still count as operations). On MarqPDF's model, free accounts get 20 AI operations a month and paid plans 1,000.

Authentication

Send your key on every request as Authorization: Bearer mp_live_… (or X-API-Key: mp_live_…). Keys are shown once, when you create them, and stored only as a hash; if one leaks, revoke it under Account → Developers and it stops working at once. Keep keys on your server: a key in a web page or a mobile app is a key anyone can copy.

GET /v1 and GET /v1/openapi.json need no key.

Files

Upload: POST /v1/files

Send multipart/form-data with the file in a field named file (and optionally name), or JSON with a URL for MarqPDF to fetch:

curl -s https://marqpdf.com/v1/files \
  -H "Authorization: Bearer $MARQPDF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/statement.pdf"}'

Up to 100 MB. PDFs, images (JPG, PNG, WebP, TIFF), Word, Excel and PowerPoint files, HTML, Markdown and text. A file's contents are checked against its name: a .pdf that isn't a PDF is refused. URLs must be http or https on a public address; private and internal addresses are refused.

The file object

{
  "object": "file",
  "id": "file_8rX2…",
  "name": "contract.pdf",
  "size": 2840113,
  "content_type": "application/pdf",
  "pages": 12,
  "created_at": "2026-10-10T08:15:00Z",
  "expires_at": "2026-10-11T08:15:00Z",
  "source": "upload",                       // upload | url | job
  "download_url": "https://marqpdf.com/v1/files/file_8rX2…/content?e=…&t=…"
}

download_url works without a key for one hour, so you can hand it to a browser, a customer or an AI agent. GET /v1/files/{id} returns a fresh one; GET /v1/files/{id}/content with your key always works while the file exists.

Other file calls

CallWhat it does
GET /v1/filesYour files from the last 24 hours, newest first (?limit= up to 200).
GET /v1/files/{id}One file's details, with a fresh download_url.
GET /v1/files/{id}/contentThe file's bytes.
DELETE /v1/files/{id}Delete it now rather than after 24 hours.

Operations

Every operation is POST /v1/{operation} with a JSON body naming its input as file (one file id) or files (a list), plus its options. Each one that succeeds counts as one operation.

OperationInputOptionsWhat it does
POST /v1/compressfiletarget_mb optional
level optional (screen | ebook | printer | prepress)
Compress a PDF. Smallest file that fits under `target_mb`, or a fixed quality `level`. In the browser
POST /v1/mergefiles—Merge PDFs. Join 2–50 PDFs, in the order given. In the browser
POST /v1/splitfileevery optional
ranges optional
Split a PDF. A ZIP of PDFs: every N pages, or the page ranges you give. In the browser
POST /v1/rotatefileangle (90 | 180 | 270)
pages optional
Rotate pages. Counts as one operation when it succeeds. In the browser
POST /v1/organizefilepagesReorder or drop pages. Keep `pages`, in that order.
POST /v1/remove-pagesfilepagesRemove pages. Counts as one operation when it succeeds.
POST /v1/page-numbersfileposition optional (bottom-center | bottom-right | bottom-left | top-center | top-right | top-left)Add page numbers. Counts as one operation when it succeeds.
POST /v1/watermarkfiletextAdd a text watermark. Counts as one operation when it succeeds. In the browser
POST /v1/protectfilepasswordPassword-protect a PDF. Counts as one operation when it succeeds. In the browser
POST /v1/unlockfilepassword optionalRemove a PDF's password. Needs the password unless the PDF only restricts printing or copying. In the browser
POST /v1/ocrfile—Make a scanned PDF searchable. Adds a text layer (English and Arabic) under the page images. In the browser
POST /v1/convertfileto (pdf | docx | xlsx | pptx | jpg)Convert a file. PDF → docx, xlsx, pptx or jpg (a ZIP of page images); images, Office files, HTML, Markdown or text → pdf. PDF → docx OCRs scans first. In the browser
POST /v1/images-to-pdffiles—Images to one PDF. One page per image, in the order given. In the browser
POST /v1/comparefiles—Compare two PDFs. A PDF marking what changed between `files[0]` and `files[1]`.
POST /v1/extract-textfileocr optional (auto | always | never)Extract text. The PDF's text (also as a .txt output file). `ocr: auto` reads scans with OCR when there's no text layer. In the browser
POST /v1/extractfilefields optional
schema optional
instructions optional
model optional
Extract data with AI. Read a PDF or an image (invoice, ID, statement, form) and return the fields you ask for as JSON in output.data (also saved as a .json output file). Name the fields, or pass a JSON Schema. A field the document doesn't contain comes back null. Runs on MarqPDF's model or one of your own (model).
POST /v1/askfilequestion
instructions optional
model optional
Ask a document. Answer a question about a PDF or an image, from the document only; the answer is in output.text. Runs on MarqPDF's model or one of your own (model).
POST /v1/agentfilesgoal
max_steps optional
model optional
Run the agent. Give a goal in plain words; the agent plans and runs MarqPDF operations on your files with the model you chose (pipeline "agent"), reading each result before the next step. output.text is its summary, output.files the files it hands back, output.steps every call it made. Each operation it runs counts as one.

Two options work on every operation: wait (see jobs) and webhook_url (see webhooks). Requests are checked before anything runs: a wrong option, a missing file or an image sent to a PDF-only operation fails at once with a 400 that says what to change, and costs nothing.

Jobs and waiting

Every operation runs as a job. The request waits for it up to wait seconds (default 60, at most 300, 0 to not wait):

ResponseMeaning
200Finished: the job, with output.files (and output.text for extract-text).
202Still running. Poll GET /v1/jobs/{id} (also in the Location header) until status is succeeded or failed, or use a webhook.
4xx / 5xxFinished but failed: {"error": {…}, "job": {…}}.
{
  "object": "job",
  "id": "job_3Fq…",
  "operation": "ocr",
  "status": "succeeded",                    // queued | running | succeeded | failed
  "created_at": "…", "started_at": "…", "finished_at": "…",
  "input": { "files": ["file_8rX2…"], "options": {} },
  "output": { "files": [ { "object": "file", "id": "file_Hk1…", … } ] },
  "error": null
}

GET /v1/jobs lists recent jobs. Jobs are kept for 7 days. A job interrupted by a server restart fails with interrupted and isn't counted; submit it again.

Webhooks

Add "webhook_url": "https://your.app/hooks/marqpdf" to any operation, and when the job finishes MarqPDF POSTs:

{ "type": "job.succeeded",                  // or job.failed
  "created_at": "2026-10-10T08:16:02Z",
  "data": { "object": "job", "id": "job_3Fq…", … } }

Answer with any 2xx. A delivery that fails is retried after 30 seconds and again after 5 minutes; the job's webhook field shows how it went. Redirects aren't followed, and the URL must be a public http or https address on port 80 or 443.

Verifying the signature

Each delivery has a MarqPDF-Signature: t=<unix time>,v1=<hex> header: an HMAC-SHA256, keyed with your webhook signing secret (Account → Developers), of the timestamp, a dot, and the raw body. Check it, and reject old timestamps:

// Node.js (Express: use express.raw() so you get the exact bytes)
import crypto from "node:crypto";
function verify(rawBody, header, secret) {
  const { t, v1 } = Object.fromEntries(header.split(",").map(p => p.split("=")));
  const want = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
  return fresh && crypto.timingSafeEqual(Buffer.from(want), Buffer.from(v1));
}
# Python
import hashlib, hmac, time
def verify(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    want = hmac.new(secret.encode(), parts["t"].encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    return abs(time.time() - int(parts["t"])) < 300 and hmac.compare_digest(want, parts["v1"])

Errors

Errors are JSON with a stable code and a message meant for a person:

{ "error": { "code": "invalid_request", "message": "`angle` is 90, 180 or 270." } }
StatusCodes
400invalid_request, invalid_file, empty_file, fetch_failed
401missing_api_key, invalid_api_key
402quota_exceeded: the free plan's operations for the month are used
403link_expired: a download_url older than an hour
404file_not_found, job_not_found, unknown_operation
410file_expired: deleted after 24 hours
413 / 415file_too_large / unsupported_file_type
429rate_limited, too_many_jobs, with a Retry-After header
5xxservice_unavailable, internal_error, interrupted: safe to retry

Limits and pricing

FreeWeek Pass, Pro, Team
Operations100 a month (UTC calendar month)Unlimited, within fair use
Requests120 a minute per key
Jobs waiting or running20 per account
File size100 MB per file

Only operations that succeed count. Uploading, downloading, listing and polling are free. AI operations on MarqPDF's model also have their own monthly cap (20 free, 1,000 paid); on your own key they don't. GET /v1/usage returns this month's count. See pricing for the plans.

Your data

Files you send and the files operations produce are deleted after 24 hours (sooner if you DELETE them). Job records keep the operation, its options with any password removed, and file ids, for 7 days. Files are stored and processed on our servers in Google Cloud's Mumbai (India) region. All operations except extract and ask run with open-source tools there, and nothing is sent to an AI provider. extract and ask send the document to the model chosen for them: yours under your key, or MarqPDF's (Google Gemini or OpenAI). We don't train AI models on your files. Details in the privacy policy; for client documents, see the Data Processing Addendum.

Questions, or an operation you need that isn't here? Email support@marqpdf.com.
📄document.pdf
Pages
Optimise & secure
Convert
✍️
Try
MarqPDF reads the whole page and changes exactly what you describe — the rest stays untouched. Or draw a box above to pinpoint a spot.
⬚ Edit this spot, tell me what to do
Say an instruction, not just text, e.g. “make it August 1, 2026” or “delete this”.