API and Server Actions
Cashlytics primarily uses Next.js Server Actions as the application API layer. A separate read-only REST API v1 exists for external integrations.
Implementation Model
- Mutations and queries used by the UI are implemented in
src/actions/*. Every action checks the session withrequireAuth(), and update actions verify ownership. - Features that need streaming, file bodies, or external callers are Route Handlers in
src/app/api. - Server Actions return a consistent success/error shape (see below). Route Handlers do not share one shape; the formats are listed per route.
Action Domains
Server Actions live in src/actions/:
- Accounts
- Expenses, daily expenses, subscriptions, and attachments
- Income
- Transfers
- Categories
- Dashboard and analytics
- Forecasts (
getAccountForecast) - Reserves and reserve pots (
getReservePlan,setReservePot) - Insights
- Merchant category learning (
getMerchantCategoryHints,learnMerchantCategory) - Search and AI conversations
- Documents (
uploadDocument,downloadDocument, and list/delete) - CSV import staging, reconciliation, conflict decisions, and confirmation
- User settings
- API tokens (create, list, revoke)
- Authentication (login, register, logout, forgot and reset password)
- Tool preview
Exchange rates and push subscriptions are not actions. Rates come from src/lib/exchange-rates.ts; push and cron are API routes.
API Routes
| Route | Purpose | Notes |
|---|---|---|
/api/auth/[...nextauth] | next-auth handlers | Excluded from the session check |
POST /api/chat | AI assistant chat and tool orchestration | 401 without session, 503 without OPENAI_API_KEY, 20 requests/min per user. Error body { error } |
POST /api/receipt-scan | Receipt image extraction | JPG/PNG, 5 MB, 5 requests/min per user. Returns 400 without OPENAI_API_KEY. Body { success, data } or { success: false, error } |
GET /api/documents | List user documents | Authenticated, scoped to the session user |
POST /api/documents | Legacy upload route, not used by the UI | Currently defective: it validates expenseId/dailyExpenseId as integers while IDs are UUIDs. Uploads go through the Server Action uploadDocument |
GET /api/documents/[id] | Fetch one document | Authenticated, scoped to the session user; Content-Disposition: inline, Cache-Control: private, max-age=3600 |
DELETE /api/documents/[id] | Delete one document | Authenticated, scoped to the session user |
GET /api/exchange-rates | EUR-based exchange rates as a flat object ({ "EUR": 1, "USD": ... }) | Requires a session. No bundled client calls it |
GET /api/push/vapid-public-key | Browser push public key | { key: null } with status 503 if VAPID is not fully configured |
POST /api/push/subscribe | Save push subscription | Authenticated |
DELETE /api/push/subscribe | Remove push subscription | Authenticated |
GET /api/cron/upcoming-payments | Send upcoming payment reminders | Authorization: Bearer <CRON_SECRET>; 500 if the secret is not set, 401 on mismatch |
GET /api/cron/insights | Generate insights for all users | Same cron authentication; a failing user does not stop the run |
/api/v1/* | Read-only REST API | Personal API token; see REST API |
The in-memory rate limits are applied per signed-in user (or per token for /api/v1) and per server process. See Scheduled Jobs for the cron endpoints.
Import Actions
CSV import is implemented in src/actions/import-actions.ts and supporting services under src/lib/import. The flow stages rows first, runs deterministic and AI reconciliation, records conflict decisions, and only writes income/expense records during final confirmation.
Server Action Contract
type ApiResponse<T> =
| { success: true; data: T }
| { success: false; error: string };
Revalidation Strategy
After successful writes, affected paths are revalidated so dashboard and list views stay consistent.
Related Docs
- REST API v1
- AI Capabilities and Guardrails
docs/api/rest-v1.mdin the repository