One HTTPS call. Send the older and the newer PDF, receive the change list as JSON, the redlined PDF and the side-by-side HTML. The same engine that powers the web tool.
https://api.simplefiletools.com
All requests use HTTPS. Responses are JSON.
Send your key in the X-API-Key header. Keys are issued to enterprise accounts and can be rotated at any time. Requests without a key are treated as anonymous demo calls and are limited to 30 pages per document and 20 calls a day per IP address.
X-API-Key: sk_live_…
Signed-in users of the web tool authenticate with a bearer token instead, and their calls are metered against their plan.
POST /api/v1/compare
Content-Type: multipart/form-data
| Field | Type | Description |
|---|---|---|
| old | file | The older PDF (baseline). Required. |
| new | file | The newer PDF (revision). Required. |
| output | string | all (default), json, html or pdf. json returns only the summary and change list and is the fastest. |
| old_name | string | Label shown for the old side in the rendered outputs. Defaults to the file name. |
| new_name | string | Label shown for the new side. Defaults to the file name. |
curl -X POST https://api.simplefiletools.com/api/v1/compare \
-H "X-API-Key: $PDF_COMPARE_KEY" \
-F "old=@agreement-v1.pdf" \
-F "new=@agreement-v2.pdf" \
-F "output=json"
import base64, requests
r = requests.post(
"https://api.simplefiletools.com/api/v1/compare",
headers={"X-API-Key": KEY},
files={"old": open("agreement-v1.pdf", "rb"),
"new": open("agreement-v2.pdf", "rb")},
data={"output": "all"},
timeout=120,
)
r.raise_for_status()
body = r.json()
print(body["summary"], len(body["changes"]), "changes")
open("redline.pdf", "wb").write(base64.b64decode(body["redline_pdf"]))
import fs from "node:fs";
const fd = new FormData();
fd.append("old", new Blob([fs.readFileSync("agreement-v1.pdf")]), "agreement-v1.pdf");
fd.append("new", new Blob([fs.readFileSync("agreement-v2.pdf")]), "agreement-v2.pdf");
fd.append("output", "json");
const res = await fetch("https://api.simplefiletools.com/api/v1/compare", {
method: "POST", headers: { "X-API-Key": process.env.PDF_COMPARE_KEY }, body: fd,
});
const body = await res.json();
console.log(body.summary, body.changes.length);
{
"success": true,
"summary": { "insertions": 8, "deletions": 7, "formatting": 0 },
"pages": { "old": 12, "new": 13 },
"degenerate": false,
"ocr_source": false,
"elapsed_ms": 1840,
"changes": [
{
"id": 0,
"kind": "replaced",
"level": "substantive",
"ins": 1, "del": 1,
"old": "twelve (12",
"new": "twenty-four (24",
"page": 1, "old_page": 1, "new_page": 1
}
],
"redline_pdf": "<base64>",
"html": "<base64>"
}
| Field | Meaning |
|---|---|
| summary | Counts of inserted, deleted and formatting-changed text segments. |
| changes[].kind | inserted, deleted, replaced, moved or formatting. |
| changes[].level | substantive (text changed), formatting (style only) or whitespace. |
| changes[].old / new | The affected text on each side, trimmed to 280 characters. |
| changes[].page | 1-based page number where the change appears, with old_page and new_page for each side. |
| degenerate | true when the two documents share almost no text and a line-level comparison is not meaningful. |
| ocr_source | true when a document's text layer came from OCR, in which case formatting differences are unreliable. |
| redline_pdf | Base64 of the new document marked up with insertions and deletions. Present for output=all or pdf. |
| html | Base64 of a standalone side-by-side HTML page with page images inlined. Present for output=all or html. |
| Caller | Pages per document | Request size | Rate |
|---|---|---|---|
| Anonymous | 30 | 20 MB per file, 25 MB combined | 20 per day per IP |
| Signed-in user | 100 | 20 MB per file, 25 MB combined | Plan quota |
| API key | 300, more on request | 20 MB per file, 25 MB combined; more on a private deployment | Per contract |
Requests are limited to 120 seconds; a 100-page pair typically completes in under 15. For very large documents use output=json, which skips rendering, or ask about a private deployment with higher limits.
| Status | Meaning |
|---|---|
| 400 | Missing file, unreadable PDF, or password-protected PDF. |
| 401 | Invalid API key or expired bearer token. |
| 409 | Plan quota exhausted (signed-in users). The body carries upgrade_url. |
| 413 | Files too large or too many pages for the caller's tier. |
| 429 | Anonymous rate limit reached. |
| 500 | The comparison failed. The body carries an error message. |
Every error body has the shape {"success": false, "error": "…"}.
GET /api/v1/health
→ { "success": true, "service": "pdf-compare", "engine": "pdfdiff" }