Transparency

How your data works.

Timetide handles data in two distinct ways — one that never leaves your device, and one that's entirely opt-in. Pick what you want to understand.

Data flow · Personal

Your iPhone
All data written locally · SwiftData
Stays on your device
Timetable · lesson schedule · stats
Always
optional iCloud sync
iCloud Private Database
Apple's infrastructure · only you control it
Optional
Your other Apple devices
Synced privately through your iCloud
Timetide servers — not in this chain. Structurally impossible, not just policy.

01

Your data never leaves your iPhone.

Everything is written to SwiftData — local storage on your device. There's no server-side database because there's no way for the data to get there. Structurally impossible, not just policy.

  • Timetable data
  • Personal stats

02

No account. No profile. Nothing.

Your personal data has no server-side destination — there's nowhere to sign into and no account to create. We have no name, email, or identifier that could connect you to your timetable. Can't sell what we don't have.

  • Your name or email
  • Device or advertising ID
  • IP address or location
  • In-app behaviour tracking

03

iCloud sync is yours. Not ours.

Enable iCloud sync and your data moves through Apple's encrypted infrastructure — which only you control. We're not in that chain. We never see it pass through.

04

Personal Recap calculated locally.

Every personal stat is calculated on-device. None of it needs to leave to be computed. It's all local arithmetic.

Data flow · Community (opt-in only)

Your iPhone
Collects: subjects · lesson types · school
Never collected
Your name Apple ID Location Lesson notes Hardware identifiers
Random UUID generated
No connection to your name, Apple ID, or any identity
Anonymous
weekly sync
Supabase (EU)
EU-hosted Postgres · GDPR-compliant
Profile counters
School, faculty & student type stored as aggregate counts only. No individual record. Increments and decrements when you change a value.
Stats row (upsert)
One anonymous row per user per year. Weekly sync replaces the previous entry — never accumulates. Keyed to your random UUID only.
Year in Review
Anonymised community stats · published annually

01

Off by default. You choose.

If you opt in, your anonymised data contributes to the community Year in Review — aggregate trends across all contributing students, like subject distribution across faculties or lesson type breakdowns by school. Your personal data is always private regardless. You can opt out at any time.

02

Only what's necessary. Nothing more.

Never sent
  • Your name
  • Apple ID or email
  • Device or hardware ID
  • Location
  • Lesson notes
Sent (opt-in only)
  • School, faculty & student type
  • Subject names & count
  • Lesson type breakdown

03

Profile on opt-in. Stats weekly.

Profile data (school, faculty, student type) is sent when you opt in. After that it's only updated if you change one of those values — changing your student type doesn't touch your school or faculty count.

Stats data (subject names and lesson type distribution) is sent weekly — the next time you open the app after 7 days have passed. Each sync replaces the previous entry entirely. If you had 2 subjects last week and now have 3, the community total reflects 3, not 5.

04

Profile fields become counters. Stats become one row.

Profile data (school, faculty, student type) is stored as aggregate counts only — the server tracks how many students belong to each value, not a list of who they are. When you change your student type, the old count decrements and the new one increments. No individual record is kept.

Stats data is stored as one anonymous row per user per year, always reflecting your latest state. Each row is keyed only to a random UUID — we can see what an anonymous user contributed, but there is no way to connect that to a person, a name, or any identity.

05

Data belongs to the year it happened.

Stats are bucketed by the year the events actually occurred — not the year the sync runs. Subject and lesson creation is attributed to the year the item was created.

This means if you create lessons in December 2026 but the app syncs in January 2027, that data still counts towards the 2026 Year in Review — not 2027.

06

A random ID. No identity attached.

Each weekly sync needs to update an existing entry rather than create a duplicate, so the app generates a random UUID the moment you first opt in. This UUID is per user — not per device. It has no connection to your name, Apple ID, or any personal information. It's stored in iCloud Key-Value Store — a separate, always-on iCloud mechanism that works independently of the app's iCloud sync setting — so it carries over automatically whenever you get a new device on the same iCloud account.

