Skip to main content

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 with requireAuth(), 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​

RoutePurposeNotes
/api/auth/[...nextauth]next-auth handlersExcluded from the session check
POST /api/chatAI assistant chat and tool orchestration401 without session, 503 without OPENAI_API_KEY, 20 requests/min per user. Error body { error }
POST /api/receipt-scanReceipt image extractionJPG/PNG, 5 MB, 5 requests/min per user. Returns 400 without OPENAI_API_KEY. Body { success, data } or { success: false, error }
GET /api/documentsList user documentsAuthenticated, scoped to the session user
POST /api/documentsLegacy upload route, not used by the UICurrently defective: it validates expenseId/dailyExpenseId as integers while IDs are UUIDs. Uploads go through the Server Action uploadDocument
GET /api/documents/[id]Fetch one documentAuthenticated, scoped to the session user; Content-Disposition: inline, Cache-Control: private, max-age=3600
DELETE /api/documents/[id]Delete one documentAuthenticated, scoped to the session user
GET /api/exchange-ratesEUR-based exchange rates as a flat object ({ "EUR": 1, "USD": ... })Requires a session. No bundled client calls it
GET /api/push/vapid-public-keyBrowser push public key{ key: null } with status 503 if VAPID is not fully configured
POST /api/push/subscribeSave push subscriptionAuthenticated
DELETE /api/push/subscribeRemove push subscriptionAuthenticated
GET /api/cron/upcoming-paymentsSend upcoming payment remindersAuthorization: Bearer <CRON_SECRET>; 500 if the secret is not set, 401 on mismatch
GET /api/cron/insightsGenerate insights for all usersSame cron authentication; a failing user does not stop the run
/api/v1/*Read-only REST APIPersonal 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.