Skip to main content

Bearer Token

All authenticated endpoints require a valid JWT access token in the Authorization header.
Authorization: Bearer <access_token>

Token Types

The API issues two token types:
TokenPurposeLifetime
Access tokenAuthenticates API requestsShort-lived (configurable, typically 30 min)
Refresh tokenUsed to obtain a new access tokenLong-lived (configurable, typically 7 days)

Token Claims

Access tokens contain the following claims:
ClaimTypeDescription
substringThe user’s unique ID
tenant_idstringThe tenant this user belongs to
rolestringLegacy role enum (STUDENT, FACULTY, ADMIN, SUPER_ADMIN)
role_idstringThe user’s dynamic role ID (used for permission checks)
typestringAlways "access" for access tokens
expnumberToken expiration timestamp (Unix epoch)
The role claim is kept for backward compatibility. Authorization is now handled by the dynamic permission system via role_id. See Roles & Permissions for details.

Authorization Model

The API uses a permission-based authorization system. Each role has a set of permissions organized by module and action:
ActionDescription
can_viewRead access (list, get)
can_createCreate new resources
can_editUpdate existing resources
can_deleteDelete resources
Modules include: USER_MANAGEMENT, ROLE_MANAGEMENT, TENANT_MANAGEMENT, CASE_STUDIES, KNOWLEDGE_BASES, ASSESSMENTS, SESSIONS, FEEDBACK. Console endpoints (/v1/console/*) require specific module permissions. User-facing endpoints (/v1/*) are available to any authenticated user.

Handling Token Expiry

When an access token expires, the API returns:
{
  "success": false,
  "error": "Token has expired",
  "code": "TOKEN_EXPIRED",
  "details": {}
}
How to handle this:
  1. Catch any 401 response where code is TOKEN_EXPIRED
  2. Call your refresh endpoint with the refresh token to get a new access token
  3. Retry the original request with the new access token
  4. If the refresh also fails, redirect the user to login

Auth Error Responses

No Authorization header was provided.
{
  "success": false,
  "error": "Missing authentication token",
  "code": "AUTHENTICATION_ERROR",
  "details": {}
}
The header doesn’t follow the Bearer <token> format.
{
  "success": false,
  "error": "Invalid authorization header format. Expected 'Bearer <token>'.",
  "code": "AUTHENTICATION_ERROR",
  "details": {}
}
The token signature is invalid, the token is corrupted, or claims are missing.
{
  "success": false,
  "error": "Invalid or malformed token",
  "code": "AUTHENTICATION_ERROR",
  "details": {}
}
The access token has expired. Use the refresh token to get a new one.
{
  "success": false,
  "error": "Token has expired",
  "code": "TOKEN_EXPIRED",
  "details": {}
}
The user’s role does not have the required permission for this endpoint.
{
  "success": false,
  "error": "Permission denied: CASE_STUDIES.can_create required",
  "code": "AUTHORIZATION_ERROR",
  "details": {}
}

Frontend Pseudocode

async function apiCall(url: string, options: RequestInit) {
  let response = await fetch(url, {
    ...options,
    headers: {
      ...options.headers,
      Authorization: `Bearer ${getAccessToken()}`,
      "Content-Type": "application/json",
    },
  });

  const data = await response.json();

  if (!data.success && data.code === "TOKEN_EXPIRED") {
    const refreshed = await refreshAccessToken();
    if (refreshed) {
      // Retry with new token
      return apiCall(url, options);
    }
    // Refresh failed — force login
    redirectToLogin();
    return;
  }

  return data;
}