Documents and Storage
Cashlytics stores documents in PostgreSQL rather than an external object store.
Data Model
The documents table stores:
iduserId- optional
expenseId - optional
dailyExpenseId fileNamemimeTypesize- 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.tssrc/app/api/documents/route.tssrc/app/api/documents/[id]/route.tssrc/components/molecules/document-list.tsxsrc/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.