If you opt out and opt back in, the same UUID is reused — your data slot stays consistent and no duplicate entry is created. A new UUID is only generated if you sign into a different iCloud account, in which case previously contributed data becomes permanently orphaned and cannot be identified or removed.

07

Anonymous data isn't personal data.

Under GDPR, truly anonymous data falls outside the definition of personal data — there's no identifiable data subject and no way to link contributions back to an individual. That's not a loophole. It's why we designed the system this way: anonymise before it leaves your device, aggregate on arrival, store nothing that could identify you.

Right to object — Opt out anytime in Settings → Community Year in Review. Syncing stops immediately. No forms, no waiting.

Supabase EU · GDPR-compliant · Debug builds are excluded from Year in Review totals · Questions: hello@timetide.eu

The sync, field by field

Every field, accounted for.

The complete shape of a weekly sync — taken from the code that sends it. If it isn't here, it never leaves the phone.

Sync payload schema — mirrors the app's sync code

FieldTypeExampleWhy it exists
Profile sent on opt-in — after that, only when a value changes
universitytextCharles UniversityPowers the schools chart. Set by you in the app — there is no account to read it from.
facultytextComputer SciencePowers the faculty breakdown.
studentTypetext, optionaluniversityLets stats be split by study level. Optional — absent if you never set it.
universityChanged · facultyChanged · studentTypeChangedbool ×3falseTell the server whether a profile counter needs to move at all. On a normal weekly sync, all three are false.
previousUniversity · previousFaculty · previousStudentTypetext, optional ×3Included only when a value just changed, so the server can decrement the old counter as it increments the new one. Otherwise absent.
Stats one bucket per calendar year, cumulative — replaced in full on every sync
statsByYearmap year → stats{ "2026": … }Everything below lives in per-year buckets, so December data synced in January still lands in the right review.
subjectCountint9How many subjects you studied that year.
subjectNamestext[]["Linear Algebra", …]Powers "what the world studied". Subject names only — never lesson names, rooms, or notes.
scheduledByTypemap type → intlecture: 86, seminar: 41, …The lesson-types chart. Counts per type, attributed to the year the lesson was created.
Sync metadata
deviceIduuid8f3a6c2e-…Random, generated at opt-in, kept in iCloud key-value storage — per user, not per device, linked to nothing and no one. Lets each sync replace the last instead of double-counting.
appVersion / appBuildtext ×21.4 / 213Catches version-specific data bugs before they skew the totals.
environmenttextreleaseDebug builds flag themselves and are excluded from every total.

Planned for a future update: anonymous day-of-week and start-hour histograms, which will power the rhythm and start-time charts in the Year in Review. They'll be documented in this table the moment they ship — until then, that data doesn't leave the phone either.

One complete sync — everything that leaves the phone

{
  "deviceId": "8f3a6c2e-91d4-4c7b-b2ae-67f0c14e4b9d",  // random — created at opt-in
  "university": "Charles University",
  "faculty": "Computer Science",
  "studentType": "university",
  "universityChanged": false,    // nothing changed → no profile counters move.
  "facultyChanged": false,       // "previous…" fields are only included
  "studentTypeChanged": false,   // when one of these is true
  "statsByYear": {
    "2026": {
      "subjectCount": 9,
      "subjectNames": ["Linear Algebra", "Operating Systems", "Statistics", "…"],
      "scheduledByType": { "lecture": 86, "seminar": 41, "lab": 28 }
    }
  },
  "appVersion": "1.4",
  "appBuild": "213",
  "environment": "release"       // debug builds are excluded from totals
}

No name. No email. No hardware identifiers. No lesson names, no rooms, no notes, no times of day. A sync is the year's running totals — not a copy of your timetable. And it's write-only by design: the app can call exactly one server function — sync. Nothing reads individual rows back out.

Summary

Data When sent How stored Linked to you?
School name On opt-in; if changed Aggregate count No
Faculty On opt-in; if changed Aggregate count No
Student type On opt-in; if changed Aggregate count No
Subject names & count On opt-in, then weekly Per-user row (upsert) No — UUID only
Lesson type distribution On opt-in, then weekly Per-user row (upsert) No — UUID only
Your name Never
Apple ID / email Never