Skip to content
Documentation menu

JSON format

The structured redline behind format=json: blocks, segments, and deterministic change positions.

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 a paragraph, heading, or table.
  • content — a block's text as an ordered list of segments.
  • Segments — a segment without a type is 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 insertion segments.
  • Revised document — concatenate everything except deletion segments (and skip table rows whose change is a deletion).

Segment types

typeMeaning
(absent)Unchanged text
insertionText the revision added
deletionText the revision removed
moved_from / moved_toThe two halves of one relocation, tied by a shared integer move_id
conflictCompeting 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.