> ## Documentation Index
> Fetch the complete documentation index at: https://docs.baanx.com/llms.txt
> Use this file to discover all available pages before exploring further.

# OAuth Troubleshooting

> Common issues and solutions for OAuth 2.0 implementation

## Overview

This guide covers common issues you might encounter when implementing OAuth 2.0, along with their solutions and prevention strategies.

<Note>
  **Quick Debug Tip:** Most OAuth issues fall into one of three categories: PKCE validation, state parameter mismatch, or token confusion. Check these first.
</Note>

<Info>
  **Remember:** OAuth troubleshooting is about ensuring users can successfully authorize your application to act on their behalf. Technical issues may prevent users from granting or using the permissions they intend to provide.
</Info>

## Common Errors

<AccordionGroup>
  <Accordion title="invalid_request - Missing required parameters" icon="circle-exclamation">
    **Error Response:**

    ```json theme={null}
    {
      "error": "invalid_request",
      "error_description": "Missing required parameter: code_challenge"
    }
    ```

    **Causes:**

    * Missing PKCE parameters (`code_challenge`, `code_challenge_method`)
    * Missing `response_type` or incorrect value
    * Missing `state` parameter
    * Missing `client_id` or `redirect_uri`

    **Solutions:**

    1. Verify all required parameters are present
    2. Check parameter names match exactly (case-sensitive)
    3. Ensure PKCE parameters use correct encoding (base64url)

    **Example Fix:**

    ```typescript theme={null}
    // ❌ Wrong - missing parameters
    const params = new URLSearchParams({
      client_id: 'abc123'
    });

    // ✅ Correct - all required parameters
    const params = new URLSearchParams({
      response_type: 'code',
      client_id: 'abc123',
      redirect_uri: 'https://yourapp.com/callback',
      state: generateState(),
      code_challenge: generateCodeChallenge(verifier),
      code_challenge_method: 'S256'
    });
    ```
  </Accordion>

  <Accordion title="invalid_grant - Invalid or expired authorization code" icon="hourglass-end">
    **Error Response:**

    ```json theme={null}
    {
      "error": "invalid_grant",
      "error_description": "Authorization code has expired or already been used"
    }
    ```

    **Causes:**

    * Authorization code already used (codes are single-use)
    * Code expired (10-minute lifetime)
    * Code\_verifier doesn't match code\_challenge
    * Too much time between OAuth steps

    **Solutions:**

    1. Restart OAuth flow from Step 1
    2. Don't reuse authorization codes
    3. Verify PKCE verifier matches the original challenge
    4. Complete token exchange within 10 minutes

    **Example Fix:**

    ```typescript theme={null}
    // ❌ Wrong - reusing code
    const tokens1 = await exchangeCode(code, verifier);
    const tokens2 = await exchangeCode(code, verifier); // ERROR

    // ✅ Correct - use code once
    const tokens = await exchangeCode(code, verifier);
    // Store tokens, don't exchange again
    ```
  </Accordion>

  <Accordion title="invalid_client - Authentication failed" icon="key">
    **Error Response:**

    ```json theme={null}
    {
      "error": "invalid_client",
      "error_description": "Client authentication failed"
    }
    ```

    **Causes:**

    * Incorrect `x-client-key` or `x-secret-key`
    * Using sandbox keys in production (or vice versa)
    * Keys revoked or expired
    * Missing required headers

    **Solutions:**

    1. Verify both client key and secret key are correct
    2. Ensure you're using keys for the correct environment
    3. Check headers are named exactly: `x-client-key` and `x-secret-key`
    4. Contact support if keys need to be regenerated

    **Example Fix:**

    ```typescript theme={null}
    // ❌ Wrong - incorrect header names
    headers: {
      'client-key': 'pk_abc123',
      'secret-key': 'sk_xyz789'
    }

    // ✅ Correct - proper header names
    headers: {
      'x-client-key': 'pk_abc123',
      'x-secret-key': 'sk_xyz789'
    }
    ```
  </Accordion>

  <Accordion title="State parameter mismatch" icon="shield-xmark">
    **Symptom:**
    Client-side validation fails when comparing state parameters.

    **Causes:**

    * State not stored correctly in session
    * State modified during OAuth flow
    * User restored from different session/device
    * CSRF attack attempt

    **Solutions:**

    1. Store state in secure session storage
    2. Validate state exactly (case-sensitive)
    3. Don't allow state to be modified
    4. Restart OAuth if state mismatch occurs

    **Example Fix:**

    ```typescript theme={null}
    // ❌ Wrong - storing in localStorage (can be modified)
    localStorage.setItem('oauth_state', state);

    // ✅ Correct - using secure session
    sessionStorage.setItem('oauth_state', state);

    // Validation
    const savedState = sessionStorage.getItem('oauth_state');
    if (returnedState !== savedState) {
      // Don't proceed - possible CSRF attack
      throw new Error('State mismatch - please restart login');
    }
    ```
  </Accordion>

  <Accordion title="redirect_uri mismatch" icon="arrow-turn-down-left">
    **Error Response:**

    ```json theme={null}
    {
      "error": "invalid_request",
      "error_description": "redirect_uri does not match whitelisted URI"
    }
    ```

    **Causes:**

    * Redirect URI not whitelisted in environment config
    * URI doesn't match exactly (protocol, domain, path)
    * Trailing slash mismatch
    * Query parameters in URI

    **Solutions:**

    1. Contact admin to whitelist exact redirect URI
    2. Ensure exact match including protocol (https\://)
    3. Don't include query parameters in redirect\_uri
    4. Match trailing slashes exactly

    **Example Fix:**

    ```typescript theme={null}
    // ❌ Wrong - different from whitelisted URI
    redirect_uri: 'http://app.example.com/callback'  // Missing 's' in https
    redirect_uri: 'https://app.example.com/callback/'  // Extra trailing slash
    redirect_uri: 'https://app.example.com/auth/callback'  // Different path

    // ✅ Correct - exact match
    redirect_uri: 'https://app.example.com/callback'
    ```
  </Accordion>

  <Accordion title="Session expired (10 minutes)" icon="clock">
    **Error Response:**

    ```json theme={null}
    {
      "error": "invalid_grant",
      "error_description": "OAuth session has expired"
    }
    ```

    **Causes:**

    * More than 10 minutes between initiation and authorization
    * User took too long on hosted UI
    * Network delays or user distraction

    **Solutions:**

    1. Complete OAuth flow within 10 minutes
    2. Display countdown timer to users
    3. Restart flow if session expires
    4. Don't pre-initiate OAuth too early

    **Example Fix:**

    ```typescript theme={null}
    const SESSION_TIMEOUT = 10 * 60 * 1000; // 10 minutes

    class OAuthSession {
      private startTime = Date.now();

      isExpired(): boolean {
        return Date.now() - this.startTime >= SESSION_TIMEOUT;
      }

      getRemainingTime(): number {
        return Math.max(0, SESSION_TIMEOUT - (Date.now() - this.startTime));
      }
    }

    // Show warning when time is running out
    if (session.getRemainingTime() < 60000) {
      showWarning('Please complete login within 1 minute');
    }

    // Restart if expired
    if (session.isExpired()) {
      await restartOAuthFlow();
    }
    ```
  </Accordion>

  <Accordion title="Token confusion - Using wrong token type" icon="shuffle">
    **Symptom:**
    401 Unauthorized despite having tokens.

    **Causes:**

    * Using JWT token for API calls (should use access token)
    * Using login access token for final API calls
    * Mixing up tokens between OAuth steps

    **Solutions:**

    1. JWT Token: Only in Step 3 request body (API mode)
    2. Login Access Token: Only in Step 3 Authorization header (API mode)
    3. Final Access Token: For all subsequent API calls
    4. Refresh Token: Only for token refresh endpoint

    **Example Fix:**

    ```typescript theme={null}
    // OAuth Flow Tokens (API Mode)
    const { token: jwtToken } = await initiateOAuth();  // Step 1
    const loginAccessToken = await login(email, password);  // Step 2

    // Step 3: Generate auth code
    await fetch('/v1/auth/oauth/authorize', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${loginAccessToken}`,  // Login token in header
      },
      body: JSON.stringify({
        token: jwtToken  // JWT token in body
      })
    });

    // Step 4: Get final tokens
    const { access_token: finalAccessToken } = await exchangeCode(...);

    // All subsequent API calls
    await fetch('/v1/users/me', {
      headers: {
        'Authorization': `Bearer ${finalAccessToken}`  // Final access token
      }
    });
    ```

    <Warning>
      **Common Mistake:** Using the JWT token from Step 1 for API calls. The JWT is ONLY for OAuth flow coordination.
    </Warning>
  </Accordion>

  <Accordion title="PKCE validation failed" icon="shield-xmark">
    **Error Response:**

    ```json theme={null}
    {
      "error": "invalid_grant",
      "error_description": "Code verifier validation failed"
    }
    ```

    **Causes:**

    * code\_verifier doesn't match original code\_challenge
    * Incorrect SHA256 hashing
    * Base64url encoding issues
    * Using different verifier than original

    **Solutions:**

    1. Verify SHA256 hash is correctly computed
    2. Use base64url encoding (not standard base64)
    3. Store and retrieve exact same verifier
    4. Check verifier length (43-128 characters)

    **Example Fix:**

    ```typescript theme={null}
    // ❌ Wrong - incorrect encoding
    function generateChallenge(verifier: string): string {
      return crypto
        .createHash('sha256')
        .update(verifier)
        .digest('base64');  // Wrong - should be base64url
    }

    // ✅ Correct - base64url encoding
    function generateCodeChallenge(verifier: string): string {
      return crypto
        .createHash('sha256')
        .update(verifier)
        .digest('base64url');  // Correct
    }

    // Verify stored verifier matches
    const storedVerifier = sessionStorage.getItem('code_verifier');
    if (!storedVerifier) {
      throw new Error('Code verifier not found - restart OAuth flow');
    }
    ```
  </Accordion>

  <Accordion title="OTP required but not provided" icon="mobile">
    **Error Response:**

    ```json theme={null}
    {
      "error": "otp_required",
      "message": "User has 2FA enabled",
      "requiresOtp": true
    }
    ```

    **Causes:**

    * User has OTP/2FA enabled
    * OTP code not included in login request

    **Solutions:**

    1. Check login response for `requiresOtp` field
    2. Call OTP send endpoint to trigger code delivery
    3. Prompt user for OTP code
    4. Retry login with `otpCode` parameter

    **Example Fix:**

    ```typescript theme={null}
    async function handleLogin(email: string, password: string) {
      try {
        const response = await login(email, password);
        return response;
      } catch (error) {
        if (error.requiresOtp) {
          // Send OTP to user
          await sendOTP(email);

          // Prompt user for code
          const otpCode = await promptUserForOTP();

          // Retry with OTP
          return await login(email, password, otpCode);
        }
        throw error;
      }
    }
    ```
  </Accordion>

  <Accordion title="401 after successful token exchange" icon="ban">
    **Symptom:**
    API calls fail with 401 immediately after getting access token.

    **Causes:**

    * Using wrong access token (e.g., login token instead of final token)
    * Token not being sent in Authorization header
    * Incorrect Bearer format
    * Missing x-client-key header

    **Solutions:**

    1. Use access\_token from token exchange response
    2. Include in Authorization header as "Bearer {token}"
    3. Always include x-client-key header
    4. Check for extra spaces or formatting issues

    **Example Fix:**

    ```typescript theme={null}
    // Get final tokens
    const tokens = await exchangeCodeForTokens(code, verifier);

    // ❌ Wrong - various issues
    fetch('/v1/users/me', {
      headers: {
        'Authorization': tokens.access_token  // Missing "Bearer"
      }
    });

    fetch('/v1/users/me', {
      headers: {
        'Authorization': `Bearer ${tokens.access_token}`
        // Missing x-client-key
      }
    });

    // ✅ Correct - all required headers
    fetch('/v1/users/me', {
      headers: {
        'Authorization': `Bearer ${tokens.access_token}`,
        'x-client-key': process.env.CLIENT_KEY
      }
    });
    ```
  </Accordion>

  <Accordion title="Refresh token not working" icon="rotate">
    **Error Response:**

    ```json theme={null}
    {
      "error": "invalid_grant",
      "error_description": "Refresh token is invalid or expired"
    }
    ```

    **Causes:**

    * Refresh token expired (7 days)
    * Using old refresh token (tokens rotate)
    * Token was revoked
    * Incorrect grant\_type parameter

    **Solutions:**

    1. Check refresh token hasn't expired
    2. Always use the newest refresh token (they rotate)
    3. Clear tokens and re-authenticate if refresh fails
    4. Ensure grant\_type is "refresh\_token"

    **Example Fix:**

    ```typescript theme={null}
    // ❌ Wrong - using old refresh token
    const oldToken = getStoredRefreshToken();
    const newTokens = await refreshAccessToken(oldToken);
    // Still using oldToken next time - ERROR

    // ✅ Correct - always update refresh token
    const currentToken = getStoredRefreshToken();
    const newTokens = await refreshAccessToken(currentToken);

    // Update stored token immediately
    await storeRefreshToken(newTokens.refresh_token);  // Important!
    await storeAccessToken(newTokens.access_token);

    // Handle expiration
    if (refreshResponse.status === 401) {
      // Refresh token expired - need full re-auth
      await clearAllTokens();
      await redirectToLogin();
    }
    ```
  </Accordion>
</AccordionGroup>

## Debugging Strategies

### 1. Check Request/Response

<Steps>
  <Step title="Enable Network Logging">
    Use browser DevTools or a proxy to inspect requests:

    ```typescript theme={null}
    // Add logging to your API client
    async function apiCall(url: string, options: RequestInit) {
      console.log('Request:', { url, options });

      const response = await fetch(url, options);
      const data = await response.json();

      console.log('Response:', { status: response.status, data });

      return data;
    }
    ```
  </Step>

  <Step title="Verify Headers">
    Ensure all required headers are present:

    * `x-client-key`: Always required
    * `x-secret-key`: Required for OAuth endpoints
    * `Authorization`: Required for authenticated endpoints
    * `Content-Type`: Required for POST/PUT requests
  </Step>

  <Step title="Check Response Codes">
    Different status codes indicate different issues:

    * `400`: Bad request (missing/invalid parameters)
    * `401`: Authentication failed (invalid token/credentials)
    * `403`: Forbidden (valid auth, insufficient permissions)
    * `498`: Invalid client key
    * `499`: Missing client key
  </Step>
</Steps>

### 2. Validate PKCE Flow

<CodeGroup>
  ```typescript Verify PKCE theme={null}
  function testPKCE() {
    const verifier = generateCodeVerifier();
    console.log('Verifier:', verifier);
    console.log('Length:', verifier.length); // Should be 43-128

    const challenge = generateCodeChallenge(verifier);
    console.log('Challenge:', challenge);

    // Verify character set
    const validChars = /^[A-Za-z0-9\-._~]+$/;
    console.log('Valid chars:', validChars.test(verifier));

    // Test round-trip
    const testChallenge = generateCodeChallenge(verifier);
    console.log('Matches:', challenge === testChallenge);
  }
  ```

  ```python Verify PKCE theme={null}
  def test_pkce():
      verifier = generate_code_verifier()
      print(f'Verifier: {verifier}')
      print(f'Length: {len(verifier)}')  # Should be 43-128

      challenge = generate_code_challenge(verifier)
      print(f'Challenge: {challenge}')

      # Verify character set
      import re
      valid_chars = re.match(r'^[A-Za-z0-9\-._~]+$', verifier)
      print(f'Valid chars: {bool(valid_chars)}')

      # Test round-trip
      test_challenge = generate_code_challenge(verifier)
      print(f'Matches: {challenge == test_challenge}')
  ```
</CodeGroup>

### 3. Trace Token Lifecycle

<CodeGroup>
  ```typescript Token Tracer theme={null}
  class TokenTracer {
    private events: Array<{ time: Date; event: string; data: any }> = [];

    log(event: string, data: any) {
      this.events.push({
        time: new Date(),
        event,
        data
      });
      console.log(`[${event}]`, data);
    }

    dump() {
      console.table(this.events);
    }
  }

  const tracer = new TokenTracer();

  // Track token flow
  tracer.log('OAUTH_INIT', { jwtToken: jwt.substring(0, 20) });
  tracer.log('LOGIN', { accessToken: token.substring(0, 20) });
  tracer.log('AUTH_CODE', { code: code.substring(0, 20) });
  tracer.log('TOKEN_EXCHANGE', {
    accessToken: tokens.access_token.substring(0, 20),
    refreshToken: tokens.refresh_token.substring(0, 20)
  });

  // Review trace
  tracer.dump();
  ```

  ```python Token Tracer theme={null}
  from datetime import datetime
  from typing import Any, Dict, List

  class TokenTracer:
      def __init__(self):
          self.events: List[Dict[str, Any]] = []

      def log(self, event: str, data: Any):
          self.events.append({
              'time': datetime.now(),
              'event': event,
              'data': data
          })
          print(f'[{event}]', data)

      def dump(self):
          for event in self.events:
              print(f"{event['time']}: {event['event']} - {event['data']}")

  tracer = TokenTracer()

  # Track token flow
  tracer.log('OAUTH_INIT', {'jwtToken': jwt[:20]})
  tracer.log('LOGIN', {'accessToken': token[:20]})
  tracer.log('AUTH_CODE', {'code': code[:20]})
  tracer.log('TOKEN_EXCHANGE', {
      'accessToken': tokens['access_token'][:20],
      'refreshToken': tokens['refresh_token'][:20]
  })

  # Review trace
  tracer.dump()
  ```
</CodeGroup>

## Environment-Specific Issues

### Sandbox vs Production

<CardGroup cols={2}>
  <Card title="Sandbox Issues" icon="flask">
    * HTTP allowed in sandbox only
    * Different client keys per environment
    * Sandbox data doesn't transfer to production
    * Rate limits may differ
  </Card>

  <Card title="Production Issues" icon="globe">
    * HTTPS strictly required
    * Redirect URIs must be whitelisted
    * More strict validation
    * Lower tolerance for errors
  </Card>
</CardGroup>

### Regional Routing

If using US environment:

```typescript theme={null}
// Add x-us-env header for US routing
headers: {
  'x-client-key': process.env.CLIENT_KEY,
  'x-us-env': 'true'  // Required for US environment
}
```

## Prevention Best Practices

### 1. Implement Comprehensive Error Handling

<CodeGroup>
  ```typescript TypeScript theme={null}
  class OAuthError extends Error {
    constructor(
      public code: string,
      public description: string,
      public statusCode: number
    ) {
      super(description);
    }
  }

  async function handleOAuthError(response: Response) {
    const error = await response.json();

    switch (error.error) {
      case 'invalid_grant':
        // Restart OAuth flow
        await clearOAuthState();
        throw new OAuthError(
          error.error,
          'Authorization expired. Please login again.',
          response.status
        );

      case 'invalid_client':
        // Configuration issue
        logCritical('Invalid OAuth credentials');
        throw new OAuthError(
          error.error,
          'Authentication configuration error. Please contact support.',
          response.status
        );

      case 'invalid_request':
        // Developer error
        logError('OAuth request invalid', error);
        throw new OAuthError(
          error.error,
          error.error_description || 'Invalid request',
          response.status
        );

      default:
        throw new OAuthError(
          error.error || 'unknown_error',
          error.error_description || 'An unexpected error occurred',
          response.status
        );
    }
  }
  ```

  ```python Python theme={null}
  class OAuthError(Exception):
      def __init__(self, code: str, description: str, status_code: int):
          self.code = code
          self.description = description
          self.status_code = status_code
          super().__init__(description)

  def handle_oauth_error(response: requests.Response):
      error = response.json()
      error_code = error.get('error', 'unknown_error')

      if error_code == 'invalid_grant':
          # Restart OAuth flow
          clear_oauth_state()
          raise OAuthError(
              error_code,
              'Authorization expired. Please login again.',
              response.status_code
          )
      elif error_code == 'invalid_client':
          # Configuration issue
          log_critical('Invalid OAuth credentials')
          raise OAuthError(
              error_code,
              'Authentication configuration error. Please contact support.',
              response.status_code
          )
      elif error_code == 'invalid_request':
          # Developer error
          log_error('OAuth request invalid', error)
          raise OAuthError(
              error_code,
              error.get('error_description', 'Invalid request'),
              response.status_code
          )
      else:
          raise OAuthError(
              error_code,
              error.get('error_description', 'An unexpected error occurred'),
              response.status_code
          )
  ```
</CodeGroup>

### 2. Add Validation Checks

```typescript theme={null}
function validateOAuthState() {
  const checks = [
    {
      name: 'Code Verifier',
      test: () => {
        const verifier = getCodeVerifier();
        return verifier && verifier.length >= 43 && verifier.length <= 128;
      }
    },
    {
      name: 'State Parameter',
      test: () => {
        const state = getState();
        return state && state.length >= 16;
      }
    },
    {
      name: 'Redirect URI',
      test: () => {
        const uri = getRedirectUri();
        return uri && uri.startsWith('https://');
      }
    }
  ];

  const failed = checks.filter(check => !check.test());

  if (failed.length > 0) {
    console.error('Validation failed:', failed.map(f => f.name));
    return false;
  }

  return true;
}
```

### 3. Set Up Monitoring

```typescript theme={null}
// Track OAuth success/failure rates
function trackOAuthMetric(event: string, success: boolean, error?: string) {
  analytics.track('OAuth Event', {
    event,
    success,
    error,
    timestamp: Date.now(),
    environment: process.env.NODE_ENV
  });
}

