Every comparison and merge can also be fetched as JSON — the same redline as the Word
document, shaped for code. Request it with format=json on
GET /v1/compare/{id} or
GET /v1/merge/{id} and download it like any other format.
Where the markdown rendering is for reading, this is for programs: no markup to parse, no escaping rules, and every change located deterministically — a change is an element of its paragraph's content list, so its position is structural, never a text match or a character offset you have to keep in sync.
The shape
Three levels, and only three:
blocks— the document in order. Each block is aparagraph,heading, ortable.content— a block's text as an ordered list of segments.- Segments — a segment without a
typeis unchanged text; a segment with one is a change.
{
"schema_version": 1,
"kind": "comparison",
"blocks": [
{
"type": "paragraph",
"id": "c38d5f0184dc41d79720a3f118526380",
"content": [
{ "text": "… to protect the confidentiality of certain " },
{
"type": "insertion",
"text": "valuable ",
"id": "18",
"author": "Dana Reyes",
"revision": "v2.docx",
"date": "2026-08-17T22:00:39Z"
},
{ "text": "confidential information of the Company …" }
]
}
]
}Block ids are the document's own paragraph identifiers — stable across renderings of the
same comparison, so you can reference a paragraph and find it again.
Reconstructing either version
The two sides of the comparison fall out of the segment list:
- Base document — concatenate everything except
insertionsegments. - Revised document — concatenate everything except
deletionsegments (and skip table rows whosechangeis a deletion).
Segment types
type | Meaning |
|---|---|
| (absent) | Unchanged text |
insertion | Text the revision added |
deletion | Text the revision removed |
moved_from / moved_to | The two halves of one relocation, tied by a shared integer move_id |
conflict | Competing changes over the same span — merges only |
Every change carries author, revision (which document version introduced it), and date
(ISO 8601, UTC as Z). When several revisions made the identical change, author and
revision are comma-separated lists.
Every change also carries id: the tracked revision's own identifier in the source document.
Each change segment corresponds to exactly one of the document's revision records, and the
markdown rendering carries the same value in its id
attribute, so a change can be cross-referenced between the two renderings.
Changes nest: a revision that edits inside another revision's text carries a content list
instead of text, so both attributions survive.
{
"type": "insertion",
"id": "21",
"author": "counsel@firm.com",
"date": "2026-08-17T22:00:39Z",
"content": [
{ "text": "kept " },
{ "type": "deletion", "text": "then cut", "id": "22", "author": "legal@acme.com", "date": "2026-08-18T09:12:00Z" }
]
}Numbering and headings
A numbered paragraph carries the document's own numeral, verbatim, plus its outline level:
{ "type": "paragraph", "number": "2.4(a)", "level": 2, "content": [ … ] }A paragraph with a heading style becomes { "type": "heading", "level": 1, … }.
Tables
Tables nest rows and cells; each cell holds its own blocks, so cell content is read
exactly like the document body. A whole row that was inserted or deleted carries a change
on the row itself — one edit, not one per cell.
{
"type": "table",
"id": "5b9331372a6c4dbb9c39d8f78d10f0f2",
"rows": [
{
"id": "fa55704b8f4e4babb15335a5f2f81b8a",
"change": { "type": "deletion", "id": "12", "author": "Dana Reyes", "revision": "v2.docx", "date": "2026-08-17T21:27:16Z" },
"cells": [
{ "id": "895ba24a", "blocks": [ { "type": "paragraph", "content": [ { "type": "deletion", "text": "Products", "id": "13", "author": "Dana Reyes", "revision": "v2.docx", "date": "2026-08-17T21:27:16Z" } ] } ] }
]
}
]
}Merge conflicts
When two revisions edit the same span, the competing changes are wrapped in a conflict
segment. Each insertion is one revision's proposed replacement; the deletion is the
original they each replaced, and its revision lists every revision that removed it.
Surface these to a person — don't resolve them automatically.
{
"type": "conflict",
"id": 1,
"content": [
{ "type": "deletion", "text": "April 15 2023", "id": "41", "author": "Version Story", "revision": "v3.docx, v2.docx", "date": "2026-08-17T22:07:00Z" },
{ "type": "insertion", "text": "[•]", "id": "42", "author": "Version Story", "revision": "v2.docx", "date": "2026-08-17T22:07:01Z" },
{ "type": "insertion", "text": "December 31 2022", "id": "43", "author": "Version Story", "revision": "v3.docx", "date": "2026-08-17T22:07:00Z" }
]
}What it does not carry
Bold and italic are not represented — use the markdown rendering when formatting matters.
Content inside text boxes and footnotes is not included. Spreadsheet comparisons have no
JSON rendering. Comparisons generated before this format shipped report json as pending
until they are regenerated; the Word redline always carries the same change set.