Next.js — API layer
All backend communication in the Next.js app goes through two wrapper functions. Direct calls to fetch() are prohibited by convention (AGENTS.md). The wrappers handle JWT auth, audit headers, workspace scoping, and error surfacing uniformly.
Core wrappers (src/lib/api/core.ts)
fetchAPI<T>(endpoint, options?)
Calls ${NEXT_PUBLIC_API_URL}${endpoint}. Used for all main dashboard API calls.
fetchExt<T>(endpoint, options?)
Calls ${NEXT_PUBLIC_API_URL}ext/${endpoint}. Used for widget-facing ext/ calls made from the dashboard (e.g., publish, sync).
Both delegate to the internal fetchJson<T> function which:
- Reads the JWT from
useAuthStore.getState().token(Zustand + localStorage persistence). - Attaches
Authorization: Bearer <token>if a token exists. - Sets
Content-Type: application/jsonfor non-FormData bodies. - Calls
buildAuditHeaders()if the method is POST, PUT, PATCH, or DELETE. - Throws on non-2xx. On 401, logs out and redirects to
/login. - Returns
{}on 204 No Content.
AuditRequestOptions
Extends RequestInit with four optional fields that map to the audit header system:
| Field | Header sent |
|---|---|
auditAction | X-Audit-Action |
auditResourceType | X-Audit-Resource-Type |
auditResourceId | X-Audit-Resource-Id |
auditRequestId | X-Audit-Request-Id (defaults to crypto.randomUUID()) |
Audit headers are only sent on mutating methods. X-Audit-Request-Id is always set on mutations even if the other fields are omitted.
buildWorkspaceAwareEndpoint(endpoint, workspaceId?)
Appends ?workspace_id=<id> (or &workspace_id=<id>) to an endpoint. If workspaceId is not passed, reads useWorkspaceStore.getState().activeWorkspaceId. If no workspace is active, returns the endpoint unchanged.
Most resource API calls in domain modules call this before passing the endpoint to fetchAPI.
getErrorMessageFromData(errorData, fallback)
Extracts a human-readable message from the error response JSON. Priority: message → error → first value in errors → first array value in any other key → fallback string. API modules pass the result to addAlert(message, 'error') from useUiStore.
Domain modules (src/lib/api/*.ts)
Each resource has its own file. All are thin wrappers over fetchAPI / fetchExt:
| File | Resources covered |
|---|---|
auth.ts | login, register, me, logout |
finders.ts | finder CRUD, analytics, integrations |
locations.ts | location CRUD, image upload, CSV import |
maps.ts | map CRUD |
sets.ts | set/category CRUD |
tags.ts | tag CRUD |
customFields.ts | custom field definition CRUD |
workspaces.ts | workspace CRUD, member management |
aiImports.ts | AI location import job lifecycle |
aiImprovements.ts | AI location improvement job lifecycle |
processes.ts | process polling and SSE |
admin.ts | admin-only operations |
Normalizers and payloads
normalizers.ts
Converts raw API response shapes to typed domain objects. All useQuery results pass through a normalizer before being consumed by components. This is the boundary where snake_case API fields become camelCase TypeScript types.
payloads.ts
Converts domain values to the wire format expected by the API. Used by mutation calls before sending. Handles JSON serialization of complex fields (e.g., design config, settings array).
Related pages
- Integration points: Next.js → Backend — full protocol detail
- Next.js: State management — Zustand stores, TanStack Query patterns
- Next.js: Conventions — API layer discipline rules