257 lines
6.2 KiB
Markdown
257 lines
6.2 KiB
Markdown
# yantrikdb-server HTTP error codes
|
|
|
|
Every error response across `/v1/*` carries a structured envelope:
|
|
|
|
```json
|
|
{
|
|
"error": {
|
|
"code": "stable_id",
|
|
"message": "human-readable text",
|
|
"hint": "actionable hint (optional)"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Branch on `code`, not `message`.** The message wording is allowed to
|
|
change between releases; the code is part of the stable API surface.
|
|
|
|
The source of truth for the code enum is
|
|
[`crates/yantrikdb-server/src/api/errors.rs`](../crates/yantrikdb-server/src/api/errors.rs)
|
|
(`ApiErrorCode`). This document mirrors it. **Adding a new code requires
|
|
updating both files in the same PR**, gated by the
|
|
`every_code_has_snake_case_wire_string` + `all_wire_strings_are_unique`
|
|
unit tests.
|
|
|
|
---
|
|
|
|
## 401 Unauthorized
|
|
|
|
### `unauthenticated`
|
|
|
|
The request did not present valid authentication credentials.
|
|
|
|
- Retryable: no (until credentials change)
|
|
- Client action: present a valid token
|
|
|
|
---
|
|
|
|
## 403 Forbidden
|
|
|
|
### `forbidden`
|
|
|
|
Authenticated but lacks permission for the requested action.
|
|
|
|
- Retryable: no (until token grants change)
|
|
- Client action: request a token with the required permission
|
|
|
|
### `namespace_not_found`
|
|
|
|
The requested namespace is not visible to this token. Per the issue-#39
|
|
policy: **403 reveals existence** rather than 404 indistinguishable.
|
|
This is a deliberate choice — most internal tooling already enumerates
|
|
namespaces via `/v1/identity-scope`, and distinguishing
|
|
visible-but-forbidden from non-existent helps debugging.
|
|
|
|
- Retryable: no
|
|
- Client action: call `GET /v1/identity-scope` to see visible namespaces
|
|
|
|
### `insufficient_scope`
|
|
|
|
Authenticated and namespace is visible, but the specific permission
|
|
scope required for this endpoint is not held by the token.
|
|
|
|
- Retryable: no
|
|
- Client action: request a token with the required scope
|
|
(`memory:read`, `memory:export`, `memory:graph:read`, `scope:read`,
|
|
`admin:read`)
|
|
|
|
---
|
|
|
|
## 404 Not Found
|
|
|
|
### `memory_not_found`
|
|
|
|
Memory with the given RID does not exist (or is not visible to this
|
|
token, when 403 disclosure would leak existence-status from a
|
|
restricted namespace).
|
|
|
|
- Retryable: no
|
|
- Client action: confirm the RID; if it was just written, retry with
|
|
`?min_seq=N` from the write response (see `replica_behind` below)
|
|
|
|
### `conflict_not_found`
|
|
|
|
Conflict with the given ID does not exist.
|
|
|
|
### `skill_not_found`
|
|
|
|
Skill with the given ID does not exist.
|
|
|
|
### `session_not_found`
|
|
|
|
Session with the given ID does not exist.
|
|
|
|
### `entity_not_found`
|
|
|
|
Entity with the given name does not exist.
|
|
|
|
---
|
|
|
|
## 400 Bad Request / 422 Unprocessable Entity
|
|
|
|
### `invalid_request`
|
|
|
|
Generic malformed request body or path.
|
|
|
|
### `invalid_query_parameter`
|
|
|
|
A query parameter failed validation: out-of-range value, wrong type,
|
|
unknown enum variant, etc. The `message` should identify the parameter.
|
|
|
|
### `invalid_cursor`
|
|
|
|
Pagination cursor is malformed or stale (cursor encoding changed
|
|
between releases).
|
|
|
|
- Retryable: yes, after dropping the cursor (start a fresh listing)
|
|
|
|
### `invalid_min_seq`
|
|
|
|
`min_seq` query parameter is malformed.
|
|
|
|
### `invalid_body`
|
|
|
|
Request body is not valid JSON or doesn't match the expected schema.
|
|
|
|
---
|
|
|
|
## 409 Conflict
|
|
|
|
### `op_id_collision`
|
|
|
|
The same `op_id` was used with a different mutation.
|
|
|
|
- Retryable: no — client bug; don't retry
|
|
|
|
### `unexpected_log_index`
|
|
|
|
Concurrent write race; client should re-read state and retry.
|
|
|
|
- Retryable: yes
|
|
|
|
---
|
|
|
|
## 412 Precondition Failed
|
|
|
|
### `replica_behind`
|
|
|
|
The replica receiving the request has not yet applied the `min_seq`
|
|
the client requested. This is the read-your-writes signal: the write
|
|
succeeded, but the read landed on a node that hasn't caught up.
|
|
|
|
- Retryable: yes, with backoff
|
|
- Client action: retry the request, optionally route to the leader
|
|
|
|
---
|
|
|
|
## 426 Upgrade Required
|
|
|
|
### `version_upgrade`
|
|
|
|
Wire-version mismatch during a rolling cluster upgrade.
|
|
|
|
- Retryable: no (operator must bring the cluster to a uniform version)
|
|
|
|
---
|
|
|
|
## 429 Too Many Requests
|
|
|
|
### `rate_limited`
|
|
|
|
Request exceeded the per-principal or per-namespace rate limit. The
|
|
response includes a `Retry-After` header.
|
|
|
|
- Retryable: yes, after the `Retry-After` duration
|
|
|
|
---
|
|
|
|
## 500 Internal Server Error
|
|
|
|
### `internal_error`
|
|
|
|
Catch-all for unexpected server failures. When emitted, include a
|
|
`request_id` in the response and a correlating log line so operators
|
|
can trace the failure.
|
|
|
|
- Retryable: maybe — depends on what failed
|
|
|
|
---
|
|
|
|
## 307 Temporary Redirect
|
|
|
|
### `not_leader`
|
|
|
|
The receiving node is not the cluster leader. Standard HTTP clients
|
|
will follow the redirect; the response body also carries `leader_id`
|
|
and `leader_addr` for clients that don't.
|
|
|
|
---
|
|
|
|
## 503 Service Unavailable
|
|
|
|
### `engine_unavailable`
|
|
|
|
Engine is currently unable to accept requests (e.g. mid-migration,
|
|
mid-reembed Swapping phase).
|
|
|
|
- Retryable: yes, with backoff
|
|
|
|
### `cluster_unavailable`
|
|
|
|
Cluster has no leader / no quorum.
|
|
|
|
- Retryable: yes, with backoff (election should resolve within seconds)
|
|
|
|
### `leader_unavailable`
|
|
|
|
Leader is known but currently unreachable from this node (network
|
|
partition, leader process restarting, etc.).
|
|
|
|
- Retryable: yes, with backoff
|
|
|
|
### `commit_timeout`
|
|
|
|
Write succeeded reaching the commit log but didn't apply within the
|
|
timeout. The response body carries the `op_id` — retry is idempotent.
|
|
|
|
- Retryable: yes, reusing the `op_id`
|
|
|
|
---
|
|
|
|
## Migration placeholder
|
|
|
|
### `generic`
|
|
|
|
**Do not emit from new code.** This is the placeholder code emitted by
|
|
the pre-issue-#39 `http_gateway::app_error()` helper for the ~125 call
|
|
sites that haven't yet been migrated to a specific code from this
|
|
registry. Each site migrates over time as its semantics become clear.
|
|
|
|
The wire envelope shape is identical to specific-coded errors, so
|
|
dashboards branching on specific codes treat `generic` as a fallback
|
|
("we don't know what failed, see the message").
|
|
|
|
---
|
|
|
|
## Adding a new code
|
|
|
|
1. Add a variant to `ApiErrorCode` in
|
|
[`crates/yantrikdb-server/src/api/errors.rs`](../crates/yantrikdb-server/src/api/errors.rs).
|
|
2. Add the snake_case wire string in `ApiErrorCode::as_str()`.
|
|
3. Add a section to this document with HTTP status, retryability, and
|
|
client action.
|
|
4. The unit tests in `api::errors::tests` will fail-loudly if the new
|
|
variant is missing from the test array — keep both lists in sync.
|
|
5. Existing wire strings must never be renamed — they are a stable API
|
|
surface.
|