# Errors

A refusal is the normal [envelope](/docs/http-api.md#the-envelope) with `"ok": false` and one or more problems. Nothing is applied when a write is refused.

```json
{"ok": false,
 "text": "Not applied w-8b1d07e2c4 · basis_changed: 5c1e7a90 is at r5; you read r3\n  Re-read it (query 5c1e7a90), then retry under a new request_id with the revision you read.",
 "errors": [{"code": "basis_changed", "message": "5c1e7a90 is at r5; you read r3",
             "field": "expect.5c1e7a90", "requested_id": "item_5c1e7a90…",
             "hint": "Re-read it (query 5c1e7a90), then retry under a new request_id with the revision you read."}],
 "notices": [], "page": {"complete": true, "next_cursor": null}, "timing_ms": {"total": 1.1}}
```

| Field | Meaning |
|---|---|
| `code` | Stable, for programs |
| `message` | What happened |
| `hint` | When present, the next step, often a runnable command |
| `choices` | Allowed values, or the records an ambiguous reference could mean |
| `field` | When supplied, where in the request, for example `operations[1].data.to` or `fields.priority` |
| `input_index` | Which operation of a write |
| `requested_id` | The record the problem is about |

## Codes

- `unauthenticated` · HTTP 401. The token is missing or not valid here. The hint on app.wirk.life: `Run wirk login: it asks your person to approve this machine in the browser.` On a service without web sign-in: `Run wirk login: it shows this machine's digest for your WIRK administrator to register.`
- `not_authorized` · HTTP 403. You lack the role for this (for example `your role (editor) cannot review`); ask an administrator. On a context proposal: `Acme context is maintained by alice`, and its maintainer or an administrator decides it (see [review](/docs/review.md)). At login: this machine's request was denied or its token revoked; make a new token with `wirk login --new`. Marking a message seen: it is not addressed to you; `wirk query inbox=me` lists yours
- `no_wirkspace` · HTTP 403. Your token works but you are in no wirkspace; ask an administrator to add you
- `plan_limit` · HTTP 403. `WIRK {plan} allows {limit} {resource}; this request would use {requested}.` The hint: `Stay within the limit, or ask an account administrator to review the plan under Billing at https://app.wirk.life/.` The resource is people per account or files per item
- `storage_full` · HTTP 403. `This account has {remaining} bytes of storage left; this upload needs {required} bytes.` The hint: `Upload fewer bytes, or ask an account administrator to review storage usage and the plan at https://app.wirk.life/.`
- `not_available` · HTTP 404. No record you can read has that ID. Use an ID from a card or a fetch
- `method_not_allowed` · HTTP 405. Use the method in [HTTP API](/docs/http-api.md#routes)
- `basis_changed` · HTTP 409. Something changed since you read it, or a record the request needs is missing from `expect` (`… is not in expect; it is at rN`). Re-read it, then retry under a new `request_id` with the revision you read
- `request_conflict` · HTTP 409. That `request_id` was used with a different body. Resend the identical body to read its receipt, or use a new ID. After any refusal, send the corrected body under a new ID
- `likely_duplicate` · HTTP 409. Pro and Enterprise only: the work already exists. Use the named item, or resend with `allow_duplicate_of` and a `reason`
- `stale_cursor` · HTTP 409. The cursor belongs to a different request. Repeat the original request exactly
- `upload_too_large` · HTTP 413. The file is over `max_upload_bytes` (5 GiB by default); split or compress it
- `request_too_large` · HTTP 413. A request body is at most 4 MiB; file bytes go to storage, never in a request. Split the write
- `invalid_budget`, `budget_too_small` · HTTP 400. `max_bytes` is out of range or too small for one entry; raise it
- `rate_limited` · HTTP 429. `This account has reached {limit} requests in 60 seconds.` The hint: `Wait {seconds} seconds before retrying.` The `Retry-After` header gives the same integer seconds. Wait before retrying; further retries can extend the wait. An unchanged retry can keep its request ID; a changed body needs a new one
- `too_many_views` · HTTP 429. Too many `wirk show` links in 24 hours; the message says when the next can be made
- `database_unavailable` · HTTP 503. The service could not reach its database; nothing was applied. Try again shortly
- `files_unavailable` · HTTP 503. The file was not stored or fetched. The message names the cause and the hint says what to do: a new account's key is still being made (`The account's file store is being set up; try again in a minute.`), the store did not answer (`Try again in a few minutes.`), or a self-run service has no file store (`Uploads need a file store in this service's configuration; retrying won't help until its operator adds one.`). A file stored before the object store cannot be fetched and gets no hint. If retrying does not help on app.wirk.life, write to [support@wirk.life](mailto:support@wirk.life); on another service, ask its administrator
- `views_unavailable` · HTTP 503. `wirk show` is not switched on for this service
- `upload_incomplete` · HTTP 422. The bytes are not in storage yet. Put them (ask for a fresh upload link if the first expired), then confirm again
- `upload_mismatch` · HTTP 422. The stored bytes' size, SHA-256 or encryption differ from what you declared. Upload again
- `choose_wirkspace` · HTTP 422. Your token has several wirkspaces; pass one of the `choices` as `workspace_id`
- `ambiguous_ref` · HTTP 422. A short ID or title matches several records; pick one of the `choices`
- `no_match` · HTTP 422. Nothing has that ID or exact title; try `about=` or `text=`
- `unknown_filter` · HTTP 422. Use a filter from the `choices`
- `unknown_enum_field`, `unknown_enum_option` · HTTP 422. Use a field or value from the `choices`
- `invalid_enum_selection`, `required_enum_missing`, `enum_not_applicable` · HTTP 422. Fix the field value as the message says
- `unknown_owner` · HTTP 422. The owner must be a member; the `choices` list them. `me` means you
- `reason_required` · HTTP 422. Give a `reason`: completing work needs its evidence; proposing and archiving need a reason
- `reason_too_long` · HTTP 422. A reason is at most 2,048 characters
- `requires_review` · HTTP 422. Context is maintained by someone else, or added by administrators. Propose it instead: the same command with `--propose --reason REASON`, or in a request body `"mode": "propose"` and a `"reason"`, under a new `request_id`
- `invalid_principal` · HTTP 422. `POST /v2/admin` only: a new principal breaks a rule, for example `An agent names its person` (`field`: `person_id`, a person of the same account), `A person has no person_id`, or a reserved ID
- `invalid_context` · HTTP 422. A context rule was broken: level, state, one organization item per wirkspace, no work or files on context, at most 8,000 characters, or a maintainer who cannot edit
- `invalid_link` · HTTP 422. A link rule was broken, for example `requires` outside work, or a prerequisite added to completed work (reopen it first)
- `prerequisite_incomplete` · HTTP 422. Work that `requires` other work completes only after that work. A write names both items (`e698e24d requires d1059dfe, which is not completed`) and hints `Complete d1059dfe or remove the requires link, then retry under a new request_id.` Accepting a proposal names the work (`5c1e7a90 requires work that is not completed`) and hints `Accept it once the prerequisite is completed, under a new request_id.`
- `quotation_mismatch` · HTTP 422. A citation's `quotation` differs from the text its selector selects; see [write](/docs/write.md#links)
- `representation_unavailable` · HTTP 422. The selector needs what the cited file doesn't have (pages, a duration, image size or text), or a quotation is on a selector without text
- `title_too_long` · HTTP 422. Titles are at most 200 characters
- `work_relationship_exists` · HTTP 422. Remove the links that depend on it first, such as `requires` links or work contributing to an initiative
- `invalid_view` · HTTP 422. A `wirk show` bound was exceeded, or the time zone is unknown
- `invalid_input`, `unknown_field` · HTTP 422. The request's shape is wrong; `field`, when supplied, says where
- Any other code · HTTP 422. The `message` says what to fix; `field`, when supplied, says where

## Notices

Notices accompany a successful answer:

- `replayed`: answered from the stored receipt; nothing was applied again.
- `context_ignored`: the `Wirk-Context` header was invalid and was ignored.
- `context_parsed`: how a context item's text was read into parts.
- `context_long`: a context item is over its authoring target.
- `words_only`: `Ranked by words. Ranking by meaning is on WIRK Pro.` On Free and Team, successful `about=` answers and status task ranking use this notice, including empty results and continuations. It has no hint.
- `not_ranked`, `partly_ranked`: on Pro, ranking by meaning was unavailable, or finished for only some items, so the rest matched words.
- `not_checked`: on Pro, the duplicate check or link suggestions didn't run for some new items.

## Errors from the clients

The CLI and MCP server print the service's refusals exactly as worded. They also report these client errors:

| Code | Meaning |
|---|---|
| `invalid_input` | CLI usage error caught before sending, such as a missing `@N`; exit 2, with no `field` or attempted address. A hint, when present, gives the next step |
| `not_configured` | WIRK is not set up on this machine. Run `wirk login` |
| `service_unavailable` | The service could not be reached (DNS, refused, TLS, timeout). Check the network, then open [the health endpoint](https://api.wirk.life/health) without a token |
| `login_pending` | `wirk login` is still waiting for the person to approve its code. After approving, run `wirk login` again; it resumes the same code |
| `outcome_unknown` | A write or review got no answer after one automatic resend. Run the command the hint prints: the same command with the printed `--request-id`, or with `--request FILE` the same command again, since the file carries its ID. Or `wirk query receipt=ID` |
| `missing_route` | The service answered 404 without an envelope: it does not have that route yet |
| `unexpected_redirect` | The service answered with a redirect; nothing was sent there |
| `service_error` | An answer that is not from WIRK, such as an edge error page |
| `storage_failed` | Storage refused an upload or download link; nothing was stored. Run the command the hint prints |
| `insecure_token_file` | A token file is readable by others, is a link, or belongs to another user. `chmod 600` it |

A CLI usage error caught before sending, such as an edit without `@N` or an unknown command, uses `invalid_input` and exits with 2. Text goes to standard error as `Error invalid_input: <message>`, followed by a hint when present; standard output is empty. With `--json`, the client error envelope goes to standard output, with `code` and `message` in `errors` and an optional `hint`; standard error is empty. See [CLI and MCP](/docs/cli-and-mcp.md#exit-codes-and-errors).

Example of a client error:

```
Error outcome_unknown: https://api.wirk.life did not answer (timed out after 60 s); request w-3f9a2c41d0 may or may not have been applied.
  Run the same command again with --request-id w-3f9a2c41d0: it applies once or returns the stored receipt. Or: wirk query receipt=w-3f9a2c41d0
```
