# Edit

> Upload a Word document, read its listing, send edits, and download the edited document and a redline of what changed.

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

Upload a `.docx`, read its listing, a list of its paragraphs with the address of each, then send
edits that name the paragraphs to change by those addresses. The result is two files: the edited
document, clean, with the edits accepted, and a redline in which every edit is a real Word tracked
change that a reviewer can accept or reject.

| | |
| --- | --- |
| Base URL | `https://api.versionstory.com` |
| Auth | `Authorization: Bearer vs_...`. See [Authentication](/developers/authentication) |

Non-2xx responses use the same envelope as [Compare](/developers/rest/compare), and `X-Request-Id`
works the same way.

```json
{ "error": { "code": "INVALID_EDIT", "message": "...", "request_id": "..." } }
```

## The flow

An edit says which paragraph to change by its address, such as "replace `[Company Name]` in
paragraph 7". So before you can edit, you need the document stored with us and its listing:

1. **Upload it.** [`POST /v1/files`](/developers/rest/files#post-v1files) with the `.docx` returns a
   `file_id`, which every later call uses to name the document.
2. **Read it.** `GET /v1/files/{file_id}?format=md` serves the document's listing as a download: its
   text, with every paragraph's address and the addresses of its headers, footers, notes, comments,
   tables and tracked changes. Find what you want to change and copy its address.
3. **Edit it.** `POST /v1/edit` with the `file_id` and a list of `edits` that use those addresses.
   Returns an `edit_id` and, with `wait`, the finished result.
4. **Get the result.** `GET /v1/edit/{edit_id}` until it is ready, if you did not wait. Download the
   edited document and the redline, as a PDF to show and as Word to send on.
5. **Edit again.** For further changes, read the edited document's listing, `files.clean.file_id`
   in the edit response, and send the next `POST /v1/edit` with that `file_id`.

A `file_id` names one immutable document, so its listing never changes. An edit produces a new file
with its own `file_id`; a user's updated copy is a new upload.

## The listing

`GET /v1/files/{file_id}` serves a document's listing in two formats:

- **`md`**, the listing to write edits from. A header explains each tag it prints; then each
  paragraph is one line, in document order: `<p index="n" id="…"/>` and its text. Tables, tracked
  changes (`<ins>`, `<del>`, `<moved-from>`, `<moved-to>`, each with the `revision` index a decision
  names), fields, links, pictures, equations, note references and comment anchors print where they
  are. After the body come the headers and footers, notes, comment threads, every revision, and the
  styles.
- **`json`**, the same listing as structured data, for code. See
  [Document JSON](/developers/reference/json-format).

```bash
curl -sS "https://api.versionstory.com/v1/files/3f2a9c1e-...?format=md" \
  -H "Authorization: Bearer vs_live_..."
```

```json
{ "file_id": "3f2a9c1e-...", "file_name": "nda.docx", "status": "ready",
  "downloads": [{ "format": "md", "url": "https://documents.versionstory.com/...", "file_name": "nda.md", "expires_at": "..." }] }
```

The listing is made once, when the file is prepared, and stored with it. While it is being made the
format is in `pending_formats`; poll after `Retry-After`, then fetch its `url`. A long document's
listing can be searched, or read in parts, without downloading all of it: see
[Reading the listing](/developers/rest/files#reading-the-listing).

Copy addresses from the listing rather than counting them yourself:

- **Index and id.** Everything an edit names has an `index`, where it is now, counted from 0 within
  its container: a body paragraph among the body's paragraphs, table cells' paragraphs included; a
  header's, footer's or note's paragraphs within that part; tables within their part, rows within
  their table and cells within their row; and sections, notes of a kind, comment threads, pictures
  and revisions within the document. Paragraphs, tables, rows and cells also have an `id`, which they
  keep when content moves.
- **Parts.** Each header and footer prints its `part` locator (`kind`, `type` and the first `section`
  that shows it) and `sections`, every section that shows it. Each note prints a `part` (`kind` and
  `index`) and a `label`, the mark the reader sees.
- **Counting from 1.** Only what a reader counts: a list paragraph's `list_level` (1 to 9), a
  selector's `occurrence`, and table-of-contents levels. Every `index` counts from 0.
- **Revisions.** Existing tracked changes are listed one entry per accept or reject decision, each with
  an `index`, a `type` (`insertion`, `deletion`, `move` or `property`), `author`, `date`, `on` and
  `text`. A replacement is two entries: a deletion and an insertion.
- **Comments.** Listed as threads with their `replies`, each with its `index` in document order.

The listing is for writing edits and for code, not for showing a person the document. Show them the
PDF, or [embed the HTML](/developers/rest/embed) where the view must be interactive.

## POST /v1/edit

A JSON body. A request that uploads a style template or picture files is `multipart/form-data`
instead, with the same fields as form fields and `edits` and `outputs` as JSON strings.

| Field | Required | Meaning |
| --- | --- | --- |
| `file_id` | Yes | The document to edit: an upload, an earlier edit's `files.clean.file_id`, or a Word comparison's `file.file_id` (see [Commenting on the redline's changes](/developers/rest/compare#commenting-on-the-redlines-changes)) |
| `schema_version` | Yes | The listing's `schema_version`, `1`. A mismatch is `400 INVALID_EDIT`, so a change to how the listing counts paragraphs cannot silently shift your addresses |
| `edits` | Yes | 1 to 200 edit objects, applied in order. See [Edit operations](/developers/rest/edit-operations) |
| `author` | No | Name on every tracked change and comment the request makes. Default `Version Story AI` |
| `date` | No | ISO 8601 date and time for those changes. Default: the time of the request |
| `wait` | No | `true` holds the response up to 5 seconds and answers with the finished result; an edit still running after that answers `202` with its `edit_id` to poll |
| `outputs` | No | With `wait`: the formats of both result files, as `GET /v1/edit/{edit_id}`'s `format` takes them. Default `["docx"]` |
| `inline` | No | With `wait`: `true` adds each file's bytes as `content_base64` to each ready download |
| `description` | No | What the edits change, in one plain line, such as `"Extend the termination notice to 60 days"`. Kept with the version the edit becomes and shown in the Version Story app |
| `new_version` | No | `true` makes the edit a new version even where it would amend your working draft. See [Versions](#versions) |
| `amend` | No | `true` puts the change in the version this `file_id` is and in every version after it. See [Amending a version](#amending-a-version). At most one of `new_version` and `amend` |
| `version_name` | No | The version's name, the user's name for its file. At most 255 characters |
| `style_template` | No | Multipart only: a `.docx`, `.dotx` or `.dotm` whose styles and numbering are imported before the first edit |
| `pictures` | No | Multipart only, repeated: PNG, JPEG or GIF files that picture runs name by file name. See [Pictures](/developers/rest/edit-operations#pictures) |

The body is at most 208 KB, 200 KB of it edits. Unknown fields are refused. That includes `document`:
a document is staged with `POST /v1/files`, never sent with the edit.

```bash
curl -sS https://api.versionstory.com/v1/edit \
  -H "Authorization: Bearer vs_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "file_id": "3f2a9c1e-...",
    "schema_version": 1,
    "author": "Dana Reyes",
    "wait": true,
    "outputs": ["pdf", "docx"],
    "edits": [
      {
        "type": "replace",
        "paragraphs": { "index": 7 },
        "text": { "match": "[Company Name]" },
        "content": "Acme, Inc."
      }
    ]
  }'
```

### Attributing the changes

`author` is the name Word shows on every tracked change in the redline, and on any comment that does
not name its own author. It is one line of at most 255 characters with no control characters;
surrounding spaces are trimmed. A comment edit can carry its own `author`, which takes precedence for
that comment.

`date` takes an ISO 8601 timestamp such as `2026-10-05T14:30:00Z`. Send the author's UTC offset
(`2026-10-05T10:30:00-04:00`) if you want Word versions that read only the local clock time to show
local time; without an offset, the value is UTC. It is kept to the second.

### Waiting for the result

By default, `POST /v1/edit` answers `202` with the edit's ids and you poll. With `"wait": true`, the
request waits up to 5 seconds and the response is the same body `GET /v1/edit/{edit_id}` returns:
`200` when the edit finished, `202` if it is still processing, in which case you continue by polling.
`outputs` and `inline` apply only with `wait`.

## How edits address things

This is the short version; [Edit operations](/developers/rest/edit-operations#locators) has every
form.

An edit never uses a bare number. It names what it acts on with a locator object, an address such as
`{"index": 7}`, and the listing prints each locator so you can copy it.

### Locating paragraphs

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

| Form | Names |
| --- | --- |
| `{"index": 12}` | Body paragraph 12 |
| `{"id": "8532bf246ab64e8788c26d27cfd105b4"}` | The paragraph with this id, in any part |
| `{"part": {"kind": "footnote", "index": 0}, "index": 0}` | The first paragraph of the first footnote |
| `{"part": {"kind": "footer", "type": "default", "section": {"index": 0}}, "index": 0}` | The first paragraph of the first section's default footer |
| `{"from": {"index": 9}, "through": {"index": 11}}` | The paragraphs between two locators, inclusive, within one part |
| `[{"index": 3}, {"from": ..., "through": ...}]` | An array of locators and ranges |
| `"all"` | Every paragraph of every part |
| `{"ref": "p1"}` | A paragraph this same request created |

There is no implicit scope: a whole-document replacement says `"paragraphs": "all"`. Other things
have locators of the same style: a section is `{"index": 1}`, a table is `{"index": 1}` or
`{"id": "…"}`, a row is `{"table": {"index": 1}, "index": 3}` or `{"id": "…"}`, a column is
`{"table": {"index": 1}, "index": 2}`, a cell is `{"row": {...}, "index": 0}` or `{"id": "…"}`, a
comment thread is `{"index": 2}`, a note is `{"kind": "footnote", "index": 2}`, and an existing
tracked change is `{"index": 4}`, `"all"`, or a filter by `author` and `type`.

### Selecting text

`text` selects text inside the named paragraphs. `at` names an insertion point.

```json
"text": { "match": "Series A Preferred Stock", "prefix": "the ", "suffix": " issued", "occurrence": 2 }
"at": "start"
"at": { "after": { "match": "Name:\t" } }
```

- **`match`** is the text to find. It is case-sensitive, and a match never crosses a paragraph
  boundary. Curly and straight quotes, en and em dashes and hyphens, and non-breaking and ordinary
  spaces are read alike.
- **`prefix` and `suffix`** are optional context that must sit immediately before and after the
  match. They narrow the candidates; the edit never acts on them.
- **`occurrence`** picks among several matches, counted across every paragraph the edit names: `N`
  for the Nth, or `"all"` for every one. Omitted, the match must be unique.
- **`at`** is `"start"`, `"end"`, or `{"after": selector}` or `{"before": selector}`.

A selector that matches nothing, or more than once without `occurrence`, is refused, naming the count
and the paragraphs that hold the 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")
```

### Writing content

New text is always `content`: a string, or an array of runs. In a string, `\t` becomes a tab and `\n`
a line break. Runs add formatting and richer content: tabs, breaks, fields, cross-references, notes,
links and pictures. See [Content](/developers/rest/edit-operations#content).

Any run can carry `format`, for example `{"text": "Name and title", "format": {"bold": true}}`. Plain
new text takes the formatting of the text before it, as typing does in Word.

For property objects such as `format`, 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, and an omitted key leaves
it alone. To remove bold everywhere, send `false`; `null` would leave a heading that is bold through
its style bold.

## How a request is applied

Three rules govern the list of edits in one request:

1. **Addresses mean the uploaded document.** Every locator and selector refers to the document as you
   read it, whatever earlier edits in the request have done. If edit 1 inserts a paragraph after
   paragraph 5, edit 2's `{"index": 10}` still means the paragraph the listing gave index 10.
2. **Edits apply in request order.** When two edits set the same property of the same text, the later
   one wins. Content inserted at the same point appears in request order.
3. **An edit whose target an earlier edit removed or rewrote is refused**, and so is the whole
   request. For example, replacing text that an earlier edit already deleted.

Three consequences:

- **All or nothing.** If any edit is refused, or fails while being applied, nothing is applied and no
  file is produced. Every refusal in the request is reported together.
- **Undefined means refused.** A combination whose result the reference does not define is refused
  with an error that names it. Nothing is guessed.
- **Repeating an edit repeats its effect.** Sending an insertion twice inserts the content twice.

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: read the
listing of the first result's `files.clean.file_id`, and send the second against that `file_id`.

Every edit type, with its fields, examples and refusals, is in
[Edit operations](/developers/rest/edit-operations). The machine-readable definition of each is in
the API's OpenAPI specification under `components.schemas.EditRequest`.

### Examples

Indexes in these examples are illustrative. Use the ones your document's listing prints.

Fill in a signature block. Each value goes after its label's tab, so the underlined tab that draws
the line is untouched:

```json
[
  { "type": "replace", "paragraphs": { "index": 302 }, "text": { "match": "[Insert Company Name]" }, "content": "Version Story" },
  { "type": "insert", "paragraphs": { "index": 304 }, "at": { "after": { "match": "Name:\t" } }, "content": "Dana Reyes" },
  { "type": "insert", "paragraphs": { "index": 305 }, "at": { "after": { "match": "Title:\t" } }, "content": "General Counsel" }
]
```

Replace a defined term everywhere, and un-bold a whole document:

```json
[
  { "type": "replace", "paragraphs": "all", "text": { "match": "Acme Ltd", "occurrence": "all" }, "content": "Acme Holdings Ltd" },
  { "type": "format", "paragraphs": "all", "format": { "bold": false } }
]
```

Add a comment, then reply to it. The `ref` names the new thread:

```json
[
  { "type": "add_comment", "paragraphs": { "index": 25 }, "content": "double check on this", "ref": "c1" },
  { "type": "reply_comment", "comment": { "ref": "c1" }, "content": "will do" }
]
```

Move a clause and change its text in the same request. Index 14 still names the clause after the
move, and the two edits could be listed in either order:

```json
[
  { "type": "move_paragraphs", "paragraphs": { "index": 14 }, "destination": { "paragraph": { "index": 22 }, "position": "after" } },
  { "type": "replace", "paragraphs": { "index": 14 }, "text": { "match": "thirty (30) days" }, "content": "sixty (60) days" }
]
```

## Response

`POST /v1/edit` and `GET /v1/edit/{edit_id}` return the same shape. `files` holds the edit's two
files, each a [File object](/developers/rest/files), the same one `GET /v1/files/{file_id}` answers
with. Their `file_id`s are allocated when the edit is created, so they are present while processing
and after a failure.

| Entry | Contents |
| --- | --- |
| `files.clean` | The edited document, clean: the edits are accepted. To make further changes, send its `file_id` as the next edit's `file_id` |
| `files.comparison` | The redline: the document you edited compared with the edited one, every edit a Word tracked change attributed to `author` and `date` |

### GET /v1/edit/&#123;edit_id&#125;

Safe to call as often as you like: it creates nothing. It waits up to 5 seconds for the edit and
answers as soon as it finishes.

| Parameter | In | Required | Default | Meaning |
| --- | --- | --- | --- | --- |
| `edit_id` | path | Yes | — | The id from `POST /v1/edit` |
| `format` | query | No | `docx` | The formats of both files: `pdf`, `html`, `docx`, `md`, `json`, as `GET /v1/files/{file_id}` serves them. Repeatable or comma-separated |
| `inline` | query | No | `false` | `true` adds each file's bytes as `content_base64` to each ready download |

```bash
curl -sS "https://api.versionstory.com/v1/edit/edt_...?format=pdf,docx" \
  -H "Authorization: Bearer vs_live_..."
```

### Response: ready

```json
{
  "edit_id": "edt_MTp2cy0xOnZlLTE",
  "status": "ready",
  "files": {
    "clean": {
      "file_id": "8b41d7a0-...",
      "file_name": "nda (edited).docx",
      "status": "ready",
      "downloads": [
        { "format": "pdf", "url": "https://documents.versionstory.com/...", "file_name": "nda (edited).pdf", "expires_at": "2026-10-22T22:00:00Z" },
        { "format": "docx", "url": "https://documents.versionstory.com/...", "file_name": "nda (edited).docx", "expires_at": "2026-10-22T22:00:00Z" }
      ]
    },
    "comparison": {
      "file_id": "d90c5e12-...",
      "file_name": "Redline.docx",
      "status": "ready",
      "downloads": [
        { "format": "pdf", "url": "https://documents.versionstory.com/...", "file_name": "Redline.pdf", "expires_at": "2026-10-22T22:00:00Z" },
        { "format": "docx", "url": "https://documents.versionstory.com/...", "file_name": "Redline.docx", "expires_at": "2026-10-22T22:00:00Z" }
      ]
    }
  }
}
```

`status` is the edit's own, and each file's `status` is that file's: a `ready` edit with a PDF still
converting has that file `processing`, with `Retry-After: 5`. Each `url` is signed, needs no
`Authorization` header, and stops working at `expires_at`, one hour after it was issued. Poll again
for fresh ones.

### Response: failed

Terminal: `"status": "failed"` with an `error`. Submit a corrected request to retry. With
`"wait": true`, an edit that fails while being applied answers HTTP `200` with this body, so check
`status`, not only the HTTP code.

| `error.code` | Meaning |
| --- | --- |
| `INVALID_EDIT` | The edit could not be applied as written, for instance one whose target an earlier edit removed. `error.message` names it as `edit at index N (type): ...`, so you can correct it and send the request again |
| `CARRY_CONFLICT` | An amend was not made because a later version changed the same text. `error.message` quotes each version's conflicting passage. No version changed |
| `EDIT_FAILED` | Anything else. Also returned if the edited document is missing after the edit otherwise succeeded |

Nothing is applied when an edit fails.

### Versions

Through this API only `POST /v1/documents` creates a [Document](/developers/rest/documents); an edit
never does. An edit of a file that is a version of one of your Documents (a version's own file, or an
earlier kept edit's `files.clean.file_id`) is kept as that Document's next version, where your account
keeps version history (an organization with zero-day retention never does). The response then carries
`version`, a reference to it, and `app_url`:

```json
"version": { "document_id": "...", "version_id": "...", "index": 1, "label": "v2" },
"app_url": "https://app.versionstory.com/documents/.../versions/..."
```

`index` is the version's position among the Document's versions, counted from 0, and `label` is the
label the app shows. A request names a version by `{"version_id": "..."}`. `app_url` opens the version
at its redline in the Version Story app. A response without `version` is a one-off edit: its file is
not a version of one of your Documents, such as an upload, or history is not kept. To keep an upload's
history, make it a Document's first version with `POST /v1/documents` and its `file_id`, then edit
that file.

- **A version's file.** The edit is that version's next version. A further edit of the version your
  own edit made, while it is still the Document's latest version, amends that version instead, so one
  version holds all of your changes for the same piece of work. Send `"new_version": true` for a
  separate version, such as a draft for someone else.
- **`"amend": true`.** The change goes into the named version wherever it is in the history. See
  [Amending a version](#amending-a-version).

`version_name` names the version and its file; without it, a new version keeps the name of the version
it was made from, and an amended version keeps its own. A one-off edit's `version_name` names only its
file, and its `description` is ignored.

### Amending a version

`"amend": true` puts the change in the version whose file `file_id` names, wherever it is in the
history, and carries it into every version after it: each later version gets a new file with the
change and a redline redone against the version before it. Nothing changes until every later
version's new file is made without a conflict; the edit stays `processing` until then. A ready
response lists a reference to each of those versions in `carried_versions`;
[`GET /v1/documents/{document_id}`](/developers/rest/documents#get-v1documentsdocumentid) reads each
with its new file.

An amend is refused before anything is created with `400 INVALID_REQUEST` when:

- `new_version` is also `true`;
- the account does not keep version history;
- no version holds the file;
- more than 15 versions follow it, a later version merges others, or a later version has no redline
  against the version before it.

It answers `409 CONFLICT` while a later version's redline is still being made; send it again shortly.
If a later version changed the same text, the edit fails with `CARRY_CONFLICT` and no version is
changed.

### POST /v1/edit/&#123;edit_id&#125;/acknowledge

For an organization with [zero-day retention](/developers/reference/data-handling#retention), deletes
the edit's two files as soon as you have saved what you need, instead of an hour after the edit
finished. No body; the response is `202` with `"status": "deletion_queued"`. The file you edited is
kept until its own deadline, so you can edit it again; acknowledge it with
[`POST /v1/files/{file_id}/acknowledge`](/developers/rest/files#post-v1filesfileidacknowledge) once no
further edit needs it.

## Errors

| Status | Code | Cause |
| --- | --- | --- |
| 400 | `INVALID_REQUEST` | The request itself is malformed: a missing `file_id`, a field of the wrong type, `outputs` or `inline` without `wait`, both `new_version` and `amend`, an `author` or `date` that does not meet the rules above, or an amend refused as [above](#amending-a-version) |
| 400 | `INVALID_EDIT` | An edit was refused before anything was created: a stale `schema_version`, a selector that matched nothing or too much, or a combination that is undefined. Every refusal in the request is reported together, each as `edit at index N (type): ...` |
| 400 | `UNSUPPORTED_FILE_TYPE` | The file is a type that is never prepared for editing, such as a workbook |
| 400 | `UNSUPPORTED_FORMAT` | A `format` is unknown |
| 400 | `INVALID_EDIT_ID` | The `edit_id` is malformed |
| 401 | `INVALID_API_KEY` | Missing, malformed, unknown, revoked, or expired key |
| 404 | `EDIT_NOT_FOUND` | No such edit or file, or it belongs to another organization |
| 409 | `CONFLICT` | The document is still being prepared after the wait; retry after `Retry-After`. For an amend, a later version's redline is still being made |
| 410 | `CONTENT_DELETED` | The file's content was removed by the organization's retention setting |

Validation runs before anything is created, so a refused request costs nothing.

## The whole loop, in Python

```python
import time
import requests

BASE = "https://api.versionstory.com"
headers = {"Authorization": "Bearer vs_live_..."}


def download(file_id, file_format):
    """One format of a file, once it is ready."""
    while True:
        response = requests.get(f"{BASE}/v1/files/{file_id}", headers=headers, params={"format": file_format})
        response.raise_for_status()
        body = response.json()
        if body["status"] == "failed":
            raise RuntimeError(f"{file_format} failed: {body.get('error') or body.get('failed_formats')}")
        if body["status"] == "ready":
            content = requests.get(body["downloads"][0]["url"])
            content.raise_for_status()
            return content.content
        time.sleep(int(response.headers.get("Retry-After", "1")))


with open("nda.docx", "rb") as document:
    uploaded = requests.post(f"{BASE}/v1/files", headers=headers, files={"document": document})
uploaded.raise_for_status()
file_id = uploaded.json()["file_id"]

listing = download(file_id, "md").decode()
# Find the line holding "[Company Name]" and copy its <p index="n"/>.

edit = requests.post(
    f"{BASE}/v1/edit",
    headers=headers,
    json={
        "file_id": file_id,
        "schema_version": 1,
        "author": "Dana Reyes",
        "edits": [
            {
                "type": "replace",
                "paragraphs": {"index": 7},
                "text": {"match": "[Company Name]"},
                "content": "Acme, Inc.",
            }
        ],
    },
)
edit.raise_for_status()
edit_id = edit.json()["edit_id"]

while True:
    status = requests.get(f"{BASE}/v1/edit/{edit_id}", headers=headers, params={"format": "pdf,docx"})
    status.raise_for_status()
    body = status.json()
    if body["status"] == "failed":
        raise RuntimeError(f"Edit failed: {body['error']['code']} {body['error'].get('message', '')}")
    files = body["files"]
    if body["status"] == "ready" and all(f["status"] == "ready" for f in files.values()):
        break
    time.sleep(int(status.headers.get("Retry-After", "1")))

for name in ("clean", "comparison"):
    for item in files[name]["downloads"]:
        content = requests.get(item["url"])
        content.raise_for_status()
        with open(item["file_name"], "wb") as out:
            out.write(content.content)

# To edit again, read the edited document's listing and send the next request with its file_id.
edited_listing = download(files["clean"]["file_id"], "md").decode()
```

## Related

- [Edit operations](/developers/rest/edit-operations): every edit type, in full
- [Files](/developers/rest/files): uploads, downloads and clean metadata
- [Errors](/developers/reference/errors): every error code and what to do about it
