API Error Troubleshooting

Troubleshooting guide for SCORM API errors and HTTP status codes.

Table of Contents

HTTP Status Codes

200 OK

Meaning: Request succeeded.

When: Successful GET, PUT, PATCH requests.

Action: Process response data normally.

201 Created

Meaning: Resource created successfully.

When: Successful POST requests that create resources.

Action: Use returned resource data.

400 Bad Request

Meaning: Invalid request parameters.

Common Causes:

  • Missing required fields
  • Invalid data types
  • Validation errors

Solution:

// Check error details
const error = await response.json();
console.error('Validation errors:', error.details);

// Fix request
const correctedRequest = {
  ...request,
  // Fix validation errors
};

401 Unauthorized

Meaning: Authentication failed.

Common Causes:

  • Missing API key
  • Invalid API key
  • Expired API key

Solution:

// Verify API key
const apiKey = process.env.CONNECT_API_KEY;
if (!apiKey) {
  throw new Error('API key not configured');
}

// Check key format
if (!apiKey.startsWith('scorm_')) {
  throw new Error('Invalid API key format');
}

403 Forbidden

Meaning: Access denied.

Common Causes:

  • Insufficient permissions
  • Tenant mismatch
  • Resource access denied

Solution:

// Check API key scopes
// Verify tenant ID
// Ensure resource belongs to tenant

404 Not Found

Meaning: Resource not found.

Common Causes:

  • Invalid resource ID
  • Resource deleted
  • Wrong endpoint

Solution:

// Verify resource ID
// Check resource exists
// Verify endpoint URL

409 Conflict

Meaning: Version conflict or resource conflict.

Common Causes:

  • Optimistic locking conflict
  • Duplicate resource
  • Concurrent modification

Solution:

// Implement retry logic
// Fetch latest version
// Merge changes correctly

413 Payload Too Large

Meaning: Request body too large.

Common Causes:

  • File exceeds size limit
  • Request body too large

Solution:

// Use multipart upload for large files
// Split large requests
// Compress data

429 Too Many Requests

Meaning: Rate limit exceeded.

Common Causes:

  • Too many requests in time window
  • Exceeded quota

Solution:

// Implement exponential backoff
// Reduce request frequency
// Cache responses

500 Internal Server Error

Meaning: Server error.

Common Causes:

  • Temporary server issue
  • Processing error
  • Database error

Solution:

// Retry with exponential backoff
// Log error details
// Contact support if persistent

503 Service Unavailable

Meaning: Service temporarily unavailable.

Common Causes:

  • Maintenance
  • Overloaded server
  • Temporary outage

Solution:

// Retry after delay
// Check status page
// Wait and retry

Error Response Format

All errors follow this format:

{
  "error": "Human-readable error message",
  "code": "ERROR_CODE",
  "details": {
    "field": "Additional context"
  }
}

Example Error Responses

Validation Error:

{
  "error": "Invalid request parameters",
  "code": "INVALID_REQUEST",
  "details": {
    "tenant_id": "Must be a valid UUID",
    "uploaded_by": "Required field"
  }
}

Authentication Error:

{
  "error": "Invalid API key",
  "code": "INVALID_API_KEY"
}

Resource Error:

{
  "error": "Package not found",
  "code": "PACKAGE_NOT_FOUND",
  "details": {
    "package_id": "pkg_abc123"
  }
}

Common Error Codes

Authentication Errors

Code Description Solution
INVALID_API_KEY API key is invalid Verify API key, regenerate if needed
API_KEY_EXPIRED API key has expired Generate new API key
API_KEY_REVOKED API key was revoked Generate new API key
INSUFFICIENT_SCOPE API key lacks required scope Use API key with correct scopes

Validation Errors

Code Description Solution
INVALID_REQUEST Request validation failed Check request parameters
MISSING_FIELD Required field missing Include all required fields
INVALID_FORMAT Invalid data format Check data types and formats
INVALID_UUID Invalid UUID format Use valid UUID format

Resource Errors

Code Description Solution
PACKAGE_NOT_FOUND Package doesn't exist Verify package ID
SESSION_NOT_FOUND Session doesn't exist Verify session ID
TENANT_MISMATCH Tenant doesn't match Verify tenant ID
RESOURCE_NOT_FOUND Resource doesn't exist Verify resource ID

Processing Errors

