LMS API and action error contract¶
The LMS uses one machine-readable ErrorCode set for route handlers, server actions, and internal auth helpers. User-facing copy stays in next-intl; codes map to existing message slugs via errorCodeToI18nKey().
Modules¶
| Module | Role |
|---|---|
src/lib/api/error-codes.ts |
ERROR_CODES const + ErrorCode type + default HTTP status map |
src/lib/api/errors.ts |
ApiError, factories (unauthorized, forbidden, rateLimited, …), handleApiError() |
src/lib/api/action-result.ts |
ActionResult<T>, actionOk, actionFail, fromZodError, isActionFailure |
src/lib/api/i18n.ts |
errorCodeToI18nKey(code) — SCREAMING_SNAKE → snake_case i18n keys |
src/lib/api/adapters.ts |
actionFailureToApiError() for fetch callers |
Contract tests live under src/lib/api/__tests__/.
Route handlers (HTTP)¶
Failures return JSON:
error— safe for clients; never stack traces or secrets.code— stableErrorCode(SCREAMING_SNAKE).issues— optional Zod issue list on validation failures.
Wrap handlers with handleApiError() in catch blocks. Throw ApiError or factory helpers for expected failures.
Server actions¶
Success:
Failure:
UI should use errorCodeToI18nKey(result.code) when translating to t(\error.${key}`)` or hard-coded switch keys.
Internal auth¶
authorizeCredentials, linkOrCreateGoogleUser, and consumeVerificationToken return { ok: false, code: ErrorCode } instead of ad-hoc reason strings. Server actions map these codes into actionFail().
CMS alignment¶
Shape and helper layout mirror apps/cms/src/lib/api/errors.ts. Shared package extraction is tracked in audit ticket T-006.