Error codes
Contract
Source: packages/shared/errors · doc/architecture/Error_Code_Catalog.md
Every REST error must use a code from this catalog. New codes go in Error_Code_Catalog.md first, then in code.
The error envelope:
{ "code": "<catalog_code>", "message": "human text", "correlation_id": "uuid", "details": {} }
correlation_id is required. details is required for validation_error.
Catalog
Auth
| Code |
HTTP |
Meaning |
token_missing |
401 |
No Authorization: Bearer header |
token_invalid |
401 |
Bearer present but invalid signature, format, or issuer |
token_expired |
401 |
JWT exp in the past |
token_scope_invalid |
401 |
Token does not carry required scope/role |
Authorization
| Code |
HTTP |
Meaning |
insufficient_permissions |
403 |
Authenticated but not authorized for this action |
admin_required |
403 |
Admin-only route accessed by non-admin |
ownership_required |
403 |
Resource not owned by requesting user/project |
Validation
| Code |
HTTP |
Meaning |
validation_error |
400 |
Field-level validation failure; details required |
invalid_request |
400 |
Request shape is unparseable |
Allocation
| Code |
HTTP |
Meaning |
allocation_not_found |
404 |
No matching allocation |
allocation_not_active |
409 |
Action requires active allocation |
allocation_already_releasing |
409 |
Already in releasing state |
allocation_concurrency_limit |
409 |
Per-user concurrent limit hit |
insufficient_balance |
402 |
Balance below required threshold |
sku_unavailable |
409 |
No capacity matches the request |
Node
| Code |
HTTP |
Meaning |
node_not_found |
404 |
|
node_offline |
409 |
Node not active |
node_in_use |
409 |
Node has active allocation |
node_already_exists |
409 |
Conflict on create |
User
| Code |
HTTP |
Meaning |
user_not_found |
404 |
|
user_already_exists |
409 |
|
Billing & payments
| Code |
HTTP |
Meaning |
stripe_signature_invalid |
400 |
Webhook signature failed verification |
refund_window_exceeded |
409 |
Outside refund_window_days; route to internal credit |
Storage
| Code |
HTTP |
Meaning |
storage_object_not_found |
404 |
|
storage_path_traversal |
400 |
Path escape attempt rejected |
storage_already_exists |
409 |
|
storage_quota_exceeded |
413 |
|
Catalog
| Code |
HTTP |
Meaning |
sku_not_found |
404 |
|
Rate limit
| Code |
HTTP |
Meaning |
rate_limit_exceeded |
429 |
Includes Retry-After |
Server
| Code |
HTTP |
Meaning |
internal_error |
500 |
Local defect — fix in owning layer |
upstream_error |
502/504 |
Upstream dependency failure |
service_unavailable |
503 |
Upstream temporarily unavailable |
5xx classification rule
Per Coding_Standards.md §14:
Every new/changed 5xx response path must be classified in review as one of:
- upstream dependency failure (upstream_error / service_unavailable), or
- local contract/schema/query/runtime bug (internal_error).
Do not re-label local defects as upstream issues. Fix the owning layer and add a regression test.
CI gate
scripts/ci/canonical_error_guard.sh scans for error returns that don't reference the catalog and blocks the PR.
How to add a new code
- Open a PR that adds the row to
doc/architecture/Error_Code_Catalog.md (the prose authority).
- Add the constant to
packages/shared/errors.
- Reference it in OpenAPI response schemas (
ErrorResponse enum).
- Use it in handlers/services.
The order matters: doc → constant → contract → code.
Where to look next