API Error Reference

API Version: 5.24.0

OAuth 2.0 Error Codes

Standard OAuth 2.0 errors from RFC 6749. Returned from authorization and token endpoints.

Authorization Endpoint Errors

Returned as query parameters in redirect URL:

https://yourapp.com/callback?error=ERROR_CODE&error_description=Message

access_denied

  • Cause: User denied authorization or lacks app permission
  • HTTP: 302 redirect with error in query params
  • Action: Check permission status (pending/revoked/none), show appropriate message

invalid_client

  • Cause: Unknown client_id or client suspended
  • Action: Verify client_id, contact SSO admins

invalid_scope

  • Cause: Requested scope not in client's allowed_scopes
  • Action: Only request registered scopes

unauthorized_client

  • Cause: Grant type not allowed for this client
  • Action: Verify OAuth client configuration

invalid_request

  • Cause: Missing required parameter or malformed request
  • Action: Check client_id, redirect_uri, response_type, scope

Token Endpoint Errors

Returned as JSON response body:

{
  "error": "ERROR_CODE",
  "error_description": "Human-readable message"
}

invalid_grant

HTTP 400 Bad Request

  • Cause: Authorization code expired (10 min), already used, or invalid
  • Action: Restart OAuth flow from /api/oauth/authorize
{
  "error": "invalid_grant",
  "error_description": "Authorization code expired or already used"
}

invalid_client

HTTP 401 Unauthorized

  • Cause: Wrong client_secret or invalid client_id
  • Action: Verify environment variables

redirect_uri_mismatch

HTTP 400 Bad Request

  • Cause: redirect_uri in token request differs from authorization request
  • Action: Use exact same redirect_uri in both requests

unsupported_grant_type

HTTP 400 Bad Request

  • Cause: Invalid grant_type value
  • Action: Use "authorization_code" or "refresh_token"

App Permission Error Codes

Errors specific to SSO app-level permission system:

APP_PERMISSION_DENIED

HTTP 403 Forbidden

User does not have permission to access this application.

{
  "error": {
    "code": "APP_PERMISSION_DENIED",
    "message": "User does not have permission to access this application",
    "status": "pending"  // or "revoked" or "none"
  }
}

Status-specific messages:

  • pending - "Your access request is pending admin approval."
  • revoked - "Your access has been revoked. Contact support."
  • none - "You don't have access. Contact administrator."

APP_PERMISSION_INSUFFICIENT_ROLE

HTTP 403 Forbidden

User has access but insufficient role for operation.

{
  "error": {
    "code": "APP_PERMISSION_INSUFFICIENT_ROLE",
    "message": "Admin role required",
    "currentRole": "user",
    "requiredRole": "admin"
  }
}

Authentication Error Codes

INVALID_TOKEN

HTTP 401 Unauthorized

  • Cause: Token malformed, invalid signature, or tampered
  • Action: Try refresh token, then re-authenticate

TOKEN_EXPIRED

HTTP 401 Unauthorized

{
  "error": {
    "code": "TOKEN_EXPIRED",
    "message": "Access token has expired",
    "expiredAt": "2025-10-15T17:22:16.000Z"
  }
}
  • Cause: Access token older than 1 hour
  • Action: Use refresh token to get new access token

INVALID_CREDENTIALS

HTTP 401 Unauthorized

  • Cause: Wrong email or password (direct login)
  • Action: Check credentials, note: rate limited after 10 failures

UNAUTHORIZED

HTTP 401 Unauthorized

  • Cause: No authentication provided
  • Action: Include Authorization: Bearer header

Validation Error Codes

INVALID_INPUT

HTTP 400 Bad Request

{
  "error": {
    "code": "INVALID_INPUT",
    "message": "Invalid request parameters",
    "details": [
      {
        "field": "email",
        "message": "Invalid email format"
      },
      {
        "field": "password",
        "message": "Password must be at least 8 characters"
      }
    ]
  }
}

EMAIL_EXISTS

HTTP 400 Bad Request

  • Cause: Email already registered
  • Action: User should login instead

MISSING_REQUIRED_FIELD

