Authentication¶
The API uses stateless JWT Bearer tokens (no cookies, no server sessions). There are two ways to authenticate: as a user (session tokens) or as an integration (API keys).
1. User sessions (JWT)¶
# Sign in
curl -X POST https://your-instance/api/v1/auth/signin \
-H "Content-Type: application/json" \
-d '{"email": "admin@example.com", "password": "..."}'
# → { "accessToken": "...", "refreshToken": "..." }
# Authenticated calls
curl -H "Authorization: Bearer <accessToken>" https://your-instance/api/v1/me/profile
# Refresh
curl -X POST https://your-instance/api/v1/auth/refresh \
-H "Content-Type: application/json" \
-d '{"refreshToken": "..."}'
Token details:
- Signed HS256 with
AUTH_SECRET; claims: user (sub),tenantId,roles[],mfaVerifiedandsid(the session id). - The
sidreferences a session row in the database that enables immediate revocation: closing a session from/me/security/sessions(or suspending the account) cuts access without waiting for the token to expire — an interceptor checks it on every request. POST /auth/forgot-passwordalways returns 200 (to prevent user enumeration).
MFA¶
Optional per-user TOTP (/auth/mfa/setup → /auth/mfa/enable → /auth/mfa/verify). It can be made mandatory for administrators instance-wide (DIDACTA_REQUIRE_MFA_ADMIN) or by organization policy (the feat:mfa.enforcement Enterprise capability). Where MFA is required and not verified, the API returns 403 {"code": "mfa_required"}.
2. API keys (integrations)¶
For external systems. Each key belongs to a user and their organization, and carries explicit scopes:
# Create one (as an admin) — the plaintext token is returned only ONCE
curl -X POST https://your-instance/api/v1/auth/api-keys \
-H "Authorization: Bearer <accessToken>" \
-H "Content-Type: application/json" \
-d '{"name": "CRM", "scopes": ["enrollments:write", "courses:read"], "expiresAt": null}'
# → { "token": "lmsk_..." }
# Use it — the ApiKey scheme, not Bearer
curl -H "Authorization: ApiKey lmsk_..." https://your-instance/api/v1/inscribe/courses
- Scopes available today:
enrollments:write(creating/removing enrollments through/inscribe) andcourses:read(catalog and access groups). - Keys are listed without their token and revoked with
DELETE /auth/api-keys/:id. - Endpoints with scopes are effectively API-key only: an admin JWT carries no scopes and does not pass the guard.
3. Corporate SSO¶
- OIDC (
/auth/oidc/*) and SAML 2.0 (/auth/saml/*) — configuring them is Enterprise (thefeat:sso.oidc/feat:sso.samlcapabilities); when unconfigured, the public flow returns 404. - WP-SSO (
/modules/wp-sso/*) — entry from a WordPress session with a short HMAC token (a Community module). - SCIM 2.0 (
/scim/v2/*) — provisioning from Okta/Entra with its own static bearer token, issued in the panel (Enterprise).
Real-time notifications (SSE)¶
EventSource does not accept headers, so the notification stream uses an ephemeral ticket: POST /me/notifications/stream-ticket returns a 60-second token that is passed as a query parameter when connecting to the stream.