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:
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).