PopChoice Docs

API Reference

Canonical reference for PopChoice app API route families, authentication expectations, and operational endpoints.

This page tracks the current Next.js API routes in apps/web/src/app/api. It is a practical app-internal reference, not a public OpenAPI stability contract. Prefer the persisted recommendation API for new product flows and treat compatibility, poster, and operational routes as implementation details unless a caller is explicitly documented here.

Related work: #515 and #520. For setup details, see /docs/SETUP, /docs/SERVICES, and /docs/DEVELOPMENT.

Authentication Model

Protected product APIs use withAuth and accept either:

  • API key auth: Authorization: Bearer <key> or X-API-Key: <key>. Production validates scrypt-derived digests from VALID_API_KEYS using API_KEY_HMAC_SECRET.
  • Same-origin browser auth: matching __csrf cookie plus X-CSRF-Token header. When a signed session cookie exists, handlers receive a user-scoped caller id; otherwise some withAuth routes accept a browser CSRF caller.

Account-only routes read the signed session cookie directly and do not accept API keys. Mutating browser account routes require the same-origin CSRF pair. apps/web/src/proxy.ts issues the readable __csrf cookie on non-API page navigation.

In development, protected API key checks are relaxed when VALID_API_KEYS is absent. Do not rely on that behavior in production.

Recommendation APIs

RouteMethodsAuthNotes
/api/recommendationsPOSTwithAuth; API key or same-origin browser CSRF/sessionPreferred async API. Validates quiz input, rate-limits, creates a persisted recommendation job, and returns { id } with 201. Signed-in users attach the job to their account.
/api/recommendations/[id]GETwithAuth; API key or same-origin browser CSRF/sessionPolls or loads a persisted recommendation by slug. Session users only receive records they are allowed to view. Returns 404 when missing.
/api/recommendations/[id]/feedbackPOSTwithAuth; API key or same-origin browser CSRF/sessionRecords feedback for a completed recommendation. Body accepts kind: useful, already_watched, wrong_mood, too_obvious, too_obscure, or close.
/api/recommendations/[id]/more-picksPOSTwithAuth; API key or same-origin browser CSRF/sessionEnqueues one additional TMDB more-picks job for a completed recommendation. Returns 202 when claimed and 409 when already requested or unavailable.
/api/movie-recommendationGET, POSTPOST uses withAuth; GET is unauthenticated documentation metadataCompatibility synchronous recommendation API. POST runs the legacy in-request AI pipeline and returns the full recommendation response. Prefer /api/recommendations for new flows.
/api/more-tmdb-picksPOSTwithAuth; API key or same-origin browser CSRF/sessionCompatibility endpoint for non-persisted more-picks. Requires TMDB_API_KEY; accepts quiz data, page, and excluded TMDB ids. Prefer persisted /api/recommendations/[id]/more-picks.

Recommendation routes are rate-limited and enforce bounded JSON bodies. They may return validation errors (400/422), upstream timeout responses (504), or generic server errors without exposing provider secrets.

Account And Movie Memory

These routes are browser account APIs. They are not external service APIs.

RouteMethodsAuthNotes
/api/accountGETSigned session cookieReturns the current user's email, saved recommendation summaries, and a first movie-memory page. Uses Cache-Control: no-store and Vary: Cookie.
/api/account/movie-memoryGETSigned session cookieSupports catalog search by query/q, mode=list pagination, and mode=candidates training candidates. Rate-limited and private.
/api/account/movie-memoryPOSTSigned session cookie plus same-origin CSRF pairAdds one memory item or a batch of memory items for the current user. Returns saved item data or validation errors.
/api/account/movie-memoryDELETESigned session cookie plus same-origin CSRF pairDeletes one memory item by movieKey.

Movie-memory writes tolerate incomplete catalog metadata but still depend on the database schema and TMDB/catalog enrichment paths. See /docs/SERVICES for catalog workers and backfill services.

Auth, Session, And Password Reset

These routes are browser-facing auth endpoints. They use rate limiting on expensive password operations and intentionally avoid user-enumeration details.

RouteMethodsAuthNotes
/api/auth/registerPOSTNo existing session required; rate-limited browser JSON endpointCreates a user, signs a session cookie, and returns { ok: true } with 201. Validation errors return 422.
/api/auth/loginPOSTSame-origin CSRF pair; no existing session requiredVerifies credentials, signs a session cookie, and returns { ok: true }. Invalid credentials return 401.
/api/auth/logoutPOSTwithAuth; API key or same-origin browser CSRF/sessionClears the session cookie and returns { ok: true }. In normal UI use this is a browser session call.
/api/auth/sessionGETOptional signed session cookieReturns { authenticated: true, userId } or { authenticated: false }. Clears an invalid session cookie when present.
/api/auth/delete-accountPOSTSame-origin CSRF pair plus email/password verificationDeletes the matching user account, clears the session cookie, and returns { ok: true }.
/api/auth/forgot-passwordPOSTSame-origin CSRF pair; no existing session requiredCreates a reset token for known users, sends reset email when configured, and always returns an accepted-style { ok: true } response. Development may expose resetUrl when explicitly configured.
/api/auth/reset-passwordPOSTSame-origin CSRF pair; valid reset tokenConsumes a password reset token and updates the password. Invalid or expired tokens return 400.

Do not log or expose session secrets, password reset tokens, API keys, or token digests. See /docs/SETUP for the required auth environment variables.

Catalog And Poster Helpers

These routes support UI catalog browsing and poster enrichment. The poster helpers are app-internal and should not be treated as a stable public API.

RouteMethodsAuthNotes
/api/moviesGETwithAuth; API key or same-origin browser CSRF/sessionReturns a paginated catalog page. Query params include page, pageSize, query/title, yearFrom, yearTo, duration, minScore, and ageRating/ageRatings.
/api/movie-postersPOSTNo API key/session auth; rate-limited app-internal helperAccepts a bounded batch of movie ids/names/year/TMDB ids and returns poster/localized metadata fetched server-side with TMDB_API_KEY. The TMDB key never reaches the browser.
/api/poster-proxyGETNo API key/session auth; rate-limited app-internal helperProxies allowed poster images and returns cacheable image bytes. Rejects invalid or disallowed URLs.
/api/poster-urlsGETNo API key/session auth; app-internal helperReturns popular poster URLs for ambient UI. If TMDB_API_KEY is absent or TMDB fails, returns an empty poster list.

Operational Health, Build, And Metrics

Operational routes are for deployment, monitoring, and debugging. They are not product APIs.

RouteMethodsAuthNotes
/api/healthGETNo bearer authChecks PostgreSQL and Redis with short timeouts, records dependency health metrics, and returns 200 for healthy or 503 for degraded. Responses are briefly cached in-process and sent with Cache-Control: no-store.
/api/buildGETNo bearer authReturns build metadata from getBuildInfo() with Cache-Control: no-store. Useful for deployment verification.
/api/metricsGETMetrics bearer token when configuredReturns Prometheus metrics when metrics are enabled. In production, set METRICS_ENABLED=true and require Authorization: Bearer <METRICS_BEARER_TOKEN>. When disabled it returns 404; unauthorized scrapes return 401.

For Prometheus, worker metrics, Grafana, and Coolify target configuration, see /docs/OBSERVABILITY-METRICS.

On this page