Data Policy
Last updated: May 14, 2026
Zedi is a personal-finance tool. You hand it bank statements and receipts, and it gives you back a real picture of where your money went. This page explains what we keep, where it sits, who can read it, and how to delete it. In plain English, grounded in the code.
Questions or requests: [email protected].
1. What we store
- Transactions, categories, accounts, and rules in our Postgres database. This is the data the dashboard runs on: dates, amounts, merchant names, descriptions, the categories you assign, and the auto-categorization rules you build by correcting us.
- Receipt photos and PDF statements, transiently, in Cloudflare R2. A receipt image is deleted from R2 the moment OCR succeeds: the parsed line items become the record, not the photo. If OCR fails, the image is kept so you can hit Reprocess or delete the receipt yourself. PDF statements follow the same pattern: the file lives in R2 only while the import is in its preview state, and is deleted on commit, on discard, or on statement delete.
- Your Anthropic API key, encrypted, if you choose to bring one in Settings. Stored as a ciphertext blob in user_settings.anthropic_api_key_encrypted. When unset, Zedi's shared key is used and the lifetime OCR allowance applies.
- A transaction audit log for the Memory page. Every edit, import, auto-categorizer touch, and delete on a transaction writes a row noting what changed, when, and which actor (you, an importer, the auto-categorizer) made the change. This is yours: it shows you your own history.
- Better Auth session data. Your email, name, optional handle, and the session rows that keep you logged in. If you signed up with email and password, we also store a one-way hash of that password (we never see the plaintext). If you signed up with Google or Apple, no password is stored; your provider-issued user ID lives in the linked-account row instead.
2. Where it lives
SaaS at zedi.sneeks.ai. Postgres is a Railway managed instance in a US region. Receipt and PDF blobs live in a Cloudflare R2 bucket named zedi-saas, with object keys prefixed by your user ID ({user_id}/receipts/..., {user_id}/statements/...).
Personal deployment.The same code also runs as a single-tenant box (Nicolo's personal instance on a Proxmox VM). That deployment holds exactly one user's data. If you signed up at zedi.sneeks.ai, your data is on the SaaS side, not on the personal box.
3. How long we keep it
- Receipt images: deleted from R2 the moment OCR succeeds, in the same database write that records the parsed line items. We do not retain user receipt images longer than the OCR call itself.
- Failed-OCR receipt images: held in R2 until you act on them. Either retry the OCR, in which case a successful parse triggers the same delete, or delete the receipt entirely, in which case the image is removed too.
- PDF statements: held in R2 only during the preview window between extract and commit. The blob is deleted on commit, on discard, and on statement delete.
- Anthropic-side retention: Anthropic deletes API inputs within 30 days and contractually does not train on them (Anthropic Commercial Terms, Section B). That covers receipt photos and PDF statements during the seconds-long OCR call.
- Everything else: kept until you delete it. Transactions, categories, accounts, audit log entries, your encrypted BYOK key, your account itself. No silent purges, no data lifecycles. If you want it gone, see section 6.
4. Who can read it
You. Postgres Row-Level Security isolates every user from every other user. The application runs as a Postgres role (app_user) that cannot bypass RLS, even on a bug. Every read and every write is scoped to the policy user_id = current_setting('app.user_id'), set at the start of each request from your verified session. A handler that forgets to filter by user gets blocked at the database layer.
R2 keys are user-prefixed. Every receipt image and PDF blob is stored under {user_id}/...in the bucket. The storage layer also verifies the prefix matches the requester on read and delete, so a forged database row would still not reach another user's bytes.
BYOK keys are encrypted at rest.If you store your own Anthropic API key in Settings, it is encrypted with AES-256-GCM before it touches the database. A fresh random nonce is used per encryption. The 32-byte master key lives outside the database, in the backend's environment.
The operator. Nicolo, who runs the service, has administrative access to the production database and the R2 bucket. That access exists for operations: migrations, incident response, support requests you initiate. It is not used to browse user data.
5. Third parties
A short list of services see specific slices of your data so Zedi can function. The full list:
Anthropic (Claude Vision)
Receipt photos and PDF statements are sent to Anthropic's Vision API for OCR. Per Anthropic's Commercial Terms, Section B, Anthropic may not train models on API inputs and deletes them within 30 days. CSV and QFX imports never go to Anthropic: they are parsed locally in our Rust backend.
Cloudflare
Hosts the R2 bucket where receipt photos and PDF statements sit transiently during OCR. Also serves DNS for the sneeks.ai domain. Cloudflare sees the bytes of your uploads only while they are in R2; it does not see your transactions, your categories, or your account.
Railway
Hosts the SaaS application (backend and frontend) and the Postgres database for zedi.sneeks.ai. Your transactions, categories, accounts, rules, audit log, and account record live in a Railway-managed Postgres instance in a US region.
Google Identity (optional sign-in)
If you choose “Continue with Google” on the login or signup page, Google handles the authentication handshake and returns your email, name, and a stable user ID to Zedi. Google does not see your transactions, receipts, categories, or anything else you do inside Zedi. You can skip this and use email + password instead, in which case nothing is sent to Google.
Apple Sign in with Apple (optional sign-in)
If you choose “Continue with Apple,” Apple handles the authentication handshake and returns either your email or a private-relay alias (your choice via Apple's “Hide my email”) plus a stable user ID. Apple does not see your transactions, receipts, categories, or anything else you do inside Zedi. You can skip this and use email + password instead, in which case nothing is sent to Apple.
Better Auth (library)
Open-source library that handles signup, login, and session management inside the Next.js frontend. Better Auth is not a third-party service: it runs as code inside our own backend, and its tables (users, sessions, accounts, verifications) live in our own Postgres database. No user data is sent to a Better Auth vendor, because there isn't one.
No data brokers. No advertisers. No behavior-analytics vendor on authenticated pages: no Google Analytics, no Mixpanel, no PostHog, no Amplitude, no session replay. If we ever add another sub-processor, this list updates first.
6. Deleting your data
Email [email protected] for either of two things, and you'll get a reply within a day:
- Full JSON export of everything you have in Zedi: transactions, categories, accounts, rules, receipts, audit log.
- Account closure that hard-deletes your user record, all transactions, all receipts, all categories, all rules, all audit log entries, and any remaining R2 blobs.
Self-serve buttons for both are tracked under ZEDI-71. Until that ships, email is the path, and it's a real path: it is in the marketing copy, the signup page, this policy, and the account page.
7. Changes to this policy
If we change something material (adding a sub-processor, changing how a class of data is handled, broadening retention), you see a banner inside Zedi before the change takes effect, with a pointer to the updated version of this page. Cosmetic edits (typos, clarifications) update the Last-updated date and don't get a banner.
Contact
Anything in this policy unclear, anything you want changed, anything you want a copy of, anything you want deleted: [email protected]. You'll hear back within a day.
See also: Terms of Service.