# Document Compare API: Redline PDF, Word & Text Diff (`jumblemog/document-compare`) Actor

Compare two versions of a PDF, Word, HTML or text document: get a redline PDF, a Word file with tracked changes and every change listed, with edited amounts, dates and shall/may flagged.

- **URL**: https://apify.com/jumblemog/document-compare.md
- **Developed by:** [Jumble Mog](https://apify.com/jumblemog) (community)
- **Categories:** AI, Automation, Developer tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Document Compare API: Redline PDF, Word & Text Diff

**Compare two versions of a document and see exactly what changed.** Give the original and the revised version (PDF, Word .docx, HTML, Markdown or plain text; a link, base64 or the text itself) and you get a redline back: a PDF with insertions underlined in blue and deletions struck through in red, a Word file with real tracked changes you can accept or reject, an HTML page, and every change listed as data.

- **Word-level redline** of the whole document, or of the changed paragraphs only. Changed paragraphs are highlighted, moved paragraphs are shown in green at their old and new place, and every change in the PDF and HTML links to its row in the change list.
- **Word file with tracked changes**: the revised text with each change as a Word revision. Accept All gives the revised document, Reject All the original, so you can review it in Word, Google Docs or LibreOffice like a colleague's edits.
- **A change list for code and AI agents**: each change's type (inserted, deleted, replaced, moved), section ("7. Notices"), page in each version, the old and new wording, and its context marked `[-old-]{+new+}`.
- **Flags on the edits that matter**: changed numbers, money, dates, negations (*not*, *unless*, *except*) and obligation words (*shall*, *must*, *may*), so a reviewer sees "$1,000 → $1,250" and "is liable → is not liable" first.
- **Compare across formats**: a PDF against a Word file works. PDF line breaks, hyphenation, repeated page headers and footers and page numbers are ignored, so only real wording changes count. Optionally ignore case, punctuation or renumbered clauses.

It's made for:

- Contract and policy review: what did the other side change in their markup, what changed between this year's terms and last year's, which clauses moved.
- AI agents and assistants that need the differences between two documents as data instead of reading both in full.
- Compliance, regulatory and publishing work: version-to-version diffs of filings, standards, manuals, terms of service and privacy policies.
- Automations in Zapier, Make or n8n that redline every new version of a document and send the PDF on.

![The first page of a redline made by this Actor, comparing the GNU LGPL 2.0 with the LGPL 2.1: a summary (78 changes, 646 words inserted and 447 deleted, 87.3% the same, flagged number, date, negation and obligation changes), then the license text with insertions underlined in blue, deletions struck through in red and changed paragraphs highlighted](https://api.apify.com/v2/key-value-stores/Km5b4PG3kYHdZSb1E/records/document-compare-example.png?signature=lZ3bTs6ADpH2lshszNAH)

### How to use it

1. Put the **original document** and the **revised document** in the form: a link, the text itself, or base64. Or **upload two files** (the original first).
2. Optionally choose what counts as a change (ignore case, punctuation or list numbering), whether the redline shows the whole document or only the changes, and which files to make.
3. Click **Start**. Each pair is a row in the results with its summary, the redline's first page and links to the PDF, Word, HTML and CSV files.

To compare several pairs in one run, add them under **More pairs**.

#### What you can send

```json
{
  "original": "https://example.com/contracts/msa-v1.pdf",
  "revised": "https://example.com/contracts/msa-v2.docx",
  "ignoreNumbering": true,
  "show": "changes",
  "outputs": ["pdf", "docx"]
}
```

| Field | What it takes |
|---|---|
| `original`, `revised` | A https link (Google Drive, Google Docs and Dropbox share links work), a `data:` URI, base64 of a PDF or .docx, or the text itself. In **More pairs** also accepted as `old`/`new`, `before`/`after`, `v1`/`v2`, or one line `"link1 vs link2"`. |
| `uploads` | Two uploaded files: the original first, then the revised. |
| `ignoreCase`, `ignorePunctuation`, `ignoreNumbering` | `true` to leave capitalization, punctuation, or the clause number at the start of a paragraph (1., 2.3, (a), iv.) out of the comparison. All off by default. |
| `show` | `all` (the whole document, default) or `changes` (only changed paragraphs, with one unchanged paragraph either side) in the PDF and HTML. |
| `outputs` | Any of `pdf`, `docx`, `html`, `csv` (all by default). |
| `pageSize` | `letter` (default) or `a4` for the PDF. |
| `author` | The name Word shows on each tracked change. |
| `title` | A title for the result (in **More pairs**). |

### Examples

- LGPL 2.0 against LGPL 2.1 (two text files) → 78 changes: 25 paragraphs changed, 7 inserted, 7 deleted; 646 words inserted and 447 deleted (87.3% the same), including "GNU ~~LIBRARY~~ <ins>LESSER</ins> GENERAL PUBLIC LICENSE" and "Version ~~2, June 1991~~ <ins>2.1, February 1999</ins>".
- The MIT license against the X11 license → 6 changes: the copyright line replaced, "THE ~~AUTHORS OR COPYRIGHT HOLDERS~~ <ins>X CONSORTIUM</ins>", and two paragraphs added (one flagged for *shall not*).
- The IRS Form W-9 of 2018 against the 2024 revision (two PDFs) → 227 changes, with the new "Before you begin" text and the reworded line 1 and 3 instructions.
- A services agreement where the fee went from $1,000 to $1,250, payment from 30 to 45 days, the Supplier became "not liable", clause 3 moved below clause 4 and a notices clause was added → 10 changes, with the fee flagged *number, money*, the liability edit flagged *negation*, and the move shown once with the edit inside it.

### How-to examples

Each example below is a complete input. The result line is what the run says in `summary`.

#### How to compare two versions of a contract

Paste the two versions as text, or give links to the files:

```json
{
  "original": "Services Agreement\n\n1. Fees. The Customer shall pay $1,000 per month within 30 days.\n\n2. Term. This Agreement lasts for one year.\n\n3. Law. New York law governs this Agreement.",
  "revised": "Services Agreement\n\n1. Fees. The Customer shall pay $1,250 per month within 45 days.\n\n2. Term. This Agreement lasts for two years and renews unless cancelled.\n\n3. Law. Delaware law governs this Agreement.\n\n4. Notices. Notices must be sent by email."
}
```

Result: "5 changes from original text to revised text: paragraphs 3 changed, 1 inserted, 0 deleted; 17 words inserted and 6 deleted (67.6% the same). Check 3 number changes, 1 money change, 1 negation, 1 shall/may/must change." The first change in `changes` is `"1. Fees. The Customer shall pay [-$1,000-]{+$1,250+} per month..."`, flagged `number` and `money`.

#### How to compare two PDFs and see only what changed

```json
{"original": "https://www.irs.gov/pub/irs-prior/fw9--2018.pdf", "revised": "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
 "show": "changes", "outputs": ["pdf"]}
```

Result: "227 changes from fw9--2018.pdf to fw9.pdf: paragraphs 75 changed, 34 inserted, 23 deleted; 1,912 words inserted and 1,465 deleted (72.9% the same)..." The redline PDF shows only the changed paragraphs, each with one unchanged paragraph around it. Page headers, footers and PDF line breaks don't count as changes.

#### How to get a Word file with tracked changes from two versions

Set `outputs` to `["docx"]` and `author` to the name reviewers should see:

```json
{"original": "https://example.com/msa-v1.docx", "revised": "https://example.com/msa-v2.pdf", "outputs": ["docx"], "author": "Legal"}
```

`docxUrl` is the revised document with each change as a Word revision. In Word, Accept All gives the revised version and Reject All gives the original. A PDF and a Word file can be compared with each other.

#### How to let an AI agent compare documents

Add this Actor to the [Apify MCP server](https://mcp.apify.com) with `https://mcp.apify.com/?tools=jumblemog/document-compare`. The agent gets the changes as data (`type`, `section`, the old and new wording, and flags for money, dates, negations and *shall/must/may*) instead of reading both documents in full, and can give the user `pdfUrl` for the redline.

### FAQ

**Which formats can it compare?** PDF, Word .docx, HTML, Markdown and plain text, in any mix, such as a PDF against a Word file.

**Does it work on scanned PDFs?** No. A scanned PDF is a picture with no text to compare. Run it through OCR first.

**Can it ignore renumbered clauses?** Yes. `ignoreNumbering` leaves clause numbers such as "2.3" or "(a)" out, and `ignoreCase` and `ignorePunctuation` leave those out too.

**Does it show moved paragraphs?** Yes. A moved paragraph is shown once, in green, at its old and new place, with any edits inside it.

**Does it work with Zapier, Make or n8n?** Yes. Call the Actor with its Apify app or an HTTP request and send the redline PDF on to email or Slack.

### Output

One dataset item per pair, also saved together in the run's `OUTPUT` record:

```json
{
  "request": 1, "status": "ok", "type": "comparison",
  "title": "Redline: msa-v1.pdf vs msa-v2.docx",
  "summary": "10 changes from msa-v1.pdf to msa-v2.docx: paragraphs 4 changed, 1 inserted, 0 deleted, 1 moved; 24 words inserted and 17 deleted (80.4% the same). Check 3 number changes, 1 money change, 1 negation, 1 shall/may/must change.",
  "documents": {"original": {"name": "msa-v1.pdf", "format": "pdf", "pages": 2, "paragraphs": 7, "words": 101}, "revised": {"name": "msa-v2.docx", "format": "docx", "pages": null, "paragraphs": 8, "words": 108}},
  "stats": {"unchanged": 2, "changed": 4, "inserted": 1, "deleted": 0, "moved": 1, "changes": 10, "wordsInserted": 24, "wordsDeleted": 17, "similarityPct": 80.4, "flagged": {"number": 3, "money": 1, "negation": 1, "obligation": 1}},
  "changes": [
    {"id": 2, "type": "replaced", "scope": "text", "section": "2. Fees", "original": "$1,000", "revised": "$1,250",
     "paragraph": {"original": 3, "revised": 3}, "page": {"original": 1, "revised": null},
     "context": "2. Fees. The Customer shall pay [-$1,000-]{+$1,250+} per month, due within 45 days of the invoice date.",
     "flags": ["number", "money"]},
    "..."
  ],
  "pdfUrl": "https://api.apify.com/v2/key-value-stores/.../records/001-msa-v2-redline.pdf?signature=...",
  "docxUrl": "https://api.apify.com/v2/key-value-stores/.../records/001-msa-v2-tracked-changes.docx?signature=...",
  "htmlUrl": "...", "csvUrl": "...", "imageUrl": "..."
}
```

Every item also has `warnings` (for example when the two documents share little wording), `notes`, a Markdown `report` with the change table and its link. Up to 1,000 changes are listed in the item; the CSV has all of them. A pair that can't be read has `"status": "failed"` and the reason in `error`, and isn't billed.

### How it works

1. **Reading**: PDFs are read with [PDFium](https://pdfium.googlesource.com/pdfium/), page by page; lines are joined back into paragraphs, words hyphenated at a line break are rejoined, and lines repeated at the top or bottom of most pages (headers, footers) and page numbers are dropped. Word files are read with [python-docx](https://github.com/python-openxml/python-docx), in order, with headings, list items and table rows (tracked deletions already in the file are left out). HTML, Markdown and text keep their headings, list items and table rows.
2. **Lining up paragraphs**: unchanged paragraphs are matched first; within each changed stretch, paragraphs are paired by how much wording they share, so an edited paragraph is compared with its old self. A deleted paragraph that turns up almost the same elsewhere is a move.
3. **Word by word**: each changed paragraph is diffed as words, numbers and punctuation, and one short word left between two edits is folded into them so the redline reads as phrases. Curly and straight quotes and dashes count as the same.
4. **Redlines**: the PDF is laid out with [ReportLab](https://www.reportlab.com/opensource/) in the Inter font; the Word file is written as Office Open XML with `w:ins` and `w:del` revisions.

### Pricing

You pay one low price per pair compared, at the price shown on this page. The redline PDF, Word file, HTML, CSV and report are included. Pairs that can't be read are never charged. If you set a maximum cost per run, the Actor stops before a pair that wouldn't fit.

### Speed

Two 20-page contracts compare in about a second, plus the download; a 4,000-word license pair with 78 changes takes under two seconds including all four files. A run through the API answers in about 10 seconds, including the cloud container starting.

### Tips and limits

- **Scanned PDFs** have no text to compare; the error says so. Run OCR on them first.
- **The Word output is plain**: it keeps headings and indents list items but not the originals' fonts, tables or layout. It's for reviewing and accepting changes, not for replacing the formatted original.
- **Formatting-only changes** (bold, font, colour, page breaks) don't count; this compares wording.
- **Old .doc files** need to be saved as .docx or PDF first.
- **The PDF redline uses the Inter font**, which covers Latin, Greek and Cyrillic; for other scripts use the HTML or Word output.
- **Limits**: 50 pairs per run; 50 MB, 2,000 pages and 500,000 words per document.

### For AI agents and developers

This Actor works with the [Apify MCP server](https://mcp.apify.com), so AI agents can call it as a tool: pass `original` and `revised` and read `summary` and `changes` (each with `context` marked `[-old-]{+new+}` and `flags`). A direct API call that waits for the answer:

```bash
curl -X POST "https://api.apify.com/v2/acts/jumblemog~document-compare/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"original": "https://raw.githubusercontent.com/spdx/license-list-data/v3.25.0/text/MIT.txt", "revised": "https://raw.githubusercontent.com/spdx/license-list-data/v3.25.0/text/X11.txt", "outputs": ["pdf"]}'
```

`run-sync` instead returns the run's `OUTPUT`: the status, the counts and every result as JSON.

In Python, with the `apify-client` package, comparing two local files:

```python
import base64
from apify_client import ApifyClient

client = ApifyClient("YOUR_TOKEN")
b64 = lambda path: base64.b64encode(open(path, "rb").read()).decode()
run = client.actor("jumblemog/document-compare").call(run_input={
    "original": b64("contract-v1.pdf"), "revised": b64("contract-v2.docx"), "show": "changes",
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["summary"], item["pdfUrl"], item["docxUrl"])
    for change in item["changes"]:
        print(change["section"], change["context"], change["flags"])
```

### Credits

- [pypdfium2](https://github.com/pypdfium2-team/pypdfium2) and [PDFium](https://pdfium.googlesource.com/pdfium/) (Apache-2.0 / BSD-3-Clause) read PDFs and draw the preview; [python-docx](https://github.com/python-openxml/python-docx) (MIT) reads and writes Word files; [ReportLab](https://www.reportlab.com/opensource/) (BSD) lays out the PDF, in the [Inter](https://rsms.me/inter/) font (SIL Open Font License); Python's difflib lines up the text.
- The examples compare license texts from the [SPDX License List](https://github.com/spdx/license-list-data) and the IRS Form W-9, a public US government form.

# Actor input Schema

## `original` (type: `string`):

The earlier version. A link to a PDF, Word .docx, HTML, Markdown or text file (Google Drive, Google Docs and Dropbox share links work), base64 of a PDF or .docx, or the text itself.

## `revised` (type: `string`):

The later version, compared against the original. A link to a PDF, Word .docx, HTML, Markdown or text file (Google Drive, Google Docs and Dropbox share links work), base64 of a PDF or .docx, or the text itself.

## `uploads` (type: `array`):

Upload the original first, then the revised (PDF, .docx, HTML, Markdown or text), or pick two files already in an Apify key-value store.

## `requests` (type: `array`):

Several comparisons in one run: each entry is {"original": ..., "revised": ...} (also old/new, before/after, v1/v2), or "link1 vs link2". Optional per entry: title, ignoreCase, ignorePunctuation, ignoreNumbering, show, outputs, pageSize, author. Up to 50 pairs.

## `ignoreCase` (type: `boolean`):

Treat 'Agreement' and 'agreement' as the same.

## `ignorePunctuation` (type: `boolean`):

Compare words only: added or removed commas, periods and quotes don't count.

## `ignoreNumbering` (type: `boolean`):

Leave out the number or letter at the start of a paragraph (1., 2.3, (a), iv.) so renumbered clauses don't show as changes.

## `show` (type: `string`):

For the PDF and HTML redlines. The Word file always holds the whole document.

## `outputs` (type: `array`):

The files to make for each pair. Every change is also in the dataset item.

## `pageSize` (type: `string`):

Page size of the redline PDF.

## `author` (type: `string`):

The name Word shows on each tracked change ('Document Compare' by default).

## Actor input object example

```json
{
  "original": "https://raw.githubusercontent.com/spdx/license-list-data/v3.25.0/text/LGPL-2.0-only.txt",
  "revised": "https://raw.githubusercontent.com/spdx/license-list-data/v3.25.0/text/LGPL-2.1-only.txt",
  "ignoreCase": false,
  "ignorePunctuation": false,
  "ignoreNumbering": false,
  "show": "all",
  "outputs": [
    "pdf",
    "docx",
    "html",
    "csv"
  ],
  "pageSize": "letter"
}
```

# Actor output Schema

## `results` (type: `string`):

One row per pair: the redline's first page and the summary.

## `output` (type: `string`):

The OUTPUT record: every result in one JSON object.

## `files` (type: `string`):

Redline PDFs and HTML pages, Word files with tracked changes, change CSVs and Markdown reports in the run's key-value store.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "original": "https://raw.githubusercontent.com/spdx/license-list-data/v3.25.0/text/LGPL-2.0-only.txt",
    "revised": "https://raw.githubusercontent.com/spdx/license-list-data/v3.25.0/text/LGPL-2.1-only.txt"
};

// Run the Actor and wait for it to finish
const run = await client.actor("jumblemog/document-compare").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "original": "https://raw.githubusercontent.com/spdx/license-list-data/v3.25.0/text/LGPL-2.0-only.txt",
    "revised": "https://raw.githubusercontent.com/spdx/license-list-data/v3.25.0/text/LGPL-2.1-only.txt",
}

# Run the Actor and wait for it to finish
run = client.actor("jumblemog/document-compare").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "original": "https://raw.githubusercontent.com/spdx/license-list-data/v3.25.0/text/LGPL-2.0-only.txt",
  "revised": "https://raw.githubusercontent.com/spdx/license-list-data/v3.25.0/text/LGPL-2.1-only.txt"
}' |
apify call jumblemog/document-compare --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,jumblemog/document-compare"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/5UKF3w5E0bzkJjNmn/builds/q7iBSMNiQXhjsBUaG/openapi.json
