Showing a redline? Use the PDF, or embed the HTML for an interactive view. This format is for code that acts on the changes.
format=json is a document as structured data: its paragraphs, headings and tables in order, its
text with every tracked change marked, and beside the body its headers, footers, notes, comments and
revisions. It is the same listing as the Markdown, shaped for
code. Every endpoint that carries a document serves it:
- a comparison's redline,
fileinGET /v1/compare/{comparison_id}; - a merge's document,
fileinGET /v1/merge/{merge_id}, withkindafterschema_version; - any file, an edit's two files, a created document, and a Document's versions.
Ask for it with format=json and download it from file.downloads[].url, like any other format.
The OpenAPI specification publishes its shape as components.schemas.StructuredDocument.
What it is for
Use it for what your code decides about the changes: counting insertions and deletions, listing the
paragraphs that changed, routing a document when a clause you care about changed, linking a change
or paragraph to a record in your own app (and to the embedded HTML's
vs:scrollTo), and reading the locators
an edit names.
def changes(segments):
"""Every tracked change in a list of segments, nested changes included."""
for segment in segments:
if segment.get("type") in ("insertion", "deletion", "moved_from", "moved_to"):
yield segment
yield from changes(segment.get("content", []))
def paragraphs(blocks):
"""Every paragraph and heading, table cells' included."""
for block in blocks:
if block["type"] == "table":
for row in block["rows"]:
for cell in row["cells"]:
yield from paragraphs(cell["blocks"])
else:
yield block
redline = requests.get(json_url).json()
changed = [p for p in paragraphs(redline["blocks"]) if any(changes(p.get("content", [])))]
print(f"{len(redline.get('revisions', []))} changes in {len(changed)} paragraphs")
for paragraph in changed:
if paragraph.get("label", "").startswith("7"):
print("Section 7 changed:", paragraph["id"])It is not for display. It describes the document's structure and changes, not how the document is laid out in pages, so a viewer drawn from it re-implements Word's layout and still shows less than the PDF and the HTML redline Version Story already renders.
The shape
{
"schema_version": 1,
"blocks": [
{
"type": "paragraph",
"index": 1,
"id": "f4378834652a478eb5c67af7fa1b9eea",
"style_id": "Normal",
"content": [
{ "text": "This Non-Disclosure Agreement (this “Agreement”) is entered into between " },
{ "type": "deletion", "revision": { "index": 0, "id": "3e9a1c7b5d2f4e60a8b1c2d3e4f50617" },
"content": [{ "text": "[COMPANY NAME]", "editable": false, "format": { "bold": true } }] },
{ "type": "insertion", "revision": { "index": 1, "id": "9f2c4e1a7b3d4c8e9a1f2b3c4d5e6f70" },
"content": [{ "text": "Acme, Inc.", "format": { "bold": true } }] },
{ "text": ", a Delaware corporation …" }
]
}
],
"notes": [
{
"part": { "kind": "footnote", "index": 0, "id": "2b7d9e1f3a5c4e6f8b0d2f4a6c8e0b1d" },
"label": "1",
"paragraph": { "index": 1, "id": "f4378834652a478eb5c67af7fa1b9eea" },
"text_before": "This Non-Disclosure Agreement (this “Agreement”) is entered into between Acme, Inc.",
"blocks": [
{
"type": "paragraph",
"index": 0,
"id": "b6ac5dabcec1413aa880e0a3a2d9f237",
"style_id": "FootnoteText",
"content": [
{ "type": "deletion", "revision": { "index": 2, "id": "c71d2e3f4a5b4c6d8e9f0a1b2c3d4e5f" }, "content": [{ "text": "Formerly ", "editable": false }] },
{ "text": "Acme Holdings LLC." }
]
}
]
}
],
"revisions": [
{ "index": 0, "id": "3e9a1c7b5d2f4e60a8b1c2d3e4f50617", "type": "deletion", "author": "Dana Reyes", "date": "2026-10-22T03:37:30Z",
"on": ["text"], "text": "[COMPANY NAME]",
"paragraph": { "index": 1, "id": "f4378834652a478eb5c67af7fa1b9eea" } },
{ "index": 1, "id": "9f2c4e1a7b3d4c8e9a1f2b3c4d5e6f70", "type": "insertion", "author": "Dana Reyes", "date": "2026-10-22T03:37:30Z",
"on": ["text"], "text": "Acme, Inc.",
"paragraph": { "index": 1, "id": "f4378834652a478eb5c67af7fa1b9eea" } },
{ "index": 2, "id": "c71d2e3f4a5b4c6d8e9f0a1b2c3d4e5f", "type": "deletion", "author": "Dana Reyes", "date": "2026-10-22T03:37:30Z",
"on": ["text"], "text": "Formerly ",
"part": { "kind": "footnote", "index": 0, "id": "2b7d9e1f3a5c4e6f8b0d2f4a6c8e0b1d" },
"paragraph": { "part": { "kind": "footnote", "index": 0, "id": "2b7d9e1f3a5c4e6f8b0d2f4a6c8e0b1d" }, "index": 0, "id": "b6ac5dabcec1413aa880e0a3a2d9f237" } }
]
}Beside blocks and revisions, the document carries headers, footers, notes, comments,
settings, list_styles, paragraph_styles and final_section, each present when the document has
any. A merge's document adds kind after schema_version.
Locators
Every index counts from 0 within its container: paragraphs and tables within their part (the body,
or one header, footer or note), rows within their table, cells within their row, headers and footers
and notes of a kind, and sections, comment threads, pictures, conflicts and revisions within the
document. Everything with an index also carries an id, which it keeps when content moves. The
redline's Markdown and HTML give each
the same index and id.
Where the listing points at something else, it prints that thing's locator, the object an edit sends as it is:
| Points at | Locator |
|---|---|
| A body paragraph | {"index": 12, "id": "…"}, or {"id": "…"} for one the listing gives no index |
| A header, footer or note paragraph | {"part": {...}, "index": 0, "id": "…"} |
| A header or footer | {"kind": "header", "index": 0, "id": "…", "type": "default", "section": {"index": 0, "id": "…"}}: its position among the headers (or footers), in order of the first section that shows each, its page type and that first section |
| A note | {"kind": "footnote", "index": 0, "id": "…"}: its position among the notes of its kind |
| A revision | {"index": 4, "id": "…"} |
What Word shows is a label, never a position: a list paragraph's generated number (2.4(a)) and a
note's mark.
Blocks
blocks is the body in document order. Each block's type is paragraph, heading or table.
| Field | Meaning |
|---|---|
index | A paragraph's position among its part's paragraphs, counted from 0, table cells' included; a table's among its part's tables, an outer table before those nested in it. The {"index": n} an edit names |
id | Its own identifier, kept when content moves. The Markdown and the embedded HTML give it the same id |
style_id | Its paragraph style |
label | A numbered paragraph's list label as Word shows it (2.4(a)). It is not part of the text |
heading_level | A heading's level, from its Heading 1 to 9 style |
list_level | A numbered paragraph's list level, 1 to 9 as Word counts them |
content | Its text, as a list of segments |
paragraph_format, section, fields, bookmarks | Its formatting where it sets its own, the section that ends at it, and the fields and bookmarks in it |
Tables
A table holds rows, each row holds cells, and each cell holds its own blocks, so a cell's
content is read exactly like the body.
{
"type": "table", "index": 0, "id": "5b0e91c4…", "style_id": "TableGrid",
"rows": [
{ "index": 0, "id": "6e127ae6…", "header": true, "cells": [
{ "index": 0, "id": "a91c03d2…", "column": 0, "blocks": [] },
{ "index": 1, "id": "c4d8e1f0…", "column": 1, "span": 2, "blocks": [] }
] },
{ "index": 1, "id": "0d7f52b9…", "change": { "type": "insertion", "revision": { "index": 3, "id": "0a4b8c2d6e1f4a3b9c7d5e2f1a0b9c8d" } }, "cells": [] }
]
}| Field | On | Meaning |
|---|---|---|
index, id | Row, cell | A row's position in its table, and a cell's in its row, counted from 0; and its id |
header | Row | true for a header row, repeated on each page |
column | Cell | The grid column the cell starts at, counted from 0. A column locator names it, never the cell's index |
span | Cell | The grid columns a merged cell covers, present when more than one. The next cell's column is this cell's plus its span |
change | Row, cell | A tracked insertion or deletion of the whole row or cell: {"type", "revision": {"index", "id"}} |
table_format, cell_format | Table, cell | The borders it sets itself |
Segments
A segment without a type is unchanged text, with text and, where they apply, format and link.
A segment with one is one of:
type | Meaning |
|---|---|
insertion | Text the later version added |
deletion | Text the later version removed, marked "editable": false |
moved_from / moved_to | The two halves of one relocation, both naming the same revision |
conflict | In a merge, competing changes from different revisions over the same text, with the conflict's index and id. Surface these to a person; don't resolve them automatically |
equation | A Word equation, as below |
tab, break | A tab, or a line, page or column break (kind) |
picture, object | A picture, with its index, id, size and alt_text, or a chart, shape, text box or embedded object |
A change is {"type", "revision": {"index": n, "id": "…"}} with its text when it holds plain text, or its
content when its segments carry more: formatting, a link, a break, another change, or a deletion's
"editable": false. Its author and date are on its entry in revisions, once. Changes nest: a revision that edits inside another revision's text sits in that change's
content, so both attributions survive.
In a merge's document, competing changes are wrapped in {"type": "conflict", "index": n, "id": "…", "content": [...]}.
Where two revisions replaced the same words, the merge keeps one deletion of them, and that deletion
also lists, as revisions, the revision of every change in its conflict:
{ "type": "conflict", "index": 0, "id": "8c1e5a9d3b7f4e2a6c0d8b4f2e6a1c3d", "content": [
{ "type": "deletion", "revision": { "index": 3, "id": "0a4b8c2d6e1f4a3b9c7d5e2f1a0b9c8d" }, "revisions": [{ "index": 3, "id": "0a4b8c2d6e1f4a3b9c7d5e2f1a0b9c8d" }, { "index": 4, "id": "5e6f7a8b9c0d4e1f8a2b3c4d5e6f7081" }, { "index": 5, "id": "d2e3f4a5b6c74d8e9f0a1b2c3d4e5f60" }],
"content": [{ "text": "[•]", "editable": false }] },
{ "type": "insertion", "revision": { "index": 4, "id": "5e6f7a8b9c0d4e1f8a2b3c4d5e6f7081" }, "text": "April 15, 2027" },
{ "type": "insertion", "revision": { "index": 5, "id": "d2e3f4a5b6c74d8e9f0a1b2c3d4e5f60" }, "text": "December 31, 2026" }
] }Equations
A Word equation is one segment, {"type": "equation", "content": [...]}. Its content is the
equation in a linear form, as text segments marked "editable": false: (T-D)/T is a fraction,
F_m a subscript, x^2 a superscript, ∑_(i=1)^n (q_i p_i) a sum with its limits. A tracked change
inside the equation is a change segment naming its revision.
{ "type": "equation", "content": [
{ "text": "(T-D)/", "editable": false },
{ "type": "deletion", "revision": { "index": 6, "id": "7b8c9d0e1f2a4b3c8d4e5f6a7b8c9d0e" }, "content": [{ "text": "T", "editable": false }] },
{ "type": "insertion", "revision": { "index": 7, "id": "e1f2a3b4c5d64e7f8a9b0c1d2e3f4a5b" }, "content": [{ "text": "N", "editable": false }] }
] }No text selector matches an equation's text, so no text edit changes it. The Markdown writes it as
<equation>…</equation>.
Revisions
revisions lists every tracked change once per accept or reject decision, in document order: the
body first, then headers and footers section by section, footnotes and endnotes. A replacement is two
entries, a deletion and an insertion. Counting revisions is counting changes the way a reviewer
does.
| Field | Meaning |
|---|---|
index | Its position, counted from 0 |
id | Its identifier. Every change that is part of it names it as {"index": n, "id": "…"}, and add_comment, accept_revision and reject_revision take the same locator, by index, id or both. The redline's HTML marks every piece of the change with this id, both halves of a move included |
type | insertion, deletion, move (both halves of a move) or property (a formatting, paragraph, numbering, table or section property change) |
author, date | Who made it and when. In a merge's document, author is the revision's label, such as Dana Reyes — nda_from_counsel.docx |
on | What it changes: text, paragraph_mark, paragraph, numbering, section, table, table_row or table_cell. paragraph_mark is a change to a paragraph's mark: one inserted or deleted, as a split, a join or a whole paragraph added or removed makes, or its formatting |
text | The first 200 characters of the text inserted, deleted, moved or formatted, a paragraph mark as a newline; for another property change, the text of what it changes |
part | Its part, for a revision in a header, footer or note |
paragraph | The locator of the paragraph it starts in |
table, row, cell | The table, row or cell it changes, by {"id"}, when it starts in no paragraph |
section | The section a section property change changes, {"index": n, "id": "…"} |
comments lists the comment threads, each with its index, id, author, date, text, the
paragraph its anchor starts in, anchor_text, resolved and replies.
The older comparison JSON
A comparison's response also carries a json in its deprecated top-level downloads: the
comparison's older rendering, kept for the integrations built on it. New code reads the json in
file, above.
It has schema_version, kind and blocks, and nothing else. A list paragraph's label is number,
with its list_id. Its paragraphs have their text, formatting, tracked changes and level, but no
index, heading_level, list_level, bookmarks, paragraph formatting, links, fields, pictures,
objects or equations, and its tables and cells have no position. Each change segment carries its
id (Word's own revision id), author, revision (the document version that introduced it) and
date, with text, or content when changes nest. A whole table row that was inserted or deleted
carries a change on the row itself. To reconstruct either version from it:
- The base document: everything except
insertionsegments. - The revised document: everything except
deletionsegments, and table rows whosechangeis a deletion.
Spreadsheet and presentation comparisons have no JSON.