Skip to main content

Documents and Storage

Cashlytics stores documents in PostgreSQL rather than an external object store.

Data Model​

The documents table stores:

  • id
  • userId
  • optional expenseId
  • optional dailyExpenseId
  • fileName
  • mimeType
  • size
  • base64 data
  • createdAt

Document rows cascade when their linked expense or daily expense is deleted.

Upload and Access Paths​

Document behavior is implemented in:

  • src/actions/document-actions.ts
  • src/app/api/documents/route.ts
  • src/app/api/documents/[id]/route.ts
  • src/components/molecules/document-list.tsx
  • src/app/(dashboard)/documents/client.tsx

The UI uploads through the Server Action uploadDocument and downloads through downloadDocument (base64). Listing, preview, download, and delete are scoped to the signed-in user.

POST /api/documents is a legacy route that is not used by the UI. It is currently defective: it validates expenseId and dailyExpenseId as integers, but IDs are UUIDs, so valid IDs are rejected with 400. Do not build on it. It rate-limits to 10 uploads per minute per user (in-memory, per process). GET /api/documents/[id] is authenticated and user-scoped, and answers with Content-Disposition: inline and Cache-Control: private, max-age=3600.

Limits​

  • Maximum file size: 5 MB
  • Allowed types: application/pdf, image/png, image/jpeg, image/jpg
  • A document can be linked to either an expense or a one-time expense, not both
  • Linking checks that the expense belongs to the user

Receipt-scan images are separate from stored documents; see AI Capabilities and Guardrails.

AI Boundary​

The assistant must not read uploaded document contents. Expense retrieval tools also avoid exposing document payloads to AI context.