Security
Client tax records carry weight. We handle them like they do.
Four lines we won't cross, six controls underneath, and a published stack so "where does our client's row actually live?" has a one-line answer for the firm partner asking.

What we lock in
Four lines, plainly stated.
No bank-login scraping.
We never ask for client bank passwords or proxy a banking app. The only input is the PDF the bank already sends.
Statements aren't kept.
Statements we read successfully aren't stored; we keep the transactions, with amounts, descriptions and counterparties encrypted. When our own readers can't read a statement confidently, the whole PDF is sent to OpenAI to be read, including the account holder's name and account number. The one exception is a file we could not read: it is kept encrypted for 30 days so the failure can be fixed, then deleted.
Column-level encryption.
AES-256-GCM at the column for ID numbers, tax numbers, transaction amounts, descriptions and counterparties. Names and contact details, finalised return snapshots and uploaded supporting documents are not encrypted this way.
Firm-wide audit log.
Changes your staff make, such as uploads, categorisations, overrides and returns, are recorded in an append-only log scoped to your firm, with the time and who made them. Viewing records and signing in are not logged. Open the log any time.
Underneath the four
Six guards keeping each line honest.
Column-level AES-256-GCM at rest
ID numbers, tax numbers, transaction amounts, descriptions and counterparties are encrypted column by column. Names and contact details, finalised return snapshots and uploaded supporting documents are not encrypted this way. The master key lives in Vercel project secrets, not in the source code.
Queries scoped to user and firm
Client data is read through queries scoped to the signed-in user or their firm. That isolation is enforced in the application's query layer.
Append-only audit log
Changes your staff make, such as uploads, categorisations, overrides and returns, are logged with timestamp, actor and target in a log scoped to your firm. The database rejects edits and deletions to the log with an error, not a silent overwrite. Viewing records and signing in are not logged.
Passkey, email-code or Google sign-in
Sign in with a passkey, an emailed one-time code, or Google, through Better Auth. Passkeys use WebAuthn and are optional; adding one gives a faster sign-in.
What we keep, and what we don't
Statements we read successfully aren't stored; we keep the transactions, with amounts, descriptions and counterparties encrypted. Transactions read during client onboarding are held, encrypted, with the onboarding draft. A statement we could not read is the one exception, kept encrypted for 30 days so the failure can be fixed, then deleted. Supporting documents you upload, such as IRP5s and certificates, are stored as files and are not encrypted, and removing one does not yet delete its file.
Sensitive fields removed from server error reports
Server-side error reports to Sentry have named sensitive fields, such as amounts, descriptions, ID numbers and tax numbers, removed before they are sent.
The stack
The exact rooms your row sits in.
Your clients' data is processed outside South Africa: the app and database run in Frankfurt, and some of it is sent to OpenAI. We'd rather say that plainly than stay fuzzy about it, so here is the actual stack.
- Database
- Postgres on Neon · EU, currently eu-central-1 (Frankfurt)
- Statement PDFs
- Not stored once read successfully · an unreadable one is kept encrypted, then deleted
- File storage
- Vercel Blob · uploaded supporting documents, and unreadable statements kept encrypted
- Application runtime
- Next.js 15 on Vercel · region fra1 (Frankfurt)
- Transport
- HTTPS only (HSTS) · TLS terminated by Vercel
- Sign-in
- Better Auth · passkey (WebAuthn), emailed one-time code, Google
- AI provider
- OpenAI API, not hosted in South Africa · gpt-4o-mini by default; gpt-4o reads statements our own readers can't read confidently · OpenAI's API terms exclude API data from model training
- Sent to OpenAI
- Statements our readers can't read confidently (whole PDF) · certificates and SARS documents that can't be read from their text · each transaction's description, amount and date where our rules and merchant list don't settle it, with the client's business profile · the account holder's name and a statement summary · support chat
- Resend · sign-in codes, invitations and receipts
- Error monitoring
- Sentry · named sensitive fields removed server-side before sending
Spotted a hole?
Send it to security@taxup.app. Forty-eight-hour acknowledgement promise. A formal bug-bounty programme follows public launch; in the meantime we'll thank you by name on this page if you say it's okay.
Check us. Then sign up.
We publish the model on purpose — read it, poke at it, and if you're comfortable, open a firm account.
