Error Code Catalog (Canonical)
Purpose
Define the canonical set of machine-readable code values returned in ErrorResponse.code
across all API endpoints. Every error response must include one of these codes so that clients,
SDKs, and monitoring systems can react programmatically without parsing message strings.
ErrorResponse Envelope
{
"code": "allocation_not_found",
"message": "The requested allocation does not exist or is not accessible.",
"correlation_id": "01HXYZ...",
"details": {}
}
- code — machine-readable, one of the values below (stable across versions)
- message — human-readable, English, may change without notice
- correlation_id — always present; use for support and trace lookup
- details — optional structured context (e.g. field validation errors)
Code Catalog
Authentication — 401
| Code |
When returned |
token_missing |
No Authorization header present on a protected endpoint |
token_invalid |
JWT signature or format is invalid |
token_expired |
JWT has passed its exp claim |
token_scope_invalid |
Token lacks the required scope (e.g. terminal token presented outside its allocation) |
auth_personal_disabled |
Personal auth flow is disabled by feature flag in the current environment |
auth_enterprise_required |
Request used personal auth path but input must use enterprise SSO flow |
auth_personal_required |
Request used enterprise auth path but input must use personal auth flow |
Authorization — 403
| Code |
When returned |
insufficient_permissions |
Authenticated user lacks the required role or policy permission |
admin_required |
Endpoint requires the admin role |
ownership_required |
Resource belongs to a different user and caller is not an admin |
Validation — 400
| Code |
When returned |
validation_error |
One or more request fields failed schema or constraint validation; details contains per-field errors |
invalid_request |
Request body is malformed or unparseable (e.g. not valid JSON) |
Allocation — 404 / 409 / 422
| Code |
HTTP |
When returned |
allocation_not_found |
404 |
Allocation UUID does not exist or is not visible to the caller |
allocation_not_active |
409 |
Requested action (terminal token, SSH key download) requires active allocation state |
allocation_already_releasing |
409 |
Release attempted on an allocation already in releasing or released state |
allocation_concurrency_limit |
409 |
User has reached the configured maximum concurrent allocation count |
insufficient_balance |
409 |
User balance is too low to cover the requested provision |
sku_unavailable |
409 |
No available nodes match the requested SKU |
Node — 404 / 409
| Code |
HTTP |
When returned |
node_not_found |
404 |
Node UUID does not exist |
node_offline |
409 |
Node was unreachable at provisioning or probe time |
node_in_use |
409 |
Node is currently assigned to an allocation; deletion or reassignment blocked |
node_already_exists |
409 |
A node with the same host already exists |
User — 404 / 409
| Code |
HTTP |
When returned |
user_not_found |
404 |
User UUID does not exist |
user_already_exists |
409 |
A user with the same email or username already exists |
Billing and Payments — 400 / 409
| Code |
HTTP |
When returned |
stripe_signature_invalid |
400 |
Webhook raw-body signature verification failed |
refund_window_exceeded |
409 |
Refund request is outside the configured refund eligibility window |
Storage — 404 / 409
| Code |
HTTP |
When returned |
storage_object_not_found |
404 |
Storage path does not exist |
storage_path_traversal |
400 |
Path contains traversal sequences (..) that escape the user root |
storage_already_exists |
409 |
Target path already exists (mkdir, rename destination) |
storage_quota_exceeded |
409 |
Upload would exceed the user's storage quota |
Catalog — 404
| Code |
HTTP |
When returned |
sku_not_found |
404 |
SKU identifier does not exist |
Apps — 404 / 409
| Code |
HTTP |
When returned |
app_not_found |
404 |
App catalog slug does not exist or is disabled for caller visibility |
app_version_not_found |
404 |
Requested app version does not exist for the app |
app_not_entitled |
409 |
Project is not entitled to create/use the requested app |
app_instance_not_found |
404 |
App instance UUID does not exist or is not visible to caller |
shared_runtime_not_found |
404 |
Tenant-owned shared runtime UUID does not exist or is not visible to caller |
shared_runtime_attachment_not_found |
404 |
Shared runtime attachment UUID does not exist or is not visible to caller |
shared_runtime_worker_not_found |
404 |
Shared runtime worker UUID does not exist or is not visible to caller |
shared_runtime_worker_operation_not_found |
404 |
Shared runtime worker operation UUID does not exist or is not visible to caller |
app_instance_state_invalid |
409 |
Requested operation is not valid for current app instance lifecycle state |
app_instance_quota_exceeded |
409 |
Project/tenant app instance quota or policy limit exceeded |
app_artifact_not_found |
404 |
App artifact UUID does not exist or is not visible to caller |
app_artifact_already_exists |
409 |
An artifact with the same project and digest is already registered |
app_artifact_state_invalid |
409 |
Requested operation is not valid for the current app artifact lifecycle state |
os_image_not_found |
404 |
OS image catalog entry UUID does not exist |
os_image_already_exists |
409 |
OS image catalog entry with the same slug already exists |
Rate Limiting — 429
| Code |
HTTP |
When returned |
rate_limit_exceeded |
429 |
Caller has exceeded the configured rate limit; see Retry-After header |
Server — 500 / 502 / 503
| Code |
HTTP |
When returned |
internal_error |
500 |
Unexpected server-side failure; always include correlation_id in support requests |
upstream_error |
502 |
A downstream dependency (Stripe, GPU node SSH, Temporal) returned an unexpected failure |
service_unavailable |
503 |
Service temporarily unavailable; safe to retry after back-off |
Rules
- Stable codes: once shipped, a
code value must not be renamed or removed — it is a breaking change.
- Additive only: new codes may be added in any version without a major bump.
details for validation: validation_error responses must populate details with at minimum { "fields": [{ "field": "...", "issue": "..." }] }.
- No free-form codes: implementors must not invent codes outside this catalog. Submit a PR to extend it.
- HTTP status is the primary signal:
code refines within a status — never use code as a substitute for the correct HTTP status.