Game Design/Platform
API Conventions
Last updated 2026-08-12
Standard patterns for API routes in A House Divided.
Admin Routes#
- Auth: Use
requireAdmin()from@/lib/api/requireAdminat the start of handlers. - Status code: Return 403 (Forbidden) when the user is not an admin.
- Pattern:
const auth = await requireAdmin(); if (!auth.ok) return auth.response; const { admin } = auth; // when you need admin.userId, admin.username, etc.
Error Handling#
- Pattern: Wrap the handler body in
try/catchand callhandleRouteError(error)from@/lib/api/errorsin thecatchblock. There is nowithRouteErrorwrapper; each route writes its own try/catch.export async function GET(request: Request) { try { // ... handler body } catch (error) { return handleRouteError(error); } } - Expected errors: Throw
ApiError(or its helpersbadRequest,unauthorized,forbidden,notFound,conflict,internalError) from@/lib/api/errorsfor 4xx/5xx.handleRouteErrorrecognizesApiErrorand returns itsstatus/JSON body directly. - Uncaught errors: Anything that isn't an
ApiErroris tagged, sent to Sentry viaSentry.captureExceptionandalertOps, logged, and returned as a generic 500 (internalError().toJson()).
Cron Routes#
- Auth: Use
requireCron(request)from@/lib/api/requireCron. - Header:
Authorization: Bearer ${CRON_SECRET} - Status code: Return 401 when cron auth fails.
Status Codes#
| Code | Use case |
|---|---|
| 401 | Not authenticated (no valid token) |
| 403 | Authenticated but forbidden (e.g. not admin) |
| 404 | Resource not found |
| 429 | Rate limited |
Cron vs Admin#
Some routes (e.g. /api/elections/snapshot) allow both cron and admin auth. Check cron first with requireCron(request); if false, fall back to requireAdmin().
Connected pages
References →
None← Referenced by
docsModule boundaries and layering