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.
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=…" } ] } }
# 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.
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
| Call | What it does |
|---|---|
GET /v1/files | Your 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}/content | The 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.
Each operation's options, examples and responses are in the API reference below.
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):
| Response | Meaning |
|---|---|
200 | Finished: the job, with output.files (and output.text for extract-text). |
202 | Still running. Poll GET /v1/jobs/{id} (also in the Location header) until status is succeeded or failed, or use a webhook. |
4xx / 5xx | Finished 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(","))
signed = parts["t"].encode() + b"." + raw_body
want = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
fresh = abs(time.time() - int(parts["t"])) < 300
return fresh 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."
}
}
| Status | Codes |
|---|---|
| 400 | invalid_request, invalid_file, empty_file, fetch_failed |
| 401 | missing_api_key, invalid_api_key |
| 402 | quota_exceeded: the free plan's operations for the month are used |
| 403 | link_expired: a download_url older than an hour |
| 404 | file_not_found, job_not_found, unknown_operation |
| 410 | file_expired: deleted after 24 hours |
| 413 / 415 | file_too_large / unsupported_file_type |
| 429 | rate_limited, too_many_jobs, with a Retry-After header |
| 5xx | service_unavailable, internal_error, interrupted: safe to retry |
Limits and pricing
| Free | Week Pass, Pro, Team | |
|---|---|---|
| Operations | 100 a month (UTC calendar month) | Unlimited, within fair use |
| Requests | 120 a minute per key | |
| Jobs waiting or running | 20 per account | |
| File size | 100 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.
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.
# Save a chain once
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…", … }
# Run it on any file
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
}'
# The finished job:
# 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:
| Provider | Model ids, e.g. | Reads PDFs directly |
|---|---|---|
| Anthropic (Claude) | anthropic/claude-opus-5-5, anthropic/claude-sonnet-5-5 | Yes |
| OpenAI | openai/gpt-5.1 | Yes |
| Google Gemini | gemini/gemini-2.5-pro, gemini/gemini-2.5-flash | Yes |
| OpenRouter | openrouter/ + any of its model ids | Yes |
| Any OpenAI-compatible endpoint | custom/ + 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.
API reference
Every endpoint of https://marqpdf.com/v1, generated from the OpenAPI 3.1.0 spec. Import that URL into Postman, Insomnia or an OpenAPI client generator. All requests take Authorization: Bearer mp_live_…. Every operation and workflow run also takes wait (jobs and waiting) and webhook_url (webhooks). The SDK examples take local paths; the API takes file ids from POST /files.
Files
Upload a file
POST /v1/files
multipart/form-data with a file field (and optional name); the raw bytes as the body with ?name=; or JSON {"url": …} to have MarqPDF fetch it (http/https, public addresses only). Up to 100 MB. Files are deleted after 24 hours.
Parameters
| Name | Type | Description |
|---|---|---|
name | query · string | File name, for a raw-body upload. |
Request body
multipart/form-data
| Name | Type | Description |
|---|---|---|
file required | file | — |
name | string | — |
application/json
| Name | Type | Description |
|---|---|---|
url required | string (uri) | — |
name | string | — |
application/octet-stream — The raw file; name it with ?name=contract.pdf
Responses
- 201 The stored file · File
Errors 400 401 413 415 429 what they mean
curl https://marqpdf.com/v1/files \
-H "Authorization: Bearer $MARQPDF_API_KEY" \
-F file=@contract.pdfimport { MarqPDF } from "@nucleushq/marqpdf";
const marq = new MarqPDF(); // reads MARQPDF_API_KEY
const file = await marq.files.upload("contract.pdf");
console.log(file.id);from marqpdf import MarqPDF
marq = MarqPDF() # reads MARQPDF_API_KEY
file = marq.upload("contract.pdf")
print(file["id"])Example response
{
"object": "file",
"id": "file_8rX2kq",
"name": "contract.pdf",
"size": 6214330,
"content_type": "application/pdf",
"pages": 4,
"created_at": "2026-10-17T09:30:02Z",
"expires_at": "2026-10-18T09:30:02Z",
"source": "upload",
"download_url": "https://marqpdf.com/v1/files/file_8rX2kq/content?sig=…"
}List recent files
GET /v1/files
Parameters
| Name | Type | Description |
|---|---|---|
limit | query · integer, 1–200 | — |
Responses
- 200 Files, newest first
Errors 401 what they mean
curl https://marqpdf.com/v1/files \
-H "Authorization: Bearer $MARQPDF_API_KEY"import { MarqPDF } from "@nucleushq/marqpdf";
const marq = new MarqPDF(); // reads MARQPDF_API_KEY
const files = await marq.files.list();from marqpdf import MarqPDF
marq = MarqPDF() # reads MARQPDF_API_KEY
files = marq.files()Example response
{
"object": "list",
"data": [
{
"object": "file",
"id": "file_8rX2kq",
"name": "contract.pdf",
"size": 482113,
"content_type": "application/pdf",
"pages": 4,
"created_at": "2026-10-17T09:30:02Z",
"expires_at": "2026-10-18T09:30:02Z",
"source": "upload",
"download_url": "https://marqpdf.com/v1/files/file_8rX2kq/content?sig=…"
}
]
}Get a file's details (and a fresh download_url)
GET /v1/files/{id}
Parameters
| Name | Type | Description |
|---|---|---|
id required | path · string | — |
Responses
- 200 The file · File
Errors 401 404 410 what they mean
curl https://marqpdf.com/v1/files/file_8rX2kq \
-H "Authorization: Bearer $MARQPDF_API_KEY"import { MarqPDF } from "@nucleushq/marqpdf";
const marq = new MarqPDF(); // reads MARQPDF_API_KEY
const file = await marq.files.get("file_8rX2kq");from marqpdf import MarqPDF
marq = MarqPDF() # reads MARQPDF_API_KEY
file = marq.file("file_8rX2kq")Example response
{
"object": "file",
"id": "file_8rX2kq",
"name": "contract.pdf",
"size": 6214330,
"content_type": "application/pdf",
"pages": 4,
"created_at": "2026-10-17T09:30:02Z",
"expires_at": "2026-10-18T09:30:02Z",
"source": "upload",
"download_url": "https://marqpdf.com/v1/files/file_8rX2kq/content?sig=…"
}Delete a file now
DELETE /v1/files/{id}
Parameters
| Name | Type | Description |
|---|---|---|
id required | path · string | — |
Responses
- 200 Deleted
Errors 401 404 what they mean
curl -X DELETE https://marqpdf.com/v1/files/file_8rX2kq \
-H "Authorization: Bearer $MARQPDF_API_KEY"import { MarqPDF } from "@nucleushq/marqpdf";
const marq = new MarqPDF(); // reads MARQPDF_API_KEY
await marq.files.delete("file_8rX2kq");from marqpdf import MarqPDF
marq = MarqPDF() # reads MARQPDF_API_KEY
marq.delete_file("file_8rX2kq")Example response
{ "object": "file", "id": "file_8rX2kq", "deleted": true }Download a file
GET /v1/files/{id}/content
With the API key, or with no key through a file's signed download_url (valid for an hour).
Parameters
| Name | Type | Description |
|---|---|---|
id required | path · string | — |
Responses
- 200 The file's bytes
Errors 401 404 410 what they mean
curl https://marqpdf.com/v1/files/file_8rX2kq/content \
-H "Authorization: Bearer $MARQPDF_API_KEY" \
-o contract.pdfimport { MarqPDF } from "@nucleushq/marqpdf";
const marq = new MarqPDF(); // reads MARQPDF_API_KEY
await marq.download("file_8rX2kq", "contract.pdf");from marqpdf import MarqPDF
marq = MarqPDF() # reads MARQPDF_API_KEY
marq.download("file_8rX2kq", "contract.pdf")PDF tools
Compress a PDF
POST /v1/compressTry it in the browser
Smallest file that fits under target_mb, or a fixed quality level.
Request body
application/json
| Name | Type | Description |
|---|---|---|
file required | string | A file id from POST /files. |
target_mb | number, 0.05–500 | Fit under this size, at the best quality that does. Example 1. |
level | one of "screen", "ebook", "printer", "prepress" | Default "ebook". |
Responses
- 200 Finished: the job with its output · Job
- 202 Still running: poll GET /jobs/{id} (also in Location) · Job
Errors 400 401 402 404 410 429 502 what they mean
curl https://marqpdf.com/v1/compress \
-H "Authorization: Bearer $MARQPDF_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "file": "file_8rX2kq", "target_mb": 1 }'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-compress.pdf");from marqpdf import MarqPDF
marq = MarqPDF() # reads MARQPDF_API_KEY
job = marq.compress("contract.pdf", target_mb=1)
marq.download(job, "contract-compress.pdf")Example response
{
"object": "job",
"id": "job_3Fq8nd",
"operation": "compress",
"status": "succeeded",
"created_at": "2026-10-17T09:30:00Z",
"started_at": "2026-10-17T09:30:00Z",
"finished_at": "2026-10-17T09:30:02Z",
"input": {
"files": ["file_8rX2kq"],
"options": { "target_mb": 1 }
},
"output": {
"files": [
{
"object": "file",
"id": "file_5Hn3vb",
"name": "contract-compressed.pdf",
"size": 941204,
"content_type": "application/pdf",
"pages": 4,
"created_at": "2026-10-17T09:30:02Z",
"expires_at": "2026-10-18T09:30:02Z",
"source": "job",
"job": "job_3Fq8nd",
"download_url": "https://marqpdf.com/v1/files/file_5Hn3vb/content?sig=…"
}
],
"note": "Fit 6.2 MB → 0.9 MB."
},
"error": null
}Merge PDFs
POST /v1/mergeTry it in the browser
Join 2–50 PDFs, in the order given.
Request body
application/json
| Name | Type | Description |
|---|---|---|
files required | array of strings | File ids, in order. |
Responses
- 200 Finished: the job with its output · Job
- 202 Still running: poll GET /jobs/{id} (also in Location) · Job
Errors 400 401 402 404 410 429 502 what they mean
curl https://marqpdf.com/v1/merge \
-H "Authorization: Bearer $MARQPDF_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "files": ["file_8rX2kq", "file_9Kp4tw"] }'import { MarqPDF } from "@nucleushq/marqpdf";
const marq = new MarqPDF(); // reads MARQPDF_API_KEY
const job = await marq.merge(["jan.pdf", "feb.pdf"]);
await marq.download(job, "merged.pdf");from marqpdf import MarqPDF
marq = MarqPDF() # reads MARQPDF_API_KEY
job = marq.merge(["jan.pdf", "feb.pdf"])
marq.download(job, "merged.pdf")Example response
{
"object": "job",
"id": "job_3Fq8nd",
"operation": "merge",
"status": "succeeded",
"created_at": "2026-10-17T09:30:00Z",
"started_at": "2026-10-17T09:30:00Z",
"finished_at": "2026-10-17T09:30:02Z",
"input": {
"files": ["file_8rX2kq", "file_9Kp4tw"],
"options": {}
},
"output": {
"files": [
{
"object": "file",
"id": "file_5Hn3vb",
"name": "jan-merge.pdf",
"size": 482113,
"content_type": "application/pdf",
"pages": 4,
"created_at": "2026-10-17T09:30:02Z",
"expires_at": "2026-10-18T09:30:02Z",
"source": "job",
"job": "job_3Fq8nd",
"download_url": "https://marqpdf.com/v1/files/file_5Hn3vb/content?sig=…"
}
]
},
"error": null
}Split a PDF
POST /v1/splitTry it in the browser
A ZIP of PDFs: every N pages, or the page ranges you give.
Request body
application/json
| Name | Type | Description |
|---|---|---|
file required | string | A file id from POST /files. |
every | integer, ≥ 1 | Default 1. |
ranges | array of pairs of integers | Example [[1,3],[4,9]]. |
Responses
- 200 Finished: the job with its output · Job
- 202 Still running: poll GET /jobs/{id} (also in Location) · Job
Errors 400 401 402 404 410 429 502 what they mean
curl https://marqpdf.com/v1/split \
-H "Authorization: Bearer $MARQPDF_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "file": "file_8rX2kq", "ranges": [[1, 3], [4, 9]] }'import { MarqPDF } from "@nucleushq/marqpdf";
const marq = new MarqPDF(); // reads MARQPDF_API_KEY
const job = await marq.split("contract.pdf", {
ranges: [[1, 3], [4, 9]],
});
await marq.download(job, "parts.zip");from marqpdf import MarqPDF
marq = MarqPDF() # reads MARQPDF_API_KEY
job = marq.split(
"contract.pdf",
ranges=[[1, 3], [4, 9]],
)
marq.download(job, "parts.zip")Example response
{
"object": "job",
"id": "job_3Fq8nd",
"operation": "split",
"status": "succeeded",
"created_at": "2026-10-17T09:30:00Z",
"started_at": "2026-10-17T09:30:00Z",
"finished_at": "2026-10-17T09:30:02Z",
"input": {
"files": ["file_8rX2kq"],
"options": { "ranges": [[1, 3], [4, 9]] }
},
"output": {
"files": [
{
"object": "file",
"id": "file_5Hn3vb",
"name": "contract-split.zip",
"size": 811530,
"content_type": "application/zip",
"created_at": "2026-10-17T09:30:02Z",
"expires_at": "2026-10-18T09:30:02Z",
"source": "job",
"job": "job_3Fq8nd",
"download_url": "https://marqpdf.com/v1/files/file_5Hn3vb/content?sig=…"
}
]
},
"error": null
}Rotate pages
POST /v1/rotateTry it in the browser
Counts as one operation when it succeeds.
Request body
application/json
| Name | Type | Description |
|---|---|---|
file required | string | A file id from POST /files. |
angle required | one of 90, 180, 270 | — |
pages | array of integers | Page numbers, counting from 1. |
Responses
- 200 Finished: the job with its output · Job
- 202 Still running: poll GET /jobs/{id} (also in Location) · Job
Errors 400 401 402 404 410 429 502 what they mean
curl https://marqpdf.com/v1/rotate \
-H "Authorization: Bearer $MARQPDF_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "file": "file_8rX2kq", "angle": 90, "pages": [1] }'import { MarqPDF } from "@nucleushq/marqpdf";
const marq = new MarqPDF(); // reads MARQPDF_API_KEY
const job = await marq.rotate("contract.pdf", {
angle: 90,
pages: [1],
});
await marq.download(job, "contract-rotate.pdf");from marqpdf import MarqPDF
marq = MarqPDF() # reads MARQPDF_API_KEY
job = marq.rotate("contract.pdf", angle=90, pages=[1])
marq.download(job, "contract-rotate.pdf")Example response
{
"object": "job",
"id": "job_3Fq8nd",
"operation": "rotate",
"status": "succeeded",
"created_at": "2026-10-17T09:30:00Z",
"started_at": "2026-10-17T09:30:00Z",
"finished_at": "2026-10-17T09:30:02Z",
"input": {
"files": ["file_8rX2kq"],
"options": { "angle": 90, "pages": [1] }
},
"output": {
"files": [
{
"object": "file",
"id": "file_5Hn3vb",
"name": "contract-rotate.pdf",
"size": 482113,
"content_type": "application/pdf",
"pages": 4,
"created_at": "2026-10-17T09:30:02Z",
"expires_at": "2026-10-18T09:30:02Z",
"source": "job",
"job": "job_3Fq8nd",
"download_url": "https://marqpdf.com/v1/files/file_5Hn3vb/content?sig=…"
}
]
},
"error": null
}Reorder or drop pages
POST /v1/organize
Keep pages, in that order.
Request body
application/json
| Name | Type | Description |
|---|---|---|
file required | string | A file id from POST /files. |
pages required | array of integers | Page numbers, counting from 1. |
Responses
- 200 Finished: the job with its output · Job
- 202 Still running: poll GET /jobs/{id} (also in Location) · Job
Errors 400 401 402 404 410 429 502 what they mean
curl https://marqpdf.com/v1/organize \
-H "Authorization: Bearer $MARQPDF_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "file": "file_8rX2kq", "pages": [3, 1, 2] }'import { MarqPDF } from "@nucleushq/marqpdf";
const marq = new MarqPDF(); // reads MARQPDF_API_KEY
const job = await marq.organize("contract.pdf", {
pages: [3, 1, 2],
});
await marq.download(job, "contract-organize.pdf");from marqpdf import MarqPDF
marq = MarqPDF() # reads MARQPDF_API_KEY
job = marq.organize("contract.pdf", pages=[3, 1, 2])
marq.download(job, "contract-organize.pdf")Example response
{
"object": "job",
"id": "job_3Fq8nd",
"operation": "organize",
"status": "succeeded",
"created_at": "2026-10-17T09:30:00Z",
"started_at": "2026-10-17T09:30:00Z",
"finished_at": "2026-10-17T09:30:02Z",
"input": {
"files": ["file_8rX2kq"],
"options": { "pages": [3, 1, 2] }
},
"output": {
"files": [
{
"object": "file",
"id": "file_5Hn3vb",
"name": "contract-organize.pdf",
"size": 482113,
"content_type": "application/pdf",
"pages": 4,
"created_at": "2026-10-17T09:30:02Z",
"expires_at": "2026-10-18T09:30:02Z",
"source": "job",
"job": "job_3Fq8nd",
"download_url": "https://marqpdf.com/v1/files/file_5Hn3vb/content?sig=…"
}
]
},
"error": null
}Remove pages
POST /v1/remove-pages
Counts as one operation when it succeeds.
Request body
application/json
| Name | Type | Description |
|---|---|---|
file required | string | A file id from POST /files. |
pages required | array of integers | Page numbers, counting from 1. |
Responses
- 200 Finished: the job with its output · Job
- 202 Still running: poll GET /jobs/{id} (also in Location) · Job
Errors 400 401 402 404 410 429 502 what they mean
curl https://marqpdf.com/v1/remove-pages \
-H "Authorization: Bearer $MARQPDF_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "file": "file_8rX2kq", "pages": [2] }'import { MarqPDF } from "@nucleushq/marqpdf";
const marq = new MarqPDF(); // reads MARQPDF_API_KEY
const job = await marq.removePages("contract.pdf", {
pages: [2],
});
await marq.download(job, "contract-remove-pages.pdf");from marqpdf import MarqPDF
marq = MarqPDF() # reads MARQPDF_API_KEY
job = marq.remove_pages("contract.pdf", pages=[2])
marq.download(job, "contract-remove-pages.pdf")Example response
{
"object": "job",
"id": "job_3Fq8nd",
"operation": "remove-pages",
"status": "succeeded",
"created_at": "2026-10-17T09:30:00Z",
"started_at": "2026-10-17T09:30:00Z",
"finished_at": "2026-10-17T09:30:02Z",
"input": {
"files": ["file_8rX2kq"],
"options": { "pages": [2] }
},
"output": {
"files": [
{
"object": "file",
"id": "file_5Hn3vb",
"name": "contract-remove-pages.pdf",
"size": 482113,
"content_type": "application/pdf",
"pages": 4,
"created_at": "2026-10-17T09:30:02Z",
"expires_at": "2026-10-18T09:30:02Z",
"source": "job",
"job": "job_3Fq8nd",
"download_url": "https://marqpdf.com/v1/files/file_5Hn3vb/content?sig=…"
}
]
},
"error": null
}Add page numbers
POST /v1/page-numbers
Counts as one operation when it succeeds.
Request body
application/json
| Name | Type | Description |
|---|---|---|
file required | string | A file id from POST /files. |
position | one of "bottom-center", "bottom-right", "bottom-left", "top-center", "top-right", "top-left" | Default "bottom-center". |
Responses
- 200 Finished: the job with its output · Job
- 202 Still running: poll GET /jobs/{id} (also in Location) · Job
Errors 400 401 402 404 410 429 502 what they mean
curl https://marqpdf.com/v1/page-numbers \
-H "Authorization: Bearer $MARQPDF_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "file": "file_8rX2kq", "position": "bottom-right" }'import { MarqPDF } from "@nucleushq/marqpdf";
const marq = new MarqPDF(); // reads MARQPDF_API_KEY
const job = await marq.pageNumbers("contract.pdf", {
position: "bottom-right",
});
await marq.download(job, "contract-page-numbers.pdf");from marqpdf import MarqPDF
marq = MarqPDF() # reads MARQPDF_API_KEY
job = marq.page_numbers(
"contract.pdf",
position="bottom-right",
)
marq.download(job, "contract-page-numbers.pdf")Example response
{
"object": "job",
"id": "job_3Fq8nd",
"operation": "page-numbers",
"status": "succeeded",
"created_at": "2026-10-17T09:30:00Z",
"started_at": "2026-10-17T09:30:00Z",
"finished_at": "2026-10-17T09:30:02Z",
"input": {
"files": ["file_8rX2kq"],
"options": { "position": "bottom-right" }
},
"output": {
"files": [
{
"object": "file",
"id": "file_5Hn3vb",
"name": "contract-page-numbers.pdf",
"size": 482113,
"content_type": "application/pdf",
"pages": 4,
"created_at": "2026-10-17T09:30:02Z",
"expires_at": "2026-10-18T09:30:02Z",
"source": "job",
"job": "job_3Fq8nd",
"download_url": "https://marqpdf.com/v1/files/file_5Hn3vb/content?sig=…"
}
]
},
"error": null
}Add a text watermark
POST /v1/watermarkTry it in the browser
Counts as one operation when it succeeds.
Request body
application/json
| Name | Type | Description |
|---|---|---|
file required | string | A file id from POST /files. |
text required | string, up to 120 characters | — |
Responses
- 200 Finished: the job with its output · Job
- 202 Still running: poll GET /jobs/{id} (also in Location) · Job
Errors 400 401 402 404 410 429 502 what they mean
curl https://marqpdf.com/v1/watermark \
-H "Authorization: Bearer $MARQPDF_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "file": "file_8rX2kq", "text": "CONFIDENTIAL" }'import { MarqPDF } from "@nucleushq/marqpdf";
const marq = new MarqPDF(); // reads MARQPDF_API_KEY
const job = await marq.watermark("contract.pdf", {
text: "CONFIDENTIAL",
});
await marq.download(job, "contract-watermark.pdf");from marqpdf import MarqPDF
marq = MarqPDF() # reads MARQPDF_API_KEY
job = marq.watermark(
"contract.pdf",
text="CONFIDENTIAL",
)
marq.download(job, "contract-watermark.pdf")Example response
{
"object": "job",
"id": "job_3Fq8nd",
"operation": "watermark",
"status": "succeeded",
"created_at": "2026-10-17T09:30:00Z",
"started_at": "2026-10-17T09:30:00Z",
"finished_at": "2026-10-17T09:30:02Z",
"input": {
"files": ["file_8rX2kq"],
"options": { "text": "CONFIDENTIAL" }
},
"output": {
"files": [
{
"object": "file",
"id": "file_5Hn3vb",
"name": "contract-watermark.pdf",
"size": 482113,
"content_type": "application/pdf",
"pages": 4,
"created_at": "2026-10-17T09:30:02Z",
"expires_at": "2026-10-18T09:30:02Z",
"source": "job",
"job": "job_3Fq8nd",
"download_url": "https://marqpdf.com/v1/files/file_5Hn3vb/content?sig=…"
}
]
},
"error": null
}Password-protect a PDF
POST /v1/protectTry it in the browser
Counts as one operation when it succeeds.
Request body
application/json
| Name | Type | Description |
|---|---|---|
file required | string | A file id from POST /files. |
password required | string | — |
Responses
- 200 Finished: the job with its output · Job
- 202 Still running: poll GET /jobs/{id} (also in Location) · Job
Errors 400 401 402 404 410 429 502 what they mean
curl https://marqpdf.com/v1/protect \
-H "Authorization: Bearer $MARQPDF_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "file": "file_8rX2kq", "password": "s3cret-pass" }'import { MarqPDF } from "@nucleushq/marqpdf";
const marq = new MarqPDF(); // reads MARQPDF_API_KEY
const job = await marq.protect("contract.pdf", {
password: "s3cret-pass",
});
await marq.download(job, "contract-protect.pdf");from marqpdf import MarqPDF
marq = MarqPDF() # reads MARQPDF_API_KEY
job = marq.protect(
"contract.pdf",
password="s3cret-pass",
)
marq.download(job, "contract-protect.pdf")Example response
{
"object": "job",
"id": "job_3Fq8nd",
"operation": "protect",
"status": "succeeded",
"created_at": "2026-10-17T09:30:00Z",
"started_at": "2026-10-17T09:30:00Z",
"finished_at": "2026-10-17T09:30:02Z",
"input": {
"files": ["file_8rX2kq"],
"options": { "password": "s3cret-pass" }
},
"output": {
"files": [
{
"object": "file",
"id": "file_5Hn3vb",
"name": "contract-protect.pdf",
"size": 482113,
"content_type": "application/pdf",
"pages": 4,
"created_at": "2026-10-17T09:30:02Z",
"expires_at": "2026-10-18T09:30:02Z",
"source": "job",
"job": "job_3Fq8nd",
"download_url": "https://marqpdf.com/v1/files/file_5Hn3vb/content?sig=…"
}
]
},
"error": null
}Remove a PDF's password
POST /v1/unlockTry it in the browser
Needs the password unless the PDF only restricts printing or copying.
Request body
application/json
| Name | Type | Description |
|---|---|---|
file required | string | A file id from POST /files. |
password | string | — |
Responses
- 200 Finished: the job with its output · Job
- 202 Still running: poll GET /jobs/{id} (also in Location) · Job
Errors 400 401 402 404 410 429 502 what they mean
curl https://marqpdf.com/v1/unlock \
-H "Authorization: Bearer $MARQPDF_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "file": "file_8rX2kq", "password": "s3cret-pass" }'import { MarqPDF } from "@nucleushq/marqpdf";
const marq = new MarqPDF(); // reads MARQPDF_API_KEY
const job = await marq.unlock("locked.pdf", {
password: "s3cret-pass",
});
await marq.download(job, "contract-unlock.pdf");from marqpdf import MarqPDF
marq = MarqPDF() # reads MARQPDF_API_KEY
job = marq.unlock("locked.pdf", password="s3cret-pass")
marq.download(job, "contract-unlock.pdf")Example response
{
"object": "job",
"id": "job_3Fq8nd",
"operation": "unlock",
"status": "succeeded",
"created_at": "2026-10-17T09:30:00Z",
"started_at": "2026-10-17T09:30:00Z",
"finished_at": "2026-10-17T09:30:02Z",
"input": {
"files": ["file_8rX2kq"],
"options": { "password": "s3cret-pass" }
},
"output": {
"files": [
{
"object": "file",
"id": "file_5Hn3vb",
"name": "locked-unlock.pdf",
"size": 482113,
"content_type": "application/pdf",
"pages": 4,
"created_at": "2026-10-17T09:30:02Z",
"expires_at": "2026-10-18T09:30:02Z",
"source": "job",
"job": "job_3Fq8nd",
"download_url": "https://marqpdf.com/v1/files/file_5Hn3vb/content?sig=…"
}
]
},
"error": null
}Convert and read
Make a scanned PDF searchable
POST /v1/ocrTry it in the browser
Adds a text layer (English and Arabic) under the page images.
Request body
application/json
| Name | Type | Description |
|---|---|---|
file required | string | A file id from POST /files. |
Responses
- 200 Finished: the job with its output · Job
- 202 Still running: poll GET /jobs/{id} (also in Location) · Job
Errors 400 401 402 404 410 429 502 what they mean
curl https://marqpdf.com/v1/ocr \
-H "Authorization: Bearer $MARQPDF_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "file": "file_8rX2kq" }'import { MarqPDF } from "@nucleushq/marqpdf";
const marq = new MarqPDF(); // reads MARQPDF_API_KEY
const job = await marq.ocr("scan.pdf");
await marq.download(job, "scan-searchable.pdf");from marqpdf import MarqPDF
marq = MarqPDF() # reads MARQPDF_API_KEY
job = marq.ocr("scan.pdf")
marq.download(job, "scan-searchable.pdf")Example response
{
"object": "job",
"id": "job_3Fq8nd",
"operation": "ocr",
"status": "succeeded",
"created_at": "2026-10-17T09:30:00Z",
"started_at": "2026-10-17T09:30:00Z",
"finished_at": "2026-10-17T09:30:02Z",
"input": { "files": ["file_8rX2kq"], "options": {} },
"output": {
"files": [
{
"object": "file",
"id": "file_5Hn3vb",
"name": "scan-ocr.pdf",
"size": 482113,
"content_type": "application/pdf",
"pages": 4,
"created_at": "2026-10-17T09:30:02Z",
"expires_at": "2026-10-18T09:30:02Z",
"source": "job",
"job": "job_3Fq8nd",
"download_url": "https://marqpdf.com/v1/files/file_5Hn3vb/content?sig=…"
}
]
},
"error": null
}Convert a file
POST /v1/convertTry it in the browser
PDF → docx, xlsx, pptx or jpg (a ZIP of page images); images, Office files, HTML, Markdown or text → pdf. PDF → docx OCRs scans first.
Request body
application/json
| Name | Type | Description |
|---|---|---|
file required | string | A file id from POST /files. |
to required | one of "pdf", "docx", "xlsx", "pptx", "jpg" | — |
Responses
- 200 Finished: the job with its output · Job
- 202 Still running: poll GET /jobs/{id} (also in Location) · Job
Errors 400 401 402 404 410 429 502 what they mean
curl https://marqpdf.com/v1/convert \
-H "Authorization: Bearer $MARQPDF_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "file": "file_8rX2kq", "to": "docx" }'import { MarqPDF } from "@nucleushq/marqpdf";
const marq = new MarqPDF(); // reads MARQPDF_API_KEY
const job = await marq.convert("report.pdf", {
to: "docx",
});
await marq.download(job, "report.docx");from marqpdf import MarqPDF
marq = MarqPDF() # reads MARQPDF_API_KEY
job = marq.convert("report.pdf", to="docx")
marq.download(job, "report.docx")Example response
{
"object": "job",
"id": "job_3Fq8nd",
"operation": "convert",
"status": "succeeded",
"created_at": "2026-10-17T09:30:00Z",
"started_at": "2026-10-17T09:30:00Z",
"finished_at": "2026-10-17T09:30:02Z",
"input": {
"files": ["file_8rX2kq"],
"options": { "to": "docx" }
},
"output": {
"files": [
{
"object": "file",
"id": "file_5Hn3vb",
"name": "report.docx",
"size": 482113,
"content_type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"created_at": "2026-10-17T09:30:02Z",
"expires_at": "2026-10-18T09:30:02Z",
"source": "job",
"job": "job_3Fq8nd",
"download_url": "https://marqpdf.com/v1/files/file_5Hn3vb/content?sig=…"
}
]
},
"error": null
}Images to one PDF
POST /v1/images-to-pdfTry it in the browser
One page per image, in the order given.
Request body
application/json
| Name | Type | Description |
|---|---|---|
files required | array of strings | File ids, in order. |
Responses
- 200 Finished: the job with its output · Job
- 202 Still running: poll GET /jobs/{id} (also in Location) · Job
Errors 400 401 402 404 410 429 502 what they mean
curl https://marqpdf.com/v1/images-to-pdf \
-H "Authorization: Bearer $MARQPDF_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "files": ["file_8rX2kq", "file_9Kp4tw"] }'import { MarqPDF } from "@nucleushq/marqpdf";
const marq = new MarqPDF(); // reads MARQPDF_API_KEY
const job = await marq.imagesToPdf(["p1.jpg", "p2.jpg"]);
await marq.download(job, "pages.pdf");from marqpdf import MarqPDF
marq = MarqPDF() # reads MARQPDF_API_KEY
job = marq.images_to_pdf(["p1.jpg", "p2.jpg"])
marq.download(job, "pages.pdf")Example response
{
"object": "job",
"id": "job_3Fq8nd",
"operation": "images-to-pdf",
"status": "succeeded",
"created_at": "2026-10-17T09:30:00Z",
"started_at": "2026-10-17T09:30:00Z",
"finished_at": "2026-10-17T09:30:02Z",
"input": {
"files": ["file_8rX2kq", "file_9Kp4tw"],
"options": {}
},
"output": {
"files": [
{
"object": "file",
"id": "file_5Hn3vb",
"name": "p1-images-to-pdf.pdf",
"size": 482113,
"content_type": "application/pdf",
"pages": 4,
"created_at": "2026-10-17T09:30:02Z",
"expires_at": "2026-10-18T09:30:02Z",
"source": "job",
"job": "job_3Fq8nd",
"download_url": "https://marqpdf.com/v1/files/file_5Hn3vb/content?sig=…"
}
]
},
"error": null
}Compare two PDFs
POST /v1/compare
A PDF marking what changed between files[0] and files[1].
Request body
application/json
| Name | Type | Description |
|---|---|---|
files required | array of strings | File ids, in order. |
Responses
- 200 Finished: the job with its output · Job
- 202 Still running: poll GET /jobs/{id} (also in Location) · Job
Errors 400 401 402 404 410 429 502 what they mean
curl https://marqpdf.com/v1/compare \
-H "Authorization: Bearer $MARQPDF_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "files": ["file_8rX2kq", "file_9Kp4tw"] }'import { MarqPDF } from "@nucleushq/marqpdf";
const marq = new MarqPDF(); // reads MARQPDF_API_KEY
const job = await marq.compare(["old.pdf", "new.pdf"]);
await marq.download(job, "changes.pdf");from marqpdf import MarqPDF
marq = MarqPDF() # reads MARQPDF_API_KEY
job = marq.compare(["old.pdf", "new.pdf"])
marq.download(job, "changes.pdf")Example response
{
"object": "job",
"id": "job_3Fq8nd",
"operation": "compare",
"status": "succeeded",
"created_at": "2026-10-17T09:30:00Z",
"started_at": "2026-10-17T09:30:00Z",
"finished_at": "2026-10-17T09:30:02Z",
"input": {
"files": ["file_8rX2kq", "file_9Kp4tw"],
"options": {}
},
"output": {
"files": [
{
"object": "file",
"id": "file_5Hn3vb",
"name": "old-compare.pdf",
"size": 482113,
"content_type": "application/pdf",
"pages": 4,
"created_at": "2026-10-17T09:30:02Z",
"expires_at": "2026-10-18T09:30:02Z",
"source": "job",
"job": "job_3Fq8nd",
"download_url": "https://marqpdf.com/v1/files/file_5Hn3vb/content?sig=…"
}
]
},
"error": null
}Extract text
POST /v1/extract-textTry it in the browser
The PDF's text (also as a .txt output file). ocr: auto reads scans with OCR when there's no text layer.
Request body
application/json
| Name | Type | Description |
|---|---|---|
file required | string | A file id from POST /files. |
ocr | one of "auto", "always", "never" | Default "auto". |
Responses
- 200 Finished: the job with its output · Job
- 202 Still running: poll GET /jobs/{id} (also in Location) · Job
Errors 400 401 402 404 410 429 502 what they mean
curl https://marqpdf.com/v1/extract-text \
-H "Authorization: Bearer $MARQPDF_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "file": "file_8rX2kq" }'import { MarqPDF } from "@nucleushq/marqpdf";
const marq = new MarqPDF(); // reads MARQPDF_API_KEY
const job = await marq.extractText("statement.pdf");
console.log(job.output.text);from marqpdf import MarqPDF
marq = MarqPDF() # reads MARQPDF_API_KEY
job = marq.extract_text("statement.pdf")
print(job["output"]["text"])Example response
{
"object": "job",
"id": "job_3Fq8nd",
"operation": "extract-text",
"status": "succeeded",
"created_at": "2026-10-17T09:30:00Z",
"started_at": "2026-10-17T09:30:00Z",
"finished_at": "2026-10-17T09:30:02Z",
"input": { "files": ["file_8rX2kq"], "options": {} },
"output": {
"text": "Account statement\nOpening balance 12,400.00 …",
"text_truncated": false
},
"error": null
}AI
Extract data with AI
POST /v1/extract
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).
Request body
application/json
| Name | Type | Description |
|---|---|---|
file required | string | A file id from POST /files. |
fields | array of strings or objects | What to extract: names, or {name, type, description}. Example ["invoice_number",{"name":"total","type":"number"},{"name":"due_date","type":"date"}]. |
schema | object | Instead of fields: a JSON Schema for the result (an object). |
instructions | string | Extra guidance, e.g. 'amounts in AED'. |
model | string | "marqpdf" (the default) or "provider/model" on a key you've added, e.g. "anthropic/claude-opus-5-5", "openai/gpt-5.1", "gemini/gemini-2.5-pro", "openrouter/…", "custom/…". Example "anthropic/claude-opus-5-5". |
Responses
- 200 Finished: the job with its output · Job
- 202 Still running: poll GET /jobs/{id} (also in Location) · Job
Errors 400 401 402 404 410 429 502 what they mean
curl https://marqpdf.com/v1/extract \
-H "Authorization: Bearer $MARQPDF_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"file": "file_8rX2kq",
"fields": [
"invoice_number",
{ "name": "total", "type": "number" },
{ "name": "due_date", "type": "date" }
]
}'import { MarqPDF } from "@nucleushq/marqpdf";
const marq = new MarqPDF(); // reads MARQPDF_API_KEY
const job = await marq.extract("invoice.pdf", {
fields: [
"invoice_number",
{ name: "total", type: "number" },
{ name: "due_date", type: "date" },
],
});
console.log(job.output.data);from marqpdf import MarqPDF
marq = MarqPDF() # reads MARQPDF_API_KEY
job = marq.extract(
"invoice.pdf",
fields=[
"invoice_number",
{"name": "total", "type": "number"},
{"name": "due_date", "type": "date"},
],
)
print(job["output"]["data"])Example response
{
"object": "job",
"id": "job_3Fq8nd",
"operation": "extract",
"status": "succeeded",
"created_at": "2026-10-17T09:30:00Z",
"started_at": "2026-10-17T09:30:00Z",
"finished_at": "2026-10-17T09:30:02Z",
"input": {
"files": ["file_8rX2kq"],
"options": {
"fields": [
"invoice_number",
{ "name": "total", "type": "number" },
{ "name": "due_date", "type": "date" }
]
}
},
"output": {
"data": {
"invoice_number": "INV-2041",
"total": 5136,
"due_date": "2025-04-13"
}
},
"error": null
}Ask a document
POST /v1/ask
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).
Request body
application/json
| Name | Type | Description |
|---|---|---|
file required | string | A file id from POST /files. |
question required | string | — |
instructions | string | Extra guidance, e.g. 'answer in one sentence'. |
model | string | "marqpdf" (the default) or "provider/model" on a key you've added, e.g. "anthropic/claude-opus-5-5", "openai/gpt-5.1", "gemini/gemini-2.5-pro", "openrouter/…", "custom/…". Example "anthropic/claude-opus-5-5". |
Responses
- 200 Finished: the job with its output · Job
- 202 Still running: poll GET /jobs/{id} (also in Location) · Job
Errors 400 401 402 404 410 429 502 what they mean
curl https://marqpdf.com/v1/ask \
-H "Authorization: Bearer $MARQPDF_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"file": "file_8rX2kq",
"question": "When can the tenant terminate early?"
}'import { MarqPDF } from "@nucleushq/marqpdf";
const marq = new MarqPDF(); // reads MARQPDF_API_KEY
const job = await marq.ask("lease.pdf", {
question: "When can the tenant terminate early?",
});
console.log(job.output.text);from marqpdf import MarqPDF
marq = MarqPDF() # reads MARQPDF_API_KEY
job = marq.ask(
"lease.pdf",
question="When can the tenant terminate early?",
)
print(job["output"]["text"])Example response
{
"object": "job",
"id": "job_3Fq8nd",
"operation": "ask",
"status": "succeeded",
"created_at": "2026-10-17T09:30:00Z",
"started_at": "2026-10-17T09:30:00Z",
"finished_at": "2026-10-17T09:30:02Z",
"input": {
"files": ["file_8rX2kq"],
"options": {
"question": "When can the tenant terminate early?"
}
},
"output": {
"text": "After the first 12 months, with 60 days' written notice (clause 14.2)."
},
"error": null
}Run the agent
POST /v1/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.
Request body
application/json
| Name | Type | Description |
|---|---|---|
files required | array of strings | File ids, in order. |
goal required | string | What to do, e.g. 'make these scans searchable, merge them in date order and get the result under 2 MB'. |
max_steps | integer, 1–15 | Model turns before it stops. Default 8. |
model | string | "marqpdf" (the default) or "provider/model" on a key you've added, e.g. "anthropic/claude-opus-5-5", "openai/gpt-5.1", "gemini/gemini-2.5-pro", "openrouter/…", "custom/…". Example "anthropic/claude-opus-5-5". |
Responses
- 200 Finished: the job with its output · Job
- 202 Still running: poll GET /jobs/{id} (also in Location) · Job
Errors 400 401 402 404 410 429 502 what they mean
curl https://marqpdf.com/v1/agent \
-H "Authorization: Bearer $MARQPDF_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"files": ["file_8rX2kq", "file_9Kp4tw"],
"goal": "Merge these and get the result under 2 MB"
}'import { MarqPDF } from "@nucleushq/marqpdf";
const marq = new MarqPDF(); // reads MARQPDF_API_KEY
const job = await marq.agent(["jan.pdf", "feb.pdf"], {
goal: "Merge these and get the result under 2 MB",
});
console.log(job.output.text);from marqpdf import MarqPDF
marq = MarqPDF() # reads MARQPDF_API_KEY
job = marq.agent(
["jan.pdf", "feb.pdf"],
goal="Merge these and get the result under 2 MB",
)
print(job["output"]["text"])Example response
{
"object": "job",
"id": "job_3Fq8nd",
"operation": "agent",
"status": "succeeded",
"created_at": "2026-10-17T09:30:00Z",
"started_at": "2026-10-17T09:30:00Z",
"finished_at": "2026-10-17T09:30:02Z",
"input": {
"files": ["file_8rX2kq", "file_9Kp4tw"],
"options": {
"goal": "Merge these and get the result under 2 MB"
}
},
"output": {
"text": "Merged the two files and compressed the result to 1.7 MB.",
"files": [
{
"object": "file",
"id": "file_5Hn3vb",
"name": "merged.pdf",
"size": 1782210,
"content_type": "application/pdf",
"pages": 4,
"created_at": "2026-10-17T09:30:02Z",
"expires_at": "2026-10-18T09:30:02Z",
"source": "job",
"job": "job_3Fq8nd",
"download_url": "https://marqpdf.com/v1/files/file_5Hn3vb/content?sig=…"
}
],
"steps": [
{ "operation": "merge" },
{
"operation": "compress",
"options": { "target_mb": 2 }
}
]
},
"error": null
}Models this account can use
GET /v1/models
marqpdf, plus a provider entry for each model key added under Account → Developers → Models, and which model each pipeline uses. Keys are never returned.
Responses
- 200 Models · ModelList
Errors 401 what they mean
curl https://marqpdf.com/v1/models \
-H "Authorization: Bearer $MARQPDF_API_KEY"import { MarqPDF } from "@nucleushq/marqpdf";
const marq = new MarqPDF(); // reads MARQPDF_API_KEY
const models = await marq.models();from marqpdf import MarqPDF
marq = MarqPDF() # reads MARQPDF_API_KEY
models = marq.models()Example response
{
"object": "list",
"data": [
{
"id": "marqpdf",
"provider": "marqpdf",
"model": "gemini/gemini-2.5-flash",
"key": "marqpdf"
},
{
"provider": "anthropic",
"label": "Anthropic (Claude)",
"key": "yours",
"examples": [
"anthropic/claude-opus-5-5",
"anthropic/claude-sonnet-5-5",
"anthropic/claude-haiku-5-5"
]
}
],
"pipelines": {
"extract": "marqpdf",
"ask": "marqpdf",
"edit": "marqpdf",
"agent": "anthropic/claude-opus-5-5"
}
}Workflows
Save a workflow
POST /v1/workflows
A named chain of 1–10 operations. Each step works on the previous step's output files (a step that only makes data passes its inputs on). Steps are checked when saved.
Request body
application/json
| Name | Type | Description |
|---|---|---|
name required | string | — |
steps required | array of Step | — |
Responses
- 201 The workflow · Workflow
Errors 400 401 what they mean
curl https://marqpdf.com/v1/workflows \
-H "Authorization: Bearer $MARQPDF_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Scan to small PDF",
"steps": [
{ "operation": "ocr" },
{
"operation": "compress",
"options": { "target_mb": 1 }
}
]
}'import { MarqPDF } from "@nucleushq/marqpdf";
const marq = new MarqPDF(); // reads MARQPDF_API_KEY
const steps = [
{ operation: "ocr" },
{ operation: "compress", options: { target_mb: 1 } },
];
const wf = await marq.workflows.create("Scan to small PDF", steps);from marqpdf import MarqPDF
marq = MarqPDF() # reads MARQPDF_API_KEY
steps = [
{"operation": "ocr"},
{"operation": "compress", "options": {"target_mb": 1}},
]
wf = marq.create_workflow("Scan to small PDF", steps)Example response
{
"object": "workflow",
"id": "wf_7Tz1cm",
"name": "Scan to small PDF",
"steps": [
{ "operation": "ocr" },
{
"operation": "compress",
"options": { "target_mb": 1 }
}
],
"created_at": "2026-10-17T09:30:02Z"
}curl https://marqpdf.com/v1/workflows \
-H "Authorization: Bearer $MARQPDF_API_KEY"import { MarqPDF } from "@nucleushq/marqpdf";
const marq = new MarqPDF(); // reads MARQPDF_API_KEY
const workflows = await marq.workflows.list();from marqpdf import MarqPDF
marq = MarqPDF() # reads MARQPDF_API_KEY
workflows = marq.workflows()Example response
{
"object": "list",
"data": [
{
"object": "workflow",
"id": "wf_7Tz1cm",
"name": "Scan to small PDF",
"steps": [
{ "operation": "ocr" },
{
"operation": "compress",
"options": { "target_mb": 1 }
}
],
"created_at": "2026-10-17T09:30:02Z"
}
]
}Get a workflow
GET /v1/workflows/{id}
Parameters
| Name | Type | Description |
|---|---|---|
id required | path · string | — |
Responses
- 200 The workflow · Workflow
Errors 401 404 what they mean
curl https://marqpdf.com/v1/workflows/wf_7Tz1cm \
-H "Authorization: Bearer $MARQPDF_API_KEY"import { MarqPDF } from "@nucleushq/marqpdf";
const marq = new MarqPDF(); // reads MARQPDF_API_KEY
const wf = await marq.workflows.get("wf_7Tz1cm");from marqpdf import MarqPDF
marq = MarqPDF() # reads MARQPDF_API_KEY
wf = marq.workflow("wf_7Tz1cm")Example response
{
"object": "workflow",
"id": "wf_7Tz1cm",
"name": "Scan to small PDF",
"steps": [
{ "operation": "ocr" },
{
"operation": "compress",
"options": { "target_mb": 1 }
}
],
"created_at": "2026-10-17T09:30:02Z"
}Delete a workflow
DELETE /v1/workflows/{id}
Parameters
| Name | Type | Description |
|---|---|---|
id required | path · string | — |
Responses
- 200 Deleted
Errors 401 404 what they mean
curl -X DELETE https://marqpdf.com/v1/workflows/wf_7Tz1cm \
-H "Authorization: Bearer $MARQPDF_API_KEY"import { MarqPDF } from "@nucleushq/marqpdf";
const marq = new MarqPDF(); // reads MARQPDF_API_KEY
await marq.workflows.delete("wf_7Tz1cm");from marqpdf import MarqPDF
marq = MarqPDF() # reads MARQPDF_API_KEY
marq.delete_workflow("wf_7Tz1cm")Example response
{
"object": "workflow",
"id": "wf_7Tz1cm",
"deleted": true
}Run a saved workflow
POST /v1/workflows/{id}/run
One job; output holds the last step's results and output.steps what each step did. Each step that succeeds counts as one operation.
Parameters
| Name | Type | Description |
|---|---|---|
id required | path · string | — |
Request body
application/json
| Name | Type | Description |
|---|---|---|
file | string | — |
files | array of strings | — |
Responses
Errors 400 401 402 404 410 429 what they mean
curl https://marqpdf.com/v1/workflows/wf_7Tz1cm/run \
-H "Authorization: Bearer $MARQPDF_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "file": "file_8rX2kq" }'import { MarqPDF } from "@nucleushq/marqpdf";
const marq = new MarqPDF(); // reads MARQPDF_API_KEY
const job = await marq.workflows.run("wf_7Tz1cm", "scan.pdf");from marqpdf import MarqPDF
marq = MarqPDF() # reads MARQPDF_API_KEY
job = marq.run_workflow("wf_7Tz1cm", "scan.pdf")Example response
{
"object": "job",
"id": "job_3Fq8nd",
"operation": "workflow",
"status": "succeeded",
"created_at": "2026-10-17T09:30:00Z",
"started_at": "2026-10-17T09:30:00Z",
"finished_at": "2026-10-17T09:30:02Z",
"input": { "files": ["file_8rX2kq"], "options": {} },
"output": {
"files": [
{
"object": "file",
"id": "file_5Hn3vb",
"name": "contract-workflow.pdf",
"size": 482113,
"content_type": "application/pdf",
"pages": 4,
"created_at": "2026-10-17T09:30:02Z",
"expires_at": "2026-10-18T09:30:02Z",
"source": "job",
"job": "job_3Fq8nd",
"download_url": "https://marqpdf.com/v1/files/file_5Hn3vb/content?sig=…"
}
]
},
"error": null
}Run steps without saving them
POST /v1/workflows/run
Request body
application/json
| Name | Type | Description |
|---|---|---|
steps required | array of Step | — |
file | string | — |
files | array of strings | — |
Responses
Errors 400 401 402 404 410 429 what they mean
curl https://marqpdf.com/v1/workflows/run \
-H "Authorization: Bearer $MARQPDF_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"steps": [
{ "operation": "ocr" },
{
"operation": "compress",
"options": { "target_mb": 1 }
}
],
"file": "file_8rX2kq"
}'import { MarqPDF } from "@nucleushq/marqpdf";
const marq = new MarqPDF(); // reads MARQPDF_API_KEY
const steps = [
{ operation: "ocr" },
{ operation: "compress", options: { target_mb: 1 } },
];
const job = await marq.workflows.runSteps(steps, "scan.pdf");from marqpdf import MarqPDF
marq = MarqPDF() # reads MARQPDF_API_KEY
steps = [
{"operation": "ocr"},
{"operation": "compress", "options": {"target_mb": 1}},
]
job = marq.run_steps(steps, "scan.pdf")Example response
{
"object": "job",
"id": "job_3Fq8nd",
"operation": "workflow",
"status": "succeeded",
"created_at": "2026-10-17T09:30:00Z",
"started_at": "2026-10-17T09:30:00Z",
"finished_at": "2026-10-17T09:30:02Z",
"input": { "files": ["file_8rX2kq"], "options": {} },
"output": {
"files": [
{
"object": "file",
"id": "file_5Hn3vb",
"name": "contract-workflow.pdf",
"size": 482113,
"content_type": "application/pdf",
"pages": 4,
"created_at": "2026-10-17T09:30:02Z",
"expires_at": "2026-10-18T09:30:02Z",
"source": "job",
"job": "job_3Fq8nd",
"download_url": "https://marqpdf.com/v1/files/file_5Hn3vb/content?sig=…"
}
]
},
"error": null
}Jobs and usage
List recent jobs
GET /v1/jobs
Parameters
| Name | Type | Description |
|---|---|---|
limit | query · integer, 1–100 | — |
Responses
- 200 Jobs, newest first
Errors 401 what they mean
curl https://marqpdf.com/v1/jobs \
-H "Authorization: Bearer $MARQPDF_API_KEY"import { MarqPDF } from "@nucleushq/marqpdf";
const marq = new MarqPDF(); // reads MARQPDF_API_KEY
const jobs = await marq.jobs.list();from marqpdf import MarqPDF
marq = MarqPDF() # reads MARQPDF_API_KEY
jobs = marq.jobs()Example response
{
"object": "list",
"data": [
{
"object": "job",
"id": "job_3Fq8nd",
"operation": "compress",
"status": "succeeded",
"created_at": "2026-10-17T09:30:00Z",
"started_at": "2026-10-17T09:30:00Z",
"finished_at": "2026-10-17T09:30:02Z",
"input": {
"files": ["file_8rX2kq"],
"options": { "target_mb": 1 }
},
"output": {
"files": [
{
"object": "file",
"id": "file_5Hn3vb",
"name": "contract-compressed.pdf",
"size": 941204,
"content_type": "application/pdf",
"pages": 4,
"created_at": "2026-10-17T09:30:02Z",
"expires_at": "2026-10-18T09:30:02Z",
"source": "job",
"job": "job_3Fq8nd",
"download_url": "https://marqpdf.com/v1/files/file_5Hn3vb/content?sig=…"
}
],
"note": "Fit 6.2 MB → 0.9 MB."
},
"error": null
}
]
}Get a job
GET /v1/jobs/{id}
Parameters
| Name | Type | Description |
|---|---|---|
id required | path · string | — |
Responses
- 200 The job · Job
Errors 401 404 what they mean
curl https://marqpdf.com/v1/jobs/job_3Fq8nd \
-H "Authorization: Bearer $MARQPDF_API_KEY"import { MarqPDF } from "@nucleushq/marqpdf";
const marq = new MarqPDF(); // reads MARQPDF_API_KEY
const job = await marq.jobs.get("job_3Fq8nd");
// or wait for it to finish:
const done = await marq.jobs.wait("job_3Fq8nd");from marqpdf import MarqPDF
marq = MarqPDF() # reads MARQPDF_API_KEY
job = marq.job("job_3Fq8nd")
# or wait for it to finish:
done = marq.wait("job_3Fq8nd")Example response
{
"object": "job",
"id": "job_3Fq8nd",
"operation": "compress",
"status": "succeeded",
"created_at": "2026-10-17T09:30:00Z",
"started_at": "2026-10-17T09:30:00Z",
"finished_at": "2026-10-17T09:30:02Z",
"input": {
"files": ["file_8rX2kq"],
"options": { "target_mb": 1 }
},
"output": {
"files": [
{
"object": "file",
"id": "file_5Hn3vb",
"name": "contract-compressed.pdf",
"size": 941204,
"content_type": "application/pdf",
"pages": 4,
"created_at": "2026-10-17T09:30:02Z",
"expires_at": "2026-10-18T09:30:02Z",
"source": "job",
"job": "job_3Fq8nd",
"download_url": "https://marqpdf.com/v1/files/file_5Hn3vb/content?sig=…"
}
],
"note": "Fit 6.2 MB → 0.9 MB."
},
"error": null
}curl https://marqpdf.com/v1/usage \
-H "Authorization: Bearer $MARQPDF_API_KEY"import { MarqPDF } from "@nucleushq/marqpdf";
const marq = new MarqPDF(); // reads MARQPDF_API_KEY
const usage = await marq.usage();from marqpdf import MarqPDF
marq = MarqPDF() # reads MARQPDF_API_KEY
usage = marq.usage()Example response
{
"object": "usage",
"month": "2026-10",
"operations": 37,
"limit": 100,
"plan": "free"
}Schemas
Error
| Name | Type | Description |
|---|---|---|
error required | object | — |
job | Job | — |
File
| Name | Type | Description |
|---|---|---|
object required | string | — |
id required | string | — |
name required | string | — |
size required | integer | — |
content_type required | string | — |
pages | integer | — |
created_at required | string (date-time) | — |
expires_at required | string (date-time) | — |
source required | one of "upload", "url", "job" | — |
job | string | — |
download_url required | string (uri) | Works without a key for an hour. |
Job
| Name | Type | Description |
|---|---|---|
object required | string | — |
id required | string | — |
operation required | string | — |
status required | one of "queued", "running", "succeeded", "failed" | — |
created_at required | string (date-time) | — |
started_at | string, nullable | — |
finished_at | string, nullable | — |
input | object | — |
output | object, nullable | — |
error | object, nullable | — |
webhook_url | string, nullable | — |
webhook | object, nullable | — |
ModelList
| Name | Type | Description |
|---|---|---|
object | string | — |
data | array of objects | — |
pipelines | object | — |
Step
| Name | Type | Description |
|---|---|---|
operation required | string | Example "compress". |
options | object | Example {"target_mb":1}. |
Workflow
| Name | Type | Description |
|---|---|---|
object | string | — |
id | string | — |
name | string | — |
steps | array of Step | — |
created_at | string (date-time) | — |
Usage
| Name | Type | Description |
|---|---|---|
object | string | — |
month | string | Example "2026-10". |
operations | integer | — |
limit | integer, nullable | null = unlimited |
plan | one of "free", "week", "pro", "team" | — |