API reference

Compare two texts

Diff two texts by line, word or character: unified diff, structured changes, and stats.

GET POST /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

Credentials
Remember these credentials

Not saved: you will paste them again next time.

No credentials yet? Get a client ID and secret. They are sent only to this site, in an Authorization: Basic header.

POST /api/diff with a JSON body

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