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=…" } ] } }

# 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

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.

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):

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(","))
    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."
  }
}
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.

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:

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.

Questions, or an operation you need that isn't here? Email support@marqpdf.com.

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

NameTypeDescription
namequery · stringFile name, for a raw-body upload.

Request body

multipart/form-data

NameTypeDescription
file requiredfile—
namestring—

application/json

NameTypeDescription
url requiredstring (uri)—
namestring—

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.pdf
import { 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

NameTypeDescription
limitquery · 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

NameTypeDescription
id requiredpath · 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

NameTypeDescription
id requiredpath · 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

NameTypeDescription
id requiredpath · 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.pdf
import { 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

NameTypeDescription
file requiredstringA file id from POST /files.
target_mbnumber, 0.05–500Fit under this size, at the best quality that does. Example 1.
levelone 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

NameTypeDescription
files requiredarray of stringsFile 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

NameTypeDescription
file requiredstringA file id from POST /files.
everyinteger, ≥ 1Default 1.
rangesarray of pairs of integersExample [[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

NameTypeDescription
file requiredstringA file id from POST /files.
angle requiredone of 90, 180, 270—
pagesarray of integersPage 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

NameTypeDescription
file requiredstringA file id from POST /files.
pages requiredarray of integersPage 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

NameTypeDescription
file requiredstringA file id from POST /files.
pages requiredarray of integersPage 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

NameTypeDescription
file requiredstringA file id from POST /files.
positionone 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

NameTypeDescription
file requiredstringA file id from POST /files.
text requiredstring, 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

NameTypeDescription
file requiredstringA file id from POST /files.
password requiredstring—

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

NameTypeDescription
file requiredstringA file id from POST /files.
passwordstring—

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

NameTypeDescription
file requiredstringA 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

NameTypeDescription
file requiredstringA file id from POST /files.
to requiredone 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

NameTypeDescription
files requiredarray of stringsFile 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

NameTypeDescription
files requiredarray of stringsFile 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

NameTypeDescription
file requiredstringA file id from POST /files.
ocrone 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

NameTypeDescription
file requiredstringA file id from POST /files.
fieldsarray of strings or objectsWhat to extract: names, or {name, type, description}. Example ["invoice_number",{"name":"total","type":"number"},{"name":"due_date","type":"date"}].
schemaobjectInstead of fields: a JSON Schema for the result (an object).
instructionsstringExtra guidance, e.g. 'amounts in AED'.
modelstring"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

NameTypeDescription
file requiredstringA file id from POST /files.
question requiredstring—
instructionsstringExtra guidance, e.g. 'answer in one sentence'.
modelstring"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

NameTypeDescription
files requiredarray of stringsFile ids, in order.
goal requiredstringWhat to do, e.g. 'make these scans searchable, merge them in date order and get the result under 2 MB'.
max_stepsinteger, 1–15Model turns before it stops. Default 8.
modelstring"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

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

NameTypeDescription
name requiredstring—
steps requiredarray of Step—

Responses

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"
}

List workflows

GET /v1/workflows

Responses

  • 200 Workflows

Errors 401 what they mean

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

NameTypeDescription
id requiredpath · string—

Responses

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

NameTypeDescription
id requiredpath · 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

NameTypeDescription
id requiredpath · string—

Request body

application/json

NameTypeDescription
filestring—
filesarray of strings—

Responses

  • 200 Finished · Job
  • 202 Still running · Job

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

NameTypeDescription
steps requiredarray of Step—
filestring—
filesarray of strings—

Responses

  • 200 Finished · Job
  • 202 Still running · Job

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

NameTypeDescription
limitquery · 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

NameTypeDescription
id requiredpath · 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
}

This month's operations

GET /v1/usage

Responses

Errors 401 what they mean

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

NameTypeDescription
error requiredobject—
jobJob—

File

NameTypeDescription
object requiredstring—
id requiredstring—
name requiredstring—
size requiredinteger—
content_type requiredstring—
pagesinteger—
created_at requiredstring (date-time)—
expires_at requiredstring (date-time)—
source requiredone of "upload", "url", "job"—
jobstring—
download_url requiredstring (uri)Works without a key for an hour.

Job

NameTypeDescription
object requiredstring—
id requiredstring—
operation requiredstring—
status requiredone of "queued", "running", "succeeded", "failed"—
created_at requiredstring (date-time)—
started_atstring, nullable—
finished_atstring, nullable—
inputobject—
outputobject, nullable—
errorobject, nullable—
webhook_urlstring, nullable—
webhookobject, nullable—

ModelList

NameTypeDescription
objectstring—
dataarray of objects—
pipelinesobject—

Step

NameTypeDescription
operation requiredstringExample "compress".
optionsobjectExample {"target_mb":1}.

Workflow

NameTypeDescription
objectstring—
idstring—
namestring—
stepsarray of Step—
created_atstring (date-time)—

Usage

NameTypeDescription
objectstring—
monthstringExample "2026-10".
operationsinteger—
limitinteger, nullablenull = unlimited
planone of "free", "week", "pro", "team"—
📄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”.