Errors
ScaiVault error responses are structured and stable. Your code branches on error.code, not on HTTP status alone or string-matching the message.
Error envelope#
Every error response:
1 2 3 4 5 6 7 8 9 10 | |
code— machine-readable code from a stable vocabulary. Branch on this.message— human-readable description. Display or log as-is.details— optional, code-specific context (the offending path, the missing field, the failed condition).request_id— matchesX-Request-ID. Include in any support ticket.
The HTTP status matches the error class (4xx for client, 5xx for server), but specific handling should use code.
Retry classification#
| Code | Retry? | Notes |
|---|---|---|
authentication_required |
No | Add or refresh the Authorization header. |
token_expired |
No | Refresh via OAuth client-credentials or refresh flow. |
access_denied |
No | Caller lacks permission for this action. Don't retry blindly. |
mfa_required |
No | Re-auth with MFA before retrying. |
ip_not_allowed |
No | Caller IP blocked by policy condition. |
invalid_request |
No | Fix the request body. |
validation_error |
No | details lists the failing fields. |
invalid_path |
No | Path format violates rules. |
secret_not_found |
No | Path doesn't exist. |
version_not_found |
No | Version doesn't exist or has aged out. |
secret_exists |
No | Soft-deleted path in retention. |
version_conflict |
Rarely | Concurrent write; fetch current state and retry. |
policy_in_use |
No | Detach bindings first. |
rate_limited |
Yes | Honor Retry-After. |
service_unavailable |
Yes | Transient — back off. |
internal_error |
Once | Retry once with backoff; then escalate. |
Anything not listed above is generally non-retryable.
Full error catalog#
Authentication / authorization#
| HTTP | Code | Meaning |
|---|---|---|
| 401 | authentication_required |
Missing or malformed Authorization header. |
| 401 | token_expired |
The token's exp has passed. |
| 401 | token_invalid |
Signature didn't validate. |
| 401 | token_revoked |
Token was explicitly revoked in ScaiKey. |
| 403 | access_denied |
No policy rule permits this action for this identity. |
| 403 | insufficient_scope |
The token's scopes are too narrow. |
| 403 | mfa_required |
Action requires MFA; the token is not MFA-backed. |
| 403 | ip_not_allowed |
Caller IP is outside the rule's ip_ranges. |
| 403 | time_window_violation |
Caller is outside the rule's time_window. |
| 403 | tenant_access_denied |
Explicit tenant access attempted without partner admin. |
Validation#
| HTTP | Code | Meaning |
|---|---|---|
| 400 | invalid_request |
Malformed JSON or top-level shape. |
| 400 | invalid_path |
Path violates the path format rules. |
| 400 | invalid_secret_type |
Unknown secret_type. |
| 400 | invalid_duration |
Duration string (90d, etc.) didn't parse. |
| 400 | invalid_cursor |
Pagination cursor is expired or malformed. |
| 422 | validation_error |
Field-level validation failed. details.fields lists problems. |
| 400 | invalid_policy |
Policy rules or bindings invalid. |
| 400 | invalid_glob |
Path pattern isn't a valid glob. |
Not found#
| HTTP | Code | Meaning |
|---|---|---|
| 404 | secret_not_found |
No secret at that path. |
| 404 | version_not_found |
Version doesn't exist or aged out. |
| 404 | policy_not_found |
|
| 404 | binding_not_found |
|
| 404 | webhook_not_found |
|
| 404 | subscription_not_found |
|
| 404 | rotation_policy_not_found |
|
| 404 | ca_not_found |
|
| 404 | certificate_not_found |
|
| 404 | csr_not_found |
|
| 404 | trust_anchor_not_found |
|
| 404 | engine_not_found |
|
| 404 | role_not_found |
|
| 404 | lease_not_found |
|
| 404 | tenant_not_found |
|
| 404 | identity_not_found |
|
| 404 | federation_backend_not_found |
Conflict#
| HTTP | Code | Meaning |
|---|---|---|
| 409 | secret_exists |
Path currently soft-deleted in retention window. |
| 409 | version_conflict |
Concurrent modification; refetch and retry. |
| 409 | policy_in_use |
Policy has active bindings; remove them first or use force=true. |
| 409 | engine_in_use |
Engine has active leases. |
| 409 | ca_has_certificates |
CA has issued certificates; revoke them first. |
| 409 | name_conflict |
Another resource with that name exists. |
| 410 | secret_expired |
expires_at has passed. |
Rate limit / availability#
| HTTP | Code | Meaning |
|---|---|---|
| 429 | rate_limited |
Too many requests. Retry-After tells you when. |
| 429 | quota_exceeded |
Hard quota reached for the tenant; not just a rate limit. |
| 503 | service_unavailable |
Transient dependency (DB, Redis, KMS) issue. |
| 503 | feature_disabled |
Feature gated off at deploy. |
Server errors#
| HTTP | Code | Meaning |
|---|---|---|
| 500 | internal_error |
Unexpected server error. Include request_id in a support ticket. |
| 500 | encryption_error |
KMS interaction failed. |
| 500 | storage_error |
Database or object storage error. |
| 502 | backend_error |
Federated backend or ACME upstream failed. |
| 504 | backend_timeout |
Federated backend didn't respond in time. |
Validation error details#
422 validation_error responses include details.fields:
1 2 3 4 5 6 7 8 9 10 11 12 | |
Show these to the caller verbatim.
Rate limit headers#
On any response (success or 429):
1 2 3 | |
On 429:
1 | |
Sleep Retry-After seconds before retrying. SDKs handle this automatically.
Request IDs#
Every response includes X-Request-ID. You can also send your own X-Request-ID header on the request — ScaiVault echoes it back. Use this for distributed tracing.
When filing a support ticket, always include the request_id from the failing response.
What's next#
- Error Codes Reference — complete list with HTTP statuses.
- Rate Limiting — caps by endpoint category.