# Errors

> Every error code across the REST API and the MCP server, and what to do about each.

## REST error envelope

Every non-2xx REST response has the same body. `code` is drawn from a closed vocabulary and
is what your code should branch on; `message` is human-readable and may change.

```json
{
  "error": {
    "code": "UNSUPPORTED_FILE_TYPE",
    "message": "original must be a .docx, .doc, or .pdf file",
    "request_id": "01J..."
  }
}
```

Send an `X-Request-Id` header to correlate your logs with ours. If you don't, one is minted
and echoed back in the response.

### Request errors

| Status | Code | Cause | Retry? |
| --- | --- | --- | --- |
| 400 | `INVALID_REQUEST` | Malformed request — missing, empty, or unnamed file field | No, fix the request |
| 400 | `UNSUPPORTED_FILE_TYPE` | Extension is not `.docx`, `.doc`, or `.pdf` | No |
| 400 | `DOCUMENT_UNREADABLE` | A submitted `.docx` is not a readable Word file — corrupt, or encrypted | No, check the file opens in Word |
| 400 | `INVALID_COMPARISON_ID` | The comparison id is malformed, or is a `mrg_` merge id | No |
| 400 | `INVALID_MERGE_ID` | The merge id is malformed, or is a `cmp_` comparison id | No |
| 400 | `UNSUPPORTED_FORMAT` | A requested `format` is unknown, or not valid for this operation | No |
| 401 | `INVALID_API_KEY` | Missing, malformed, unknown, inactive, or expired key | No, check the credential |
| 402 | `USAGE_LIMIT_REACHED` | The organization's monthly upload allowance is exhausted | No, it's a billing state |
| 403 | `API_KEY_ORG_MISMATCH` | The key's service account no longer belongs to the key's organization | No, issue a new key |
| 404 | `COMPARISON_NOT_FOUND` | No such comparison, or the key's user cannot access it | No |
| 404 | `MERGE_NOT_FOUND` | No such merge, or the key's user cannot access it | No |
| 413 | `FILE_TOO_LARGE` | A file exceeds the 100 MB limit | No |
| 500 | `INTERNAL_ERROR` | Something went wrong on our side | Yes, with backoff |

`COMPARISON_NOT_FOUND` and `MERGE_NOT_FOUND` intentionally cover both "doesn't exist" and
"not yours", so ids cannot be probed for existence.

`INTERNAL_ERROR` is the only code worth retrying blindly. Everything else in this table
describes a request or account condition that will produce the same result next time.

### Comparison failures

A comparison that cannot be generated is **not** an HTTP error. The request succeeded — it
correctly reported that the work will not finish — so it returns `200` with
`"status": "failed"`:

```json
{
  "comparison_id": "cmp_...",
  "status": "failed",
  "error": {
    "code": "DOCUMENT_PROTECTED",
    "upstream_code": "ONE_OR_MORE_DOCUMENTS_ARE_PROTECTED"
  }
}
```

| `error.code` | Meaning | What to do |
| --- | --- | --- |
| `DOCUMENT_PROTECTED` | A source is password-protected or permission-restricted | Remove protection and resubmit |
| `DOCUMENT_UNREADABLE` | A source could not be opened — corrupt or malformed | Check the file opens in Word |
| `DOCUMENT_CONVERSION_FAILED` | A `.doc`, `.pdf`, or email source could not be converted to `.docx` | Convert it yourself and resubmit |
| `SOURCE_UPLOAD_FAILED` | A source did not reach storage intact | Resubmit |
| `PDF_RENDER_FAILED` | The redline generated but the PDF rendering did not | Request `format=docx` instead |
| `COMPARISON_FAILED` | Catch-all — `upstream_code` carries the specific pipeline code | Inspect `upstream_code`; contact support if it recurs |

`failed` is terminal for that comparison. Waiting longer changes nothing; submit a new
comparison to retry.

`DOCUMENT_UNREADABLE` appears in both tables because a `.docx` is checked for readability
while the request is still open — an unopenable one is refused with `400` rather than
accepted and failed later. A `.doc` or `.pdf` source is opaque until its conversion step, so
the same condition surfaces there as a failed status instead.

## MCP errors

The MCP server does not use HTTP status codes for tool outcomes. Failures surface two ways.

**Tool errors** — a call that cannot proceed raises an MCP tool error carrying a message.
These are argument and permission problems: too few revisions for a merge, a project the
signed-in user cannot access, an exhausted upload allowance. Read the message; it says what
is wrong.

**Statuses** — a call that succeeded but whose artifact isn't ready reports it in the
result:

| Status | Meaning | What to do |
| --- | --- | --- |
| `ready` | The artifact exists | Fetch `download_manifest_url` |
| `processing` | Still generating | Call again with identical arguments |
| `failed` | Terminal | Read `failure_code` or `errors`; do not retry |
| `awaiting_uploads` | Sources were never transferred | Complete the `PUT`s listed in `missing_uploads` |

A comparison can be partly successful: some formats generate while others fail. Such a
comparison carries both `downloads` and `failed_options` with a `failure_code`. Anything
present in the manifest is valid and usable.

### Transfer errors

The upload endpoint returns ordinary HTTP status codes:

| Status | Cause |
| --- | --- |
| 400 | Empty request body |
| 403 | Missing, invalid, or expired upload authorization |
| 408 | The upload exceeded the 300-second timeout |
| 413 | The body exceeded the 100 MB cap |
| 502 | Storage rejected the write |
| 503 | All 16 concurrent upload slots are busy — honor `Retry-After: 1` |

A `403` most often means the upload manifest is more than an hour old. Call the create tool
again with the same `version_story_id` to stage fresh transfers.

If a transfer fails with a host-allowlist error rather than a status code, the calling
agent's network policy is blocking `*.versionstory.com`. For Claude, the fix is in
Settings → Capabilities — the illustrated steps are in the
[Claude installation guide](/developers/mcp/claude-installation-guide).

### Pipeline failure codes

`failure_code` and `upstream_code` come from the document pipeline and are more specific
than the public codes above. The ones you are most likely to see:

| Code | Meaning |
| --- | --- |
| `ONE_OR_MORE_DOCUMENTS_ARE_PROTECTED` | A source is password-protected |
| `ONE_OR_MORE_DOCUMENTS_ARE_UNABLE_TO_BE_OPENED` | A source could not be opened |
| `ONE_OR_MORE_DOCUMENTS_FAILED_TO_CONVERT_TO_DOCX` | Conversion to `.docx` failed |
| `ONE_OR_MORE_DOCUMENTS_FAILED_TO_UPLOAD_TO_S3` | A source did not reach storage |
| `FAILED_TO_SAVE_REDLINE_AS_PDF` | PDF rendering of the redline failed |
| `FAILED_TO_GENERATE_COMPARISON` | The comparison itself failed |
| `OUT_OF_MEMORY` | The documents exceeded the processing budget |
| `UNHANDLED_ERROR` | Unclassified failure |

`OUT_OF_MEMORY` on very large or deeply nested documents is worth reporting to support —
it is usually a fixable characteristic of the specific file rather than a hard limit.