Code Description Solution
INVALID_SCORM_PACKAGE Package is invalid Validate package structure
PROCESSING_FAILED Processing error Check package, retry
STORAGE_ERROR Storage backend error Retry, check storage status
QUOTA_EXCEEDED Quota limit reached Free up space or upgrade

Rate Limiting

Code Description Solution
RATE_LIMIT_EXCEEDED Too many requests Implement backoff, reduce frequency

Error Handling Best Practices

1. Always Check Response Status

async function makeRequest(url: string, options: RequestInit) {
  const response = await fetch(url, options);
  
  if (!response.ok) {
    const error = await response.json();
    throw new APIError(error.error, error.code, response.status);
  }
  
  return response.json();
}

2. Implement Retry Logic

async function makeRequestWithRetry(
  url: string,
  options: RequestInit,
  maxRetries = 3
) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);
      
      // Don't retry client errors (4xx)
      if (response.status >= 400 && response.status < 500) {
        throw new Error(`Client error: ${response.status}`);
      }
      
      // Retry server errors (5xx)
      if (response.status >= 500) {
        if (attempt < maxRetries - 1) {
          await delay(Math.pow(2, attempt) * 1000);
          continue;
        }
      }
      
      return response;
    } catch (error) {
      if (attempt === maxRetries - 1) throw error;
      await delay(Math.pow(2, attempt) * 1000);
    }
  }
}

3. Handle Rate Limits

async function makeRequestWithRateLimit(url: string, options: RequestInit) {
  const response = await fetch(url, options);
  
  if (response.status === 429) {
    const retryAfter = parseInt(
      response.headers.get('Retry-After') || '60'
    );
    
    console.log(`Rate limited. Waiting ${retryAfter} seconds...`);
    await new Promise(resolve => 
      setTimeout(resolve, retryAfter * 1000)
    );
    
    return makeRequestWithRateLimit(url, options);
  }
  
  return response;
}

4. Handle Version Conflicts

async function updateWithVersionConflict(
  updateFn: () => Promise<Response>,
  maxRetries = 3
) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const response = await updateFn();
    
    if (response.ok) {
      return await response.json();
    }
    
    if (response.status === 409 && attempt < maxRetries - 1) {
      // Version conflict - fetch latest and retry
      await delay(100 * (attempt + 1));
      continue;
    }
    
    throw new Error(`Update failed: ${response.status}`);
  }
}

5. Log Errors Appropriately

async function handleAPIError(error: any, context: string) {
  // Log for debugging
  console.error(`[${context}] API Error:`, {
    message: error.message,
    code: error.code,
    status: error.status,
    details: error.details
  });
  
  // Don't expose internal errors to users
  if (error.status >= 500) {
    return 'Service temporarily unavailable. Please try again later.';
  }
  
  // Return user-friendly messages
  return error.message || 'An error occurred';
}

Error Handling Examples

Complete Error Handler

class APIError extends Error {
  constructor(
    message: string,
    public code: string,
    public status: number,
    public details?: any
  ) {
    super(message);
    this.name = 'APIError';
  }
}

async function handleAPIRequest<T>(
  requestFn: () => Promise<Response>
): Promise<T> {
  try {
    const response = await requestFn();
    
    if (!response.ok) {
      const error = await response.json();
      throw new APIError(
        error.error || 'Request failed',
        error.code || 'UNKNOWN_ERROR',
        response.status,
        error.details
      );
    }
    
    return await response.json();
  } catch (error) {
    if (error instanceof APIError) {
      throw error;
    }
    
    // Network or other errors
    throw new APIError(
      'Network error',
      'NETWORK_ERROR',
      0,
      { originalError: error }
    );
  }
}

Usage

try {
  const package = await handleAPIRequest(() =>
    fetch('/api/v1/packages/pkg_abc123', {
      headers: { 'X-API-Key': apiKey }
    })
  );
  
  console.log('Package:', package);
} catch (error) {
  if (error instanceof APIError) {
    switch (error.code) {
      case 'PACKAGE_NOT_FOUND':
        console.error('Package not found');
        break;
      case 'RATE_LIMIT_EXCEEDED':
        console.error('Rate limit exceeded');
        break;
      default:
        console.error('API error:', error.message);
    }
  } else {
    console.error('Unexpected error:', error);
  }
}

Related Documentation


Last Updated: 2025-01-15