Legal
Privacy notice
This notice explains what Tandra collects, why, where it is stored and what you can do with it. It applies to tandra.uk (the landing site) and the hosted app at app.tandra.uk.
Last updated: 2026-10-20.
Who runs Tandra
Tandra is an open-source project. The hosted version at app.tandra.uk is operated by the project maintainer as a small personal service. It is not a commercial product.
- Controller
- The maintainer of the Tandra repository.
- Contact
- igorsiergiej@gmail.com
- Supervisory authority
- Information Commissioner’s Office (ico.org.uk) for UK residents; the equivalent authority in your country if you live elsewhere.
What we collect
The table below lists every category of personal data the hosted stack handles. It is kept in step with the project's internal data inventory.
| Category | Source | What is stored | Why |
|---|---|---|---|
| Account credentials | kivo (the auth service) | Username and argon2id password hash; refresh token held in an httpOnly cookie. | To run the service for the account holder. |
| Session telemetry | kivo | Username on failed logins (no password). Sessions have a TTL. | Abuse detection and session management. |
| User data in MongoDB | Tandra API | Lists, recipes, todos, labels, meal plans, push subscriptions, friendships, pairing codes, Discover publications and reports, item categories. | To provide the app’s features to the account holder. |
| Uploaded photos | MinIO (object store) | Recipe and item photos uploaded by users. | To attach images to recipes and list items. |
| Front-end logs | Loki | userId, userAgent, URL and server-observed clientIp. No list-item text, recipe text or transcripts. | Operational debugging and abuse response. |
| Voice transcripts and AI requests | fal.ai | Speech-to-text transcripts from voice input, recipe-import text, substitution/categorisation requests and image prompts. | To convert voice and links into structured items and recipes. |
| Push notifications | FCM (Android) / Mozilla autopush (Firefox) | Push endpoint and the notification payload. | To deliver reminders and shared-list updates to opted-in devices. |
| Network traffic | Cloudflare (tunnel and proxy) | Every request that reaches the hosted stack. | Routing, DDoS protection and caching. |
| Aggregate analytics | Self-hosted Umami | Page views and referrers on tandra.uk and the hosted app. No personal identifiers. | To measure which parts of the product are used. |
| Public Discover publications | Tandra API | The username is shown only if the user ticks “show name”; the cover photo is copied to a public key. | To make shared recipes findable in Discover. |
| Display fonts | Bundled (Bricolage Grotesque and Montserrat Variable via @fontsource) | Font files served from the site’s own origin. No third-party font CDN is contacted. | Typography without sending visitor IP to a font provider. |
Why we collect it (lawful basis)
Each purpose has a lawful basis under the UK GDPR. Where processing rests on consent, that consent is collected in-app before any data is sent to the third party.
| Purpose | Lawful basis | Notes |
|---|---|---|
| Running the service (lists, recipes, meal plans, sharing, push notifications) | Contract | Processing is required to deliver what the account holder signed up for. |
| Authentication telemetry and operational logs | Legitimate interests | Needed to keep the service secure and operable. No special-category data. |
| Aggregate analytics on the hosted sites | Statistical exception (UK GDPR §4(2)), with opt-out | Self-hosted Umami produces aggregate reports. The opt-out switch lives in app Settings (coming in a planned privacy update). |
| Session replay and any identified analytics | Consent | Off by default. A planned privacy update will prompt once and store the choice. |
| Voice transcripts sent to fal.ai | Consent (explicit, one-time prompt at first microphone use) | The microphone is not opened until the user accepts. |
Processors and where data goes
The hosted stack uses a small number of processors. Each is listed with what they do, where they sit, the transfer mechanism used for any cross-border data flow, and the data they see.
| Processor | Role | Location | Transfer mechanism | Data seen |
|---|---|---|---|---|
| fal.ai | Speech-to-text and generative models for voice input, recipe import, substitution, categorisation and image generation. | United States | EU Standard Contractual Clauses and an executed DPA. | Voice transcripts, recipe-import text, prompts derived from user content. No account credentials. |
| Cloudflare | Tunnel, proxy, caching and DDoS protection in front of the hosted stack. | United States / global edge | EU SCCs and ongoing adequacy decisions where applicable. | Every request reaching tandra.uk and the hosted app. |
| Google Firebase Cloud Messaging (FCM) | Delivery of push notifications on Android. | United States | EU SCCs (Google Cloud DPA). | Push token and notification payload. |
| Mozilla autopush | Delivery of push notifications on Firefox. | United States | EU SCCs (Mozilla DPA). | Push token and notification payload. |
| Self-hosted Umami | Aggregate, cookie-less analytics on tandra.uk and the hosted app. | Server in the EU (imapps.uk) | No transfer — self-hosted. | Page path, referrer, country (derived from IP), browser family. No personal identifiers. |
| Self-hosted Loki | Front-end log retention and search. | Server in the EU (imapps.uk) | No transfer — self-hosted. | userId, userAgent, URL, server-observed clientIp. |
| Self-hosted MinIO | Object storage for user-uploaded photos. | Server in the EU (imapps.uk) | No transfer — self-hosted. | Recipe and item photos uploaded by users. |
Google Fonts is no longer used. The bundled @fontsource-variable/bricolage-grotesque and@fontsource-variable/montserrat packages ship font files from the site’s own origin.
How long we keep it
Retention is the shortest the purpose allows. The values below mirror the operational configuration of the hosted stack.
| Item | Retention | Where it is set |
|---|---|---|
| Front-end logs (Loki) | 30 days | foundry/monitoring/config/loki/loki.yml retention_period: 30d |
| MongoDB backups (mongodump) | Until pruned at RETENTION_DAYS | foundry/docs/homelab.md — nightly mongodump; MinIO is not backed up. |
| Account data and content | Until the user closes their account | A planned privacy update will add account deletion (typed confirmation) and data export. |
| Umami aggregate analytics | As configured on the Umami instance | Self-hosted Umami retention setting. |
| Session replays | 30 days | Replay storage is still to be decided. |
| Fal.ai inputs | Per fal.ai’s published retention | fal.ai DPA — confirmed before activation. |
Your rights and how to use them
The UK GDPR gives you the rights below. Exercising them is free and we reply within one calendar month. Where the request is complex, we may write to extend that by up to two further months and explain why.
| Right | What it means |
|---|---|
| Access | A copy of the personal data we hold about you. |
| Rectification | Correction of inaccurate or incomplete data. |
| Erasure | Deletion of your account and associated content (lists, recipes, photos, friendships, publications). Account deletion will require a typed confirmation and is being added in a planned privacy update. |
| Restriction | Pausing processing while a complaint or query is resolved. |
| Portability | A JSON download of your lists, items, recipes, meal plans, todos and friends. Coming in a planned privacy update. |
| Objection | Right to object to processing based on legitimate interests, including aggregate analytics. |
| Withdraw consent | Where processing rests on consent (session replays, microphone access), you can withdraw it at any time. |
| Complain | Right to lodge a complaint with the Information Commissioner’s Office (ico.org.uk) if you believe your data has been handled incorrectly. |
To exercise any of these rights, email igorsiergiej@gmail.comfrom the address on your account. For deletion and data export, the in-app controls underSettings → Account are the fastest path once the planned privacy update ships.
Cookies and local storage
Cookies are kept to the minimum: one strictly necessary for authentication, and one that sends returning users from this landing page straight to the app. Everything else is local storage on the user’s device, which the user can clear at any time.
| Name | Kind | Purpose | Lifetime |
|---|---|---|---|
| kivo refresh-token cookie | Strictly necessary cookie | Holds the signed refresh token used to mint access tokens. httpOnly, Secure, SameSite=Strict. | Rolling; expires when the session ends or is revoked. |
| tandra_app | Functional cookie | Set on tandra.uk by the app at app.tandra.uk after you sign in, so visiting tandra.uk takes you straight to the app instead of this landing page. Holds no identifier, only the value 1. | 1 year. |
| tandra:config | localStorage | Persisted UI preferences (e.g. tutorial completion flags). | Until cleared by the user. |
| theme | localStorage | User-chosen colour theme (light / dark / system). | Until cleared by the user. |
| unitSystem | localStorage | Metric or imperial units shown across the app. | Until cleared by the user. |
| tandra.voiceKeepListening | localStorage | Whether voice input stays open after a successful transcript. | Until cleared by the user. |
| release-notes-seen | localStorage | Records which in-app release notes have already been shown. | Until cleared by the user. |
| Umami session | Cookie-less | Aggregate analytics visit grouping. Held only on the self-hosted Umami instance. | Session. |
Changes to this notice
Material changes are listed at the top with the date they take effect, and the in-app release notes link here when something you can act on changes. Earlier versions are kept in thesite repository.