API Error Client Patterns

Implement resilient response parsing, refresh, backoff, logging, validation, and user-facing error handling.

AuthOS release 0.8.2 API v1 Latest-only documentation
Updated Jul 15, 2026
On this page

Error Handling Best Practices

1. Check HTTP Status First

Always check the HTTP status code before parsing the response body:

async function makeRequest(url, options) {
  const response = await fetch(url, options);

  if (!response.ok) {
    const error = await response.json();
    throw new APIError(response.status, error);
  }

  return response.json();
}

class APIError extends Error {
  constructor(status, errorBody) {
    super(errorBody.error);
    this.status = status;
    this.code = errorBody.error_code;
    this.timestamp = errorBody.timestamp;
  }
}

2. Handle Token Expiration Automatically

Implement automatic token refresh on TOKEN_EXPIRED errors:

async function apiRequestWithRefresh(url, options) {
  let response = await fetch(url, options);

  if (response.status === 401) {
    const error = await response.json();

    if (error.error_code === 'TOKEN_EXPIRED') {
      // Refresh token
      const newTokens = await refreshAccessToken();

      // Retry original request with new token
      options.headers['Authorization'] = `Bearer ${newTokens.access_token}`;
      response = await fetch(url, options);
    }
  }

  return response;
}

3. Implement Exponential Backoff for Rate Limits

Use exponential backoff when encountering rate limits:

async function fetchWithBackoff(url, options, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    const response = await fetch(url, options);

    if (response.status === 429) {
      const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
      const backoff = Math.min(retryAfter * 1000, 2 ** i * 1000);

      console.log(`Rate limited. Retrying after ${backoff}ms...`);
      await new Promise(resolve => setTimeout(resolve, backoff));
      continue;
    }

    return response;
  }

  throw new Error('Max retries exceeded');
}

4. Handle Network Errors Separately

Distinguish between API errors and network failures:

async function apiRequest(url, options) {
  try {
    const response = await fetch(url, options);

    if (!response.ok) {
      const error = await response.json();
      throw new APIError(response.status, error);
    }

    return response.json();
  } catch (error) {
    if (error instanceof APIError) {
      // API returned an error response
      console.error('API Error:', error.code, error.message);
      throw error;
    } else {
      // Network error (timeout, DNS failure, etc.)
      console.error('Network Error:', error.message);
      throw new NetworkError(error);
    }
  }
}

5. Log Errors with Context

Include request context when logging errors:

async function apiRequest(url, options) {
  const requestId = generateRequestId();

  try {
    const response = await fetch(url, options);

    if (!response.ok) {
      const error = await response.json();

      console.error('API Request Failed', {
        requestId,
        url,
        method: options.method,
        status: response.status,
        errorCode: error.error_code,
        errorMessage: error.error,
        timestamp: error.timestamp
      });

      throw new APIError(response.status, error);
    }

    return response.json();
  } catch (error) {
    if (!(error instanceof APIError)) {
      console.error('Request Failed', {
        requestId,
        url,
        method: options.method,
        error: error.message
      });
    }
    throw error;
  }
}

6. Display User-Friendly Messages

Don’t expose raw error messages to end users:

function getUserFriendlyMessage(error) {
  const friendlyMessages = {
    'TOKEN_EXPIRED': 'Your session has expired. Please log in again.',
    'UNAUTHORIZED': 'Authentication failed. Please log in.',
    'FORBIDDEN': 'You don\'t have permission to perform this action.',
    'NOT_FOUND': 'The requested resource could not be found.',
    'SERVICE_LIMIT_EXCEEDED': 'You\'ve reached your service limit. Please upgrade your plan.',
    'RATE_LIMIT_EXCEEDED': 'Too many requests. Please try again in a moment.',
    'INTERNAL_SERVER_ERROR': 'Something went wrong. Please try again later.'
  };

  return friendlyMessages[error.code] || 'An unexpected error occurred.';
}

7. Validate Before Sending

Validate input client-side before making API requests:

function validateEmail(email) {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  if (!emailRegex.test(email)) {
    throw new ValidationError('Invalid email format');
  }
}

async function createUser(email) {
  // Validate first to avoid unnecessary API call
  validateEmail(email);

  return apiRequest('/api/service/users', {
    method: 'POST',
    headers: {
      'X-API-Key': process.env.SSO_API_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ email })
  });
}

8. Handle Specific Error Codes

Implement specific handlers for different error codes:

async function handleAPIError(error) {
  switch (error.code) {
    case 'TOKEN_EXPIRED':
      await refreshAndRetry();
      break;

    case 'ORGANIZATION_NOT_ACTIVE':
      redirectToSuspendedPage();
      break;

    case 'SERVICE_LIMIT_EXCEEDED':
      showUpgradeModal();
      break;

    case 'RATE_LIMIT_EXCEEDED':
      await retryWithBackoff();
      break;

    default:
      showGenericError(getUserFriendlyMessage(error));
  }
}