HTTP 400 Bad Request

  • Cause: Required field missing from request body
  • Action: Check API endpoint documentation for required fields

Rate Limiting Errors

rate_limit_exceeded

HTTP 429 Too Many Requests

HTTP/1.1 429 Too Many Requests
Retry-After: 60

{
  "error": "rate_limit_exceeded",
  "error_description": "Too many requests. Retry after 60 seconds.",
  "retry_after": 60
}
  • Action: Wait retry_after seconds, implement exponential backoff

Admin API Error Codes

ADMIN_AUTH_REQUIRED

HTTP 401 Unauthorized

  • Cause: Missing or invalid admin session cookie
  • Action: Redirect to /admin login

INSUFFICIENT_ADMIN_PERMISSIONS

HTTP 403 Forbidden

  • Cause: Operation requires super-admin, user is only admin
  • Action: Contact super-admin for this operation

USER_NOT_FOUND

HTTP 404 Not Found

  • Cause: User UUID doesn't exist
  • Action: Verify user ID is correct

Server Error Codes

INTERNAL_ERROR

HTTP 500 Internal Server Error

{
  "error": {
    "code": "INTERNAL_ERROR",
    "message": "An unexpected error occurred",
    "requestId": "req_abc123",
    "timestamp": "2025-10-15T16:22:16.000Z"
  }
}
  • Action: Retry with exponential backoff, contact support with requestId if persists

DATABASE_ERROR

HTTP 500 Internal Server Error

  • Cause: Database connection or query failed
  • Action: Usually transient, retry after delay

Error Handling Best Practices

1. Check Error Code, Not Message

Always use error.code for logic (messages may change):

// ✅ CORRECT
if (error.code === 'APP_PERMISSION_DENIED') {
  if (error.status === 'pending') {
    showPendingMessage();
  }
}

// ❌ WRONG
if (error.message.includes('permission')) {
  // Message text may change!
}

2. Implement Retry Logic

For transient errors (429, 500s), use exponential backoff:

async function fetchWithRetry(url, options, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await fetch(url, options);
      
      if (response.status === 429) {
        const retryAfter = response.headers.get('Retry-After') || 60;
        await sleep(retryAfter * 1000);
        continue;
      }
      
      if (response.status >= 500) {
        await sleep(Math.pow(2, i) * 1000);
        continue;
      }
      
      return response;
    } catch (err) {
      if (i === maxRetries - 1) throw err;
      await sleep(Math.pow(2, i) * 1000);
    }
  }
}

3. Show User-Friendly Messages

Map error codes to user-friendly text:

const ERROR_MESSAGES = {
  'APP_PERMISSION_DENIED': {
    pending: 'Your access is pending approval.',
    revoked: 'Your access was revoked.',
    none: "You don't have access."
  },
  'TOKEN_EXPIRED': 'Session expired. Please login.',
  'RATE_LIMIT_EXCEEDED': 'Too many requests. Wait and retry.',
  'INTERNAL_ERROR': 'Something went wrong.'
};

function getUserMessage(error) {
  if (error.code === 'APP_PERMISSION_DENIED') {
    return ERROR_MESSAGES[error.code][error.status];
  }
  return ERROR_MESSAGES[error.code] || 'An error occurred.';
}

4. Handle OAuth Errors Specially

OAuth errors come in query params OR JSON:

// Authorization callback
const params = new URLSearchParams(window.location.search);
if (params.has('error')) {
  const error = params.get('error');
  if (error === 'access_denied') {
    showAccessDenied();
  }
}

// Token endpoint
const response = await fetch('/api/oauth/token', {...});
const data = await response.json();
if (!response.ok && data.error === 'invalid_grant') {
  window.location.href = '/login';  // Restart OAuth
}

5. Log Errors (But Not Secrets!)

try {
  const response = await fetch(url, options);
  const data = await response.json();
  
  if (!response.ok) {
    console.error('API Error:', {
      url,
      status: response.status,
      error: data.error,
      requestId: data.error?.requestId,
      timestamp: new Date().toISOString()
      // NEVER log: passwords, tokens, secrets
    });
  }
} catch (err) {
  console.error('Network Error:', err);
}

Related Documentation