Error Reference

A single reference for every error you can encounter using Ravi — CLI auth errors, REST API HTTP status codes, and error response shapes.

CLI auth errors

These codes appear in ravi auth status --json and in CLI stderr output.

Auth status response shapes

ravi auth status --json returns one of three shapes:

// Authenticated
{ "authenticated": true, "email": "you@example.com", "has_encryption": true }

// Token expired
{ "authenticated": false, "error": "token_expired", "message": "Access token expired and refresh failed." }

// Not logged in
{ "authenticated": false, "error": "not_authenticated", "message": "No credentials found. Run ravi auth login." }

Use authenticated as the boolean gate in automation scripts — do not parse the message string.

Auth error codes

ErrorCauseRecovery
device_code_expiredDevice code was not authorized within 15 minutesRe-run ravi auth login and complete the OAuth flow promptly
pin_lockedPIN entered incorrectly 3 timesRun ravi auth recover and enter your recovery key from ~/.ravi/recovery-key.txt
token_expiredRefresh token invalidated (e.g. password change, logout from another device)Re-run ravi auth login; in headless environments, rotate RAVI_ACCESS_TOKEN
encryption_missingauth.json exists but PIN was never setRun ravi auth setup-encryption to initialize the vault
identity_not_foundConfig references a deleted Identity UUIDRun ravi identity list and update .ravi/config.json with a valid UUID
not_authenticatedNo credentials found at allRun ravi auth login; in CI/containers, set RAVI_ACCESS_TOKEN env var

Guarding long-running processes

check_auth() {
  local status
  status=$(ravi auth status --json 2>/dev/null)
  if ! echo "$status" | jq -e '.authenticated' > /dev/null 2>&1; then
    echo "Ravi auth failed — re-login required" >&2
    exit 1
  fi
}
import subprocess, json, sys

def check_auth():
    result = subprocess.run(
        ["ravi", "auth", "status", "--json"],
        capture_output=True, text=True
    )
    try:
        status = json.loads(result.stdout)
    except json.JSONDecodeError:
        sys.exit("ravi auth status returned unexpected output")
    if not status.get("authenticated"):
        sys.exit(f"Ravi auth error: {status.get('error')} — re-login required")

REST API errors

HTTP status codes

All REST API endpoints return standard HTTP status codes. Error responses include a JSON body.

StatusMeaningCommon causeRecovery
400Bad requestMissing required field, invalid formatCheck the request body against the endpoint schema
401UnauthorizedMissing or expired Bearer tokenRefresh via POST /api/auth/token/refresh/ or re-authenticate
402Payment requiredAction requires an active Ravi subscriptionUpgrade at ravi.app/billing
403ForbiddenToken valid but missing scope, or identity not owned by authenticated accountVerify X-Ravi-Identity UUID belongs to the authenticated account
404Not foundUUID does not exist or was deletedRe-list the resource to confirm it still exists
409ConflictIdentity name or email already takenChoose a different name or list existing identities
429Rate limitedToo many requestsBack off and retry; email send limits are 60/hr and 500/day
500Server errorUnexpected server-side failureRetry with exponential backoff; report persistent errors

Error response shape

Standard error:

{
  "detail": "Authentication credentials were not provided.",
  "code": "not_authenticated"
}

Validation error (400) — field-level detail:

{
  "name": ["This field is required."]
}

Token refresh

When a 401 is returned with "code": "token_expired", refresh the access token:

curl -s -X POST https://ravi.app/api/auth/token/refresh/ \
  -H "Content-Type: application/json" \
  -d '{"refresh": "<refresh_token>"}' \
  | jq -r .access
import httpx

def refresh_token(refresh_token: str) -> str:
    resp = httpx.post(
        "https://ravi.app/api/auth/token/refresh/",
        json={"refresh": refresh_token},
    )
    resp.raise_for_status()
    return resp.json()["access"]

The refresh token is stored in ~/.ravi/auth.json under the refresh_token key, or passed via environment for headless deployments.


Rate limits

OperationLimit
Email send60 / hour, 500 / day
Identity createContact support for bulk limits
Inbox pollingNo documented hard limit — use 2–10 s intervals

See also