# Edit operations

> Every operation the edit API accepts: locators, text selectors, content runs, property values, how a request is applied, and each operation's fields, values, examples and refusals.

This is the complete reference for the `edits` array of [`POST /v1/edit`](/developers/rest/edit).
Each edit is a JSON object with a `type` and the fields that type defines. Unknown fields are
refused, and edits are applied in request order. The full machine-readable definition of each
edit is in the API's OpenAPI specification under `components.schemas.EditRequest`.

Edits name the things they act on by locators, and the locators come from the document content:
the response of `GET /v1/files/{file_id}/content?format=json`. Read that first and copy the
locators it prints instead of computing them. [How a request is applied](#how-a-request-is-applied)
says how several edits in one request combine.

## Operations at a glance

| 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_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 |
| [`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 |
| [`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 |
| [`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 | 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.

## Locators

Everything an edit names is a locator object, never a bare number. The document content prints
every locator, so you copy locators instead of computing them.

### Paragraph locators

```jsonc
{"index": 12}                                              // body paragraph 12
{"part": {"kind": "footnote", "number": 3}, "index": 0}    // first paragraph of footnote 3
{"part": {"kind": "header", "type": "first", "section": {"number": 2}}, "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 the order the document content
  lists them, including paragraphs in table cells.
- **Tracked text.** The document is seen as the content 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 `number` 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 document content
    prints both, as `number` and `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 document content
    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 content'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.
- **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 | `{"number": 2}`; `set_page_setup` also takes an array of section locators, or `"all"` |
| a table | `{"number": 2}`: 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` |
| a row or column | `{"table": {"number": 2}, "number": 4}`, or a paragraph locator inside one of its cells (`{"index": 142}`), which names the row or column of the innermost table holding that paragraph. Columns count grid columns, and a paragraph in a cell that spans several grid columns can't name a column |
| a comment thread | `{"number": 3}`: top-level threads in document order. Replies aren't counted, since replying and resolving act on a thread |
| a note | `{"kind": "footnote", "number": 3}` |
| revisions | `{"number": 5}`, one entry of the document content's `revisions`; `"all"`; or a filter `{"author": "...", "kind": "insertion"}` where both keys are optional and `kind` is `insertion`, `deletion`, `move` or `property`. `property` covers every formatting, paragraph, numbering, table and section property change |

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

### Counting

`index` is the only value counted from 0, because it is a position in the content's arrays.
Everything else counts from 1, as a reader counts: note, comment, revision, section, table,
row and column numbers; `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.

### The `paragraphs` field

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 document 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.
- 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 `{"number": 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, for `delete` only,
  without `prefix` or `suffix`.
- **`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 document 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, for example 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 document content. 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 and images 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 document content 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:

```text
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 shapes the document content uses.

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

- 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.
- `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.

## 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 document content.
- **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 document content 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 document content 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 read of the document. The request is all or nothing: if any
edit is refused, or fails while being applied, nothing is applied and no file is produced.
Decisions on existing tracked changes (`accept_revision`, `reject_revision`) go alone: a
request that contains one contains only decisions. To accept changes and then edit, send two
requests, the second against the first's result. See [Edit](/developers/rest/edit) for the
request and response around the `edits` array.

### Addresses

- **Indexes don't shift.** If edit 1 inserts a paragraph after paragraph 5, edit 2's
  `{"index": 10}` still means the paragraph the document content called 10, now eleventh in the
  document.
- **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 document content;
  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.
- **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 API can
  compute; any others are marked for Word to update on open.

### 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 above (or below, for a new first row), and a new column copies the column to
  the left (or right, for a new first 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 document content. 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.

```jsonc
{"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.

```jsonc
{"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.

```jsonc
{"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`, `darkBlue`, ..., `lightGray`), `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
document content reads formatting back in the same terms: each text segment's `format` holds
the properties above that the text has through its styles; `color`, `font_size` and `font`
appear where they differ from the document's default text formatting.

## 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, an empty paragraph) and, optionally, `style_id`,
  `paragraph_format`, `list`, `level` (1 to 9) and `ref`.
- **Operation-level values.** `style_id`, `paragraph_format`, `list` and `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. `level` defaults to the operation's, else the
  `same_as` paragraph's, else 1.

A destination is a paragraph directly in the body or a table cell. A missing paragraph style
fails the edit 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}]}
```

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 last paragraph of the document, a header, a footer
or a note (delete its text with `delete`); a paragraph that ends a section; and paragraphs that
are all that keeps two tables apart, since Word joins tables that touch.

### `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.

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, 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 edit 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 edit 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` |

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. The document content
gives each paragraph a `paragraph_format` in these terms where it sets a property or differs
from the document's default.

### `set_numbering`

`set_numbering` changes list numbering by `action`. Levels count from 1, as the document
content's `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"}, "level": 1}
{"type": "set_numbering", "paragraphs": {"index": 21}, "action": "set_level", "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", "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 `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 edit 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 document content shows each numbered paragraph's `number`,
`level` and `list_id`, 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 document content shows them as that paragraph's `section`, and the last section's as
`final_section`. 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. `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 number 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.

### `remove_section_break`

`remove_section_break` removes the break that ends `section`. Its content joins the next
section and takes that section's properties, headers and footers, as in Word.

```json
{"type": "remove_section_break", "section": {"number": 2}}
```

The merged section starts as the removed one started (its section type and page-number
restart), a tracked `w:sectPrChange`. 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. 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 | Values |
| --- | --- |
| `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`) |

`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).

### `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`).
`ref` names the new part.

```json
{"type": "add_header_footer", "part": {"kind": "footer", "type": "default", "section": {"number": 1}}, "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. The section stops linking to an earlier
section's part, and later sections that 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 that already has its own part of that kind and type (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 document content shows a table's `number`, `id` and `style_id`, each row's `header` flag
and tracked `change`, and each cell's `row`, `column`, `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`, `level`, `ref`).

### `insert_table`

`insert_table` places a table at a destination. `rows` holds each row's cell values, every row
the same length (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.

```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"}]}, "—"]]}
```

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": {"number": 3}}
```

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 grid column; `ref`
names it. `delete_table_row` deletes a row as a tracked row deletion (`w:trPr/w:del`) with its
text deleted.

```json
{"type": "insert_table_row", "destination": {"row": {"index": 311}, "position": "after"}, "ref": "google",
 "cells": ["Google LLC", "Google Cloud Support", "AI-Enhanced PDF Conversion"]}
{"type": "delete_table_row", "row": {"table": {"number": 1}, "number": 4}}
```

The new row copies the neighbouring row's cell layout and formatting, without its revisions,
vertical merges or header flag (the row above, or below for a new first row), 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 grid's.

### `insert_table_column` and `delete_table_column`

`insert_table_column` adds a grid column before or after `column`, with one cell value per row;
`delete_table_column` deletes a column.

```json
{"type": "insert_table_column", "destination": {"column": {"table": {"number": 2}, "number": 3}, "position": "after"},
 "cells": ["Q3", "12", "9"]}
{"type": "delete_table_column", "column": {"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. A deleted column's cells are tracked cell deletions
(`w:cellDel`) with their text deleted; accepting gives each cell's width to the cell before it.
Refused: a column through a cell spanning several grid columns, a cell count other than the
table's rows, and a paragraph locator in a spanning cell, which names no one column.

## Fields and links

### Field and cross-reference runs

A `field` run inserts a document field by its Word field code; a `cross_reference` run inserts
a field pointing to another paragraph, `to`.

```jsonc
{"type": "insert", "paragraphs": {"part": {"kind": "footer", "type": "default", "section": {"number": 1}}, "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 the API 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 |

A field is one tracked insertion: begin, instruction, result and end inside a `w:ins`, in the
formatting of the text before it. No field that fetches or runs content (`INCLUDETEXT`,
`INCLUDEPICTURE`, `DDE`, `DDEAUTO`, `IMPORT`, `LINK`, `MACROBUTTON`, ...) is offered.

| `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 document content shows a link on its segments as
`"link": {"url": ..., "anchor": ...}`, and each paragraph's `fields` and `bookmarks`.

### `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`. The API cannot 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.

```jsonc
{"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", "number": 2}}
```

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 document content shows 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, or to a range of paragraphs. `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."}
```

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 `number` from the document content'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": {"number": 3}}]
```

Replies and resolutions are annotations, applied untracked. Replies to one thread appear in
request order. The document content gives each thread's `number`, `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 document content's `revisions` (`{"number": n}`), `"all"`, or a
filter by `author` and/or `kind` (`insertion`, `deletion`, `move`, or `property`: every
formatting, paragraph, numbering, table and section property change).

```json
[{"type": "reject_revision", "revision": "all"},
 {"type": "accept_revision", "revision": {"author": "Jane Doe"}},
 {"type": "accept_revision", "revision": {"number": 4}}]
```

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 4. 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. An empty filter is refused. One revision
entry is one thing to decide: a move (both halves), one author's contiguous insertion or
deletion, or one property change; a replacement is two entries. A Word 2003 numbering change
(`w:numberingChange`) cannot be rejected; rejecting it fails the edit.

## 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 document content shows the document's own `settings`.

## Related

- [Edit](/developers/rest/edit): upload, read, send edits, fetch the result
- [Compare](/developers/rest/compare): two documents, one redline
- [Errors](/developers/reference/errors): every error code and what to do about it
