Search Documentation
Search across all documentation pages
Error Handling
ExoVault uses standard HTTP status codes and consistent JSON error responses. This page covers all error types, their formats, and recommended retry strategies.
Error Response Format#
Authentication & Authorization Errors#
{
"error": "Missing or invalid agent key"
}Status: 401
Validation Errors#
{
"error": "Validation failed",
"fields": {
"content": "String must contain at least 1 character(s)",
"importance": "Number must be less than or equal to 5"
}
}Status: 400
The fields property maps field names to their specific validation errors. This only appears on 400 validation responses.
Server Errors#
{
"error": "Internal server error"
}Status: 500
Error Types#
AuthError (401, 403)#
Thrown by the authentication and authorization middleware.
| Status | Error Message | Cause |
|---|---|---|
401 | Missing or invalid agent key | No Authorization header or key not found |
401 | Invalid agent key | Key hash does not match any stored key |
401 | Agent key has been revoked | Key was revoked from the dashboard |
401 | Agent key has expired | Key's expiresAt is in the past |
401 | User not found | User account associated with key no longer exists |
403 | Integration is inactive or deleted | The integration was deactivated |
403 | SCOPE_DENIED: This key does not have '...' permission | Key lacks the required scope |
403 | SCOPE_DENIED: This key does not have '...' permission for vault ... | Per-vault scope insufficient |
403 | SCOPE_DENIED: This key does not have access to the requested vault | Vault not in allowedVaultIds |
403 | Integration encryption is not configured... | Missing wrapped MEK |
400 | Invalid vaultId for this user | Vault does not belong to the user |
ValidationError (400)#
Thrown when the request body fails Zod schema validation. The fields property provides per-field details.
Common validation failures:
| Field | Constraint | Example Error |
|---|---|---|
content | min(1), max(100_000) | "String must contain at least 1 character(s)" |
memoryType | min(1), max(50) | "String must contain at least 1 character(s)" |
importance | int, min(1), max(5) | "Number must be less than or equal to 5" |
topK | int, min(1), max(50) | "Number must be less than or equal to 50" |
threshold | min(0), max(1) | "Number must be less than or equal to 1" |
vaultId | uuid | "Invalid uuid" |
Quota Errors (429)#
{
"error": "QUOTA_EXCEEDED: Memory monthly quota exceeded"
}Status: 429
Thrown when the user's monthly memory usage quota is exceeded. Applies to both write-memory and search-memories operations.
Rate Limit Errors (429)#
Certain routes (e.g., send-message) have per-user rate limits:
{
"error": "Too many requests",
"retryAfter": 60
}Status: 429
Error Codes Summary#
| Code | Pattern | Description |
|---|---|---|
SCOPE_DENIED | 403 | Insufficient permissions for the operation or vault |
QUOTA_EXCEEDED | 429 | Monthly usage quota exceeded |
These codes appear as prefixes in the error message string (e.g., "SCOPE_DENIED: This key does not have...") to enable programmatic detection.
Retry Strategies#
Retryable Errors#
| Status | Retryable | Strategy |
|---|---|---|
429 (rate limit) | Yes | Exponential backoff, respect retryAfter |
429 (quota) | Limited | Retry next billing period or upgrade plan |
500 | Yes | Exponential backoff with jitter (max 3 retries) |
503 | Yes | Exponential backoff (service temporarily unavailable) |
Non-Retryable Errors#
| Status | Retryable | Action |
|---|---|---|
400 | No | Fix the request body |
401 | No | Check agent key validity |
403 | No | Check permissions or integration configuration |
404 | No | Verify resource exists |
Recommended Retry Pattern#
async function callWithRetry(
fn: () => Promise<Response>,
maxRetries = 3
): Promise<Response> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const response = await fn();
if (response.ok) return response;
if (response.status === 429) {
const body = await response.json();
const waitMs = (body.retryAfter ?? 60) * 1000;
await sleep(waitMs);
continue;
}
if (response.status >= 500 && attempt < maxRetries) {
const waitMs = Math.min(1000 * 2 ** attempt, 30000);
const jitter = Math.random() * waitMs * 0.1;
await sleep(waitMs + jitter);
continue;
}
throw new Error(`API error: ${response.status}`);
}
}Idempotency#
Several write routes support idempotency via externalWriteId:
write-memorycreate-taskcontext-checkpoint(auto-generated fromagentRunId)
When a request with the same externalWriteId is received, the server returns the existing resource instead of creating a duplicate:
{
"memoryId": "existing-id",
"idempotent": true
}Status: 200 (not 201)
This makes it safe to retry write operations without risk of duplication.
Related Pages#
- API Overview -- Request/response format
- Authentication -- Auth errors and scopes
- Agent Routes -- Route-specific parameters
- Limits and Quotas -- Quota details