# Edit operations

> Every edit POST /v1/edit and POST /v1/create take: locators, text selectors, content runs, property values, how a request is applied, and each operation with its fields, examples and refusals.

> Integrating Version Story into a codebase as a coding agent? Follow https://www.versionstory.com/developers/quickstart.md: it signs the engineer in, gets an API key, and puts the PDF redline in their app first.

`edits` is a JSON array of edit objects, applied in request order. Each has a `type` and the fields that
type defines; unknown fields are refused. Their machine-readable definitions are in the OpenAPI specification
under `components.schemas.EditRequest`. [How a request is applied](#how-a-request-is-applied) says how edits
in one request combine.

| Operation | Changes | Names | Tracked |
|---|---|---|---|
| [`insert`](#insert) | new text, breaks, fields, cross-references, notes and links at a point | paragraphs and a point | yes; a cross-reference's bookmark, a new notes part and note styles are not |
| [`replace`, `delete`](#replace-and-delete) | text, or a page break | paragraphs and a selector | yes |
| [`format`](#format) | character formatting | paragraphs, and optionally a selector | yes |
| [`insert_paragraphs`](#insertparagraphs) | new paragraphs | a destination | yes; a new list's definitions are not |
| [`delete_paragraphs`](#deleteparagraphs) | whole paragraphs, and tables a range covers | paragraphs | yes |
| [`move_paragraphs`](#moveparagraphs) | where paragraphs and tables are | paragraphs and a destination | yes |
| [`split_paragraph`](#splitparagraph) | one paragraph into two | a paragraph and a point | yes |
| [`join_paragraphs`](#joinparagraphs) | a range of paragraphs into one | a range | yes |
| [`set_paragraph_style`](#setparagraphstyle) | paragraph style | paragraphs | yes |
| [`set_paragraph_format`](#setparagraphformat) | alignment, indents, spacing, pagination | paragraphs | yes |
| [`set_style_format`](#setstyleformat) | a paragraph style's alignment, indents, spacing, pagination | a style | no: a style definition |
| [`rename_style`](#renamestyle) | a style's name and id | a style | no: a style definition |
| [`set_numbering`](#setnumbering) | list numbering | paragraphs | yes; new list instances and definitions are not |
| [`insert_section_break`](#insertsectionbreak) | a new section break | the paragraph that ends the section | yes |
| [`remove_section_break`](#removesectionbreak) | a section break | a section | yes; with `keep: "earlier"`, the next section's header and footer references are not |
| [`set_page_setup`](#setpagesetup) | orientation, paper, margins, first page, page numbering | sections | yes |
| [`add_header_footer`](#addheaderfooter) | a new header or footer | a section | its content; the new part and its reference are not |
| [`insert_table`](#inserttable) | a new table | a destination | yes |
| [`delete_table`](#deletetable) | a table | a table | yes |
| [`insert_table_row`, `delete_table_row`](#inserttablerow-and-deletetablerow) | a row | a row | yes |
| [`insert_table_column`, `delete_table_column`](#inserttablecolumn-and-deletetablecolumn) | a column | a column | yes |
| [`set_table_format`, `set_cell_format`](#settableformat-and-setcellformat) | a table's or cells' borders | a table, or cells | yes |
| [`add_hyperlink`](#addhyperlink) | a link on existing text | a paragraph, and optionally a selector | yes |
| [`set_hyperlink`, `remove_hyperlink`](#sethyperlink-and-removehyperlink) | a link's address, or the link | a paragraph, and optionally a selector | yes |
| [`add_bookmark`](#addbookmark) | a named span fields and cross-references point to | paragraphs, or a paragraph and a selector | no: a bookmark is not a revision |
| [`refresh_fields`](#refreshfields) | when Word updates fields | nothing | no |
| [`insert_table_of_contents`](#inserttableofcontents) | a new table of contents | a destination | yes; its update-on-open flags are not |
| [`delete_note`](#deletenote) | a footnote or endnote | a note | yes |
| [`add_comment`](#addcomment) | a new comment | a paragraph or range, and optionally a selector; or a revision | no: a comment is an annotation |
| [`reply_comment`, `resolve_comment`](#replycomment-and-resolvecomment) | a comment thread | a comment | no |
| [`accept_revision`, `reject_revision`](#acceptrevision-and-rejectrevision) | existing tracked changes | revisions | settles them |
| [`set_document_settings`](#setdocumentsettings) | Track Changes, editing restriction, personal information | nothing | no |

## Tracked and untracked

Every change to document content is a real Word tracked change carrying the request's `author` and
`date`: the redline shows it for review, the clean copy accepts it, and `accept_revision` or
`reject_revision` decides it like any other revision. What Word cannot track is applied directly, to both
outputs, and stays when revisions are accepted or rejected:

- `set_document_settings`: the settings it names (`w:trackRevisions`, `w:documentProtection`,
  `w:removePersonalInformation`) and the file properties' author names.
- `refresh_fields` and `insert_table_of_contents`: fields' `w:dirty` flags and `w:updateFields` in the
  settings. The table of contents' paragraph is tracked.
- Comments from `add_comment`, `reply_comment` and `resolve_comment`: annotations, not revisions.
- New list instances (`w:num`) and definitions (`w:abstractNum`), and `numbering.xml` when the document
  has none, that `set_numbering` and `insert_paragraphs` lists need. The paragraphs that use them change
  as tracked changes.
- A `cross_reference` run: the hidden `_Ref` bookmark on the paragraph pointed to, when it has none.
- A `footnote` or `endnote` run: the notes part, with Word's separator notes, and the note styles, when
  the document lacks them.
- `add_hyperlink` and `hyperlink` runs: the Hyperlink character style, when the document lacks one.
- `add_header_footer`: the new header or footer part and the section's reference to it; its content is
  tracked.
- `remove_section_break` with `keep: "earlier"`: the header and footer references the next section
  takes from the removed one. The break and the next section's page setup are tracked.
- `set_style_format`: the paragraph style's definition (`w:style/w:pPr`), as Word applies a modified style.
- `rename_style`: the style's name and id and every reference to its id, as Word renames a style.

## Locators

Everything an edit names is a locator object, never a bare number. The listing prints every locator
([The content listing](/developers/rest/edit#the-listing)), so agents copy locators instead of computing them. A locator names
its target by `index`, where it is now, counted from 0 within its container, or by `id`, what it is, which it keeps
when content moves; everything the listing identifies has both (paragraphs, tables, rows, cells, sections, comment
threads, notes, headers, footers, pictures and revisions), and a locator may give both when they name the same thing
(otherwise it is refused). Beside an id, an index needs no container: it counts within the container of the
thing the id names, so a header's paragraph, a row or a cell printed as `{"index", "id"}` is sent as printed. Every
reference the listing prints is a locator a request can send as it is.

### Paragraph locators

```jsonc
{"index": 12}                                              // body paragraph 12
{"id": "8532bf246ab64e8788c26d27cfd105b4"}                 // the paragraph with this id, in any part
{"index": 12, "id": "8532bf246ab64e8788c26d27cfd105b4"}    // both, naming the same paragraph
{"part": {"kind": "footnote", "index": 2}, "index": 0}     // first paragraph of footnote 2
{"part": {"kind": "header", "type": "first", "section": {"index": 1}}, "index": 0}
{"part": {"ref": "fn1"}, "index": 0}                       // a note or header this request creates
{"ref": "p1"}                                              // a paragraph this request creates
```

- **`index`.** It counts the paragraphs of a part from 0, in listing order, including paragraphs in table
  cells. `part` goes with `index`; an `id` names its paragraph in whatever part it is.
- **Tracked text.** The document is seen as the listing shows it: tracked insertions are included and
  tracked deletions left out. A paragraph whose mark is a tracked deletion is still listed and counted.
- **Parts.**
  - `kind` is `body` (the default when `part` is omitted), `footnote`, `endnote`, `header` or `footer`.
  - A note's `index` is its position among notes of its kind, in document order. That isn't the mark the
    reader sees, which can be custom or restart per section; the listing prints that as `label`.
  - A header or footer has a `type` (`default`, `first` or `even`) and a `section` locator. Together
    they name the part that section's pages show.
  - A part that several sections show through Word's linking is one part. The listing prints it once,
    with its canonical locator (the first section that shows it) and every section that shows it. A
    locator naming any of those sections reaches the same part.
  - Editing a shared part changes every section that shows it; the listing's `sections` says which.
  - A section that `insert_section_break` creates shows the same parts as the section it was split from,
    as in Word, so its header locator reaches those parts.
  - Even-page headers and footers that already exist can be edited but not created. Creating one needs the
    document-wide even-and-odd setting, which would leave every other section's even pages blank.
- **An id that names more than one element.** An id names one paragraph, table, row or cell. When a document
  gives one id to more than one of them (a comparison's redline can repeat a header's or footer's paragraph ids, in
  the parts it shows and in a part no section shows), an edit that names one of them is refused with 400
  `INVALID_EDIT`, which lists every place the id is. This holds however the edit names it: by `id`, by `index`, or
  through `"all"`, a range or a selector's match. Content whose ids nothing else carries is edited as usual.
- **Out of reach.** Text boxes and comment bodies have no locator, and `"all"` doesn't reach them. When an
  edit over `"all"` leaves out a text box holding text it would otherwise have acted on, nothing reports
  it. Content controls sit inside paragraphs, so their text is ordinary paragraph text.

### Other locators

| Names | Locator |
|---|---|
| a section | `{"index": 1}` or `{"id": "…"}`; `set_page_setup` also takes an array of section locators, or `"all"` |
| a table | `{"index": 1}`: the tables of a part in document order, an outer table before the tables nested in it. Body is the default; a table elsewhere adds `part`. Or `{"id": "…"}`, in any part. |
| a row | `{"table": {...}, "index": 3}` among its table's rows, `{"id": "…"}`, or `{"paragraph": locator}`, the row holding that paragraph in the innermost table holding it |
| a column | `{"table": {...}, "index": 2}`, counting the table's grid columns from 0 (a cell's `column` in the listing, never its index), or `{"paragraph": locator}`, the column of the cell holding that paragraph; a paragraph in a cell that spans several grid columns can't name a column |
| a cell | `{"row": {...}, "index": 1}` among its row's cells, `{"id": "…"}`, or `{"paragraph": locator}` |
| a comment thread | `{"index": 2}`: top-level threads in document order, or `{"id": "…"}`. Replies aren't counted, since replying and resolving act on a thread. |
| a note | `{"kind": "footnote", "index": 2}`, or `{"kind": "footnote", "id": "…"}` |
| a header or footer | `{"kind": "header", "type": "first", "section": {"index": 1}}`, `{"kind": "header", "index": 0}` (the headers in order of the first section that shows each), or `{"kind": "header", "id": "…"}` |
| revisions | `{"index": 4}` or `{"id": "…"}`, one entry of the listing's revisions; `"all"`; or a filter `{"author"?: "…", "type"?: "insertion" \| "deletion" \| "move" \| "property"}`, where `property` covers every formatting, paragraph, numbering, table and section property change |

Any locator but a cell's may instead be `{"ref": "…"}`, which names something this request creates
([Refs](#refs)).

### Counting

Every `index` counts from 0, because it is a position in the listing's arrays: paragraphs, tables, rows, cells,
grid columns, sections, notes, comment threads, pictures and revisions. What counts from 1 counts as a reader does:
`occurrence`; `list_level` (1 to 9, as Word's interface shows it), so `pattern` writes level 3 as `%3`; and
table-of-contents heading levels.

### `paragraphs`

Every operation that acts on paragraphs names them in one required field, `paragraphs`:

| Form | Names |
|---|---|
| a locator | that paragraph |
| `{"from": locator, "through": locator}` | the uploaded paragraphs between the two, inclusive, in uploaded order, within one part. A range of refs from one `insert_paragraphs` names those new paragraphs. |
| an array of locators and ranges | each of them; a paragraph named twice counts once |
| `"all"` | every paragraph of every part (body, headers, footers, footnotes, endnotes) |
| `{"all": true, "part": {...}}` | every paragraph of the matching parts; a `part` with only `kind` matches every part of that kind. `part` is required in this form. |

- **No implicit scope.** A whole-document `replace` says `"paragraphs": "all"`.
- **Only uploaded paragraphs.** The `"all"` forms, and ranges between uploaded locators, name uploaded
  paragraphs only ([How a request is applied](#how-a-request-is-applied)).
- **Ranges and tables.**
  - A range that covers a whole table includes the table, so moving or deleting the range moves or deletes
    the table.
  - A range for `delete_paragraphs`, `move_paragraphs` or `join_paragraphs` that covers only part of a
    table is refused, whichever end is inside it. So is one that spans more than one cell. A range wholly
    inside one cell is fine.
  - Deleting every paragraph of a cell is refused.
- **How many.** `split_paragraph`, `add_hyperlink`, `set_hyperlink` and `remove_hyperlink` take exactly one
  locator, since a Word hyperlink can't cross paragraphs. `add_comment` takes one locator or one range; a
  range makes one comment spanning it. `join_paragraphs` takes one range.

### Destinations

`insert_paragraphs`, `move_paragraphs`, `insert_table` and `insert_table_of_contents` place content with
`"destination": {"paragraph": locator, "position": "before" | "after"}`.

- A destination at a paragraph in a table cell places the content in that cell. An `insert_table` there
  makes a nested table.
- `{"table": locator, "position": "before" | "after"}` places content directly before or after a table.
- `{"position": "start" | "end"}`, naming no paragraph or table, places content at the start of the body,
  before its first block, or at its end, after its last block. `start` and `end` take no paragraph or table, and
  `before` and `after` need one. Content placed at the start comes in request order;
  content placed at the end goes after what earlier edits placed there. A table placed at the end gets the
  empty paragraph Word needs after it, and content placed at the end later goes before that paragraph and,
  when it ends in a paragraph, takes its place. A document with no paragraph to name, such as the blank
  document `POST /v1/create` starts from, takes its first content here.
- Content placed after one paragraph or table comes before content placed before the next one.
- `insert_table_row` uses `{"row": locator, "position": …}`, and `insert_table_column` uses
  `{"column": locator, "position": …}`.

### Refs

An edit that creates something can carry `"ref": "<name>"`, and later edits use `{"ref": "<name>"}` wherever
that kind of locator is expected. Ref names form one namespace per request, and each is defined once,
before it is used.

| Created by | Names |
|---|---|
| `new_paragraphs` items | the new paragraph |
| `split_paragraph` | the second half |
| `insert_section_break` | the new section after the break; the original `{"index": n}` still names the section before it |
| `insert_table`, `insert_table_row`, `insert_table_column` | the new table, row or column |
| `add_header_footer` | the new part: `{"part": {"ref": "hdr1"}, "index": 0}` |
| note runs | the new note: `{"part": {"ref": "fn1"}, "index": 0}` |
| `add_comment` | the new thread: `reply_comment` with `"comment": {"ref": "c1"}` |

## Selecting text

`text` selects text in the named paragraphs, and `at` names an insertion point:

```text
"text": {"match": "Series A Preferred Stock", "prefix": "the ", "suffix": " issued", "occurrence": 2}
"at": "start" | "end"
"at": {"after": {"match": "Name:\t"}}       // a selector: prefix, suffix and occurrence apply
"at": {"before": {"match": "Exhibit"}}
```

`insert` and `split_paragraph` take `at`. An `insert` applies once at each point it names: with `"start"` or
`"end"`, once per named paragraph; with a selector, once per match.

- **What it matches.** A selector matches only the uploaded text of the named paragraphs, and a match never
  crosses a paragraph boundary.
- **Matching rules.**
  - Matching is case-sensitive.
  - In a `match` string, `\t` matches a tab run and `\n` a line break.
  - These characters are read alike: a non-breaking, thin or hair space and a space; curly and straight
    quotes; en and em dashes and a hyphen; a character with a one-character Unicode compatibility form and
    that form. Nothing else is folded (`…` is not `...`).
- **Run form.** A `match` is a string or an array of runs. The run form is for content with no characters:
  `[{"type": "break", "kind": "page"}]` matches a page break and `[{"type": "picture", "index": n}]` (or
  `"id": "…"`, or both) the listing's picture `n`, for `delete` only, without `prefix` or `suffix`. The picture must be in the named
  paragraphs, and not in a tracked deletion or a move's old place.
- **`prefix` and `suffix`.** Optional text that must sit immediately before and after the match. They
  filter the candidates before counting, and the edit never acts on them.
- **`occurrence`.** It counts across every paragraph the edit names, in listing order: the body; for each
  section in turn, its header then its footer, each in the order `default`, `first`, `even`; footnotes;
  endnotes. A shared part counts once, at its canonical section.
  - Omitted: the match must be unique across them.
  - `N`: the Nth match, e.g. the second "Acme" in the document with `"paragraphs": "all"`.
  - `"all"`: every match. At least one must exist.
- **Character ranges are half-open.** Two that touch end to end don't overlap.
- **One match when an edit creates something.**
  - `add_comment`, `add_hyperlink`, `set_hyperlink`, `remove_hyperlink` and `split_paragraph` must select
    exactly one range or point.
  - So must an `insert` whose `content` holds a note run with a `ref`.
  - `split_paragraph` at `"start"` or `"end"` is refused, since it would make an empty paragraph. Use
    `insert_paragraphs` for that.
  - `add_hyperlink` over text that is already linked is refused. Use `set_hyperlink`.
- **Required or optional.**
  - `delete` and `replace` require `text`.
  - `format`, `add_comment` and `add_hyperlink` without `text` act on the whole paragraph.
  - `set_hyperlink` and `remove_hyperlink` without `text` act on the paragraph's only hyperlink.
- **New content.** Text in a paragraph this request creates can't be selected, since it isn't in the
  listing. The paragraph itself can be named by ref for paragraph-level edits. New sections, tables, rows,
  columns, notes and comments can be named by ref by any edit that takes that kind of locator.
- **Hidden content.** Note references, comment anchors, bookmarks, pictures and objects are skipped by matching, so
  `"409A."` matches across a footnote mark between "409A" and ".". A `replace` or `delete` whose range
  would contain a note reference or image is refused. Comment anchors and bookmarks stay in place.
- **Field results.** A field's computed result counts as text, as the listing shows it, and can anchor a
  selector, but a `replace`, `delete` or `format` range holding part of a result, and an insertion point
  strictly inside one, are refused, since updating the field would undo the edit; edit the field's target
  instead. A range holding a whole result, such as "Section 1.3 below" where "1.3" is a `REF` result,
  removes the field and is accepted. A `HYPERLINK` field's result is ordinary link text.
- **Splits and joins.** A split or join doesn't change what a selector sees. Text is selected through the
  uploaded paragraph that held it: the original locator of a split paragraph, or the merged-in
  paragraph's own locator.

A selector that matches nothing, or more than once without `occurrence`, is refused naming the count and,
when the text is elsewhere, the paragraphs holding it: `edit at index 0 (replace): "Preferred" matches 3
times in paragraph 8; give prefix or suffix so it matches once, or occurrence (1 to 3, or "all")`.

**Signature lines.** A signature line is usually a label, a plain tab to where the line starts, and an
underlined tab drawing the line to the next tab stop (`"Name:"`, tab, underlined tab). Insert the value
after the label's tab, `"at": {"after": {"match": "Name:\t"}}`: it takes the plain tab's formatting and the
underlined tab still draws the rest of the line. To have the value replace the line, replace the
underlined tab: `"text": {"match": "\t", "prefix": "Name:\t"}`. The new text takes the tab's formatting,
underlined; a run with `"format": {"underline": false}` gives plain text.

## Content

New text is always `content`: in `insert`, `replace`, `add_comment`, `reply_comment` and
`add_header_footer`, in note runs, in table cells and in `new_paragraphs` items. `content` is a string or an
array of runs, in the listing's shapes.

| Run | Inserts |
|---|---|
| `{"text": "…"}` | text |
| `{"type": "tab"}` | a tab |
| `{"type": "break", "kind": "line" \| "page" \| "column"}` | a break; page and column breaks only in the body and its tables |
| `{"type": "field", "code": "PAGE"}` | a field ([Field and cross-reference runs](#field-and-cross-reference-runs)) |
| `{"type": "cross_reference", "to": locator, "show": "text" \| "number" \| "page" \| "above_below"}` | a reference to a paragraph |
| `{"type": "footnote" \| "endnote", "content": …}` or `{…, "new_paragraphs": [{"content": …}]}` | a note, with one paragraph or several ([Note runs](#note-runs)) |
| `{"type": "hyperlink", "url": "…", "content": …}` | new linked text |
| `{"type": "picture", "source": {"file": "…"} \| {"data": "…"}, "width"?, "height"?, "alt_text"?}` | a picture in line with the text ([Pictures](#pictures)) |

- Any run can carry `format` ([Property values](#property-values)). A note run can carry a `ref`.
- In a `content` string or a text run, `\t` becomes a tab run and `\n` a line break. New paragraphs come
  only from `insert_paragraphs`, `split_paragraph` and the `new_paragraphs` forms.
- A table cell's value is `content` (one paragraph) or `{"new_paragraphs": […]}`, whose items are
  `insert_paragraphs` items.
- A comment's `content` (`add_comment`, `reply_comment`) is one paragraph: a string, or text, tab and
  line-break runs. Other runs are refused.
- A note run's content holds no picture or note. A hyperlink's content holds text, tab and break runs.
- `replace` requires non-empty `content`. Removing text is a `delete`.
- Plain new text takes the formatting of the text before it, or of the text after it at a paragraph's
  start, as typing does in Word; in an empty paragraph, the paragraph mark's. A `replace`'s plain
  `content` takes the formatting of the first character it replaces.
- Text an edit writes may not hold a control character other than tab, newline and carriage return, nor
  U+FFFE or U+FFFF, which a Word document cannot store.

## Pictures

A `picture` run inserts an image in line with the text, as a tracked insertion, wherever content goes:
`insert`, `replace`, `insert_paragraphs` items, table cells, `add_header_footer`, and paragraphs of a header,
footer or note. A comment and a note run hold no picture.

```json
{"type": "insert", "paragraphs": {"index": 0}, "at": "start",
 "content": [{"type": "picture", "source": {"file": "logo.png"}, "width": 144, "alt_text": "Acme logo"}]}
```

| Field | |
|---|---|
| `source` | `{"file": name}`, the file name of a `pictures` file uploaded with the request (multipart), or `{"data": base64}`, the image's bytes in base64, which count toward the 200 KB of edits. Exactly one. A request sent as JSON alone gives `data`. |
| `width`, `height` | Optional, in points, above 0 and at most 1584 (22 inches). With one, the other keeps the image's proportions; with neither, the picture takes the image's own size at its resolution (96 dpi when the file states none), made smaller to fit the text or cell width. An explicit size is not fitted. |
| `alt_text` | Optional: the picture's alternative text. |
| `format` | Optional character format of the picture's run. |

An image is a PNG, JPEG or GIF, its type read from its bytes rather than its file name. Linked (external)
images are not supported. Each image is at most 5 MB and 10,000 pixels on a side; a request inserts at most
20 different images, 25 MB in all (the same image used twice counts once), and uploads at most 20 files.
Each of these is refused at request time with `INVALID_EDIT`, together with the request's other refusals:
an unsupported or corrupt image, one past a limit, `source` with both or neither of `file` and `data`,
`data` that is not base64, a `file` no upload has, an upload no picture run names, two uploads with the
same file name, `width` or `height` at or below 0 or above 1584, and a picture in a comment or note run.

```bash
curl -sS -X POST "https://api.versionstory.com/v1/edit" -H "Authorization: Bearer vs_live_..." \
  -F file_id=3f2a… -F schema_version=1 -F pictures=@logo.png \
  -F 'edits=[{"type": "insert", "paragraphs": {"index": 0}, "at": "start",
              "content": [{"type": "picture", "source": {"file": "logo.png"}}]}]'
```

A listed picture is deleted with `delete` and its run-form match:
`{"type": "delete", "paragraphs": {"index": 3}, "text": {"match": [{"type": "picture", "index": 1}]}}`.

## Naming rules

- **Reserved keys.** `type`, `text` and `at` are reserved among an edit's keys. Inside a run, `type` is the
  run's type and `text` its characters, as in the listing.
- **Property objects.** As a key, `format` always holds a character-format object. Paragraph properties
  nest under `paragraph_format` and page properties under `page_setup`, the same objects the listing
  returns.
- **Field names.** Styles are named by `style_id`. `insert_paragraphs` takes `new_paragraphs`, because
  `paragraphs` is a locator field.
- **Operation names.** `insert_*` places new content at a position, and `add_*` attaches something to
  existing content. `delete*` removes content, and `remove_*` unwraps something and keeps its content.
- **Enums.** Every enum value is snake_case: `lower_roman`, `next_page`, `at_least`.

## Property values

For every property in `format`, `paragraph_format` and `page_setup`:

| Value | Effect |
|---|---|
| a value | sets the property |
| `false` | turns it off, even where the style turns it on |
| `null` | removes the direct setting, so the style decides |
| key omitted | leaves the property alone |

- **Toggles.** `bold`, `italic`, `small_caps`, `keep_with_next` and the like take `true`, `false` or `null`.
- **Enums with an off state.** `underline`, `highlight` and `strike` take an enum value, `false` for off, or
  `null`. `true` means `"single"` for `underline` and `strike`; it is refused for `highlight`.
- **Everything else.** Other properties take a value or `null`.
- **Page setup.** A section has no style for `null` to fall back to, so in `page_setup` only
  `different_first_page` and `page_numbering` (and its fields) take `null`.
- **Nested objects merge field by field.**
  - For example, `{"indent": {"left": 108}}` keeps the paragraph's other direct indent values.
  - Setting `first_line` removes `hanging`, and setting `hanging` removes `first_line`.
  - `null` for a whole object (`"indent": null`) removes all of its direct values.
  - The same merge applies to `margins`, `spacing` (which holds `before`, `after` and `line_spacing`),
    `page_numbering` and `settings`.
  - `line_spacing` is one value in one of three forms (`{"multiple": 1.15}`, `{"exact": 14}` or
    `{"at_least": 14}`), so it is replaced whole.
- **Whole paragraphs.** A `format` without `text` formats the paragraph mark too, as selecting a whole
  paragraph in Word does.
- **Removing everywhere.** To "remove X everywhere", send `false`. `null` only removes direct formatting,
  so a heading that is bold through its style would stay bold.
- **Units.** Indents, spacing, margins, paper size and `font_size` are in points.

## How a request is applied

Three rules:

1. **Addresses mean the document as you read it.** Every locator and selector refers to the uploaded
   document, the one the listing described, whatever earlier edits in the request have done.
2. **Edits apply in request order.** When two edits set the same property of the same text or paragraph,
   the later one wins.
3. **An edit whose target an earlier edit removed or rewrote is refused**, and so is the whole request.

The result is the same as sending each edit as its own request in turn, except that every edit's addresses
come from the one listing the agent read. The request is all or nothing, and revision decisions go alone
(see [Editing documents](/developers/rest/edit#how-a-request-is-applied)).

### Addresses

- **Indexes don't shift.** If edit 1 inserts a paragraph after paragraph 5, edit 2's `{"index": 10}` still
  means the paragraph the listing called 10, now eleventh in the document. An `id` names the same thing it named
  in the listing, wherever earlier edits moved it.
- **Table rows and columns don't shift.** `{"table": {"index": 1}, "index": 3}` is the listing's row 3
  (or grid column 3) whatever earlier edits inserted. A new row's or column's `cells` count the table as
  the earlier edits left it ([`insert_table_row`](#inserttablerow-and-deletetablerow)).
- **Positions don't shift.** A selector matches the uploaded text of the paragraphs it names. Text that
  earlier edits inserted is never matched; text they removed can't be matched (rule 3).
- **Wide forms.** `"all"`, `{"all": …}`, ranges and arrays name uploaded paragraphs. Paragraphs an earlier
  edit removed are skipped.
- **Refs name what an earlier edit created.** A ref used before the edit that creates it is refused. A new
  paragraph can be named by ref for paragraph-level edits: `set_paragraph_style`, `set_paragraph_format`,
  `set_numbering`, `format` without `text`, `add_comment` without `text`, `insert` at `"start"` or `"end"`,
  and as a `destination` or cross-reference `to`. Its text can't be selected, since it isn't in the
  listing; give it its formatting in the creating edit, or format the whole paragraph.
- **Moved paragraphs.** A locator for a paragraph that an earlier edit moved still names it, in its new
  place. A destination at it places content next to its new place.
- **Moves and other edits to the same paragraphs.** A `move_paragraphs` can come before or after
  edits to the paragraphs it moves (`replace`, `format`, `set_paragraph_style`, a comment, a
  hyperlink, ...); the result is the same either way, with the edits' changes inside the moved
  paragraphs.
- **Split paragraphs.** The original locator names both halves for paragraph-level edits. Its text is
  selected through the original locator. The second half can also be named by the split's ref.
- **Joined paragraphs.** A paragraph that an earlier `join_paragraphs` merged away can't be named for
  paragraph-level edits. Its text is still selected through its own locator, since joining keeps the text.
- **Deleted paragraphs as destinations.** A destination at a paragraph an earlier edit deleted places the
  content where that paragraph was.
- **`style_template`** styles are imported before the first edit.
- **Field results** are computed after the last edit, so a new table of contents or cross-reference
  reflects the edited document. `refresh_fields` does the same for existing fields. Results that need page
  layout are those the field tables below say the server can compute; any others are marked for Word to
  update on open.
- **Cross-references stay current.** After the last edit, every `REF` field in the document (body, headers,
  footers and notes) is recomputed; see [Cross-references after edits](#cross-references-after-edits).

### Order

- **Later wins.** "Unbold everything" then "bold Purchase Price" leaves only Purchase Price bold. Listed
  the other way round, everything ends up unbolded.
- **Lists and indents.** The indent a list level brings never overrides a direct `indent`, in either order,
  as in Word.
- **Same position.** Content inserted at the same point appears in request order, across operation types.
  - A `replace` puts its new text at the start of its range, so an `insert` at that point and the
    replacement come out in request order.
  - An insert at the end of a replaced or deleted range goes after it.
  - Two destinations are the same point only when they name the same paragraph or table and position.
    Content placed after one paragraph or table comes before content placed before the next one.
  - Content placed after the paragraph holding a new section break goes into the new section.
- **Comments.** Comments added to the same range, and replies to one thread, appear in request order. A
  comment's range follows its text, so a later `replace` inside it leaves the deleted and the new text
  inside the comment.
- **Revision decisions** apply in request order too, and the later decision wins for a revision. "Reject
  everything, then accept author A's" is `reject_revision` with `"all"`, then `accept_revision` with
  `{"author": "A"}`.

### Positions

- **Points.** An insertion point is a position in the uploaded text. `"start"` is before everything in the
  paragraph, and `"end"` after everything, hidden content included.
- **Hidden content.** Matching skips note references, comment anchors, bookmarks and tracked deletions, so
  a point next to them needs a side.
  - A point after a match sits immediately after its last character, before any hidden content.
  - A point before a match sits immediately before its first character, after any hidden content.
  - In a paragraph ending in a footnote mark, after the last word and `"end"` are different points.
- **Formatting of plain new text.** See [Content](#content).
- **Clearing never deletes characters.** A leading tab is removed with a `delete`.
- **New table cells** copy the paragraph and run formatting of a neighbouring cell. A new row copies the row it
  is placed beside (its destination `row`), and a new column copies its destination `column`.

### Refused

Every refusal in a request is reported together, naming the edits involved.

**Rule 3: the target is gone or rewritten.**

| An edit that… | after an earlier edit… |
|---|---|
| selects text (`text` or an `at` anchor; `prefix` and `suffix` excluded) overlapping characters | replaced or deleted them |
| has an insertion point strictly inside a range | replaced or deleted that range |
| names a paragraph for a paragraph-level edit, or as a destination | merged it away with `join_paragraphs`, or deleted it (except as a destination, above) |
| names a table, row, column, section or note | deleted it, or removed the section's break |
| has an insertion point at a split point, or a `replace` or `delete` crossing one | split the paragraph there |
| splits inside a range | replaced or deleted that range |
| names a header or footer through a section that now shows a different part | added a header or footer with `add_header_footer` (name the new part by its ref) |

`prefix` and `suffix` only check context, so they may overlap text an earlier edit changed.

**Refused in any order.**

| Combination | Why |
|---|---|
| A `replace`, `delete`, `insert` or `format` inside a field result | Updating the field would undo it. Edit the field's target instead. |
| Two `add_hyperlink` ranges that overlap | Word can't nest hyperlinks. |
| A `move_paragraphs` whose destination is inside the paragraphs it moves | The destination moves with them. |
| A row insert inside a vertically merged span, or a column insert or delete through a cell spanning several grid columns | The grid can't keep or split the merge. |
| Selecting text in a paragraph this request creates | It isn't in the listing. Describe it in the creating edit. |
| A `ref` on something an edit would create more than once | It couldn't name one thing. |

## Text

### `insert`

`insert` puts `content` at `at` in each named paragraph: once per paragraph with `"start"` or `"end"`,
once per match with a selector. Inserted content is a tracked insertion (`w:ins`); accepting keeps it and
rejecting removes it.

```json
{"type": "insert", "paragraphs": {"index": 304}, "at": {"after": {"match": "Name:\t"}}, "content": "Jordan Bryan"}
{"type": "insert", "paragraphs": {"index": 27}, "at": "start", "content": "2026"}
{"type": "insert", "paragraphs": {"index": 31}, "at": {"after": {"match": "Signature:"}},
 "content": [{"type": "tab", "format": {"underline": true}}, {"type": "break", "kind": "line"},
             {"text": "Name and title", "format": {"bold": true}}]}
{"type": "insert", "paragraphs": {"index": 9}, "at": {"after": {"match": "See "}},
 "content": [{"type": "hyperlink", "url": "https://example.com/schedule-a", "content": "Schedule A"}]}
```

To fill an empty paragraph, insert at `"start"`. A break is a run holding `w:br` (a line break has no
type; `w:type="page"` or `"column"`) and a tab one holding `w:tab`, inside the insertion. A `hyperlink`
run is written as a `HYPERLINK` field, the form in which Word can track a link, in one `w:ins`.

### Page breaks

A page break is a `break` run of `kind` `page`. Insert one with `insert`, and remove one with `delete`
whose `match` is that run (with `occurrence` when the paragraph holds several). Each is tracked: the
break's run in `w:ins`, or in `w:del` split from any text sharing it. A paragraph that starts a new page
through `page_break_before` holds no break: [`set_paragraph_format`](#setparagraphformat) with
`"page_break_before": false` removes the property. Turning a page break into a section break that
restarts page numbering is three edits:

```json
[{"type": "delete", "paragraphs": {"index": 129}, "text": {"match": [{"type": "break", "kind": "page"}]}},
 {"type": "insert_section_break", "paragraphs": {"index": 129}, "section_type": "next_page", "ref": "schedule"},
 {"type": "set_page_setup", "section": {"ref": "schedule"}, "page_setup": {"page_numbering": {"start": 1}}}]
```

Inserting one:

```json
{"type": "insert", "paragraphs": {"index": 42}, "at": "start", "content": [{"type": "break", "kind": "page"}]}
```

A page or column break goes in the body and its tables only: Word offers neither in a header, footer,
note or text box.

### `replace` and `delete`

`replace` deletes the selected text and inserts `content` (required, non-empty) at the start of its
range; `delete` deletes it. Deleted text is a tracked deletion (`w:del`), new text a tracked insertion.

```json
{"type": "replace", "paragraphs": {"index": 8}, "text": {"match": "Preferred", "prefix": "other Series A "}, "content": "Common"}
{"type": "replace", "paragraphs": "all", "text": {"match": "Acme Ltd", "occurrence": "all"}, "content": "Acme Holdings Ltd"}
{"type": "delete", "paragraphs": {"index": 71}, "text": {"match": "[", "occurrence": "all"}}
```

A `replace` or `delete` whose text isn't in the named paragraphs is refused, naming the paragraphs where
the text is. A page break is removed with `delete`; `replace` can't select one.

### `format`

`format` changes the character formatting of the selected text, or of whole paragraphs, marks
included, without `text`. The text is split into runs of its own, each recording its previous formatting
in a `w:rPrChange`, a tracked formatting change: the redline shows "Formatted" changes and the clean copy
keeps the new formatting.

```json
{"type": "format", "paragraphs": {"index": 12}, "text": {"match": "Effective Date"}, "format": {"bold": true, "highlight": "yellow"}}
{"type": "format", "paragraphs": "all", "format": {"bold": false}}
```

| `format` property | Values |
|---|---|
| `bold`, `italic`, `small_caps`, `all_caps`, `superscript`, `subscript` | `true`, `false` or `null` |
| `underline` | `true` (single), `single`, `double`, `thick`, `dotted`, `dash`, `wave`, `words`, `false` or `null` |
| `strike` | `true` (single), `single`, `double`, `false` or `null` |
| `highlight` | a Word highlight color (`yellow`, `green`, `cyan`, `magenta`, `blue`, `red`, `black`, `white`, `dark_blue`, `dark_cyan`, `dark_green`, `dark_magenta`, `dark_red`, `dark_yellow`, `dark_gray`, `light_gray`), `false` or `null` |
| `color` | six hex digits (`1F3864`), or `null` |
| `font_size` | points, in halves (`10.5`), or `null` |
| `font` | a font family name, or `null` |

At least one property is required, and `superscript` and `subscript` cannot both be `true`. The listing
reads formatting back as this object, which an edit takes unchanged: each text segment's `format` holds the
properties above that the text has through its styles, in these values (`"strike": "double"` for a double
strike); `color`, `font_size` and `font` appear where they differ from the document's default text formatting.
An underline style no edit sets, such as a dot-dash one, is left out.

## Paragraphs

### `insert_paragraphs`

`insert_paragraphs` places `new_paragraphs` at a destination, each item a Word paragraph in array order
and a tracked insertion of its text and paragraph mark.

```json
{"type": "insert_paragraphs", "destination": {"paragraph": {"index": 24}, "position": "after"},
 "style_id": "Normal", "list": {"new": "bulleted"}, "paragraph_format": {"indent": {"left": 108, "hanging": 18}},
 "new_paragraphs": [{"content": "Constitutional Amendments.", "style_id": "Heading3", "list": null},
                    {"content": "First Amendment (1791): freedom of religion and speech"},
                    {"content": [{"text": "Second Amendment", "format": {"italic": true}}, {"text": " (1791)"}], "ref": "second"}]}
```

- **Items.** Each takes `content` (omitted or `""`, an empty paragraph) and, optionally, `style_id`,
  `paragraph_format`, `list`, `list_level` (1 to 9) and `ref`.
- **Operation-level values.** `style_id`, `paragraph_format`, `list` and `list_level` on the edit apply to
  items that don't set their own; an item's `paragraph_format` merges over the operation's. With no style
  from either, the document's default paragraph style applies.
- **`list`.** `{"new": "bulleted" | "numbered"}` on the edit makes its items one new list; on an item it
  starts a list of its own. `{"same_as": locator}` joins an existing list. An item's `"list": null`
  leaves it out of the operation's list, so its style's numbering, if any, applies; `false` gives it no
  numbering. `list_level` defaults to the operation's, else the `same_as` paragraph's, else 1. A paragraph
  whose style already numbers it in that list at that level, as a heading style numbers a heading, joins
  it through its style, with no numbering of its own, so it keeps the style's indents as the list's other
  paragraphs do; otherwise the paragraph names the list and level itself and takes the level's indents.
- **Typography.** A new paragraph in the style of the destination paragraph continues that
  paragraph's character formatting, as pressing Enter in Word does: its text takes the destination's
  paragraph mark formatting, with each run's `format` over it. A paragraph in another style takes the
  formatting most of the text of the nearest paragraph of its own style has (in the same part and at the same
  level, body or table cell, before the destination, else after it), so it reads as the document's other
  paragraphs of that style do where a template sets their size or font on the text rather than in the
  style; with none, it starts from its style alone. A new paragraph's mark, which sets the size and font
  of a list number, takes the formatting its text shares; an empty paragraph's mark takes the one it
  continued.

A destination is a paragraph directly in the body or a table cell. A missing paragraph style fails the
job with `INVALID_EDIT`. For Markdown input, parse it in the client and send each paragraph's text,
runs and style; `.md` and `.txt` uploads are unsupported, so start from a blank DOCX.

### `delete_paragraphs`

`delete_paragraphs` deletes whole paragraphs as tracked deletions of their text and paragraph marks, so
accepting it leaves the following paragraph exactly as it was. A range covering a whole table deletes the
table's rows as tracked row deletions.

```json
{"type": "delete_paragraphs", "paragraphs": [{"from": {"index": 9}, "through": {"index": 11}}, {"index": 171}]}
```

The last paragraph of the document, a cell, a header, a footer or a note holds the mark Word requires
there, so deleting it deletes its text and the mark of the paragraph before it instead, and that mark's
properties pass to the last one as tracked changes: accepting leaves the paragraph before it last, as it
was, and rejecting restores both.

Refused: a range covering part of a table or more than one of its cells; every paragraph of a cell
(delete the row, column or table); the only paragraph of the document, a header, a footer or a note, and
a last paragraph after a table or content control, or after a paragraph that ends a section (delete its
text with `delete`); a paragraph that ends a section; paragraphs that are all that keeps
two tables apart, since Word joins tables that touch; and a paragraph holding a bookmark that a `REF` or
`PAGEREF` field in another paragraph points to, unless the request deletes that paragraph too or a `replace` or
`delete` removes the field (the refusal names the referencing paragraphs; see
[Cross-references after edits](#cross-references-after-edits)).

### `move_paragraphs`

`move_paragraphs` moves paragraphs, marks included, to a destination as a Word tracked move. A range
covering whole tables moves them too: Word can't track a table move, so the table's rows are deleted where
they were and an inserted copy, keeping the ids, goes to the destination.

```json
{"type": "move_paragraphs", "paragraphs": {"from": {"index": 9}, "through": {"index": 11}},
 "destination": {"paragraph": {"index": 19}, "position": "after"}}
```

At the old place a copy of each paragraph stays as moved-from content (`w:moveFrom`, between
`w:moveFromRangeStart` and `w:moveFromRangeEnd`); at the destination the paragraphs themselves, keeping
their ids, are moved-to content (`w:moveTo`), the two ranges sharing one `w:name`. Accepting leaves the
paragraphs at the destination only; rejecting restores them, and the copies have new ids. Deciding either
half decides the whole move. A moved footnote or endnote reference refers to a new note holding the
note's paragraphs, marked inserted, while the old note's text is marked deleted; deciding the move decides
them, so each note is in the document once either way. Bookmark and comment anchors inside the moved
paragraphs move with them; a bookmark reaching outside them that no field or hyperlink names keeps its
inside end at the old place.

`pending_changes` is optional: `"accept"` or `"reject"` settles the uploaded document's tracked changes
inside the moved paragraphs (and their notes) before the move, so the move carries clean content.
`"accept"` keeps insertions and drops deletions; `"reject"` drops insertions and restores deletions. Without
it, a move of paragraphs that hold tracked changes is refused, and the refusal lists those changes. It
settles only the changes inside the moved paragraphs; changes elsewhere are left as they are.

```json
{"type": "move_paragraphs", "paragraphs": {"index": 9}, "pending_changes": "accept",
 "destination": {"paragraph": {"index": 19}, "position": "after"}}
```

A move does not depend on where it sits in the request relative to other edits to the same paragraphs:
`replace`, `format`, `set_paragraph_style`, `add_comment` and the like give the same document before or
after the move.

Refused: a destination among the moved paragraphs, where they already are, or after the last paragraph
of the body or a cell; paragraphs outside the body and its tables; a range covering part of a table;
moving the last paragraph of the body or a cell, a paragraph that ends a section, or paragraphs that keep
two tables apart; moved paragraphs or their notes holding tracked changes (unless `pending_changes` is given), pictures, embedded objects,
content controls, permission ranges, or a field that fetches or runs content (`INCLUDETEXT`, `INCLUDE`,
`INCLUDEPICTURE`, `DDE`, `DDEAUTO`, `IMPORT`, `LINK`, `MACROBUTTON`, `DATABASE`, `RD`); part of a field
or comment reaching outside them; and a moved table holding tracked changes, notes, comments or
bookmarks.

### `split_paragraph`

`split_paragraph` ends a paragraph at one point with a new paragraph mark, as pressing Enter does. `at`
is a selector matching once; `"start"` and `"end"` are refused (use `insert_paragraphs`). `ref` names
the second half.

```json
{"type": "split_paragraph", "paragraphs": {"index": 12}, "at": {"after": {"match": "as follows:"}}, "ref": "rest"}
```

The split is tracked as an inserted paragraph mark (`w:pPr/w:rPr/w:ins`) ending the first half, which
keeps the paragraph's id; the second half has a new id. Both keep the paragraph's properties, and the
second half takes the original mark with what it carries (a section break, the mark's formatting).
Accepting keeps two paragraphs; rejecting merges them back. The original locator names both halves for
paragraph-level edits. A point inside a hyperlink, a field, a content control or a tracked change fails
the job with `INVALID_EDIT`.

### `join_paragraphs`

`join_paragraphs` merges a range of paragraphs into its first, as deleting the pilcrows does in Word.

```json
{"type": "join_paragraphs", "paragraphs": {"from": {"index": 40}, "through": {"index": 41}}}
```

Each join is tracked as a deleted paragraph mark (`w:pPr/w:rPr/w:del`). The joined paragraph keeps the
first paragraph's properties: where a following paragraph's differ, they change as a tracked property
change (`w:pPrChange`). Accepting leaves one paragraph; rejecting restores them. Refused: a range of
fewer than two paragraphs, across table cells or partly in a table; a paragraph ending a section, one
whose mark is already deleted, or one followed by a table.

### `set_paragraph_style`

`set_paragraph_style` gives paragraphs a paragraph style of the document or of the `style_template`.
`clear` takes any of `typography`, `numbering` and `paragraph`, or `"all"`.

```json
{"type": "set_paragraph_style", "paragraphs": [{"index": 3}, {"from": {"index": 5}, "through": {"index": 9}}],
 "style_id": "Heading3", "clear": "all"}
```

- `typography`: direct font family (theme fonts included), size, colour and character spacing. Bold,
  italics, underline, caps, highlight and character styles are kept.
- `numbering`: direct list numbering.
- `paragraph`: spacing, indentation, alignment, tab stops and outline level. Pagination and section
  boundaries are kept.

Anything outside those three categories, such as borders and shading, is kept.

The style and the cleared paragraph properties are one tracked paragraph property change (`w:pPrChange`),
and cleared typography a tracked formatting change (`w:rPrChange`). Clearing never deletes text: a
leading tab is removed with a `delete`. A style the paragraph already has, with nothing to clear, records nothing. A style the document
and `style_template` lack fails the job with `INVALID_EDIT`.

### `set_paragraph_format`

`set_paragraph_format` sets alignment, indents, spacing and pagination.

```json
{"type": "set_paragraph_format", "paragraphs": [{"index": 12}, {"index": 13}],
 "paragraph_format": {"alignment": "justify", "indent": {"left": 36, "first_line": 18},
                      "spacing": {"before": 0, "after": 6, "line_spacing": {"multiple": 1.15}}, "keep_with_next": true}}
```

| Property | Values |
|---|---|
| `alignment` | `left`, `center`, `right`, `justify`, or `null` |
| `indent` | `left` and `right` (points, -1584 to 1584), and `first_line` or `hanging` (0 to 1584) |
| `spacing` | `before` and `after` (points, 0 to 1584), `line_spacing` |
| `keep_with_next`, `keep_lines_together`, `page_break_before`, `widow_control` | `true`, `false` or `null` |
| `borders` | by side (`top`, `left`, `bottom`, `right`, `between`, `bar`), each a [border](#settableformat-and-setcellformat) or `null` |

Measures are rounded to the twentieth of a point and replace the paragraph's own value in every form
Word reads. The change is tracked: each paragraph's previous properties go into a `w:pPrChange`, so
rejecting restores them; a format the paragraph already has records nothing. Header, footer, note and
table-cell paragraphs are formatted the same way. Each listed paragraph's `paragraph_format` is this object,
which this edit takes unchanged, where the paragraph sets a property or differs from the document's default.

### `set_style_format`

`set_style_format` sets a paragraph style's own formatting, which every paragraph in the style, and in the
styles based on it, takes: keep with next on the section heading style keeps each heading on the page of
the clause below it.

```json
{"type": "set_style_format", "style_id": "Heading3", "paragraph_format": {"keep_with_next": true}}
```

`paragraph_format` takes `set_paragraph_format`'s properties; `null` removes the style's own setting, so
it inherits the setting of the style it is based on. Paragraphs that set a property themselves keep their
own value. The change is applied to the style definition untracked, as Word applies a modified style
(see [Tracked and untracked](#tracked-and-untracked)). The listing's `paragraph_styles` gives each
paragraph style in use, and the styles they are based on, with its `paragraph_format` in these terms. A
style the document does not define, or one that is not a paragraph style, fails the job with
`INVALID_EDIT`.

### `rename_style`

`rename_style` gives a style a new name, as Word's Modify Style › Name does. Word matches styles by name
when text comes in from another document, through an `INCLUDETEXT` field or a paste: a style whose name the
other document also uses takes that document's definition there, so a DPA's "heading 2" included in an
agreement whose "heading 2" is centred and bold becomes centred and bold. Under a name of its own, the
style comes in with its own definition and the text keeps its formatting wherever it goes.

```json
{"type": "rename_style", "style_id": "Heading2", "name": "DPA Heading 2"}
```

The style's new `style_id` is the name's letters and digits (`DPAHeading2`, numbered on when another style
has it), and everything that named the old id follows: the paragraphs, runs and tables in the style in every
part, including the earlier properties tracked changes hold, the styles based on it, following it or linked
to it, and the list levels it numbers. Its linked character style, named "Heading 2 Char" as Word names one,
becomes "DPA Heading 2 Char". A built-in heading's outline level, which Word gives "heading 1" to "heading
9" by name, is written into the style, so the renamed heading stays in the navigation pane and tables of
contents. Later edits in the request name the style by its new id. The rename is applied untracked, as Word
applies a modified style (see [Tracked and untracked](#tracked-and-untracked)).

Refused with `INVALID_EDIT`: a style the document does not define; the document's default style of its type
(Normal), which text without a style is in whatever its name; a name another style or one of Word's
built-in styles has, whatever its case; a name with a comma, which Word reads as separating a style's names;
and a style a `STYLEREF` or table of contents field names, since it would stop finding the style.

### `set_numbering`

`set_numbering` changes list numbering by `action`. `list_level` counts from 1, as the listing's `list_level` does.

```json
{"type": "set_numbering", "paragraphs": {"from": {"index": 20}, "through": {"index": 22}}, "action": "apply", "list": {"same_as": {"index": 19}}}
{"type": "set_numbering", "paragraphs": [{"index": 5}, {"index": 6}], "action": "apply", "list": {"new": "bulleted"}, "list_level": 1}
{"type": "set_numbering", "paragraphs": {"index": 21}, "action": "set_level", "list_level": 2}
{"type": "set_numbering", "paragraphs": {"index": 30}, "action": "restart", "start": 1}
{"type": "set_numbering", "paragraphs": {"index": 31}, "action": "continue"}
{"type": "set_numbering", "paragraphs": {"index": 40}, "action": "remove"}
{"type": "set_numbering", "paragraphs": {"from": {"index": 24}, "through": {"index": 41}},
 "action": "set_number_format", "list_level": 3, "number_format": "lower_roman", "pattern": "(%3)"}
```

- **`apply`.** The paragraphs become one list instance: a `{"new": "bulleted" | "numbered"}` list, with
  Word's own nine levels for its Bullets or Numbering button, or the list of a `same_as` paragraph. Each
  keeps its level (1 if it had none) unless `list_level` is given.
- **`restart` and `continue`.** At the first named paragraph, `restart` numbers from `start`, and
  `continue` resumes the previous list of the same definition, as Word's "Continue numbering" does. The
  items after it in its list, up to the next item at a higher level, go with it.
- **`set_number_format`.** Changes one level's `number_format` (`decimal`, `decimal_zero`,
  `lower_roman`, `upper_roman`, `lower_letter`, `upper_letter`, `ordinal`, `cardinal_text`,
  `ordinal_text`, `bullet`, `none`) and optional `pattern` (`(%3)` shows level 3's number in brackets)
  for the named paragraphs at that level. A run is a maximal sequence of named paragraphs at that level
  with only deeper-level paragraphs between them; a paragraph at that level that isn't named, or any
  paragraph at a parent level, ends the run. Each run gets its own list instance, with that level
  overridden, starting at the run's first current number, so numbers keep their values and change only
  their format; the deeper-level paragraphs between and directly after them move into it, so numbering
  still restarts under each item. A pattern showing another level's number is refused, and so, when the job runs, is a
  deeper level moving into the instance whose pattern shows a level above the changed one (level 4
  `"%2.%4"` when level 3 changes).

Each paragraph's numbering change is a tracked paragraph property change (`w:pPrChange`); removing
numbering a style gives writes `w:numId` 0, as Word does. The list instances and definitions a new list,
a restart or a format change needs are added untracked: Word cannot track numbering definitions. The
listing shows each numbered paragraph's `label` and `list_level`, and `list_styles`. Refused: a
paragraph that isn't numbered for `set_level`, `restart`, `continue` or `remove`, a level the list
doesn't define, and a `continue` with no earlier list of the same definition.

## Sections

A section break is the end of a paragraph that carries the properties of the section it ends: the
listing shows them as that paragraph's `section`, and the last section's as `final_section`, each with its
`section_type` and its `page_setup` in `set_page_setup`'s terms. OOXML keeps
one record of a section's earlier properties, so a section edit that must record a change on a section
whose properties already carry a tracked change is refused (accept or reject it first).

### `insert_section_break`

`insert_section_break` ends a section at each named paragraph, which must be directly in the body, not
already end a section and not be the body's last paragraph. A paragraph an earlier edit of the request
inserts may be named by its `ref`, so a request can add content and start a new section after it (an
exhibit or schedule on a new page). `section_type` is how the new section starts:
`next_page`, `continuous`, `even_page` or `odd_page`. `ref` names the section after the break; the
original index still names the one before it.

```json
{"type": "insert_section_break", "paragraphs": {"index": 129}, "section_type": "next_page", "ref": "schedule"}
```

Both sections keep the original's page setup, columns, headers and footers; the new one starts as
`section_type` says. The break is tracked as Word tracks an inserted section break: the paragraph ends
with an inserted paragraph mark carrying the new section's properties, and its original mark, tracked as
deleted, ends an empty paragraph after it; a change to how the following section starts is a
`w:sectPrChange`. All of it is one revision. A paragraph the request inserts carries the section's
properties on its own mark, part of that paragraph's insertion: rejecting it removes the paragraph and
its break together.

### `remove_section_break`

`remove_section_break` removes the break that ends `section`, joining its content to the next section.
`keep` says whose properties the merged section has:

- `"earlier"` (the default): `section`'s own headers, footers (every type), page setup, columns, page
  numbering, different first page and section type. The next section's settings as they were are a tracked
  `w:sectPrChange`; its header and footer references change untracked, since a `w:sectPrChange` cannot
  hold them, so rejecting restores the break and the next section's page setup but leaves that section
  with `section`'s headers and footers.
- `"later"`: the next section's properties, headers and footers, as Word does. The merged section starts
  as the removed one started (its section type and page-number restart), a tracked `w:sectPrChange`.

```json
{"type": "remove_section_break", "section": {"index": 1}}
```

The break is tracked as a deleted paragraph mark carrying the removed section's properties; a paragraph
holding nothing but the break is deleted whole. All of it is one revision. Removing several breaks in one
request gives the same clean copy in either order. Refused: the last section, a break that is itself a tracked
insertion (reject that revision instead), and a paragraph mark already tracked as deleted or moved.

### `set_page_setup`

`set_page_setup` changes the page of `section`: a section locator, an array of them, or `"all"`.

```json
{"type": "set_page_setup", "section": "all",
 "page_setup": {"orientation": "landscape", "paper": "a4", "margins": {"left": 72, "right": 72, "top": 54},
                "different_first_page": true, "page_numbering": {"start": 1, "number_format": "lower_roman"}}}
```

| Field | |
|---|---|
| `orientation` | `portrait` or `landscape` |
| `paper` | `letter`, `legal` or `a4`; or `page_width` and `page_height` (points, 72 to 1584) instead |
| `margins` | points by side: `top`, `bottom`, `left`, `right`, `header`, `footer`, `gutter` |
| `different_first_page` | `true` gives the first page its own header and footer (`w:titlePg`) |
| `page_numbering` | `start` (0 to 32767) and `number_format` (`decimal`, `lower_roman`, `upper_roman`, `lower_letter`, `upper_letter`) |
| `columns` | `count` (1 to 45) columns of equal width, and `spacing` between them in points (Word's 36 when the section sets none); the listing's `page_setup` shows a section's columns, in these terms, when its text runs in more than one of equal width |

`null` is accepted only for `different_first_page` and `page_numbering` (and its fields), since a section
has no style to fall back to. The change is tracked: the section's earlier properties are kept in a `w:sectPrChange`, and
a setting the section already has records nothing. `different_first_page` alone leaves the first page
with whatever first-page header and footer the section has, usually none; add them with
[`add_header_footer`](#addheaderfooter). The listing prints each section's `page_setup` as this object, which
this edit takes unchanged.

### `add_header_footer`

`add_header_footer` gives a section a header or footer of its own: `part` names `kind` (`header` or
`footer`), `type` (`default` or `first`) and `section`. Its text is `content` (one paragraph) or
`new_paragraphs` (1 to 100, each `content`, `style_id` and `paragraph_format`); with neither, the part is
one empty paragraph, a blank header or footer, and an item without `content` is an empty paragraph. `ref`
names the new part.

```json
{"type": "add_header_footer", "part": {"kind": "footer", "type": "default", "section": {"index": 0}}, "ref": "ftr",
 "new_paragraphs": [{"content": [{"text": "Page "}, {"type": "field", "code": "PAGE"}, {"text": " of "},
                                 {"type": "field", "code": "NUMPAGES"}], "paragraph_format": {"alignment": "center"}}]}
{"type": "format", "paragraphs": {"part": {"ref": "ftr"}, "index": 0}, "format": {"font_size": 9}}
```

The content is tracked as Word tracks typing into a new header: every run is a tracked insertion and
every paragraph mark but the last an inserted one, so rejecting leaves it empty. The new part
(`header1.xml`, `footer1.xml`, …) and the section's reference to it are applied untracked: Word records
neither as a revision.

A section without its own reference of a kind and type shows the previous section's part (Word's "Link to
Previous"), and the two sections `insert_section_break` makes name the same parts, so one part can show in
several sections. Adding to a section whose part another section also shows gives that section alone a new
part; every other section keeps what it showed, the next section getting its own reference to the old part
when it was linked to this one. When the section showed no part of the kind and type, later sections linked
to it show the new one. A `first` part shows only while `different_first_page` is on, and adding one
doesn't turn that on. Refused: a section whose part of that kind and type no other section shows, which is
its own (edit its paragraphs), and an `even` part, since creating one needs the document-wide even-and-odd
setting. After it, a locator naming that header or footer through a section that now shows the new part
is refused; name the new part by its ref.

## Tables

The listing shows a table's `index`, `id` and `style_id`, each row's `index`, `id`, `header` flag and tracked
`change`, and each cell's `index`, `id`, `span` and `change`. Every table edit is tracked, and `accept_revision`
and `reject_revision` decide each row, cell, grid and text revision it makes. A cell value is `content`
(one paragraph; `\n` is a line break) or `{"new_paragraphs": [...]}`, whose items are
[`insert_paragraphs`](#insertparagraphs) items (`content`, `style_id`, `paragraph_format`, `list`,
`list_level`, `ref`).

### `insert_table`

`insert_table` places a table at a destination. `rows` holds each row's cell values, every row spanning the
same number of grid columns (at most 63); `header_row` (default `false`), when `true`, repeats the first row on
each page and gives it the table style's header-row formatting. `ref` names the table. A cell of several
paragraphs may give `span`, the grid columns it is merged across (`{"new_paragraphs": [...], "span": 2}`, as
the listing gives a merged cell's `span`; 1 when left out): it counts as that many columns of its row.
`insert_table_row` and `insert_table_column` copy the cells beside them, so their cells take no `span`.

```json
{"type": "insert_table", "destination": {"paragraph": {"index": 49}, "position": "after"}, "header_row": true,
 "rows": [["Class", "Shares authorized"], ["Common stock", "[__________]"],
          [{"new_paragraphs": [{"content": "Preferred stock"}, {"content": "Series A", "style_id": "Normal"}]}, "—"],
          [{"new_paragraphs": [{"content": "Total authorized"}], "span": 2}]]}
```

A cell value of `""` is an empty cell: a paragraph with no text. The same holds for the `cells` of
`insert_table_row` and `insert_table_column`. Everywhere else, empty text is refused.

Every row is a tracked row insertion (`w:trPr/w:ins`) and its text a tracked insertion, so rejecting
removes the table. The table has single borders like Word's Table Grid, and its columns share the text
width of the section (or of the cell, for a nested table). Where it would touch another table or end its
container, an empty paragraph with a tracked inserted mark is added beside it. Headers, footers and notes
hold tables too, tracked the same way.

### `delete_table`

`delete_table` deletes a table as tracked row deletions of every row and its text; accepting removes it.

```json
{"type": "delete_table", "table": {"index": 2}}
```

A row another author already deleted is left as it is.

### `insert_table_row` and `delete_table_row`

`insert_table_row` adds a row before or after `row`, with one cell value per cell of `row`, the row it copies,
as the listing shows them (a cell spanning two grid columns is one value); `ref` names
it. `delete_table_row` deletes a row as a tracked row deletion (`w:trPr/w:del`) with its text deleted.

Rows and columns of one table can be inserted and deleted in one request, and each new row or column
counts its values against the table as the earlier edits of the request left it, the same as sending
the edits in turn. Locators still give the listing's row and column indexes.

- A row copies every cell of `row` as it is then: a column an earlier edit inserted gives it one more
  cell, and one more value.
- A column covers every row as it is then: a row an earlier edit inserted gets a cell in it, and one more
  value.
- A cell with a tracked deletion (`change` of type `deletion` in the listing, or a column an earlier edit
  deleted) is copied as an empty cell and takes no value. A row with a tracked deletion (or one an earlier
  edit deleted) gets an empty cell in a new column and takes no value.

```json
{"type": "insert_table_column", "destination": {"column": {"table": {"index": 1}, "index": 2}, "position": "after"},
 "cells": ["Integrated since", "March 15, 2021", "September 12, 2022"]}
{"type": "insert_table_row", "destination": {"row": {"table": {"index": 1}, "index": 2}, "position": "after"},
 "cells": ["Datadog, Inc.", "Sarah Mitchell", "Application performance monitoring", "June 3, 2024"]}
```

In the other order, the row takes three values and the column four.

```json
{"type": "insert_table_row", "destination": {"row": {"paragraph": {"index": 311}}, "position": "after"},
 "ref": "google", "cells": ["Google LLC", "Google Cloud Support", "AI-Enhanced PDF Conversion"]}
{"type": "delete_table_row", "row": {"table": {"index": 0}, "index": 3}}
{"type": "delete_table_row", "row": {"id": "6e127ae6b2e144759046e4f3f1fe01de"}}
```

The new row copies the cell layout of the `row` it is placed beside and formatting, without its revisions, vertical
merges or header flag (the `row` it is placed beside), and is a tracked row insertion with
its text tracked. Each new cell's text takes the run formatting carried by the most plain text in the
neighbouring cell, as text typed into a row Word inserts does. Refused: a row inside a vertically merged
span, and a cell count other than the number of cells in the row it copies (a merged cell counts once, a
deleted cell not at all).

### `insert_table_column` and `delete_table_column`

`insert_table_column` adds a grid column before or after `column`, with one cell value per cell of `column`,
the column it copies (one per row; a row whose cells stop short of the new column, or one with a tracked
deletion, takes none);
`delete_table_column` deletes a column.

```json
{"type": "insert_table_column", "destination": {"column": {"table": {"index": 1}, "index": 2}, "position": "after"},
 "cells": ["Q3", "12", "9"]}
{"type": "delete_table_column", "column": {"paragraph": {"index": 143}}}
```

Word does not record inserting or deleting a column as one tracked change; it displays and decides
tracked cell insertions and deletions, which is what these edits write. Each new cell is a tracked cell
insertion (`w:cellIns`) with its text a tracked insertion, formatted like the cell beside it, and the
table grid's earlier columns are kept in `w:tblGridChange`, which carries no author: a decision that
settles all of a table's cell insertions and deletions settles its grid change the same way.

The table keeps its width. The new column starts as wide as the column it is placed beside, then all the
columns, the new one included, shrink by the same proportion until the grid is as wide as it was. A
table 9630 twips wide with columns of 2790, 3397 and 3443 gets a fourth column after the third of 2537,
the others becoming 2055, 2502 and 2536. The table's own width (`w:tblW`, in twips, a percentage or auto)
is not changed. Each cell's width follows the columns it spans, as a tracked property change
(`w:tcPrChange`) that keeps its earlier width; rejecting restores the grid and the widths.

A deleted column's cells are tracked cell deletions (`w:cellDel`) with their text deleted; accepting gives
each cell's grid column and width to the cell before it, so the table keeps its width (a deleted first
column has no cell before it, and leaves its rows a column short). Refused: a column through a cell spanning several grid columns, a cell
count other than the column's cells, and a paragraph locator in a spanning cell, which names no one column.

### `set_table_format` and `set_cell_format`

`set_table_format` sets a table's borders; `set_cell_format` sets the borders of `cells`: one cell locator
(`{"row": {...}, "index": n}`, `{"id": "…"}` or `{"paragraph": locator}`), or `{"from": cell, "through": cell}`,
every cell of one table whose row lies between the two cells' rows and whose first grid column lies between the
grid columns they start at.

```json
{"type": "set_table_format", "table": {"index": 0},
 "table_format": {"borders": {"inside_horizontal": {"style": "single", "width": 0.5, "color": "auto"}, "left": null}}}
{"type": "set_cell_format", "cells": {"row": {"table": {"index": 0}, "index": 1}, "index": 3},
 "cell_format": {"borders": {"bottom": {"style": "dotted", "width": 0.5}}}}
{"type": "set_cell_format",
 "cells": {"from": {"row": {"table": {"index": 0}, "index": 2}, "index": 1},
           "through": {"row": {"table": {"index": 0}, "index": 2}, "index": 3}},
 "cell_format": {"borders": {"bottom": {"style": "none"}}}}
```

A table's sides are `top`, `left`, `bottom`, `right`, `inside_horizontal` (between rows) and
`inside_vertical` (between columns); a cell's are the same, plus `diagonal_down` and `diagonal_up`, and a
paragraph's (`set_paragraph_format`) are `top`, `left`, `bottom`, `right`, `between` and `bar`.

| Border field | Values |
|---|---|
| `style` | `none`, `single`, `thick`, `double`, `dotted`, `dashed`, `dot_dash`, `dot_dot_dash`, `triple`, `wave`, `double_wave`, `dash_small_gap`, `dash_dot_stroked`, `three_d_emboss`, `three_d_engrave`, `outset`, `inset` and the thin-thick gaps (`thin_thick_small_gap`, ... `thin_thick_thin_large_gap`) |
| `width` | points, 0.25 to 12 in eighths; 0.5 when left out |
| `color` | six hex digits, or `auto` (the default) |
| `theme_color` | a theme colour (`text1`, `accent1`, ...), which Word prefers to `color` |
| `space` | points between the border and the text, 0 to 31 |
| `shadow`, `frame` | `true` or `false` |

A side given a border is set to it; `null` removes the side's own setting, so the table's or style's
applies; a side left out is kept. Style `none` is an explicit absence (`w:val="nil"`): it keeps a border
the table, its style or a neighbouring cell would draw from being drawn, and takes no other field. Where
two cells meet, Word draws the border either sets, so removing a line between two rows means the upper
cells' `bottom` and the lower cells' `top`. The change is tracked: the table's previous properties go
into a `w:tblPrChange`, each cell's into a `w:tcPrChange`, so rejecting restores them; a format the table
or cell already has records nothing. The listing reports each table's `table_format`, each cell's
`cell_format` and each paragraph's `paragraph_format.borders`, the borders they set themselves, in these
terms. Refused: a cell no row has at that row and column, and a table this request creates.

## Fields and links

### Field and cross-reference runs

A `field` run inserts a document field by its `code`, Word's field code, as the listing's `fields` print
it; a `cross_reference` run inserts a field pointing to another paragraph, `to`.

```json
{"type": "insert", "paragraphs": {"part": {"kind": "footer", "type": "default", "section": {"index": 0}}, "index": 0},
 "at": {"after": {"match": "Page "}}, "content": [{"type": "field", "code": "PAGE \\* ROMAN"}]}
{"type": "insert", "paragraphs": {"index": 12}, "at": {"after": {"match": "see Section "}},
 "content": [{"type": "cross_reference", "to": {"index": 30}, "show": "number"}]}
```

| Field code | Result Version Story writes |
|---|---|
| `PAGE`, `NUMPAGES`, `SECTIONPAGES` | `1`, which Word replaces when it lays out the pages |
| `DATE` | the edit's date; Word shows the current date on open |
| `CREATEDATE`, `SAVEDATE` | the document's created or last-saved date |
| `TITLE`, `AUTHOR` | the document's title or author |
| `DOCPROPERTY` | the named property: a custom property, or `Title`, `Subject`, `Author`, `Keywords`, `Comments`, `Category`, `Company`, `Manager`, `LastSavedBy` |
| `FILENAME` | none; marked for Word to compute on open |
| `INCLUDETEXT "file" [bookmark]` | the run's `result`, such as `"[Exhibit A loads here: select all and press F9]"`, until the reader updates the field |

A field is one tracked insertion: begin, instruction, result and end inside a `w:ins`, in the formatting
of the text before it. `INCLUDETEXT` shows another Word file's text, or one bookmark of it, once the
reader updates the field in Word: its code names the file in quotes, as the reader's computer finds it,
and optionally a bookmark, with `\* MERGEFORMAT` the only switch. Version Story cannot read that file, so the
field shows the run's `result` (1 to 1000 characters; none, an empty result) and is never marked to update
on open; only an `INCLUDETEXT` run takes a `result`. No other field that fetches or runs content
(`INCLUDE`, `INCLUDEPICTURE`, `DDE`, `DDEAUTO`, `IMPORT`, `LINK`, `MACROBUTTON`, …) is offered, and
`refresh_fields` still refuses a document that holds one, `INCLUDETEXT` included.

```json
{"type": "insert_paragraphs", "destination": {"paragraph": {"index": 160}, "position": "after"},
 "new_paragraphs": [{"content": [{"type": "field", "code": "INCLUDETEXT \"/Templates/DPA.docx\" DPABody",
                                  "result": "[The DPA loads here. Select all and press F9.]"}]}]}
```

| `show` | Field | Result |
|---|---|---|
| `number` | `REF _Ref… \r \h` | the paragraph's list number, without trailing periods (`2.1`) |
| `text` | `REF _Ref… \h` | the paragraph's text |
| `page` | `PAGEREF _Ref… \h` | `1`, marked for Word to compute on open |
| `above_below` | `REF _Ref… \p \h` | "above" or "below", by where the paragraph is |

A cross-reference's result is the one its paragraph has after the last edit, with every revision
accepted. The paragraph pointed to needs a hidden `_Ref` bookmark around its text: an existing one is
reused, otherwise one is added, untracked, and stays if the cross-reference is rejected; the field is a
tracked insertion. `number` for a paragraph without a list number, and `text` for one without text, are
refused.

### `add_hyperlink`

`add_hyperlink` links existing text: the one match of `text`, or the whole paragraph without it. `url`
is an absolute `http`, `https` or `mailto` address.

```json
{"type": "add_hyperlink", "paragraphs": {"index": 4}, "text": {"match": "our website"}, "url": "https://example.com"}
```

The link is a `HYPERLINK` field, the form Word can track: its code is inserted before and after the text
(`w:ins`) and the text takes the Hyperlink character style as a tracked formatting change. Accepting
gives the link; rejecting restores the text. The Hyperlink style is added, untracked, when the document
has none. Refused: addresses that run code, reach local files or embed content (`javascript:`,
`vbscript:`, `file:`, `data:`), other schemes, relative and UNC addresses, and addresses with spaces,
quotes, backslashes or angle brackets (percent-encode them); text that is already a link (use
`set_hyperlink`); and two `add_hyperlink` ranges that overlap.

### `set_hyperlink` and `remove_hyperlink`

`set_hyperlink` gives a hyperlink a new `url`; `remove_hyperlink` unlinks it and keeps its text. With
`text`, the hyperlink holding the match; without it, the paragraph's only hyperlink.

```json
{"type": "set_hyperlink", "paragraphs": {"index": 4}, "text": {"match": "our website"}, "url": "https://example.com/new"}
{"type": "remove_hyperlink", "paragraphs": {"index": 4}}
```

Both are tracked: a new address deletes the old field code (`w:delInstrText` in `w:del`) and inserts the
new one; unlinking deletes the field code and removes the Hyperlink style as a tracked formatting change.
A link held as a `w:hyperlink` element or a simple field is first rewritten as the equivalent `HYPERLINK`
field. Refused when the selection covers no hyperlink, or more than one. The listing shows a link on its
segments as `"link": {"url": …, "anchor": …}`, and each paragraph's `fields` and `bookmarks`.

### `add_bookmark`

`add_bookmark` names a span of the document, as Word's Insert › Bookmark does: whole paragraphs, from the
start of the first to the end of the last, or one text match in a paragraph. A field or cross-reference
names the span by it, such as an `INCLUDETEXT` field in another document that includes only that part of
this one ([Field and cross-reference runs](#field-and-cross-reference-runs)).

```json
{"type": "add_bookmark", "paragraphs": {"from": {"index": 2}, "through": {"index": 331}}, "name": "DPABody"}
{"type": "add_bookmark", "paragraphs": {"index": 14}, "text": {"match": "Effective Date"}, "name": "EffectiveDate"}
```

`name` is a letter, then letters, digits and underscores, 40 at most. Refused: a name starting with an
underscore, which Word keeps for the bookmarks it hides (`_Ref`, `_Toc`), and a name the document already
uses, whatever its case, since Word keeps one bookmark of each name. A bookmark is not a revision: it is
added untracked, as Word adds one with changes tracked, and the listing shows it among the `bookmarks` of the
paragraph where it starts.

### Cross-references after edits

After the last edit, the server recomputes every `REF` field in the body, headers, footers and notes, so a
cross-reference shows the number or text its target has in the edited document: a paragraph that was
renumbered by an insert, a heading whose text was replaced, a clause moved elsewhere. A field whose result
changes is updated as a tracked change (the old result deleted, the new one inserted, with the request's
author and date), so rejecting restores the old result.

- `REF` fields only are recomputed. A `PAGEREF` or other page-dependent field is only marked for Word to
  update on open (`w:dirty`), since the page depends on layout.
- Plain-text references, such as "see Section 2.1" typed as text, are not fields and are left untouched.
- A `REF` or `PAGEREF` whose target paragraph an edit deletes is refused, and so is the request; a field
  inside another field is left alone. The backend checks `delete_paragraphs` at request time from the
  listing (each paragraph's `fields` and `bookmarks`) and names the referencing paragraphs; edit or remove
  those references in the same request (a `replace` of the reference text removes the field, or delete the
  referencing paragraph), or keep the target. A `join_paragraphs` keeps the joined paragraphs' text and
  bookmarks, so it never removes a target. A reference in a footnote or endnote the request removes (a
  `delete_note`, or a `delete_paragraphs` of the paragraph holding the note's reference) goes with the note.
  Where a `replace` or `delete` in a paragraph removes a field, the backend leaves that paragraph to the
  edit as it is applied, against the edited document. A deletion of the target that the listing cannot show, such as one
  inside a table deleted as a whole, fails the edit with `INVALID_EDIT`.

### `refresh_fields`

`refresh_fields` marks every field in the body, headers, footers and notes for update (`w:dirty`) and sets
`w:updateFields`, so Word asks the reader to update fields when the document opens.

```json
{"type": "refresh_fields"}
```

Both changes are applied untracked, to both outputs. A document holding a field that fetches or runs
content (as listed for moves, `RD` included) is refused, since refreshing would run it on open.

### `insert_table_of_contents`

`insert_table_of_contents` places a `TOC` field as a new paragraph at a destination in the body outside
tables. `from_level` and `to_level` (1 to 9) are the heading levels; `hyperlinks` (`\h`) and
`page_numbers` default to `true`.

```json
{"type": "insert_table_of_contents", "destination": {"paragraph": {"index": 2}, "position": "after"},
 "from_level": 1, "to_level": 3}
```

The defaults with levels 1 to 3 give Word's own `TOC \o "1-3" \h \z \u`; `"page_numbers": false` adds
`\n`. Version Story does not lay out pages, so the field shows a placeholder and is marked for update, with
`w:updateFields` set, both untracked: Word writes the entries when the document opens. The paragraph is
one tracked insertion. A heading above it is an `insert_paragraphs` at the same destination, earlier in
the request.

## Footnotes and endnotes

### Note runs

A `footnote` or `endnote` run in an `insert`'s content adds a note whose reference goes at that point.
Its text is `content`, or `new_paragraphs` for several paragraphs; it holds text, tabs, line breaks,
fields, cross-references and links, not page or column breaks or other notes. `ref` names the note, and
`{"part": {"ref": …}, "index": n}` its paragraphs.

```json
{"type": "insert", "paragraphs": {"index": 56}, "at": {"after": {"match": "409A."}},
 "content": [{"type": "footnote", "ref": "fn1",
              "content": [{"text": "As defined in "}, {"text": "Section 1", "format": {"italic": true}}, {"text": "."}]}]}
{"type": "insert", "paragraphs": {"index": 30}, "at": {"before": {"match": "Schedule"}},
 "content": [{"type": "endnote", "content": "See the Schedule."}]}
```

The reference and the note's text are tracked insertions, written as Word writes a note inserted with
Track Changes on, and share one revision, so deciding it decides the note whole. The notes part, with
Word's separator notes, and the note styles are added untracked when the document lacks them.

### `delete_note`

`delete_note` deletes a note and its reference mark.

```json
{"type": "delete_note", "note": {"kind": "footnote", "index": 1}}
```

The reference run and the note's text become tracked deletions; accepting removes the note. Text edits
reach a note's paragraphs through its `part` locator. The listing prints each note's `part`, `label`,
`paragraph` (where its reference is), `text_before` (the 200 characters before the reference) and
`blocks`, its paragraphs.

## Comments

### `add_comment`

`add_comment` anchors a comment to the one match of `text` in a paragraph, to the whole paragraph, to
a range of paragraphs, or, with `revision` instead of `paragraphs`, to a tracked revision the listing's
`revisions` gives by `index`. `content` is a string or text, tab and line-break runs; `author`
overrides the request's for this comment; `ref` names the thread.

```json
{"type": "add_comment", "paragraphs": {"index": 7}, "text": {"match": "30 days"}, "content": "Should this be 45?", "ref": "c1"}
{"type": "add_comment", "paragraphs": {"from": {"index": 25}, "through": {"index": 27}}, "content": "Double check this section."}
{"type": "add_comment", "revision": {"index": 3}, "content": "Changed at the client's request."}
```

A comment on a revision covers what the revision changes: the text it inserts, deletes or moves or whose
formatting it changes, through the end of a paragraph whose mark it changes; the whole paragraph whose
properties or numbering it changes; and the whole table, row or cell it changes. A move's comment is at
its destination. A revision of paragraph marks alone (a split or a join) covers those paragraphs. One
that runs from the text around a text box into the box covers that text, the box included. Its
reference is outside the revision, so accepting or rejecting the revision keeps the comment. A revision
in a header, footer or note, or one that changes only the last section's page setup, is refused. To
annotate a comparison's redline, read its `revisions` from the json listing of the comparison's `file.file_id`
(`GET /v1/files/{file_id}?format=json`), send one
`add_comment` per revision, and download the result's `files.comparison`, which keeps the tracked
changes beside the comments.

A comment is not a tracked change: it appears in the redline's comment pane and is kept in the clean
copy, unless its reference goes with deleted text. A comment's range follows its text, so a later
`replace` inside it leaves the deleted and the new text inside the comment. Comments go on body and
table-cell paragraphs; Word doesn't support them in headers or footers, and they aren't added to notes.

### `reply_comment` and `resolve_comment`

`reply_comment` answers a thread, and `resolve_comment` marks it resolved. `comment` is a thread's
`index` from the listing's `comments`, or the ref of a thread this request adds.

```json
[{"type": "reply_comment", "comment": {"ref": "c1"}, "content": "Agreed; changed to 45.", "author": "Lawyer A"},
 {"type": "resolve_comment", "comment": {"index": 2}}]
```

Replies and resolutions are annotations, applied untracked. Replies to one thread appear in request
order. The listing gives each thread's `index`, `author`, `date`, `text`, `paragraph`, `anchor_text`,
`resolved` and `replies`.

## Tracked revisions

### `accept_revision` and `reject_revision`

`accept_revision` and `reject_revision` decide the document's existing tracked changes. `revision` is one
entry of the listing's `revisions` (`{"index": n}`, `{"id": "…"}`, or both), `"all"`, or a filter by `author`
and/or `type` (`insertion`, `deletion`, `move`, or `property`: every formatting, paragraph, numbering, table and
section property change). A revision's `id` is the one the redline's HTML carries, so a change a reviewer picked in
an [embedded redline](/developers/rest/embed#accepting-or-rejecting-a-change) is decided by its `anchorId`.

```json
[{"type": "reject_revision", "revision": "all"},
 {"type": "accept_revision", "revision": {"author": "Jane Doe"}},
 {"type": "accept_revision", "revision": {"index": 3}},
 {"type": "accept_revision", "revision": {"id": "9f2c4e1a7b3d4c8e9a1f2b3c4d5e6f70"}}]
```

A request holding a decision holds only decisions; to decide and then edit, send two requests. Decisions
apply in request order and the later one wins for a revision, so the example rejects everything but
Jane Doe's changes and revision 3. Accepting keeps what an insertion or a move's destination adds, drops
what a deletion or a move's source removes, and keeps a property change's new properties; rejecting does
the opposite. A decision is not itself a tracked change: the redline keeps every revision no decision
covered, under its original author and date. Decisions cover the body, headers, footers and notes.
Rejecting all of an edit set's revisions gives back the document it was applied to. Revisions no decision covered
keep their `id` in the edit's redline; their `index` can change. An empty filter is
refused, and so is a filter that selects no revision, unless the decision sets `skip_if_none: true`: then it
decides nothing. Accepting every insertion and rejecting every deletion of a document that may lack either,
keeping its moves and formatting changes tracked in the edit's `redline`:

```json
[{"type": "accept_revision", "revision": {"type": "insertion"}, "skip_if_none": true},
 {"type": "reject_revision", "revision": {"type": "deletion"}, "skip_if_none": true}]
```

`skip_if_none` goes only with a filter. A revision unit is a move (both halves), one author's contiguous insertion or deletion, or one
property change; a replacement is two units. A Word 2003 numbering change (`w:numberingChange`) cannot be
rejected; rejecting it fails the job.

## Document settings

### `set_document_settings`

`set_document_settings` sets how the output behaves in Word. Settings are not document content and Word
cannot track them, so they are applied untracked to both outputs, and deciding revisions doesn't change
them.

```json
{"type": "set_document_settings",
 "settings": {"track_changes": true, "protection": {"edit": "tracked_changes", "password": "s3cret"},
              "remove_personal_information": true}}
```

| Field | Effect in the output |
|---|---|
| `track_changes` | `true` turns on Track Changes (`w:trackRevisions`); `false` turns it off. |
| `protection` | Restrict Editing, enforced (`w:documentProtection`), replacing any protection the document had. `edit` is `tracked_changes` (every change is tracked and tracking can't be turned off), `read_only` or `comments`. With `password`, Word asks for it to stop the protection. `false` removes protection. |
| `remove_personal_information` | `true` sets Word's "remove personal information from file properties on save" and clears the output's creator and last-modified-by names; `false` turns it off. |

Fields left out are left as the document has them; at least one is required. `password` is 1 to 15
printable ASCII characters, hashed when the request arrives as Word hashes it, and only the hash is
stored and written; Restrict Editing guards against accidental changes and is not encryption.
A request holds at most one `set_document_settings` edit. `track_changes: false` with `tracked_changes` protection is refused. The even-and-odd headers setting isn't
included. The listing shows the document's own `settings`.

---
