Reference — Core and cross-cutting
Authentication, account, setup, license, branding, storage, auditing, notifications, SSO, SCIM, external enrollment and webhooks. Every route hangs off /api/v1 except those marked with ⚠.
Authentication — /auth
| Method |
Route |
Auth |
What it does |
| GET |
/auth/tenant-context |
Public |
Resolves the tenant from the Host header and returns the sign-in screen branding (logo, headlines, stats), or { tenant: null }. |
| POST |
/auth/signup |
Public |
Registers a user in the tenant (if sign-up is enabled) and returns tokens. |
| POST |
/auth/signin |
Public |
Email + password sign-in → tokens + mfaRequired + user data. |
| POST |
/auth/refresh |
Public (refresh token in the body) |
Refreshes the access token and records IP/device on the session. |
| POST |
/auth/forgot-password |
Public |
Sends the reset email. Always returns 200 (anti-enumeration). |
| POST |
/auth/reset-password |
Public |
Confirms the reset with the token from the email + a new password (12–128). |
Relevant errors: signin returns a generic 401 ("invalid credentials"); if the email exists in several organizations it returns AMBIGUOUS_TENANT with the candidate slugs so the sign-in can be repeated with tenantSlug. signup returns 403 if registration is disabled and 409 if the email already exists in the tenant.
MFA — /auth/mfa
| Method |
Route |
Auth |
What it does |
| POST |
/auth/mfa/setup |
Bearer |
Generates the TOTP secret + QR + recovery codes (it does not enable MFA yet). |
| POST |
/auth/mfa/enable |
Bearer |
Confirms with the first 6-digit code; audits it and reissues tokens with mfaVerified=true. |
| POST |
/auth/mfa/verify |
Bearer |
Verifies the second factor (TOTP or a recovery code) and elevates the session. |
API keys — /auth/api-keys
| Method |
Route |
Auth |
What it does |
| POST |
/auth/api-keys |
Bearer |
Creates an API key: { name, scopes[], expiresAt? }. The lmsk_… token is only ever returned here. |
| GET |
/auth/api-keys |
Bearer |
Lists the user's keys (without tokens). |
| DELETE |
/auth/api-keys/:id |
Bearer |
Revokes it (sets revokedAt) → 204. |
My account — /me
| Method |
Route |
Auth |
What it does |
| GET · PATCH |
/me/profile |
Bearer |
Full profile · editing (name, bio, job title, locale, timezone, avatar, validated national ID). |
| GET · POST |
/me/onboarding/status · complete |
Bearer |
Onboarding status (missing[]) · marking it complete (422 if fields are missing). |
| GET · PUT |
/me/notification-preferences |
Bearer |
Category × channel matrix (COMMUNITY\|LEARNING\|ASSESSMENTS\|SYSTEM × EMAIL\|IN_APP). |
| POST |
/me/security/password |
Bearer |
Changes the password after verifying the current one; closes every session. |
| GET |
/me/security/sessions |
Bearer |
Up to 20 active sessions (dates, IP, user agent). |
| DELETE |
/me/security/sessions/:id |
Bearer |
Closes one specific session (immediate effect). |
| POST |
/me/security/sessions/revoke-others |
Bearer |
Closes every session. |
| GET |
/me/modules |
Bearer |
The tenant's active modules + Enterprise capabilities (this feeds the menu). |
Public profiles — /users
| Method |
Route |
Auth |
What it does |
| GET |
/users/public?ids=a,b,c |
Bearer |
A batch of { id, name, avatarUrl } (max. 100) for feed avatars. |
| GET |
/users/:id/public-profile |
Bearer |
Public profile (name, avatar, job title, bio). Never email, national ID or roles. |
Notifications — /me/notifications
| Method |
Route |
Auth |
What it does |
| GET |
/me/notifications |
Bearer |
My in-app notifications (max. 100, most recent first). |
| POST |
/me/notifications/:id/read · read-all |
Bearer |
Marks one / all as read (idempotent). |
| POST |
/me/notifications/stream-ticket |
Bearer |
Issues a 60 s JWT ticket for the stream. |
| GET |
/me/notifications/stream?ticket= |
SSE ticket |
SSE — real-time stream (notification / ping). |
Setup — /setup
| Method |
Route |
Auth |
What it does |
| GET |
/setup/status |
Public |
Does the instance already have an organization? |
| GET |
/setup/available-modules |
Public |
Modules for the wizard (isCore, enabledByDefault). |
| POST |
/setup/init |
Public |
First-start bootstrap. 409 ALREADY_INITIALIZED if tenants already exist. |
| Method |
Route |
Auth |
What it does |
| GET |
⚠ /api/license |
Public |
Public license status (status, capabilities, notices). Exempt from rate limiting. |
| GET |
/branding/options |
Public |
The tenant's branding for the UI (logoUrl, primaryColor, poweredByDidacta). |
| GET · POST |
/branding/white-label/preview · configure |
admin + the feat:white_label capability |
White-label status and configuration. Requires an administrator session; 402 without a license. |
| GET |
/system/version-check |
Public |
A proxy to the Docker Hub tags for the "new version" banner (15 min cache). |
| GET |
⚠ /healthz · /livez |
Public |
Liveness (version, uptime). |
| GET |
⚠ /readyz |
Public |
Readiness: checks the database, Redis and storage; 503 if anything is degraded. |
| GET |
⚠ /metrics |
Bearer <METRICS_TOKEN> if defined |
Prometheus/OpenMetrics metrics. |
Storage — /storage
| Method |
Route |
Auth |
What it does |
| POST |
/storage/upload |
Bearer (any role) |
Uploads an image or document as base64 (max. 10 MiB, MIME from a closed list); optimises images to WebP. |
| POST |
/storage/optimize |
Bearer (instructor+) |
Re-optimises an already uploaded image and returns the new URL. |
| GET |
/storage/file/* |
Public |
Serves a file from local storage by its key (with CSP: sandbox and nosniff). With S3, presigned URLs are used. |
Tenant settings — /tenant-settings (admin)
| Method |
Route |
What it does |
| GET |
/tenant-settings[/:scope[/:key]] |
Lists/reads settings; secrets return their non-sensitive fields and redact credentials. |
| PUT |
/tenant-settings/:scope/:key |
Creates/updates ({ value, isSecret }); encrypted at rest if it is a secret. |
| DELETE |
/tenant-settings/:scope/:key |
Deletes the setting. |
| POST |
/tenant-settings/notifications/smtp/test |
Sends a test email using the tenant's SMTP configuration. |
Auditing — /audit (admin or auditor)
| Method |
Route |
Auth |
What it does |
| GET |
/audit/entries |
admin/auditor |
The audit log with filters (actorId, action, resourceType, dates). On Community the range is truncated to 90 days; with feat:audit.long_retention it is unlimited. |
| GET |
/audit/verify |
admin/auditor |
Verifies the integrity of the log's hash chain. |
| GET |
/audit/retention-info |
admin/auditor |
The active policy: { plan, maxDays, capability }. |
| GET |
/audit/entries.zip?from=&to= |
admin + feat:reports.advanced_signed |
A signed ZIP export (manifest + NDJSON + signature), verifiable offline. 402 without the capability. |
SSO (public flows)
| Method |
Route |
What it does |
| GET |
/auth/oidc/:tenantSlug/status · start · /auth/oidc/callback |
OIDC: is it enabled? · redirect to the IdP (state+nonce+PKCE) · the callback that issues the session and redirects to the frontend. |
| GET/POST |
/auth/saml/:tenantSlug/status · login · acs · metadata |
SAML 2.0: status · AuthnRequest · Assertion Consumer Service (form-urlencoded) · the SP's XML metadata. |
| GET |
/modules/wp-sso/:tenantSlug/status · callback?token= |
WP-SSO: public configuration · exchanging WordPress's HMAC token for a Didacta session (302). |
With no configuration enabled, the flows return 404. Errors always redirect to /auth/error?reason=… with readable codes (state_expired, email_not_allowed, user_not_provisioned…).
SCIM 2.0 — ⚠ /scim/v2 (its own SCIM bearer token)
| Method |
Route |
What it does |
| GET |
/scim/v2/ServiceProviderConfig · ResourceTypes · Schemas |
Discovery (not gated). |
| GET · POST |
/scim/v2/Users |
Lists with SCIM pagination and a userName eq filter · creates a user (201). |
| GET · PATCH · DELETE |
/scim/v2/Users/:id |
Reads · applies a PatchOp (active, name, locale…) · soft deletes (204). |
All five CRUD operations require the feat:scim capability (402 without a license). The token is issued at /admin/scim/token. The full guide —attribute mapping, error format and what is not supported— is in SCIM provisioning.
External enrollment — /inscribe (API keys)
| Method |
Route |
Scope |
What it does |
| POST |
/inscribe |
enrollments:write |
Creates or reuses a user by email and enrolls them in courseIds/accessGroupIds. Idempotent. |
| POST |
/inscribe/revoke |
enrollments:write |
Removes API-originated enrollments (a refund). A non-existent email → userFound: false, not a 404. |
| GET |
/inscribe/courses |
courses:read |
The catalog with status, to map product → course. |
| GET |
/inscribe/access-groups |
courses:read |
Access groups with kind and courseCount. |
Auth: Authorization: ApiKey lmsk_…. A user JWT does not pass these endpoints: they require API key scopes.
Read access for external sites — /integrations (API keys)
The other half of /inscribe: that one writes, this one reads. Meant for an external site to render a course page with Didacta's data and know whether the visitor is already a student.
| Method |
Route |
Scope |
What it does |
| GET |
/integrations/courses |
courses:read |
Courses filterable by slug, by status or by externalId + externalSource (mapping from the source LMS). |
| GET |
/integrations/courses/:courseId |
courses:read |
The full record: metadata, instructor, totals, syllabus module by module and the purchase offer. Never a lesson's content. |
| GET |
/integrations/learners/state |
enrollments:read |
A student's state by email: enrollments, progress and nextLesson with a direct URL. known:false if the email belongs to nobody. |
enrollments:read is deliberately separate from enrollments:write: it allows asking about any email in the organisation.
Watch out for hasAccess:false + status:"PAUSED": that is a suspended student (typically for non-payment) with intact progress, not somebody who never bought.
Full guide with examples: Selling and integrating from outside.
Outgoing webhooks — /webhooks (admin)
| Method |
Route |
What it does |
| GET |
/webhooks/info |
The active tier, the effective limits and the catalog of subscribable events. |
| GET |
/webhooks/endpoints[/:id] |
List / detail (with the secret masked). |
| POST |
/webhooks/endpoints |
Creates one: { url (https), eventTypes[] ("*" = all), secret?, active? }. The plaintext secret is returned exactly once. |
| PUT |
/webhooks/endpoints/:id |
Updates it; sending secret rotates it (one-shot). |
| DELETE |
/webhooks/endpoints/:id |
Deletes it (204, idempotent). |
Errors: 409 for a duplicate URL · 422 webhook_limit_exceeded when the plan limit is exceeded (Community: 1 endpoint / 3 event types; Enterprise: 20 / unlimited).