# Integraties en privacy

Het privacy-hoofdstuk van Tendlo: welke persoonsgegevens waar staan, welke toestemmingslagen er zijn, wat naar derde partijen gaat en waar de AVG-gaten zitten. Dit document vervangt het oude `docs/archive/superseded/2026-07-11-docs-overhaul/docs/2026-06-14-privacy-and-subprocessors.md` (gearchiveerd als [../archive/superseded/2026-07-11-docs-overhaul/docs/2026-06-14-privacy-and-subprocessors.md](../archive/superseded/2026-07-11-docs-overhaul/docs/2026-06-14-privacy-and-subprocessors.md)). Peildatum: 2026-07-11.

> **Geen juridisch advies.** Dit is de feitelijke basis (welke data, welke verwerkers, welke regio's) voor een privacyverklaring en store-privacy-labels. Verwerkingsverantwoordelijke: **Aqua-IT B.V.**

## Kernprincipe: local-first, cloud is een Plus-feature

Standaard staat **alle data lokaal op het toestel** (Drift/SQLite). Zonder cloud-configuratie (`lib/core/config/app_config.dart`) verlaat niets het toestel, op de expliciet genoemde integraties na (weer-API-coördinaten; Health-/device-agenda-reads blijven on-device). Sinds de freemium-omslag (owner-besluit 2026-06-16/17, zie [../business/verdienmodel-en-pricing.md](../business/verdienmodel-en-pricing.md)) is cloud-sync gepositioneerd als onderdeel van de betaalde Plus-tier — niet langer louter een "optionele opt-in": wie Plus met gezinssync gebruikt, gebruikt de cloud. De gratis kern blijft volledig lokaal.

## 1. Welke persoonsgegevens, waar

| Opslagplek | Wanneer | Wat |
|---|---|---|
| **Toestel (altijd)** | altijd | alle domeindata in Drift/SQLite; app-lock-PIN in flutter_secure_storage; instellingen |
| **Supabase EU** (Postgres, project `noeffngomyiglttudamq`, eu-west-1) | alleen cloud-modus | e-mailadres (auth); ledenprofielen per gezinslid — naam/initiaal/kleur/rol, **inclusief kinderen** (`members`); alle 49 gesyncte tabellen, waaronder de gevoelige categorieën **financieel** (12 tabellen: o.a. inkomsten, leningen, bankrekeningen, betaalpassen), **gezondheid** (7 tabellen: o.a. gewicht, medicatie, **cyclusdata**), co-ouderschapsdata en het zakgeld-ledger per kind |
| **Supabase Storage EU** | alleen cloud-modus | bijlagen (foto's/documenten) in de private bucket `attachments`, pad `<householdId>/<ownerId>/<id>.<ext>`; Storage-RLS op het eerste pad-segment (migratie 0004); signed URLs met TTL 1 uur (`lib/core/attachments/data/attachment_storage_service.dart`) |
| **PowerSync Cloud EU** (`powersync_cloud/service.yaml: region: eu`) | alleen cloud-modus | doorvoer/replicatie van de gesyncte rijen |
| **`ai_usage_logs`** (Supabase, server-only) | bij AI-gebruik | household_id, auth_user_id, feature-label, model, tokentellingen en statuscode per AI-call — **géén promptinhoud** (`supabase/functions/ai-proxy/index.ts`, migratie 0011) |

Financieel en gezondheid syncen uitsluitend via de owner-scoped `user_private`-stream — nooit gezinsbreed, tenzij een rij expliciet gericht gedeeld wordt (visibility=2; zie [security-en-rechten.md](security-en-rechten.md)).

## 2. Consent-lagen

- **Analytics is opt-in, default uit.** `ConsentGatedAnalytics` (`lib/core/observability/analytics.dart`) gate't elk event op `AppSettings.analyticsConsent`; toggle in het privacy-scherm (`lib/features/settings/presentation/privacy_screen.dart`). PostHog draait op de EU-host (`eu.i.posthog.com`, default in `app_config.dart`), zonder autocapture/session-replay en met `personProfiles: identifiedOnly` terwijl er nooit geïdentificeerd wordt → uitsluitend anonieme, expliciete events (`lib/core/observability/posthog_analytics.dart`).
- **Crash reporting zonder PII.** Sentry is alleen actief met een DSN (anders no-op); `sendDefaultPii = false`, `tracesSampleRate = 0` (`lib/core/observability/sentry_crash_reporter.dart`).
- **AI kent vier lagen** (`lib/core/ai/ai_gate.dart`): Plus-entitlement → hoofdschakelaar → per-capability-toggle → **sensitive-opt-in**: de domeinen financieel en gezondheid zijn standaard van AI uitgesloten en vereisen expliciete per-domein opt-in (`AiSettings.sensitiveOptInDomains`). Zolang instellingen laden weigert de gate alles (veilige default). Details: [ai.md](ai.md).
- **OS-permissies op het gebruiksmoment**: locatie (weer), camera/fotobibliotheek (scan), microfoon/spraakherkenning, agenda-toegang, Health-toegang en notificaties worden pas gevraagd wanneer de gebruiker de betreffende functie aanzet (zie §5–6).

## 3. Export en verwijdering (rechten van betrokkenen)

- **Export**: gescopede JSON-export via `/backup` (`lib/features/backup/data/backup_service.dart`). De export bevat alleen wat het aanvragende lid mag zien: gezinsbrede rijen, eigen rijen en gericht-met-mij-gedeelde rijen — een backup omzeilt de RLS-/sync-afscherming dus niet.
- **Lokale verwijdering**: het privacy-scherm biedt "alle lokale gegevens verwijderen" (`DataResetService.wipeLocalData`, `lib/features/settings/data/data_reset_service.dart`); in cloud-modus loopt de wipe via `disconnectAndClear` zodat er niets meer geüpload wordt en server-/gezinsdata intact blijft, gevolgd door sign-out (`privacy_screen.dart`).
- **GAT — geen server-side accountverwijdering.** Er bestaat geen delete-account Edge Function (de enige functions zijn `ai-proxy`, `invite-member`, `relocate-attachments`). Een cloud-gebruiker kan zijn serverdata en auth-account dus niet zelf verwijderen; dat kan nu alleen handmatig via de beheerder. Dit is een bekende to-do, ingepland in fase M1 (zie [../product/roadmap.md](../product/roadmap.md)) en moet vóór externe gebruikers gedicht zijn.
- **GAT — geen retentiejobs.** Er is geen automatische opschoning voor `ai_usage_logs`, `merge_audit` of soft-deletes (`deleted_at`); de bewaartermijn is de facto onbeperkt. Ook dit hoort in een retentiebeleid vóór launch.

## 4. Sub-processors

| Verwerker | Doel | Data | Regio | Wanneer actief |
|---|---|---|---|---|
| **Supabase** (project `noeffngomyiglttudamq`) | auth (magic-link), database, storage, edge functions | e-mail, ledenprofielen, gesyncte domeindata, bijlagen | **EU** (eu-west-1) | alleen cloud-modus |
| **PowerSync** | sync lokaal ↔ cloud | gesyncte domeindata | **EU** (bevestigd: `powersync_cloud/service.yaml: region: eu`) | alleen cloud-modus |
| **OpenRouter** | managed AI (LLM-inferentie) | uitsluitend de content die de gebruiker expliciet laat verwerken | **VS** | alleen bij AI-gebruik (Plus, ingelogd); globale spend-cap $30/mnd |
| **Resend** | e-mailbezorging: uitnodigingsmails (`invite-member`, afzender `Tendlo <noreply@tendlo.app>`) + Supabase-auth-mails via SMTP op tendlo.app | e-mailadressen | VS-bedrijf (regio bezorg-infrastructuur niet geverifieerd) | bij invites en auth-mails — **ontbrak in de oude sub-processor-lijst** |
| **PostHog** | productanalytics | anonieme events, geen PII | **EU** (`eu.i.posthog.com`) | alleen na opt-in |
| **Sentry** | crash-/foutrapportage | stacktraces, toestel-/OS-info, geen PII | **EU** (`de.sentry.io`, organisatie `aqua-it-bv-eu`) | bij een crash/fout; DSN staat in de build en is actief |
| **Apple / Google** | betaalrails (IAP), notificatie-OS-diensten n.v.t. (alleen lokale notificaties) | aankoopdata (t.z.t.) | VS/EU | pas bij abonnementen |
| **RevenueCat** | abonnement-entitlements | aankoop-/abonnementstatus | VS | **gepland, nog niet aangesloten** — geen `purchases_flutter` in `pubspec.yaml`; opnemen zodra B5 landt |

Doorgifte buiten de EU (OpenRouter, Resend, t.z.t. RevenueCat — **Sentry niet meer sinds 13 augustus 2026**) vereist passende waarborgen (SCC's) in de privacyverklaring; DPA's per verwerker formeel accepteren blijft een open actiepunt (overgenomen uit het oude privacy-doc).

## 5. Wat gaat er per integratie naar derden — en wat niet

- **OpenRouter (AI)**: uitsluitend content die de gebruiker **expliciet** laat verwerken: gefotografeerde documenten/foto's (base64 in het bericht), gedeelde tekst of opgehaalde pagina-tekst, de assistent-vraag plus gerichte context. Altijd via de server-side `ai-proxy` (key staat nooit in de app); financieel/gezondheid alleen na aparte opt-in. Er worden geen prompts gelogd — alleen tokentellingen (§1).
- **Open-Meteo (weer)**: alleen coördinaten, zonder key of account (`lib/features/weather/data/open_meteo_weather_service.dart`). Default vaste Amsterdam-coördinaten; echte locatie alleen na OS-opt-in via `geolocator`, met stille fallback naar de default bij elke faalmodus.
- **Apple Health en device-agenda**: de **reads zelf** blijven on-device — er gaat geen HealthKit- of agenda-query naar buiten. Health is opt-in per lid, device-lokaal geadministreerd (`lib/features/gezondheid/gewicht/presentation/gewicht_health_providers.dart`); de agenda-koppeling is read-only en pas actief na expliciet koppelen (`lib/features/agenda/data/device_calendar_service.dart`).

  **Maar de afgeleide gegevens synchroniseren wél** (correctie 27-07, eigenaar-besluit: documentatie eerlijk maken in plaats van de sync beperken). Gewichtsmetingen, gewichtsdoelen, dagelijkse stappen en stapdoelen staan in het PowerSync-schema en vier keer in `powersync_cloud/sync-config.yaml`, en gaan in cloud-modus dus naar Supabase (EU). Ze blijven binnen het eigen huishouden en gaan niet naar derden, maar de eerdere formulering *"er gaat niets naar Tendlo-servers"* was onjuist en is hiermee ingetrokken. Wie dit wél on-device wil houden, zet de cloud-sync niet aan — dat is de enige stand waarin de oude belofte klopt. Dit raakt App Store-richtlijn 5.1.3, dus de privacyverklaring moet dit expliciet benoemen.
- **Spraak**: on-device OS-spraakherkenning (`lib/shared/speech/speech_recognizer.dart`); er gaat niets door de AI-proxy. **Kanttekening**: OS-dictatie kan platformafhankelijk cloud-verwerking door Apple/Google inhouden — dat valt buiten de app maar hoort wel in de privacyverklaring.
- **PostHog**: alleen anonieme, grove events na opt-in. **Sentry**: alleen technische foutdata.
- **Resend**: alleen e-mailadres + uitnodigingscontext van invite-mails en auth-mails.

## 6. Overige integraties (permissie-momenten en foutgedrag)

| Integratie | Techniek | Permissie/auth-moment | Foutafhandeling |
|---|---|---|---|
| Notificaties | uitsluitend **lokaal** (`flutter_local_notifications`, `timezone`); géén push/FCM/APNs | OS-notificatiepermissie bij inschakelen (`lib/core/notifications/notification_permission_service.dart`) | budget-planner voor de iOS-cap van 64 pending; quiet hours; module-gate |
| Weer | Open-Meteo REST, key-loos | locatie-permissie alleen bij "gebruik mijn locatie" | elke fout → stille fallback naar default-locatie |
| Device-agenda | `device_calendar_plus`, read-only | agenda-permissie op koppel-moment | stub-service zolang niet gekoppeld |
| Apple Health | `health`-package, alleen gewicht-import, niet schrijven | Health-permissie op koppel-moment | dedupe met external-id + "skipped uuids"-geheugen tegen resurrectie |
| Share-sheet (in) | `receive_sharing_intent` + iOS ShareExtension (app-group `group.com.aquait.tendlo`) | geen aparte permissie | de-dup tegen dubbele cold-start-aflevering; queue voor payloads vóór login/boot |
| Share-sheet (uit) | `share_plus` (quote delen, backup-export) | geen | n.v.t. |
| Spraak | `speech_to_text` on-device, achter een seam | microfoon-/spraakpermissie bij eerste gebruik | n.v.t. |
| E-mail (Resend) | `invite-member` Edge Function met `RESEND_KEY`-secret | n.v.t. (server-side) | invite-mail is best-effort: mailfout laat de invite zelf intact |
| iCal | `icalendar_parser` voor sportkalender-import (migratie 0018) | geen (gebruiker plakt/kiest feed) | parse-fouten aan de gebruiker gemeld |
| AI-proxy | zie [ai.md](ai.md) | Plus + AI-toggles | OpenRouter-respons wordt verbatim doorgegeven (status + body); 401/429/502-paden expliciet |

## 7. Rechtsgronden (concept, geactualiseerd)

Overgenomen uit het oude privacy-doc (2026-06-14) en aangepast aan de freemium-omslag:

- **Cloud-sync**: uitvoering van de **overeenkomst** (Plus-abonnement) zodra de tiers live zijn; tot die tijd feitelijk toestemming (gebruiker kiest zelf voor inloggen/sync). Intrekbaar door uit te loggen en lokaal te wissen.
- **Analytics**: **toestemming** (opt-in-toggle in-app, intrekbaar).
- **Crash reporting**: **gerechtvaardigd belang** (minimaal, geen PII).
- **Abonnement/betaling** (t.z.t. RevenueCat/stores): **overeenkomst**.
- **Kinderdata**: ouderlijke verantwoordelijkheid binnen het household; geen verwerking buiten de huishoud-functies; verhoogde zorgvuldigheid (kinderen hebben ledenprofielen en zakgeld-/wensenlijst-data).
- **AI-verwerking** (OpenRouter, VS): expliciete handeling van de gebruiker per verwerking + aparte opt-in voor gevoelige domeinen; benoem de VS-doorgifte expliciet in de privacyverklaring.

De checklist voor de uiteindelijke privacyverklaring (verantwoordelijke, doelen, bewaartermijnen, rechten van betrokkenen, AP-klachtroute, store-labels) staat in het gearchiveerde doc en blijft gelden; de store-labels-mapping ("in de lokale modus wordt niets verzameld") blijft een sterk verhaal.

---
*Bronnen: `lib/core/config/app_config.dart`, `lib/core/observability/` (`analytics.dart`, `posthog_analytics.dart`, `sentry_crash_reporter.dart`), `lib/core/ai/ai_gate.dart`, `lib/core/attachments/data/attachment_storage_service.dart`, `lib/features/settings/presentation/privacy_screen.dart`, `lib/features/settings/data/data_reset_service.dart`, `lib/features/backup/data/backup_service.dart`, `lib/features/weather/data/open_meteo_weather_service.dart`, `lib/features/agenda/data/device_calendar_service.dart`, `lib/features/gezondheid/gewicht/presentation/gewicht_health_providers.dart`, `lib/shared/speech/speech_recognizer.dart`, `supabase/functions/` (ai-proxy, invite-member, relocate-attachments), `supabase/migrations/0004`, `0011`, `powersync_cloud/service.yaml`, `pubspec.yaml`, docs/2026-06-14-privacy-and-subprocessors.md (gearchiveerd, 2026-06-14). Geverifieerd op 2026-07-11.*
