Edit text inside existing PDFs: replace, delete, redact, insert, fill forms; original fonts kept.
Maintain this server? Claim it
Call this server's tools from your browser. Nothing to install.
Requests go through mcptry because browsers can't call most MCP servers directly. Header values are sent to the server for that request only and are never stored or logged.
Tool list from our last check. Connect live to refresh it.
Apply the same edits to one template PDF once per record, producing one output document per record — a mail merge over an existing PDF. Returns a `job_id`: call `pdf_job` with it for the per-record results. To change text on one page only, set `scope.pages`; to pick one of several identical matches, use `scope.after` with nearby text or `scope.occurrence`. Use `op: 'redact'` when the text must be unrecoverable.
The PDF: an absolute (or ~) local path, which this server uploads for you, an https:// URL the API fetches, or an `f_…` file id from an earlier call. Used as the template for every record.
The edits applied to each record, with `{field}` placeholders filled from it.
One object per output document.
A CSV or JSONL file of records, instead of `records`.
This tool isn't marked read-only, so it may change something on the server or in an account you connected.
Apply the same edits to one template PDF once per record, producing one output document per record — a mail merge over an existing PDF. Returns a `job_id`: call `pdf_job` with it for the per-record results. To change text on one page only, set `scope.pages`; to pick one of several identical matches, use `scope.after` with nearby text or `scope.occurrence`. Use `op: 'redact'` when the text must be unrecoverable.
| Argument | Type | Description |
|---|---|---|
| file* | string | The PDF: an absolute (or ~) local path, which this server uploads for you, an https:// URL the API fetches, or an `f_…` file id from an earlier call. Used as the template for every record. |
| edits* | array | The edits applied to each record, with `{field}` placeholders filled from it. |
| options | object | |
| records | array | One object per output document. |
| password | string | |
| records_url | string | A CSV or JSONL file of records, instead of `records`. |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"file",
"edits"
],
"properties": {
"file": {
"type": "string",
"minLength": 1,
"description": "The PDF: an absolute (or ~) local path, which this server uploads for you, an https:// URL the API fetches, or an `f_…` file id from an earlier call. Used as the template for every record."
},
"edits": {
"type": "array",
"items": {
"type": "object",
"properties": {
"at": {
"anyOf": [
{
"type": "object",
"required": [
"page"
],
"properties": {
"x": {
"type": [
"number",
"null"
],
"description": "Baseline origin x. Points from the page's left edge, or a fraction of the page width when units='fraction'."
},
"y": {
"type": [
"number",
"null"
],
"description": "Baseline origin y: where the first line sits, measured from the page's TOP edge (PDF viewer convention, as every bbox and span origin in this API). A fraction of the page height when units='fraction'."
},
"page": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 1
},
"after": {
"type": [
"string",
"null"
],
"description": "Anchor text to insert after; the first occurrence on the page wins, and the style is inherited from it."
},
"units": {
"enum": [
"pt",
"fraction"
],
"type": "string",
"description": "(default: \"pt\")"
},
"placement": {
"enum": [
"inline",
"below"
],
"type": "string",
"description": "With `after`: 'inline' writes one space past the anchor's right edge on its own baseline; 'below' writes at the anchor line's left edge, one leading down. (default: \"inline\")"
}
},
"additionalProperties": false
},
{
"type": "null"
}
]
},
"op": {
"enum": [
"replace",
"delete",
"redact",
"insert",
"form"
],
"type": "string",
"description": "(default: \"replace\")"
},
"fit": {
"enum": [
"auto",
"reflow",
"strict"
],
"type": "string",
"description": "(default: \"auto\")"
},
"find": {
"type": [
"string",
"null"
]
},
"text": {
"type": [
"string",
"null"
]
},
"match": {
"type": "object",
"properties": {
"mode": {
"enum": [
"exact",
"icase",
"word",
"regex"
],
"type": "string",
"description": "(default: \"exact\")"
},
"normalize": {
"type": "boolean",
"description": "(default: true)"
},
"across_lines": {
"type": "boolean",
"description": "(default: false)"
}
},
"additionalProperties": false
},
"scope": {
"type": "object",
"properties": {
"after": {
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "null"
}
]
},
"pages": {
"anyOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
}
}
],
"description": "(default: \"all\")"
},
"before": {
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "null"
}
]
},
"layers": {
"type": "array",
"items": {
"enum": [
"content",
"forms",
"annotations"
],
"type": "string"
},
"description": "Page layers to match in. All three are implemented; a match on the 'forms' layer is applied to the field's value, not to the page."
},
"region": {
"anyOf": [
{
"type": "object",
"required": [
"x0",
"x1",
"y0",
"y1"
],
"properties": {
"x0": {
"type": "number"
},
"x1": {
"type": "number"
},
"y0": {
"type": "number"
},
"y1": {
"type": "number"
},
"units": {
"enum": [
"fraction",
"pt"
],
"type": "string",
"description": "(default: \"fraction\")"
}
},
"additionalProperties": false
},
{
"type": "null"
}
]
},
"span_ids": {
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "null"
}
]
},
"occurrence": {
"anyOf": [
{
"enum": [
"all",
"first",
"last"
],
"type": "string"
},
{
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
}
],
"description": "(default: \"all\")"
},
"page_labels": {
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "null"
}
]
}
},
"additionalProperties": false
},
"style": {
"anyOf": [
{
"type": "object",
"properties": {
"font": {
"type": [
"string",
"null"
]
},
"size": {
"anyOf": [
{
"type": "number",
"maximum": 200,
"minimum": 1
},
{
"type": "null"
}
]
},
"color": {
"anyOf": [
{
"type": "string",
"pattern": "^#[0-9a-fA-F]{6}$"
},
{
"type": "null"
}
]
}
},
"additionalProperties": false
},
{
"type": "null"
}
]
},
"fields": {
"anyOf": [
{
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
{
"type": "null"
}
]
},
"flatten": {
"type": "boolean",
"description": "(default: false)"
},
"replace": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
},
"maxItems": 200,
"minItems": 1,
"description": "The edits applied to each record, with `{field}` placeholders filled from it."
},
"options": {
"type": "object",
"properties": {
"ocr": {
"enum": [
"auto",
"on",
"off"
],
"type": "string",
"description": "(default: \"auto\")"
},
"font": {
"type": "object",
"properties": {
"policy": {
"enum": [
"auto",
"metric_only",
"exact_only"
],
"type": "string",
"description": "(default: \"auto\")"
}
},
"additionalProperties": false
},
"tidy": {
"type": "boolean",
"description": "(default: true)"
},
"async": {
"type": "boolean",
"description": "(default: false)"
},
"atomic": {
"type": "boolean",
"description": "(default: true)"
},
"output": {
"enum": [
"zip",
"files"
],
"type": "string",
"description": "(default: \"files\")"
},
"retain": {
"type": "boolean",
"description": "(default: true)"
},
"strict": {
"type": "boolean",
"description": "(default: false)"
},
"dry_run": {
"type": "boolean",
"description": "(default: false)"
},
"preview": {
"enum": [
"changes",
"pages",
"none"
],
"type": "string",
"description": "(default: \"changes\")"
},
"filename": {
"type": [
"string",
"null"
]
},
"ocr_lang": {
"type": "array",
"items": {
"enum": [
"zh",
"en"
],
"type": "string"
}
},
"fail_fast": {
"type": "boolean",
"description": "(default: false)"
},
"watermark": {
"enum": [
"auto",
"on",
"off"
],
"type": "string",
"description": "(default: \"auto\")"
},
"webhook_url": {
"anyOf": [
{
"type": "string",
"format": "uri",
"maxLength": 2083,
"minLength": 1
},
{
"type": "null"
}
],
"description": "Called with POST when the job finishes: the body is the job's result (what GET /v1/jobs/{id} returns) plus `event` (job.done, job.failed, batch.done ...) and `job_id`, signed in `X-Signature` (hex HMAC-SHA256 of the raw body with the account's webhook secret). Non-2xx answers are retried after 1, 5 and 30 minutes. https only."
},
"redact_style": {
"enum": [
"box",
"text"
],
"type": "string",
"description": "(default: \"box\")"
},
"break_signature": {
"type": "boolean",
"description": "(default: false)"
},
"redact_metadata": {
"type": "boolean",
"description": "(default: false)"
},
"redact_verify_ocr": {
"type": "boolean",
"description": "(default: true)"
},
"collateral_threshold": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0,
"description": "(default: 8)"
}
},
"additionalProperties": false
},
"records": {
"type": "array",
"items": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"description": "One object per output document."
},
"password": {
"type": "string"
},
"records_url": {
"type": "string",
"format": "uri",
"description": "A CSV or JSONL file of records, instead of `records`."
}
},
"additionalProperties": false
}Edit the text of an existing PDF in place: replace, delete, redact, insert, or fill form fields, keeping the original layout, fonts and everything you did not point at. `edits[]` is taken exactly as the API takes it (`op`, `scope`, `match`, `fit`), and the result carries up to 3 before/after previews of the pages that changed. To change text on one page only, set `scope.pages`; to pick one of several identical matches, use `scope.after` with nearby text or `scope.occurrence`. Use `op: 'redact'` when the text must be unrecoverable.
| Argument | Type | Description |
|---|---|---|
| file* | string | The PDF: an absolute (or ~) local path, which this server uploads for you, an https:// URL the API fetches, or an `f_…` file id from an earlier call. |
| edits* | array | The edits to apply, in order, exactly as `POST /v1/edit` takes them. |
| options | object | |
| password | string | The document open password, when it has one. |
| output_path | string | Where to write the edited PDF (stdio mode only). Default: `<name>.edited.pdf` next to the input, or `<name>.edited.2.pdf`, `.3.pdf`, ... when that exists; an earlier result is never overwritten. |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"file",
"edits"
],
"properties": {
"file": {
"type": "string",
"minLength": 1,
"description": "The PDF: an absolute (or ~) local path, which this server uploads for you, an https:// URL the API fetches, or an `f_…` file id from an earlier call."
},
"edits": {
"type": "array",
"items": {
"type": "object",
"properties": {
"at": {
"anyOf": [
{
"type": "object",
"required": [
"page"
],
"properties": {
"x": {
"type": [
"number",
"null"
],
"description": "Baseline origin x. Points from the page's left edge, or a fraction of the page width when units='fraction'."
},
"y": {
"type": [
"number",
"null"
],
"description": "Baseline origin y: where the first line sits, measured from the page's TOP edge (PDF viewer convention, as every bbox and span origin in this API). A fraction of the page height when units='fraction'."
},
"page": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 1
},
"after": {
"type": [
"string",
"null"
],
"description": "Anchor text to insert after; the first occurrence on the page wins, and the style is inherited from it."
},
"units": {
"enum": [
"pt",
"fraction"
],
"type": "string",
"description": "(default: \"pt\")"
},
"placement": {
"enum": [
"inline",
"below"
],
"type": "string",
"description": "With `after`: 'inline' writes one space past the anchor's right edge on its own baseline; 'below' writes at the anchor line's left edge, one leading down. (default: \"inline\")"
}
},
"additionalProperties": false
},
{
"type": "null"
}
]
},
"op": {
"enum": [
"replace",
"delete",
"redact",
"insert",
"form"
],
"type": "string",
"description": "(default: \"replace\")"
},
"fit": {
"enum": [
"auto",
"reflow",
"strict"
],
"type": "string",
"description": "(default: \"auto\")"
},
"find": {
"type": [
"string",
"null"
]
},
"text": {
"type": [
"string",
"null"
]
},
"match": {
"type": "object",
"properties": {
"mode": {
"enum": [
"exact",
"icase",
"word",
"regex"
],
"type": "string",
"description": "(default: \"exact\")"
},
"normalize": {
"type": "boolean",
"description": "(default: true)"
},
"across_lines": {
"type": "boolean",
"description": "(default: false)"
}
},
"additionalProperties": false
},
"scope": {
"type": "object",
"properties": {
"after": {
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "null"
}
]
},
"pages": {
"anyOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
}
}
],
"description": "(default: \"all\")"
},
"before": {
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "null"
}
]
},
"layers": {
"type": "array",
"items": {
"enum": [
"content",
"forms",
"annotations"
],
"type": "string"
},
"description": "Page layers to match in. All three are implemented; a match on the 'forms' layer is applied to the field's value, not to the page."
},
"region": {
"anyOf": [
{
"type": "object",
"required": [
"x0",
"x1",
"y0",
"y1"
],
"properties": {
"x0": {
"type": "number"
},
"x1": {
"type": "number"
},
"y0": {
"type": "number"
},
"y1": {
"type": "number"
},
"units": {
"enum": [
"fraction",
"pt"
],
"type": "string",
"description": "(default: \"fraction\")"
}
},
"additionalProperties": false
},
{
"type": "null"
}
]
},
"span_ids": {
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "null"
}
]
},
"occurrence": {
"anyOf": [
{
"enum": [
"all",
"first",
"last"
],
"type": "string"
},
{
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
}
],
"description": "(default: \"all\")"
},
"page_labels": {
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "null"
}
]
}
},
"additionalProperties": false
},
"style": {
"anyOf": [
{
"type": "object",
"properties": {
"font": {
"type": [
"string",
"null"
]
},
"size": {
"anyOf": [
{
"type": "number",
"maximum": 200,
"minimum": 1
},
{
"type": "null"
}
]
},
"color": {
"anyOf": [
{
"type": "string",
"pattern": "^#[0-9a-fA-F]{6}$"
},
{
"type": "null"
}
]
}
},
"additionalProperties": false
},
{
"type": "null"
}
]
},
"fields": {
"anyOf": [
{
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
{
"type": "null"
}
]
},
"flatten": {
"type": "boolean",
"description": "(default: false)"
},
"replace": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
},
"maxItems": 200,
"minItems": 1,
"description": "The edits to apply, in order, exactly as `POST /v1/edit` takes them."
},
"options": {
"type": "object",
"properties": {
"ocr": {
"enum": [
"auto",
"on",
"off"
],
"type": "string",
"description": "(default: \"auto\")"
},
"font": {
"type": "object",
"properties": {
"policy": {
"enum": [
"auto",
"metric_only",
"exact_only"
],
"type": "string",
"description": "(default: \"auto\")"
}
},
"additionalProperties": false
},
"tidy": {
"type": "boolean",
"description": "(default: true)"
},
"async": {
"type": "boolean",
"description": "(default: false)"
},
"atomic": {
"type": "boolean",
"description": "(default: true)"
},
"retain": {
"type": "boolean",
"description": "(default: true)"
},
"strict": {
"type": "boolean",
"description": "(default: false)"
},
"dry_run": {
"type": "boolean",
"description": "(default: false)"
},
"preview": {
"enum": [
"changes",
"pages",
"none"
],
"type": "string",
"description": "(default: \"changes\")"
},
"ocr_lang": {
"type": "array",
"items": {
"enum": [
"zh",
"en"
],
"type": "string"
}
},
"watermark": {
"enum": [
"auto",
"on",
"off"
],
"type": "string",
"description": "(default: \"auto\")"
},
"webhook_url": {
"anyOf": [
{
"type": "string",
"format": "uri",
"maxLength": 2083,
"minLength": 1
},
{
"type": "null"
}
],
"description": "Called with POST when the job finishes: the body is the job's result (what GET /v1/jobs/{id} returns) plus `event` (job.done, job.failed, batch.done ...) and `job_id`, signed in `X-Signature` (hex HMAC-SHA256 of the raw body with the account's webhook secret). Non-2xx answers are retried after 1, 5 and 30 minutes. https only."
},
"redact_style": {
"enum": [
"box",
"text"
],
"type": "string",
"description": "(default: \"box\")"
},
"break_signature": {
"type": "boolean",
"description": "(default: false)"
},
"redact_metadata": {
"type": "boolean",
"description": "(default: false)"
},
"redact_verify_ocr": {
"type": "boolean",
"description": "(default: true)"
},
"collateral_threshold": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0,
"description": "(default: 8)"
}
},
"additionalProperties": false
},
"password": {
"type": "string",
"description": "The document open password, when it has one."
},
"output_path": {
"type": "string",
"description": "Where to write the edited PDF (stdio mode only). Default: `<name>.edited.pdf` next to the input, or `<name>.edited.2.pdf`, `.3.pdf`, ... when that exists; an earlier result is never overwritten."
}
},
"additionalProperties": false
}Read the text of a PDF: every span with its page, position, font and span id, optionally filtered by `query` (the same four `match` modes an edit uses) and `pages`, over the `content`, `forms` and `annotations` layers. Paginated: pass the returned `next_cursor` back as `cursor` for the next page. Use it before `pdf_edit` to check what a document actually says and to pick the `scope` that selects the match you mean.
| Argument | Type | Description |
|---|---|---|
| file* | string | The PDF: an absolute (or ~) local path, which this server uploads for you, an https:// URL the API fetches, or an `f_…` file id from an earlier call. |
| limit | integer | (default: 200) |
| match | object | |
| pages | any | |
| query | string | null | |
| cursor | string | null | |
| layers | array | |
| password | string |
The result of an async edit or a batch, by `job_id`: the same body `pdf_edit` returns once the job is done, plus `progress` while it is still queued or running, and `records` for a batch.
| Argument | Type | Description |
|---|---|---|
| job_id* | string | The `job_id` an async edit or a batch returned. |
| output_path | string | Where to write the finished document (stdio mode, single-document jobs only). |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"job_id"
],
"properties": {
"job_id": {
"type": "string",
"minLength": 1,
"description": "The `job_id` an async edit or a batch returned."
},
"output_path": {
"type": "string",
"description": "Where to write the finished document (stdio mode, single-document jobs only)."
}
},
"additionalProperties": false
}Render one page of a PDF as a PNG image block, optionally cropped to `region` and with rectangles drawn over `highlight`. Use it to see a page before editing it, or to check the result afterwards.
| Argument | Type | Description |
|---|---|---|
| file* | string | The PDF: an absolute (or ~) local path, which this server uploads for you, an https:// URL the API fetches, or an `f_…` file id from an earlier call. |
| page | integer | (default: 1) |
| scale | any | (default: 2) |
| region | any | |
| password | string | |
| highlight | any |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"file"
],
"properties": {
"file": {
"type": "string",
"minLength": 1,
"description": "The PDF: an absolute (or ~) local path, which this server uploads for you, an https:// URL the API fetches, or an `f_…` file id from an earlier call."
},
"page": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 1,
"description": "(default: 1)"
},
"scale": {
"anyOf": [
{
"type": "number",
"const": 1
},
{
"type": "number",
"const": 2
},
{
"type": "number",
"const": 3
}
],
"description": "(default: 2)"
},
"region": {
"anyOf": [
{
"type": "array",
"items": {
"type": "number"
},
"maxItems": 4,
"minItems": 4
},
{
"type": "null"
}
]
},
"password": {
"type": "string"
},
"highlight": {
"anyOf": [
{
"type": "array",
"items": {
"type": "array",
"items": {
"type": "number"
}
},
"maxItems": 200
},
{
"type": "null"
}
]
}
},
"additionalProperties": false
}{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"file"
],
"properties": {
"file": {
"type": "string",
"minLength": 1,
"description": "The PDF: an absolute (or ~) local path, which this server uploads for you, an https:// URL the API fetches, or an `f_…` file id from an earlier call."
},
"limit": {
"type": "integer",
"maximum": 1000,
"minimum": 1,
"description": "(default: 200)"
},
"match": {
"type": "object",
"properties": {
"mode": {
"enum": [
"exact",
"icase",
"word",
"regex"
],
"type": "string",
"description": "(default: \"exact\")"
},
"normalize": {
"type": "boolean",
"description": "(default: true)"
},
"across_lines": {
"type": "boolean",
"description": "(default: false)"
}
},
"additionalProperties": false
},
"pages": {
"anyOf": [
{
"anyOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
}
}
]
},
{
"type": "null"
}
]
},
"query": {
"type": [
"string",
"null"
]
},
"cursor": {
"type": [
"string",
"null"
]
},
"layers": {
"type": "array",
"items": {
"enum": [
"content",
"forms",
"annotations"
],
"type": "string"
},
"minItems": 1
},
"password": {
"type": "string"
}
},
"additionalProperties": false
}