/api/diff
MCP tool text_diff Compares before with after using jsdiff (Myers' O(ND) algorithm). changes is the full sequence of added, removed and unchanged pieces at the chosen granularity; concatenating the unchanged and removed values gives before, unchanged and added gives after (when ignoring whitespace or case, unchanged pieces are taken from after). unified and hunks are always a line-level unified diff (GNU diff -u format) with context lines, whatever the mode. identical is true when no differences remain under the chosen options; exact_match compares the raw strings. Limits: 200,000 characters per text; at most 5000 differing lines, or 2000 differing words or characters.
Browser version (runs locally): Text Diff.
Try it
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
before required | string (≤ 200000 chars) | — | The original text. |
after required | string (≤ 200000 chars) | — | The changed text. |
mode | lines | words | characters | lines | Granularity of changes and stats: lines, words (runs of letters/digits/underscore; each punctuation mark is its own token), or characters (Unicode code points). |
ignore_whitespace | boolean | false | lines: compare lines with leading and trailing whitespace trimmed. words: ignore differences in the whitespace between words. Not supported in characters mode (422). |
ignore_case | boolean | false | Compare case-insensitively (JavaScript toLowerCase), in every mode and in the unified diff. |
context | integer (0–1000) | 3 | Unchanged lines shown around each change in unified and hunks (GNU diff default: 3). |
from_label | string (≤ 200 chars) | before | Label for the --- header line of the unified diff, e.g. a file name a/src/app.ts. |
to_label | string (≤ 200 chars) | after | Label for the +++ header line of the unified diff. |
Also accepted on every endpoint: format (json | text).
Parameters may come from the query string, a JSON object body, an urlencoded or multipart form,
or (for input) a text/plain body.
Global parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
format | json | text | json | json returns the documented object. text returns only the primary value as text/plain (see "Text output"). |
Response 200 application/json
| Field | Type | Description |
|---|---|---|
identical | boolean | True when the texts have no differences under the chosen options. |
exact_match | boolean | True when before and after are byte-for-byte equal strings. |
mode | "lines" | "words" | "characters" | Granularity used. |
unit | "line" | "word" | "character" | Unit of counts in stats and changes. |
options | object | |
options.ignore_whitespace | boolean | As requested. |
options.ignore_case | boolean | As requested. |
options.context | integer | Context lines used. |
options.from_label | string | --- label used. |
options.to_label | string | +++ label used. |
stats | object | |
stats.added | integer | Total added, in units of unit (lines, words, or Unicode code points). |
stats.removed | integer | Total removed, in units of unit (lines, words, or Unicode code points). |
stats.unchanged | integer | Total unchanged, in units of unit (lines, words, or Unicode code points). |
stats.change_blocks | integer | Number of contiguous runs of added and/or removed pieces. |
stats.lines_added | integer | + lines in the unified diff (what diffstat counts). |
stats.lines_removed | integer | - lines in the unified diff. |
stats.chars_added | integer | Unicode code points in added pieces. |
stats.chars_removed | integer | Unicode code points in removed pieces. |
changes | array of object | Every piece of both texts in order. A replacement appears as removed followed by added. |
changes[].type | "added" | "removed" | "unchanged" | Kind of piece. |
changes[].value | string | The text of the piece, including its line breaks and whitespace. |
changes[].count | integer | Size of the piece, in units of unit (lines, words, or Unicode code points). |
hunks | array of object | Line-level hunks of the unified diff. Empty when identical. |
hunks[].header | string | Hunk header, e.g. @@ -1,3 +1,4 @@. |
hunks[].before_start | integer | First line in before (1-based; 0 when the hunk has no before lines). |
hunks[].before_lines | integer | Number of before lines in the hunk. |
hunks[].after_start | integer | First line in after (1-based; 0 when the hunk has no after lines). |
hunks[].after_lines | integer | Number of after lines in the hunk. |
hunks[].lines | array of string | Lines of the hunk. |
unified | string | Unified diff text: --- from_label, +++ to_label, then the hunks. Empty string when identical. |
Text output (format=text, text/plain): The unified diff (empty when identical).
Errors
Errors are JSON: {"error": "<message>", "code": "<code>", "param": "<name>"?}.
| Status | Code | When |
|---|---|---|
400 | invalid_parameter | A text exceeds the length limit, or context is out of range. |
422 | unprocessable_input | The texts differ in more than the edit limit for the mode, ignore_whitespace is used with characters mode, or a label contains a line break. |
400 | missing_parameter | A required parameter is absent. |
400 | unknown_parameter | A parameter name is not in the table above (typos are rejected, not ignored). |
413 | payload_too_large | The request body exceeds 1 MiB. |
405 | method_not_allowed | A method other than GET, POST or OPTIONS. |
Behaviour
- Idempotent: the same arguments always return the same result.
-
Requires a client ID and secret (authentication). Each call is
recorded as one metadata row against your client ID; inputs and outputs are never stored or
logged. Responses carry
cache-control: no-store. - CORS: any origin may call this endpoint from a browser, using your credentials.
Examples
Responses below were produced by running each request through this code at build time. Long strings are shortened.
Line diff with file labels
curl -X POST 'https://util.caruana.me/api/diff' \
-H 'content-type: application/json' \
-d '{"before":"alpha\nbeta\ngamma\n","after":"alpha\nBETA\ngamma\ndelta\n","from_label":"a/notes.txt","to_label":"b/notes.txt"}'
Response 200
{
"identical": false,
"exact_match": false,
"mode": "lines",
"unit": "line",
"options": {
"ignore_whitespace": false,
"ignore_case": false,
"context": 3,
"from_label": "a/notes.txt",
"to_label": "b/notes.txt"
},
"stats": {
"added": 2,
"removed": 1,
"unchanged": 2,
"change_blocks": 2,
"lines_added": 2,
"lines_removed": 1,
"chars_added": 11,
"chars_removed": 5
},
"changes": [
{
"type": "unchanged",
"value": "alpha\n",
"count": 1
},
{
"type": "removed",
"value": "beta\n",
"count": 1
},
{
"type": "added",
"value": "BETA\n",
"count": 1
},
{
"type": "unchanged",
"value": "gamma\n",
"count": 1
},
{
"type": "added",
"value": "delta\n",
"count": 1
}
],
"hunks": [
{
"header": "@@ -1,3 +1,4 @@",
"before_start": 1,
"before_lines": 3,
"after_start": 1,
"after_lines": 4,
"lines": [
" alpha",
"-beta",
"+BETA",
" gamma",
"+delta"
]
}
],
"unified": "--- a/notes.txt\n+++ b/notes.txt\n@@ -1,3 +1,4 @@\n alpha\n-beta\n+BETA\n gamma\n+delta\n"
}
Same call as an MCP tools/call
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "text_diff",
"arguments": {
"before": "alpha\nbeta\ngamma\n",
"after": "alpha\nBETA\ngamma\ndelta\n",
"from_label": "a/notes.txt",
"to_label": "b/notes.txt"
}
}
}Word diff, ignoring case
curl 'https://util.caruana.me/api/diff?before=The%20quick%20brown%20fox&after=the%20quick%20red%20fox&mode=words&ignore_case=true'
Response 200
{
"identical": false,
"exact_match": false,
"mode": "words",
"unit": "word",
"options": {
"ignore_whitespace": false,
"ignore_case": true,
"context": 3,
"from_label": "before",
"to_label": "after"
},
"stats": {
"added": 1,
"removed": 1,
"unchanged": 3,
"change_blocks": 1,
"lines_added": 1,
"lines_removed": 1,
"chars_added": 3,
"chars_removed": 5
},
"changes": [
{
"type": "unchanged",
"value": "the quick ",
"count": 2
},
{
"type": "removed",
"value": "brown",
"count": 1
},
{
"type": "added",
"value": "red",
"count": 1
},
{
"type": "unchanged",
"value": " fox",
"count": 1
}
],
"hunks": [
{
"header": "@@ -1,1 +1,1 @@",
"before_start": 1,
"before_lines": 1,
"after_start": 1,
"after_lines": 1,
"lines": [
"-The quick brown fox",
"\\ No newline at end of file",
"+the quick red fox",
"\\ No newline at end of file"
]
}
],
"unified": "--- before\n+++ after\n@@ -1,1 +1,1 @@\n-The quick brown fox\n\\ No newline at end of file\n+the quick red fox\n\\ No newline at end of file\n"
}
Same call as an MCP tools/call
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "text_diff",
"arguments": {
"before": "The quick brown fox",
"after": "the quick red fox",
"mode": "words",
"ignore_case": true
}
}
}