Skip to content

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#

json
{
  "error": "Missing or invalid agent key"
}

Status: 401

Validation Errors#

json
{
  "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#

json
{
  "error": "Internal server error"
}

Status: 500

Error Types#

AuthError (401, 403)#

Thrown by the authentication and authorization middleware.

StatusError MessageCause
401Missing or invalid agent keyNo Authorization header or key not found
401Invalid agent keyKey hash does not match any stored key
401Agent key has been revokedKey was revoked from the dashboard
401Agent key has expiredKey's expiresAt is in the past
401User not foundUser account associated with key no longer exists
403Integration is inactive or deletedThe integration was deactivated
403SCOPE_DENIED: This key does not have '...' permissionKey lacks the required scope
403SCOPE_DENIED: This key does not have '...' permission for vault ...Per-vault scope insufficient
403SCOPE_DENIED: This key does not have access to the requested vaultVault not in allowedVaultIds
403Integration encryption is not configured...Missing wrapped MEK
400Invalid vaultId for this userVault 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:

FieldConstraintExample Error
contentmin(1), max(100_000)"String must contain at least 1 character(s)"
memoryTypemin(1), max(50)"String must contain at least 1 character(s)"
importanceint, min(1), max(5)"Number must be less than or equal to 5"
topKint, min(1), max(50)"Number must be less than or equal to 50"
thresholdmin(0), max(1)"Number must be less than or equal to 1"
vaultIduuid"Invalid uuid"

Quota Errors (429)#

json
{
  "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:

json
{
  "error": "Too many requests",
  "retryAfter": 60
}

Status: 429

Error Codes Summary#

CodePatternDescription
SCOPE_DENIED403Insufficient permissions for the operation or vault
QUOTA_EXCEEDED429Monthly 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#

StatusRetryableStrategy
429 (rate limit)YesExponential backoff, respect retryAfter
429 (quota)LimitedRetry next billing period or upgrade plan
500YesExponential backoff with jitter (max 3 retries)
503YesExponential backoff (service temporarily unavailable)

Non-Retryable Errors#

StatusRetryableAction
400NoFix the request body
401NoCheck agent key validity
403NoCheck permissions or integration configuration
404NoVerify resource exists
typescript
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-memory
  • create-task
  • context-checkpoint (auto-generated from agentRunId)

When a request with the same externalWriteId is received, the server returns the existing resource instead of creating a duplicate:

json
{
  "memoryId": "existing-id",
  "idempotent": true
}

Status: 200 (not 201)

This makes it safe to retry write operations without risk of duplication.