// Usage
try {
  await initiateOAuth();
  trackOAuthMetric('initiate', true);
} catch (error) {
  trackOAuthMetric('initiate', false, error.message);
}
```

## Testing Tools

### OAuth Flow Tester

<CodeGroup>
  ```typescript Test Suite theme={null}
  import { describe, it, expect } from 'vitest';

  describe('OAuth Flow', () => {
    it('generates valid PKCE parameters', () => {
      const verifier = generateCodeVerifier();
      expect(verifier.length).toBeGreaterThanOrEqual(43);
      expect(verifier.length).toBeLessThanOrEqual(128);
      expect(verifier).toMatch(/^[A-Za-z0-9\-._~]+$/);

      const challenge = generateCodeChallenge(verifier);
      expect(challenge).toBeDefined();
      expect(challenge.length).toBeGreaterThan(0);
    });

    it('validates state parameter', () => {
      const state = generateState();
      expect(state.length).toBeGreaterThanOrEqual(16);
    });

    it('handles token refresh correctly', async () => {
      const tokens = await refreshAccessToken('valid_refresh_token');
      expect(tokens.access_token).toBeDefined();
      expect(tokens.refresh_token).toBeDefined();
    });
  });
  ```

  ```python Test Suite theme={null}
  import pytest

  def test_pkce_generation():
      verifier = generate_code_verifier()
      assert len(verifier) >= 43
      assert len(verifier) <= 128

      import re
      assert re.match(r'^[A-Za-z0-9\-._~]+$', verifier)

      challenge = generate_code_challenge(verifier)
      assert challenge
      assert len(challenge) > 0

  def test_state_generation():
      state = generate_state()
      assert len(state) >= 16

  @pytest.mark.asyncio
  async def test_token_refresh():
      tokens = await refresh_access_token('valid_refresh_token')
      assert 'access_token' in tokens
      assert 'refresh_token' in tokens
  ```
</CodeGroup>

## Getting Additional Help

<CardGroup cols={2}>
  <Card title="Check API Status" icon="signal">
    Verify there are no ongoing incidents or maintenance:

    * Check status page
    * Review error rates in dashboard
    * Confirm environment availability
  </Card>

  <Card title="Review Logs" icon="file-lines">
    Enable detailed logging to capture:

    * Request/response bodies
    * Header values (sanitize secrets!)
    * Timing information
    * Error stack traces
  </Card>

  <Card title="Contact Support" icon="headset">
    When contacting support, include:

    * Error messages and codes
    * Request/response examples (sanitized)
    * Environment (sandbox/production)
    * Approximate timestamp of issues
    * Client key (NOT secret key)
  </Card>

  <Card title="Community Resources" icon="users">
    Get help from the community:

    * Search documentation
    * Review example implementations
    * Check for similar issues
    * Share non-sensitive code samples
  </Card>
</CardGroup>

## Quick Reference

### OAuth Error Codes

| Error Code            | Meaning                | Typical Cause              | Solution                             |
| --------------------- | ---------------------- | -------------------------- | ------------------------------------ |
| `invalid_request`     | Missing/invalid params | Incorrect API call         | Check all required parameters        |
| `invalid_grant`       | Code invalid/expired   | Used code twice or expired | Restart OAuth flow                   |
| `invalid_client`      | Auth failed            | Wrong keys                 | Verify client credentials            |
| `access_denied`       | User denied            | User clicked deny          | Expected behavior, handle gracefully |
| `unauthorized_client` | Client not authorized  | OAuth not enabled          | Contact administrator                |

### HTTP Status Codes

| Code  | Meaning            | Action                      |
| ----- | ------------------ | --------------------------- |
| `400` | Bad Request        | Check request parameters    |
| `401` | Unauthorized       | Check authentication tokens |
| `403` | Forbidden          | Check permissions/scope     |
| `404` | Not Found          | Check endpoint URL          |
| `498` | Invalid Client Key | Verify x-client-key header  |
| `499` | Missing Client Key | Add x-client-key header     |
| `500` | Server Error       | Retry or contact support    |

## Next Steps

<CardGroup cols={2}>
  <Card title="OAuth Quick Start" icon="rocket" href="/guides/oauth/quickstart">
    Start implementing OAuth from scratch
  </Card>

  <Card title="Security Best Practices" icon="shield" href="/guides/oauth/security">
    Review security guidelines
  </Card>

  <Card title="Token Management" icon="rotate" href="/guides/oauth/token-management">
    Master token lifecycle
  </Card>

  <Card title="API Reference" icon="book" href="/api-reference/introduction">
    Detailed endpoint documentation
  </Card>
</CardGroup>
