Error Handling

All API errors follow a consistent JSON response format with appropriate HTTP status codes.

Error Response Format

{
  "error": "error_code",
  "message": "Human readable error message"
}

HTTP Status Codes

StatusDescription
400Bad Request - Invalid request data
401Unauthorized - Missing or invalid authentication
403Forbidden - Insufficient permissions
404Not Found - Resource doesn't exist
409Conflict - Resource already exists
429Too Many Requests - Rate limit exceeded (future)
500Internal Server Error - Unexpected error

Authentication Errors (401)

Missing Authorization Header

{
  "error": "unauthorized",
  "message": "Missing Authorization header"
}

Invalid Token Format

{
  "error": "unauthorized",
  "message": "Invalid Authorization header format. Expected: Bearer {token}"
}

Invalid or Expired Token

{
  "error": "unauthorized",
  "message": "Invalid access token"
}

Revoked Token

{
  "error": "unauthorized",
  "message": "Token has been revoked"
}

Suspended App

{
  "error": "unauthorized",
  "message": "External application has been suspended"
}

Permission Errors (403)

Insufficient Scope

{
  "error": "forbidden",
  "message": "Insufficient permissions. Required scope: read"
}

Validation Errors (400)

Invalid Request Data

{
  "error": "validation_error",
  "message": "Invalid request data",
  "details": [
    {
      "path": ["externalCourseId"],
      "message": "Required"
    }
  ]
}

Resource Errors

Not Found (404)

{
  "error": "not_found",
  "message": "Course not found or not linked to this external app"
}

Already Exists (409)

{
  "error": "course_already_exists",
  "message": "Course with externalCourseId 'xyz' already exists",
  "existingCourse": {
    "id": "course_123",
    "name": "Example DGC"
  }
}

Server Errors (500)

{
  "error": "internal_server_error",
  "message": "An unexpected error occurred"
}

Best Practices

1. Check HTTP Status First

if (!response.ok) {
  const error = await response.json()
  console.error(`Error ${response.status}:`, error.message)
}

2. Handle Specific Error Codes

const error = await response.json()

switch (error.error) {
  case 'unauthorized':
    // Refresh token or re-authenticate
    break
  case 'forbidden':
    // Request additional permissions
    break
  case 'not_found':
    // Handle missing resource
    break
  case 'validation_error':
    // Show validation errors to user
    break
}

3. Implement Retry Logic

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

      if (response.status === 401) {
        // Don't retry auth errors
        throw new Error('Authentication failed')
      }

      if (response.ok) {
        return response
      }

      // Retry on 500 errors with exponential backoff
      if (response.status >= 500) {
        await new Promise(resolve =>
          setTimeout(resolve, Math.pow(2, i) * 1000)
        )
        continue
      }

      // Don't retry client errors
      throw new Error(`HTTP ${response.status}`)
    } catch (error) {
      if (i === maxRetries - 1) throw error
    }
  }
}

4. Log Error Details

try {
  const response = await fetch(url, options)
  if (!response.ok) {
    const error = await response.json()
    console.error('API Error:', {
      status: response.status,
      error: error.error,
      message: error.message,
      url,
      timestamp: new Date().toISOString()
    })
  }
} catch (error) {
  console.error('Network Error:', error)
}

Troubleshooting

Token expired

Solution: Use refresh token to get a new access token

Token revoked

Solution: User needs to re-authorize your application

Permission denied

Solution: Request appropriate scopes during authorization

Resource not found

Solution: Verify the resource ID and ensure it's linked to your app

Next Steps