Skip to content

LMS OpenAPI specification

The LMS publishes a machine-readable contract for route handlers under src/app/api/**. CMS-owned REST operations remain documented in the upstream edx-cms OpenAPI document.

Artefact location

Item Path
Generated spec apps/lms/public/openapi.yaml
Registry (Zod + paths) apps/lms/src/schemas/openapi-registry.ts
Generator CLI apps/lms/scripts/openapi-generate.ts

At runtime the file is served as /openapi.yaml and loaded by the admin Swagger UI at /admin/api-docs.

Regenerating

From the repo root:

npm run openapi:generate -w apps/lms

Commit public/openapi.yaml when registry or route documentation changes.

Drift guards

Two Vitest files keep the contract honest (same pattern as CMS):

Test Purpose
tests/unit/openapi-sync.test.ts Committed public/openapi.yaml must equal buildOpenApiYaml()
tests/unit/openapi-coverage.test.ts Every src/app/api/**/route.ts handler is registered or explicitly excluded
tests/unit/openapi-validity.test.ts Generated document validates as OpenAPI 3.0 and enforces path-template hygiene

CI runs these via turbo run test:coverage — no separate workflow step and no change to npm run check. CMS now uses the same route-coverage guard pattern in apps/cms/src/lib/api/openapi/__tests__/openapi-coverage.test.ts, with explicit exclusions listed in OPENAPI_EXCLUDED_PATH_PREFIXES.

Documented routes

Method Path Auth
POST /api/cms/revalidate CMS_WEBHOOK_SECRET (header or Bearer)
POST /api/admin/impersonate/start Auth.js session cookie
POST /api/admin/impersonate/stop Auth.js session cookie
GET /api/health Public (DB connectivity probe)
GET /api/favicon Public (tenant favicon proxy)
GET /api/avatar Auth.js session cookie (current user)
GET /api/aeo/markdown/{slug} Public (published top-level pages only)
GET /api/aeo/markdown/blog/{slug} Public (published blog pages only)
GET /api/aeo/markdown/learn/course/{slug} Public (published course pages only)
GET /api/cms/courses/{courseSlug}/assets/{assetId} Public (published course only)
GET /api/cms/courses/{courseSlug}/assets/{assetId}/download Public (published course only)
GET /api/cms/site/assets/{assetId} Public
GET /api/cms/site/assets/{assetId}/download Public

Component schemas for CMS content shapes (courses, entries, quizzes, etc.) are registered for reference and future public API work; they are not bound to HTTP paths in this document.

Deliberate exclusions

These handlers exist in the tree but are not part of the LMS OpenAPI contract:

  • /api/auth/\[...nextauth\] — NextAuth framework transport; unstable across Auth.js upgrades.
  • /api/test/oauth/google — local/test-only.

Exclusions are listed in OPENAPI_EXCLUDED_PATH_PREFIXES inside openapi-registry.ts.

Catch-all route policy

OpenAPI path parameters are single-segment, so Next.js catch-all filesystem routes (for example src/app/api/aeo/markdown/[...path]/route.ts) should be documented as explicit rewrite shapes instead of {...path} placeholders.

When a catch-all route is represented by multiple explicit paths, register the split in OPENAPI_ROUTE_PATH_ALIASES inside src/schemas/openapi-registry.ts so the coverage guard can verify all documented aliases.

Error responses

JSON error bodies follow the T-003 contract documented in API and Action Errors: { error, code, issues? } with code from ERROR_CODES. The OpenAPI component ApiErrorEnvelope mirrors that shape.

Server actions

Server actions are internal React transport and are intentionally out of scope for this spec (see monorepo audit standing decisions).