The localised versions of this document (English / German / French) are available in the app at brevetcycling.club/privacy. The German version is binding for users in Germany, Austria and Switzerland.
Brevet Cycling Club
Last updated: 23 September 2026 (policy version 2026-09-23)
23 September 2026 (policy version
2026-09-23) — the one thing the account-less tools keep. Nothing about your member data changes. Until now §6b could say the public tools store nothing at all. That is no longer exactly true, and the change is worth a version rather than a footnote. Once your result is on screen, the ride analysis sends a description of the FILE — the make and model of the device that wrote it (never its serial number), the message and field codes it used, which channels it held, and the shape of its sampling in coarse bands. No value from your ride, no coordinate, no date or time, and no heart rate, not even a note that the channel was there. It is kept because the page must open files from head units nobody here owns, and identical descriptions collapse into one row with a counter, so it grows with the number of recording DIALECTS rather than with the number of visitors. §6b now describes it in full. You will be asked once to confirm the current version before your next AI request.
21 September 2026 (policy version
2026-09-21) — two fields that stay on your device. Nothing new reaches us, and nothing changes about how your member data is processed. The ride analysis in §6b now also sorts your heart rate into zones. A heart-rate zone is a share of a maximum, so behind the same consent tick the page asks for one: a maximum you have measured, or your age, from which it estimates one with a published formula. Both are used where you typed them — in your browser — and the request has no field for either, exactly as it has none for the heart rate itself. §6b now says this instead of leaving it unsaid. We bump the version anyway, because the consent ledger has to be able to say which text you accepted, and because a page that asks something new about your body should say so in the document that describes what it asks. That means you will be asked to confirm the current version once before your next AI request. Corrected the same day, before it mattered to anyone's data: §6b said the web server writes no access log for these requests and left it there. It does not — but when it turns a request away for arriving too fast it notes that refusal, with the address, in its error log, which is how an attack is seen while it happens. §6b now says so instead of letting the silence imply otherwise, and it also names the aggregate counts we keep of answered and refused requests.
20 September 2026 (policy version
2026-09-20) — two public tools that work without an account. Nothing changes about how your member data is processed. (1) New §6b: the single-ride analysis and the pacing card open your file in your own browser; the file is never uploaded, only number series (power, cadence, speed, distance, elevation — or the distance-and-elevation shape of a route) reach our server, and nothing is stored. Heart rate is processed in your browser only, after a separate explicit consent, and is never sent to us. (2) §4, clarification: power and cadence are special-category data where we combine them with your heart-rate, sleep and recovery data, as in a member account — not on their own. Our Terms gained Section 14 on the same date. Corrected the same day, before it mattered to anyone's data: §6b first said a file's date and time of day “is not read”. It is read — inside your browser, to measure the seconds between samples — and it is never sent; §6b now says exactly that, and names .TCX beside .FIT as a file the ride analysis opens.
9 September 2026 (policy version
2026-09-09) — routes you upload, and what the map service receives. Riders can now upload their own GPX routes to ride indoors — private by default, or shared with a group or the club. (1) §2, §3: a GPX is location data, and it usually begins at your front door; when we build the route we cut 500 m off both ends, permanently, so it never starts at your door — not even for you. Uploading, building and sharing run on contract performance (Art. 6(1)(b)); sharing is your per-route choice and reversible at any time. (2) §6, correction: for every route we build — yours or the catalogue's — our server sends a thinned copy of the route (one point every 50 m, rounded to about 11 m, ends removed; no name, account, time or file) to OpenStreetMap's Overpass API to read what stands beside the road. This has been the case since the visual-world update of 7 September and was not described here; the earlier sentence that Brevet "never transmits location anywhere" was written about the device permission and now says so. (3) §7, §8: routes are kept until you delete them or your account; deleting one removes the built track, its world file and the uploaded GPX; your data export includes your routes as GPX. Because (2) discloses a processing that was already happening, we ask you to re-confirm the current policy version before your next AI request.
4 August 2026 (policy version
2026-08-04) — two optional ride themes, and what they do NOT do. This update describes two opt-in themes and, more importantly, the storage and the third-party contact each one does and does not create. (1) Podcast player (§6, §7, §10, §11): your subscriptions and listening positions stay in your browser and never reach us — we do not know which shows you follow or what you have listened to. Episode audio streams from the publisher's own server straight to your player, so that publisher receives your IP address and browser identification as part of the media request — exactly as with any podcast app, and it is what a show counts its listeners from. The episode LIST is fetched by our server instead, so a publisher never learns WHO subscribes. Show search is forwarded through our server to Apple's public directory, so your own address never reaches Apple. (2) News board (§7, §10): your source list and the "already shown" marks stay in your browser; your browser contacts no news site at all, because we fetch the feeds and nothing is embedded — no images, no article pages, no third-party scripts. (3) §7 additions: both feed caches are transient server memory (30 minutes for podcasts, 5 for news), addressed by a hash of the feed address, shared across all riders, with no user identifier and no address in plaintext. (4) Ride share links (new §6a): sharing a ride has always been off by default and per-ride; §6a now says so in the policy, and describes the new per-ride choice of what a link publishes — heart rate is off unless you switch it on, and what you leave off is never sent. Riders who accepted an earlier version are asked to re-confirm the current policy version before their next AI request.
2 August 2026 (policy version
2026-08-02) — AI context, corrected in both directions. An audit found the AI prompt did not match this document. (1) Fixed to match the promise (§5): the model used to receive your latest RAW recovery readings — HRV (RMSSD), resting heart rate, sleep hours and quality, SpO2, body fat. It now receives only DERIVED statements against your own baseline ("HRV below their own baseline"), which is what "What is NOT sent" always said. No raw reading leaves this server. (2) Newly disclosed (§5): the free-text NOTE you type with a life event is sent to the model in your own words, so the coach can answer what you actually wrote — this was already happening and was not described here. The event TYPE is generalised first: the model is told "health event", never a condition name. If you would rather a note stayed on this server, log the event without a note. Because (2) widens the described processing, we ask you to re-confirm before your next AI request.
23 July 2026 (policy version
2026-07-23) — club programme update. This update aligns the policy with the club programme. (1) Removed features (§2, §7): the leaderboards and competitive leagues were removed in 2026 and are no longer described as processing purposes; any historical leaderboard/league data still on file is retained until account deletion and is no longer processed. (2) New club processing (§2, §3, §6, §7): your sequential membership number (assigned when you join; shown club-internally as the number only, no names, in this version), the homologation ledger of your completed club brevets, and the transactional club letters (welcome letter, weekly homologation confirmation). These run on contract performance (Art. 6(1)(b)) — no new consent is required. (3) §2 addition: on club brevet rides, the contrôle stamps and in-ride fueling/effort (RPE) declarations you record are part of your training data. Riders who accepted an earlier version are asked to re-confirm the current policy version before their next AI request.Earlier — 17 July 2026 (policy version
2026-07-17) — accuracy & completeness corrections. This update corrects and completes several statements following an internal data-protection review: (1) Retention truth for AI/Coach data (§5, §7): the 30-day window applies to the AI job queue and conversation log only; the Coach's conversation memory is kept for up to 365 days and durable Coach facts until account deletion — both were already how the platform worked, and both are under your control ("Clear history", "Reset Coach memory"). (2) Email delivery (§6, §11): our SMTP setup is now named specifically (own EU server, AWS SESeu-north-1relay under the AWS GDPR DPA). (3) Model development & research (§5): new transparency note on background training-response models; any research use beyond in-product development requires a separate opt-in (model_research). (4) §7 additions: retention rows for the security audit log, companion telemetry, and Coach memory. (5) §4/§11 addition: transient in-memory live-ride telemetry cache (EU, no persistence) — disclosed both in the health-data section and in the international-transfers table. (6) §10 addition: device-local storage of Spotify OAuth tokens (browser localStorage, §25(2) No. 2 TDDDG). Riders who accepted an earlier version are asked to re-consent before their next AI request.Earlier — 30 June 2026 — transparency addition (draft — to be confirmed by admitted counsel before launch). This update discloses the YouTube visual theme as a third-party integration. The theme is opt-in; when a rider selects it, a click-to-load gate now requires an explicit, prior action before the embedded privacy-enhanced player (
youtube-nocookie.com) loads. §2, §3, §6, §10 and §11 now name Google LLC (USA) as an independent controller for the embedded player and for the optional OAuth connect, and describe the resulting international transfer and its safeguard. This is a transparency and consent-mechanics addition; it does not change the consent baseline for health-data (GDPR Art. 9) or AI processing and so does not by itself require renewed health/AI consent.Earlier — 11 June 2026 (policy version
2026-06-11— transparency corrections: §10 now lists all four first-party cookies the web platform sets (not one); §6/§11 reclassify Strava as an independent controller; §2/§7 add the contact-form processing; session-cookie lifetime corrected to what the code enforces (7-day idle / 30-day absolute, not 90 days); the retired Google Calendar integration is no longer described as active and its leftover tokens are deleted; §1/§13 state the documented DPO position instead of announcing an appointment. Riders who accepted an earlier version are asked to re-consent before their next AI request.)
This privacy policy explains how Brevet Cycling Club ("we", "us", "Brevet") collects, uses, stores, and protects your personal data when you use our virtual cycling simulator and companion services (the "Platform"). We are committed to transparency and to protecting your rights under the General Data Protection Regulation (GDPR) and other applicable data protection laws.
Data Controller (Verantwortlicher, Art. 4(7) GDPR):
Manuel Bröer - Fotodesign Eisenbahnstr. 201 63303 Dreieich Germany
Privacy contact: privacy@brevetcycling.club General contact: info@brevetcycling.club
Data Protection Officer (DPO): Brevet processes special-category data (Art. 9 GDPR — heart-rate variability, sleep, recovery and other health metrics) at the core of its coaching service. We have assessed the appointment duty (Art. 37 GDPR, §38 BDSG) and documented the result: at the platform's current scale — a small, invite-only beta operated by a single person — no DPO is appointed. The assessment is revisited at defined triggers (substantial growth of the user base, opening of public registration, or commercial launch), and an external DPO will be appointed and published here when one is met. Until then, all data-protection requests are handled by the privacy contact above and answered within the statutory 30-day window of Art. 12(3) GDPR.
If you believe we are processing your personal data unlawfully, you can lodge a complaint with your local supervisory authority (Art. 77 GDPR). For users in Germany the territorially competent authority depends on your federal state -- for our place of business it is the Hessische Beauftragte für Datenschutz und Informationsfreiheit (HBDI), Gustav-Stresemann-Ring 1, 65189 Wiesbaden, poststelle@datenschutz.hessen.de. Users elsewhere in the EEA can contact their own national authority; users in Switzerland the Eidgenössischer Datenschutz- und Öffentlichkeitsbeauftragte (EDÖB); users in the United Kingdom the Information Commissioner's Office (ICO).
| Category | Data Collected | Purpose |
|---|---|---|
| Identity | Email address, display name, country code, birth year, gender, avatar image | Account creation, personalisation, club membership |
| Authentication | OTP codes (hashed with SHA-256, 60-minute expiry), session tokens (7-day idle timeout, 30-day absolute maximum) | Secure login and session management |
| Health & Biometric (GDPR Art. 9 special category) | Read from Apple HealthKit (iOS) and Google Health Connect (Android): cycling workouts (power, cadence, heart rate, distance, speed), HRV (RMSSD on Android, SDNN on iOS), resting heart rate, sleep phases and quality scores, body temperature, respiratory rate, body fat percentage, SpO2, weight, height, lean body mass, body water percentage, basal metabolic rate, VO2max, active calories. Optionally and via a separate consent prompt: menstrual flow dates (the raw flow dates stay on your device; the derived current cycle phase is stored on our EU backend and, when you use AI coaching, included in the pseudonymised context sent to Google Vertex AI — see §4 and §5) | Recovery analysis, readiness scoring, training load management, automated personalised coaching |
| Cycling rides written back to Apple Health / Google Health Connect | Workout records (activity type = cycling, start time, end time, total distance, total calories) for completed Brevet rides, written from the companion app to the device-local Apple HealthKit (iOS) or Google Health Connect (Android) store | Lets your other fitness apps (Strava, Training Peaks, etc.) see your indoor Brevet rides via the same on-device hub. Opt-in via the "Export workouts" toggle in Sync settings; off by default |
| Fitness & Training | Ride data (power, heart rate, speed, cadence, distance, elevation, duration), training plans, fitness log (CTL/ATL/TSS/BES metrics), workout history, personal bests, and — on club brevet rides — the contrôle stamps and in-ride fueling/effort (RPE) declarations you record | Core platform functionality, performance tracking |
| Routes you upload | GPX files you upload to ride your own routes indoors: the route geometry (coordinates, elevation), the name you give it, who you share it with (private, a group, the club). A route is location data and usually begins where you live — so when we build it we cut 500 m off both ends, permanently; the ride never starts at your door, not even for you. | Riding your own routes in winter; sharing them with your group or the club |
| Nutrition | Calories, protein, carbohydrates, fat, water intake (synced from Apple HealthKit / Google Health Connect) | Holistic training load and recovery analysis; AI fueling assistant feedback |
| Third-party Integrations | Strava: OAuth tokens and athlete ID. Spotify: OAuth tokens (in-ride music control). YouTube (Google): when you select the YouTube visual theme and load the embedded privacy-enhanced player (youtube-nocookie.com), your browser communicates directly with Google/YouTube, which receives your IP address and player interactions; optionally, OAuth tokens if you connect your own YouTube account to load your playlists/channels (tokens are held in your browser, never stored on our servers — see §6). The former Google Calendar integration was retired in 2026 — its endpoints are disabled and no new Calendar tokens are collected; the training-plan calendar is now an iCal feed served by us with no third-party processing. | Importing rides from Strava; controlling your Spotify playback during rides; optional YouTube video playback on the opt-in YouTube theme |
| Contact form | Email address, subject and message text you submit via the contact form, plus a hashed source IP and user-agent string (abuse prevention) | Answering your inquiry; messages are stored in our EU database and forwarded to the operator inbox |
| Social | Group memberships, challenge participation, feedback submissions | Community features |
| Club membership & register | A sequential membership number assigned when you join (shown club-internally in the register and chronicle — the number only, no names, in this version); the homologation ledger of your completed club brevets (ride format, submission and certification dates, an eternal club-global certification number) | Running the club register you joined: recording your membership and displaying your homologated brevets on your personal Carte |
| AI Coaching | Pseudonymised training summaries processed by Google Vertex AI (Gemini, EU multi-region endpoint — EU-only processing); AI coaching responses | AI-powered training analysis and recommendations |
| Push Notifications | Device token (FCM), platform type (iOS/Android) | Delivering push notifications for AI coach reports and training plan changes |
| Communication | Coaching email log, weekly progress reports, morning notification preferences | Personalised coaching emails and notifications |
| Location (device permission only) | Coarse and fine location grants the OS uses to populate distance, speed and route metrics in the workout records we read from Apple HealthKit / Google Health Connect. Brevet never opens a continuous location subscription, never collects raw location coordinates from the device, and never transmits device location anywhere. The permission lives entirely on your device. Routes you upload yourself are different — they are location data you hand us on purpose; see "Routes you upload" above and Section 6. | Lets outdoor cycling workouts surface real distance and route data instead of the OS's indoor-mode estimate |
| Companion telemetry (off by default; cohort-gated by Brevet) | Dart-side crash stack traces (PII-stripped server-side: emails, OS user paths, IP addresses, bearer tokens and long digit runs are redacted before the event reaches our log pipe), caught application errors, a hand-curated list of UX failure event slugs (e.g. ble_pair_fail, ride_save_fail). Each event is tagged with the rider's stable but server-opaque pseudonym Athlete-XXXXXX, an install-scoped UUIDv4 device id, app version and OS version. | Diagnosing companion crashes + BLE/sync bugs that don't reach our backend on their own. Off by default for every rider; admins are implicit-on; individual beta testers are enabled by Brevet on request via the support flow, with a one-tap revocation path. |
| Technical | IP address (used transiently for rate limiting; a hashed IP plus coarse geolocation — country and ASN — and a coarse user-agent hash are retained in the login-security log for 90 days), session cookies, user-agent string (login notifications) | Security, abuse prevention, login notifications |
We process your personal data under the following legal bases (GDPR Art. 6):
| Data Category | Legal Basis | Explanation |
|---|---|---|
| Identity & Authentication | Contract performance (Art. 6(1)(b)) | Necessary to provide you with an account and access to the Platform |
| Fitness & Training data | Contract performance (Art. 6(1)(b)) | Core functionality of the virtual cycling simulator |
| Club membership, register & letters | Contract performance (Art. 6(1)(b)) | Operating the club register you join: assigning your membership number, recording your homologated brevet rides, and sending the transactional club letters (welcome letter and weekly homologation confirmation), which respect the umbrella email-notifications opt-out in your profile |
| Health & Biometric data | Explicit consent (Art. 9(2)(a)) | You explicitly opt in when syncing health data from your device or Health Connect |
| AI Coaching | Explicit consent (Art. 6(1)(a)) | You must accept the AI disclaimer before any data is processed by AI |
| Third-party integrations | Consent (Art. 6(1)(a)); for the terminal-device access the YouTube embed triggers, additionally §25(1) TDDDG consent | You initiate OAuth connections to Strava, Spotify or YouTube. For the embedded YouTube player you give a separate, prior click-to-load consent before any cookie is set or device storage is accessed |
| Contact form | Contract / legitimate interest (Art. 6(1)(b)/(f)) | Answering the inquiry you sent us; hashed IP + user agent for abuse prevention |
| Push notifications | Consent (Art. 6(1)(a)) | OS permission dialog is explicit opt-in; no push without permission |
| Communication (emails) | Consent (Art. 6(1)(a)) with double opt-in | Marketing/coaching emails require confirmed email consent |
| Social features | Legitimate interest (Art. 6(1)(f)) | Community features you actively participate in |
| Technical (rate limiting) | Legitimate interest (Art. 6(1)(f)) | Protecting the Platform from abuse |
| Nutrition | Explicit consent (Art. 9(2)(a)) | You explicitly sync nutrition data from Health Connect |
| Location permission | Consent (Art. 6(1)(a)) | OS permission dialog is an explicit opt-in; denying it leaves the rest of the platform functional, outdoor rides simply fall back to indoor-mode distance estimates |
| Companion telemetry | Consent (Art. 6(1)(a)) | Off by default for every rider; Brevet enables it per-account through a documented support flow, with the rider's prior agreement, and exposes a one-tap revocation in the support response |
| Routes you upload | Contract performance (Art. 6(1)(b)) | Uploading, building and sharing a route is the feature you asked for. Sharing a route with a group or the club is your choice, made per route, and reversible at any time |
Health and biometric data (heart rate variability, sleep data, body composition, SpO2, respiratory rate, body temperature, VO2max, cycling power and cadence) is classified as special category data under GDPR Art. 9.
What makes power and cadence sensitive here — and what does not. On their own, power and cadence are measurements of a machine: the work a power meter recorded, the revolutions of a crank. They say nothing about your health. Inside a member account we combine them with heart rate, heart-rate variability, sleep and recovery data into one picture of how your body is coping, and in that combination they share the protection of the data they are combined with — which is why they are listed above. Where they are processed alone, without that link, as in the public tools of Section 6b, they are not special-category data, and we do not treat them as such.
We process this data only with your explicit consent (Art. 9(2)(a)). Consent is recorded in our consent_log table together with timestamp, policy version, locale and (hashed) source-IP, so we can prove when and on which version of this policy you opted in.
We read health data from the platform-native, on-device health hubs:
NSHealthShareUsageDescription to request read access. Per Apple App Store Review Guideline 5.1.3, we do not store HealthKit data in iCloud, do not use it for advertising, and do not share it with data brokers.android.permission.health.READ_* permissions necessary for cycling coaching and recovery analysis (see the manifest in our public app bundle for the exact list). Each permission is justified individually in the Google Play "Health Apps declaration form".The companion app can also write completed Brevet indoor rides back into Apple Health (iOS) or Google Health Connect (Android) so that your other fitness apps (Strava, Training Peaks, Apple Fitness, etc.) see them. This is opt-in via the "Export workouts" toggle in the Sync settings, and off by default.
When the toggle is on, for each completed ride the companion writes one workout record of type "Cycling" containing:
A monotonic watermark in the companion app's secure storage prevents the same ride from being written twice. We do not write per-second power, heart-rate or cadence sample streams, GPS routes, or any data unrelated to the indoor ride.
The write goes from our EU backend → the companion app on your device → your device's Apple Health / Health Connect store. From there it stays under the OS's control: Apple/Google govern who else can read it. No third-party transfer happens during the write-back path.
iOS uses the key NSHealthUpdateUsageDescription and Android declares android.permission.health.WRITE_EXERCISE, WRITE_DISTANCE, WRITE_TOTAL_CALORIES_BURNED, WRITE_HEART_RATE and WRITE_EXERCISE_ROUTE for this purpose. Permissions Brevet declares but does not currently use (the optional HR samples and route polylines) are kept in the manifest only so a future opt-in to richer write-back does not require a new permission prompt.
To stop the write-back at any time: toggle "Export workouts" off in the Sync settings, or revoke health-sync consent entirely (which also stops the read direction).
Brevet Cycling Club's use and transfer to any other app of information received from Google APIs (including Google Health Connect) will adhere to the Google API Services User Data Policy, including the Limited Use requirements.
In practice this means:
ACCESS_COARSE_LOCATION / ACCESS_FINE_LOCATION, iOS NSLocationWhenInUseUsageDescription)Brevet's companion app requests the operating system's location
permission for exactly one purpose: to let Apple HealthKit
and Google Health Connect return the route- and distance-derived
fields of the workout records we already read under your
health-data consent. Without this grant, Health Connect's
ExerciseSessionRecord returns null for distance and speed on
outdoor rides, so your outdoor workouts show up with the OS's
indoor-mode estimate (typically zero kilometres) instead of the
real route metrics.
What Brevet does NOT do with the location permission:
CLLocationManager.startUpdatingLocation call on iOS, no
FusedLocationProviderClient.requestLocationUpdates call on
Android.The grant only affects how much detail the OS includes in the workout records it hands us when we read them. The OS itself remains in control of which slice of the route to expose, governed by your per-workout privacy settings inside Apple Health / Health Connect.
You can deny this permission and continue using the rest of the platform unchanged. Outdoor workouts will simply show with the OS's indoor-mode distance estimate.
READ_HEALTH_DATA_IN_BACKGROUND, iOS HealthKit Background Delivery)To keep your readiness numbers fresh without forcing you to open the app every morning, Brevet reads health data in the background from the same two sources:
android.permission.health.READ_HEALTH_DATA_IN_BACKGROUND,
which Health Connect classifies as a "special access"
permission. Google's permission dialog surfaces it as a
separate toggle alongside the per-data-type grants. Once
granted, our existing 12-hour WorkManager sync job (which
runs while the app is in the background) can read the same
data types it reads in the foreground; without it the job
still wakes up but Health Connect returns empty results.HKObserverQuery for the recovery-
critical data types (heart-rate variability, resting heart
rate, sleep analysis, VO2 max) and calls
HKHealthStore.enableBackgroundDelivery(for:frequency:.immediate)
for each. When your watch, ring, scale or other connected
device writes a new sample of one of those types, iOS wakes
the Brevet companion process briefly so it can fetch the
fresh reading. The wake-up budget is bound by iOS's
background-task policy and cannot be expanded by us.The background path reads exactly the same data types listed in the §2 "Health & Biometric" row — there is no expanded, background-only data set. We do not "wake up early" to read data we wouldn't otherwise read. Background access is a freshness mechanism, not a data-expansion mechanism.
Background reads are subject to the same retention rules,
storage location, limits on human access, third-party-sharing
prohibition and revocation rules as foreground reads (every
other subsection of §4). Revoking the health-sync consent
immediately turns the background path off along with everything
else. On iOS, the companion also calls
HKHealthStore.disableAllBackgroundDelivery() so iOS stops
queuing wake-ups for data we no longer have permission for.
The declared purpose for both platforms is identical: recovery analysis, readiness scoring and training-load management for the rider, on the rider's own behalf — generated automatically. The data is not used for advertising, profiling for third-party purposes, or any other downstream use.
The companion app schedules a single local notification
(flutter_local_notifications.zonedSchedule, Android channel
brevet_health_reminder) that fires once a day at the time you
choose — default 09:00 local, adjustable in the Sync settings
between 00:00 and 23:59 or disabled entirely. The notification:
GET /api/companion/health-sync-summary, which only
reports a boolean "is today's data complete?" + the latest
readiness score) confirms your data is already up to date.
This avoids redundant buzzes for riders who already synced
via another device or via background delivery.The reminder lives entirely under your control. Turn it off in the Sync settings, change the time, or simply ignore it — the underlying sync still runs on the same WorkManager / BGTask schedule regardless of whether the notification fires.
Brevet's AI coaching is powered by Google Vertex AI (Gemini models), served exclusively through Google's EU multi-region endpoint — every request is processed inside the European Union (Google may serve it from any of its EU regions, e.g. Frankfurt or Eemshaven); we never use the global endpoint. Vertex AI acts as a data processor for Brevet under Art. 28 GDPR. Google does not use the prompts or responses to train its general-purpose models — this is contractually pinned via Google's Vertex AI Service Specific Terms (cloud.google.com/terms/service-terms), Vertex AI section, which prohibits Google from using customer data to train foundation models without separate written agreement. Brevet has not entered any such agreement; the default no-training contract applies.
We picked a managed Gemini deployment over a self-hosted model because it gives Brevet a state-of-the-art coaching model with predictable latency at a cost we can sustain at the platform's current scale. We do not fine-tune the model. We do not ship custom weights. Every request is processed by Google's stock Gemini deployment under our project's service account.
The AI pipeline has three components:
ai_jobs table) inside our EU PostgreSQL database.
The backend strips your name and replaces it with a stable
pseudonym (Athlete-XXXXXX) before the job leaves the
database boundary.hmac.compare_digest(); it never receives
your user_id, email or display name — only the
pre-pseudonymised payload.aiplatform.eu.rep.googleapis.com,
location eu) — EU-only by construction; embedding requests
are pinned to the single EU region europe-west3 (Frankfurt).
We do not use a global Vertex endpoint, and the broker
refuses to start if any Vertex setting points outside the EU.A retrieval-augmented generation (RAG) step on the broker side looks up cycling-science context from a Postgres pgvector store on Brevet infrastructure; that store contains only public cycling-science material, never user data.
Athlete-XXXXXX as the rider's display name
for the model to refer toai_jobs,
ai_conversation_log): the pseudonymised prompt payloads and the
model's responses are stored in our EU PostgreSQL for 30
days and then deleted by an automatic cleanup task.coach_conversations): each chat turn — and
the coaching text from your morning/weekly/post-ride emails — is
stored as text (with an optional similarity index) for up to 365
days, then removed by a daily cleanup task. You can delete all of
it at any time with "Clear history" in the Coach chat panel.coach_user_facts): a short list of things you
have told the Coach — your goal, constraints, preferences, planned
events — is kept until you delete your account or clear it
yourself. You can review everything the Coach remembers, delete a
single fact, or "Reset Coach memory" entirely from
Profile → Dangerous Zone.expires_at) so
abandoned jobs are garbage-collected.Google Cloud is operated by Google LLC, headquartered in the
United States. Even though our Vertex requests target the EU
region (europe-west3 / europe-west), Google may need to
transfer limited operational metadata to the US for billing,
service operation and abuse detection. We rely on Google's
EU Standard Contractual Clauses (Module 2: controller →
processor) and on Google's adherence to the EU-U.S. Data
Privacy Framework as the legal basis for any such transfer.
The Cloud Data Processing Addendum we countersigned with Google
incorporates both safeguards.
We do not use OpenAI, Anthropic, Microsoft Azure OpenAI, AWS Bedrock or any other LLM provider beyond Vertex AI today. If we ever add a second provider, this section will be updated and your renewed AI consent will be requested.
Model development & research (beta). To improve the automated coaching
engine, we run new training-response models in the background on your existing
training and recovery data, alongside the coaching you already receive — these
run silently, are never shown to you as a second opinion, and produce no
separate advice. Any use of your data for research outside this in-product
development — for example exporting time series into a separate research
environment — happens only after you give a specific, separate opt-in consent
(the model_research purpose) and can be withdrawn at any time. Research
copies are pseudonymous, stay on our EU systems, are never shared or sold, and
are deleted when you withdraw consent or delete your account.
POST /api/ai/revoke-consent or in the Platform UITo diagnose companion-side crashes and UX failures that never reach our backend (BLE pairing failures, sync errors, mid-ride app deaths), Brevet operates a narrow telemetry pipe. It is off by default for every rider; turning it on requires a deliberate per-account action by Brevet, with the rider's prior agreement.
| Event type | When it fires | Fields |
|---|---|---|
| crash | Uncaught Dart exception via runZonedGuarded, FlutterError.onError or PlatformDispatcher.instance.onError | Exception class name, message, Dart stack trace, app version, OS version, platform |
| error | Caught exception we still want to log (e.g. BLE connect failure) | Same fields as crash, plus a short context string identifying the call site |
| ux_event | Hand-curated UX failure slugs: ble_pair_fail, ble_scan_fail, ride_save_fail, sync_fail, … | The slug, a short message, and up to ten small structured hints (e.g. {"device_name": "Wahoo-Kickr"}) |
The pipe never carries free-form analytics, never carries health data, and never carries the rider's name, email or session token.
Every string field (message, stack, context values) passes
through a redaction filter on our backend before it reaches
our log pipe:
<email><token>/Users/<name>, /home/<name>,
C:\Users\<name>) → /<user> / <user><ipv4> / <ipv6><digits>Each event is tagged with the rider's stable but server-opaque
pseudonym Athlete-XXXXXX (a SHA-256-derived 6-character
identifier) and an install-scoped UUIDv4 device id. The
broker / log pipeline never sees the rider's real name, email
or user id. Only a privileged admin endpoint
(/admin/telemetry/lookup, audit-logged on every call) can
reverse-map the pseudonym back to the rider, and only when
Brevet needs to triage a specific report.
The companion sends batches over HTTPS to our EU backend
(POST /api/telemetry/companion), which strips PII as described
and forwards each event to our self-hosted observability stack
(SigNoz on Brevet infrastructure inside the EU). No
third-party crash reporter is involved: we deliberately do
not use Firebase Crashlytics, Sentry SaaS, Bugsnag, or any
other vendor for this pipe. The data never leaves Brevet.
Crash events that fire while you are offline are written to a small persistent FIFO on your device (50-entry cap, SharedPreferences-backed). The next time the companion launches with a working network connection it flushes the queue and clears the local copy.
Crashes that happen before you sign in (onboarding bug, login
loop) reach a separate anonymous endpoint
(POST /api/telemetry/companion/pre-auth) that accepts ONLY events
of type crash. Rate-limited at 5 requests per minute per IP
and body-capped at 32 KB. These events carry only the device
UUIDv4 — no rider binding — until you sign in for the first
time, at which point the device id is linked to your account so
later forensic lookup can attribute past pre-auth crashes to
you.
POST /api/admin/telemetry/opt-in/{user_id} with enabled=false),
logged in audit_log like every other admin action. The
companion stops emitting at the next launch, and any pending
local crashes are still flushed once (so a crash that
triggered the revocation doesn't disappear with it).We share your data with the following third parties, and only for the purposes described:
| Third Party | Data Shared | Purpose |
|---|---|---|
| Google Cloud Vertex AI (Google LLC, EU multi-region endpoint) | Pseudonymised training summary, demographic context (age, gender, weight), training goal, RAG-retrieved cycling-science snippets, pseudonym Athlete-XXXXXX. NEVER: real name, email, user ID, OAuth tokens, raw health samples. | LLM inference for AI coaching (see §5). Google acts as data processor under the Cloud Data Processing Addendum and Vertex AI Service Specific Terms — prompts and responses are NOT used to train Google's foundation models. Processing pinned to the EU; international transfer of operational metadata relies on EU SCCs + EU-U.S. Data Privacy Framework. |
| Strava (Strava, Inc., USA — an independent controller, not our processor) | OAuth tokens (stored by us), ride data (imported from Strava to us) | Ride import, only when you connect your Strava account. The connection happens at your direction under your own Strava account and Strava's privacy policy; data flows Strava → Brevet. Disconnecting in the Connections page deletes the stored tokens. |
| YouTube / Google (Google LLC, USA — an independent controller for the data it receives, not our processor) | From the embedded player: your IP address and player interactions, sent by your browser directly to Google/YouTube once you load it. If you connect your own YouTube account: OAuth tokens (held in your browser, never stored on our servers) used read-only to list your playlists/subscriptions for selection. | Optional in-ride video playback on the opt-in YouTube visual theme. The player is gated by click-to-load and loads from the privacy-enhanced domain youtube-nocookie.com. Because the player is embedded in our page, we obtain your prior consent before it loads; Google then processes the data it receives as an independent controller under the Google Privacy Policy and the YouTube Terms of Service. |
| Google Firebase Cloud Messaging (Google Ireland Ltd.) | Device token, IP address, notification content | Push notification delivery for AI coach reports and training plan changes. Google acts as data processor under Firebase Data Processing Terms. Consent via OS permission dialog (Art. 6(1)(a)). Token deleted on logout/account deletion. |
Email delivery — our own SMTP server (EU, Brevet infrastructure) with Amazon Web Services (AWS SES, region eu-north-1, Stockholm) as the sending relay | Email address; email subject/body for the messages we send you | Sending login codes, coaching emails, weekly/morning reports, and the transactional club letters (welcome, weekly homologation confirmation). AWS acts as a data processor under the AWS GDPR Data Processing Addendum; message content stays in the EU (eu-north-1). Limited operational metadata may transit AWS in the US — covered by EU Standard Contractual Clauses + the EU-U.S. Data Privacy Framework (see §11). |
| Podcast publishers and their hosting / CDN providers (independent controllers, not our processors — location varies with the show) | When you press play: your IP address and browser user-agent, sent by your browser directly to whichever server the show's own feed names. We do not proxy, cache or store the audio. | Playing the episode you picked on the opt-in podcast theme. This is how podcast distribution works everywhere — a "download" IS an IP-and-user-agent request, and it is what a show's own listener figures are counted from. We deliberately do not shield it: standing between a show and its audience numbers would turn us from a player into a distributor. The feed (a show's episode list) is fetched by our server instead, so a publisher never learns which of our riders follow them — only that Brevet asked for the list. |
| Apple (Apple Inc., USA — independent controller) | The words you type into podcast search. Not your IP address, session or identity: the query is forwarded by our server, not by your browser. | Finding a show by name in Apple's public podcast directory, so nobody has to type an RSS address with a TV remote. Used for that lookup only; we keep neither the query nor the results beyond a short shared cache. |
| OpenStreetMap / Overpass API (OpenStreetMap Foundation, UK, and its public mirrors — we use EU-hosted ones; independent controllers, not our processors) | For every route we build (yours or the catalogue's), a thinned copy of the route — one point every 50 m, rounded to about 11 m, with the first and last 500 m removed. No name, no account, no time, no file. The request is made by our server; your IP address never reaches them. | Reading what stands beside the road (forest, houses, water, fields) so the visual world matches the route. Map data © OpenStreetMap contributors (ODbL). |
We do not:
Use of YouTube API Services. The optional YouTube visual theme uses YouTube API Services — the embedded IFrame player and, if you connect your account, the YouTube Data API (read-only). By using the theme you agree to the YouTube Terms of Service; any data Google receives is handled under the Google Privacy Policy. When you connect your YouTube account, the read-only access (your playlists, subscriptions and your region's trending feed) is used only to let you pick what to watch during a ride; we do not store that data on our servers, never transfer it to third parties, and never use it for advertising. You can revoke Brevet's access at any time in your Google Account permissions.
This section is not about anyone we hand data to. It is about the one place where you are the publisher, and it is worth its own heading because the consequences are different: what you publish here, we cannot un-publish for you once someone has copied it.
It is off. Every ride is private. Nothing about a ride is reachable from outside your account unless you tap "Create share link" on that specific ride.
Per ride, never per account. Sharing one ride shares that ride. It does not open your profile, your history, your fitness numbers, or your other rides.
The link is the key. We mint a random 128-bit token and the ride lives at that address. It is not listed anywhere, not in search, not on your profile — but there is also no login in front of it. Anyone holding the link can open the ride, and can pass the link on. If you post it somewhere public, treat it as public: it can be copied, re-shared and indexed by search engines like any other public URL.
You choose what it shows. Four things are always on the page, because they are what a shared ride is: the route name, the duration, the distance and the elevation. Everything beyond that is a switch you control:
| Block | What it publishes | Default |
|---|---|---|
| Power & verdict | Average, maximum and normalised power, energy, cadence, climbing rate, your BES score and tier | On |
| Curve | The power and elevation profile over the ride, reduced to 300 points | On |
| Heart rate | Your average and maximum heart rate and the heart-rate curve | Off |
Heart rate is off unless you switch it on. That is deliberate: it is the health-adjacent end of a training file, and a default that publishes it would be a default nobody chose.
"Off" means not sent, not merely not drawn. The page you share is built from your selection on our server. A block you leave off is never put into the response at all — so it is not hiding in the page's data for someone who looks past the visuals.
Some things can never be shared this way. Beat-to-beat heart intervals (the raw signal HRV is computed from), your GPS positions, your speed and cadence traces, your critical-power and weight values, your notes, and anything your Coach has written are not part of any block and cannot be published through a share link at all.
The preview card obeys the same choice. When the link is pasted into a chat or a social post, the small preview card that appears is generated from exactly the same filtered data — it cannot show a number the page itself would not.
Changing your mind. Turning a block off updates what the link shows immediately, and keeps the same link working. "Stop sharing" deletes the token: every link you have already handed out goes dead at once, and re-sharing later mints a brand-new address, so the old link never revives. What we cannot undo is a copy someone already made while the link was live.
Two things on this site work without an account and without a login: you choose one file, you get numbers back, and that is the end of it. This section spells out what happens, because "we keep nothing" is a claim that is only worth something in detail.
Your file stays on your device. It is opened by your own browser. It is never uploaded — our server has no way to receive it on these routes. What your browser sends is a handful of number series it has read out of the file, together with the values you type in:
One more thing goes, and it is the only thing we keep. Once your result is on the screen, your browser sends a description of the FILE — never of the ride: which make and model of device wrote it (never its serial number), which message and field codes it used, which channels it held, and the shape of its sampling in coarse bands like "1 s" or "under 30 minutes". No value from your ride, no coordinate, no date or time of day, and no heart rate — not even a note that the channel was there. We keep it because this page has to open files from head units nobody here owns, and the failures that matter are recording dialects we cannot invent: a firmware that writes one speed field and not the other, a ride chained out of three files after a battery change, a file whose checksum fails because the battery went first. Identical descriptions collapse into one row with a counter, so what we hold grows with the number of DIALECTS, not with the number of visitors, and there is nothing in it that could point back at you.
Your device also sends its IP address with the request, as it does with every request on the internet.
Your file carries more than that — and it is not read. A file recorded outdoors usually carries GPS coordinates; most files carry a device serial number and the real date and time of the ride; a route file carries where the road is, which often means where you live. The code that opens your file reads fields from a fixed list. Coordinates, names and serial numbers are not on it and are never read. The clock time of each sample IS read — in your browser only, because there is no other way to know how many seconds lie between two samples — and what is sent is the time since the start of the ride, never the date or the time of day. The requests have no field for a coordinate, a name, a serial number or a clock time, and our server refuses any request that carries a field it does not know.
Heart rate never reaches us. Heart rate says something about your body and not only about your riding, so it is special-category data under Art. 9 GDPR and we treat it as such: it is processed in your browser only. By default the heart-rate channel of your file is not read at all. Tick the separate box on the page and your browser reads it, draws it and relates it to your other channels on your own device — that tick is your explicit consent under Art. 9(2)(a) GDPR and it covers that one analysis. Behind that same tick the page asks two more things about your body, because a heart-rate zone is a share of a maximum and means nothing without one: a maximum you have measured, or your age, from which it estimates one. Both are used where you typed them, in your browser. What the request carries has no field for any of the three — not for a heart rate, not for a maximum, not for an age (nor for a coordinate) — and our server refuses a request that carries one. Untick the box, or close the page, and there is nothing left: neither here nor with us, because it never was with us.
Nothing is written down. The numbers are held in server working memory for the length of the calculation — typically well under a second — and released when the response is sent. They are not written to a database, to disk or to a cache, and they are not attached to any account, including yours if you happen to be logged in: these requests are sent without cookies. Our application log records that a calculation ran, how many data points it had and how long it took — no values, no address — and we count how many requests each tool answered and how many we refused, as totals with no address in them. The web server in front of it writes no access log for these requests, and they are excluded from our request tracing. One exception, and it exists to keep the tool working: when the web server turns a request away because it arrived too fast, it notes that refusal — including the address it came from — in its error log, so an attack can be seen while it is happening. A request that is answered leaves no such line. The response tells your browser not to store it. Close the page and the result is gone; we could not show it to you again.
Nobody else receives it. No third party receives your numbers or anything computed from them. In particular, nothing is sent to Google Vertex AI or to any other AI service: the public tools do no AI processing at all. The AI coaching described in Section 5 exists only inside a member account and only with the separate consent described there. The pages load no third-party scripts, fonts, maps or trackers.
Abuse protection. So that the tools stay available, we count requests per source address. Our application holds a keyed hash of your address — not the address — in a memory-only store, and the entry expires within the hour. The web server in front of it additionally keeps the address itself in working memory for a short time (typically about a minute) to smooth bursts; it is not written anywhere. Legal basis: our legitimate interest in keeping the service available (Art. 6(1)(f) GDPR, Recital 49).
Legal bases, in short. Computing the figures you asked for is the service you requested, so we process the performance data and your entries to deliver it (Art. 6(1)(b) GDPR). Rate limiting runs on legitimate interest (Art. 6(1)(f) GDPR). The heart-rate part happens on your device and we receive nothing from it; the tick that starts it is your explicit consent (Art. 6(1)(a) and Art. 9(2)(a) GDPR) for that processing in your own browser.
Your rights, honestly stated. Because we keep nothing and attach nothing to you, we cannot find "your" analysis afterwards and cannot identify you from it (Art. 11 GDPR). So there is nothing for us to give you access to, correct or erase — and equally, nothing that could later be handed over, breached or repurposed. Your other rights, including the right to lodge a complaint with a supervisory authority, are described in Sections 8 and 9. If you want your rides kept, compared and coached over time, that is what an account is for; the rest of this policy describes what changes when you open one.
Under 16? Use the public tools with your parents' agreement.
Not a medical device. The tools describe what is in your file. They do not assess your health and are not a medical device — see Section 5 of our Terms of Service.
We retain your data for specific periods based on purpose and legal requirements:
| Data Type | Retention Period | Deletion Method |
|---|---|---|
| User account | Until you delete your account | DELETE /api/auth/account or email request |
| Session tokens | 7-day idle timeout, 30-day absolute maximum | Automatic cleanup (hourly) |
Login-security log (auth_login_events) — hashed IP, coarse geo (country/ASN), coarse user-agent hash, risk outcome; purpose: security & abuse detection | 90 days from the login event | Automatic cleanup (daily) |
Recognised-device records (auth_devices) — device-recognition rows that suppress redundant new-login notifications; purpose: security & abuse detection | 90 days from last use | Automatic cleanup (daily); or revoke in Profile → Security & Sessions |
| OTP codes | 60 minutes from creation | Automatic cleanup (hourly) |
| OTP attempt counters | 15 minutes (lockout period) | Automatic cleanup |
| Fitness log & ride data | Until account deletion | Deleted with account |
| Health/recovery data | Until account deletion | Deleted with account |
AI job queue & conversation log (ai_jobs, ai_conversation_log) | 30 days from creation | Automatic cleanup (hourly) |
| Training plans | Until account deletion | Deleted with account |
| Coaching email log | Until account deletion | Deleted with account |
| Group invites | 7 days from creation | Automatic cleanup (hourly) |
| Push notification tokens | Until logout or account deletion | Deleted via API or ON DELETE CASCADE |
| Strava OAuth tokens | Until you disconnect Strava or delete account | Deleted with account |
| Feedback submissions | Until account deletion | Deleted with account |
| Historical data from removed features (leaderboard entries, league standings — the leaderboards and leagues were removed in 2026) | Retained until account deletion; no longer processed | Deleted with account |
Club membership number (users.member_no) | For the life of the account; the number is never reissued after deletion | Deleted with the account; the retired number is handed to no one, and once your account is gone it is no longer linked to any person — it holds no personal data |
Homologation ledger (homologations) | Until account deletion | Deleted with the account (cascade); the club-global certification-number counters carry no user link and are left untouched |
| Avatar images | Until account deletion | File deleted with account |
| Contact-form messages | As long as needed to handle your inquiry; deleted on request | Email privacy@brevetcycling.club |
Security / admin audit log (audit_log) — admin actions, security-relevant events | Until account deletion, then anonymised (identifiers stripped, the event fact retained) | On account deletion: personal identifiers wiped under GDPR Art. 17(3)(b)/(e) |
| Companion telemetry (§5a — crash / error / UX-failure events) | Rolling retention of our self-hosted SigNoz log store (EU) | Aged out automatically by the observability store's retention window |
Coach conversation memory (coach_conversations) | Up to 365 days from creation | Daily cleanup task; or "Clear history" in the Coach chat panel |
Coach durable facts (coach_user_facts) | Until account deletion or you clear them | Deleted with account; or "Reset Coach memory" (Profile → Dangerous Zone) |
Consent ledger (consent_log) | Until account deletion (append-only while the account exists — see §4) | Deleted with account |
| Podcast subscriptions & listening positions | Not retained by us at all — they exist only in your browser's local storage (see §10) | Remove a show in the player, or clear your browser storage |
| Podcast feed cache (shows' episode lists) | Up to 30 minutes, in server memory only — no database row, no disk, addressed by a hash of the feed address and shared across all riders | Expires automatically; gone entirely on any restart |
| News-board sources & seen headlines | Not retained by us at all — the source list and the "already shown" marks exist only in your browser's local storage (see §10) | Edit the sources in the board, or clear your browser storage |
| News feed cache (headline lists) | Up to 5 minutes, in server memory only — no database row, no disk, addressed by a hash of the feed address and shared across all riders | Expires automatically; gone entirely on any restart |
Routes you upload (tracks, the built world file, the uploaded GPX in the processing job) | Until you delete the route or your account | Delete the route in the track library, or with the account. Other riders' logged rides on a route you shared keep the route's name and their own numbers, never the geometry |
The AI coaching feature produces individualised training recommendations (target weekly load, ride structure, rest-day suggestions, post-ride verdict). These outputs always remain advisory: every recommendation is generated automatically, it never changes your structured plan without your action, and you can override or ignore any AI suggestion at any time.
For this reason, our AI coaching does not constitute a "decision based solely on automated processing, including profiling, which produces legal effects ... or similarly significantly affects" you within the meaning of GDPR Art. 22(1).
Independently of that, you always have the right to:
We log AI requests for 30 days (Section 5) so that human review can re-construct what the model was shown when generating a contested recommendation.
Under GDPR, you have the following rights regarding your personal data:
| Right | Description |
|---|---|
| Right of access (Art. 15) | Request a copy of all your personal data |
| Right to rectification (Art. 16) | Correct inaccurate personal data |
| Right to erasure (Art. 17) | Request permanent deletion of all your data |
| Right to data portability (Art. 20) | Receive your data in a structured, machine-readable format |
| Right to restriction (Art. 18) | Request that we limit processing of your data |
| Right to object (Art. 21) | Object to processing based on legitimate interest |
| Right to withdraw consent (Art. 7(3)) | Withdraw any consent given, at any time |
| Right to lodge a complaint | File a complaint with your local data protection authority |
| Your routes in the export | Your data export (Art. 20) includes the routes you uploaded, as GPX |
GET /api/auth/export.DELETE /api/auth/account.GET /api/auth/data-retention, which returns the current periods as JSON.PUT /api/auth/profile.POST /api/ai/revoke-consent -- immediately stops AI data processingDELETE /api/consent/health-sync (or the "Revoke health sync consent" button in the companion Sync tab) -- immediately stops the companion app from reading and uploading data from Apple Health or Google Health Connect, including the background path described in §4GET /api/consent -- returns the full history of consents you have granted or revoked, with policy version and timestampsdisableAllBackgroundDelivery() at the next launch so iOS stops queueing wake-ups.privacy@brevetcycling.club requesting telemetry opt-out. Admins toggle the flag through the audited support flow; the change takes effect on the next companion launch and pending local-queue crashes are drained once on the way out (so a crash that triggered your request isn't lost).For any request you cannot complete self-service, or if you wish to exercise your right to restriction or objection, contact us:
Email: privacy@brevetcycling.club
We will respond to your request within 30 days as required by GDPR Art. 12(3). If your request is complex, we may extend this by a further 60 days, but we will inform you of the extension within the initial 30-day period.
We may need to verify your identity before processing your request to protect against unauthorised access.
Brevet sets four first-party cookies on the web side — all of them functional, none of them tracking:
| Cookie | Purpose | Type | Duration | Attributes |
|---|---|---|---|---|
brevet_session | Session authentication | Essential (first-party) | 7-day idle timeout, 30-day absolute maximum | HttpOnly, SameSite=Lax, Secure (production) |
brevet_csrf | Cross-site-request-forgery protection (double-submit token) | Essential (first-party) | 30 days | SameSite=Lax, Secure (production); readable by the app's own JavaScript by design |
brevet_device_id | Recognising a device you have logged in from before, so we can suppress redundant "new login" security notifications | Security (first-party) | 2 years | HttpOnly, SameSite=Lax, Secure (production) |
brevet_client | Set by the companion app's embedded WebView only (value companion), so the platform can adapt to running inside the app. Never set for normal browsers. | Functional (first-party) | Companion install lifetime | First-party hint, no identifier |
If you connect Spotify to control playback during a ride, the Spotify OAuth tokens are stored in your browser's local storage on your own device (a PKCE public-client flow — there is no Spotify secret on our servers). They are used only to control your own playback and never leave your device except in the direct calls your browser makes to Spotify. Because this storage is strictly necessary for the music service you explicitly requested, it falls under the §25(2) No. 2 TDDDG exemption; no consent banner is required. Disconnecting Spotify, or clearing your browser storage, removes the tokens.
The four cookies above are the only cookies Brevet sets, and all are functional. The optional YouTube visual theme is different: when its embedded player loads, YouTube can set third-party cookies and read or write information on your device. Those are not strictly necessary and do not fall under the §25(2) TDDDG exemption, so we do not load them by default.
Instead the player is gated by click-to-load: it stays a static placeholder describing what will happen until you take an explicit, prior action ("Load player & continue"). Only then does the embed load — from the privacy-enhanced domain youtube-nocookie.com, which defers tracking/advertising cookies until playback. That action is the §25(1) TDDDG consent for the device access and the Art. 6(1)(a) consent for the data Google then receives as an independent controller (see §6 and §11). Your choice is remembered for the current browser session only, so each new session re-confirms it; switching away from the YouTube theme stops the embed loading.
The optional podcast theme keeps everything about your listening on your own device, in your browser's local storage under the key brevet-podcast:
| Value | Purpose |
|---|---|
| Subscriptions | The shows you follow — feed address, title, author, cover image |
| Positions | How far into an episode you got, and which episodes you finished |
None of it is sent to us and we hold no copy: we do not know which shows you follow or what you have listened to. That is a deliberate design decision rather than an omission. A list of the podcasts someone follows is a map of their interests, and podcast catalogues carry politics, faith, health and sexuality on the cover — so the safest place for that list is the one where we cannot read it. The cost is stated rather than hidden: your shows do not follow you from one device to another. If that ever changes it will be an explicit opt-in with its own entry in this policy, never a silent upgrade.
"Remove" clears one subscription; clearing your browser storage clears everything at once. Because this storage is strictly necessary for the player you explicitly asked for, it falls under the §25(2) No. 2 TDDDG exemption; no consent banner is required.
The optional news board shows headlines from feeds you choose. It is the most contained surface we ship, and the detail is worth stating because most people assume the opposite of a news reader:
brevet-news, together with the marks for which headlines have already been shown. We hold no copy, so we do not know what you read. A list of the news someone follows is a political profile; the safest place for one is where we cannot read it.The starter list is a suggestion, not an endorsement — every source is removable and the board is perfectly happy empty. Because this storage is strictly necessary for the board you explicitly asked for, it falls under the §25(2) No. 2 TDDDG exemption; no consent banner is required.
The companion app stores a small number of values on your device for the app itself to read — these are NOT cookies, NOT analytics, hold NO health data, and never leave the device:
| Key | Storage | Purpose |
|---|---|---|
brevet_session_token, brevet_csrf_token, brevet_user_id, brevet_user_email, brevet_user_name | flutter_secure_storage (encrypted) | Session resume across app restarts |
brevet_sync_settings | flutter_secure_storage (encrypted) | Health-sync toggles + daily reminder hour/minute |
brevet_health_perm_question_hash, brevet_health_perm_outcome, brevet_health_perm_asked_at | SharedPreferences | Hash + outcome of the last health-permission prompt. Lets the in-app banner stop reappearing forever when the OS API is inconclusive without nullifying our positive history. Cleared automatically when the permission contract changes. |
brevet_telemetry_pending | SharedPreferences | Local crash queue when offline (see §5a). Capped at 50 entries; flushed and cleared after a successful send. |
brevet_companion_device_id | flutter_secure_storage (encrypted) | Per-install UUIDv4 used by the telemetry pipe to link pre-login crashes to your account after sign-in. |
brevet_last_written_ride_ts, brevet_writeback_version, brevet_last_full_sync_ts, brevet_last_widget_refresh_ts | flutter_secure_storage (encrypted) | Health-Connect / HealthKit writeback watermark + foreground-sync staleness gate. |
Deleting the companion app removes all of these. Revoking health-sync consent additionally clears the permission-related entries. Revoking telemetry opt-in additionally drains and clears the local crash queue.
Your data may be transferred to or processed in the following locations:
| Service | Location | Safeguard |
|---|---|---|
| Brevet broker (request batching + RAG retrieval) | EU (self-hosted, Brevet infrastructure) | No third-country transfer for this component; the broker only forwards pseudonymised payloads to Vertex AI (see below). |
| Google Cloud Vertex AI (Gemini inference) | EU multi-region endpoint (processing inside the EU; never the global endpoint). Operational metadata may transit Google LLC in the United States. | Google Cloud DPA (Module 2: controller → processor), EU Standard Contractual Clauses, Google's adherence to the EU-U.S. Data Privacy Framework. Vertex AI Service Specific Terms prohibit Google from using our prompts/responses to train its foundation models. |
| Google Firebase Cloud Messaging | EU (Google Ireland Ltd.) / US | Google Data Processing Terms, EU Standard Contractual Clauses |
| Email delivery relay — Amazon Web Services (AWS SES) | EU (eu-north-1, Stockholm); message content stays in the EU. Limited operational metadata may transit AWS in the United States. | AWS GDPR Data Processing Addendum, EU Standard Contractual Clauses, and AWS's adherence to the EU-U.S. Data Privacy Framework. Our SMTP server itself runs on Brevet's own EU infrastructure. |
| Strava (independent controller) | United States | The transfer occurs at your direction when you connect your own Strava account (Art. 49(1)(b) GDPR — performance of your contract with Strava); Strava processes your data under its own privacy policy. We have no SCCs with Strava and none are required for this user-initiated connection. |
| YouTube / Google (independent controller) | United States | The transfer happens at your direction when you load the opt-in YouTube theme and give the click-to-load consent. Google LLC self-certifies under the EU-U.S. Data Privacy Framework, which an EU adequacy decision recognises as providing adequate protection; in addition, your explicit prior consent covers the specific transfer (Art. 49(1)(a) GDPR). We hold no SCCs with Google for the embed and none are required for this user-initiated, consent-based transfer. |
| Live-ride telemetry cache (in-memory, for multi-process real-time features) | EU (self-hosted, Brevet infrastructure) | Transient in-memory processing of live ride telemetry (power, heart rate, position) only — not persisted; used to fan out real-time ride state across server processes and dropped as soon as it is delivered. No third-country transfer. |
| Platform hosting | EU (primary) | Data stored within the EU |
| Podcast hosting / CDN of the shows you play (independent controllers) | Varies with the show — frequently the United States | The transfer happens at your direction when you press play on an episode you chose (Art. 49(1)(b) GDPR — the same basis every podcast app relies on). Only your IP address and user-agent reach the host, as part of the media request itself. We hold no SCCs with the thousands of possible hosts and none are required for this user-initiated connection. |
| Apple (podcast directory search) | United States | Only the search words you type, forwarded by our server so your own address never reaches Apple. Apple Inc. self-certifies under the EU-U.S. Data Privacy Framework. |
For transfers outside the European Economic Area (EEA), we rely on EU Standard Contractual Clauses (SCCs) as approved by the European Commission, or other appropriate safeguards under GDPR Chapter V.
We do not use any LLM provider other than Google Vertex AI today (OpenAI, Anthropic, Microsoft Azure OpenAI, AWS Bedrock, self-hosted Gemma / Llama / Mistral, etc. are all out of scope). The LLM inference itself happens on Google Cloud's EU infrastructure under the safeguards listed above, never on a US-region Vertex endpoint.
We may update this privacy policy from time to time. When we make material changes:
If you have questions, concerns, or wish to exercise your rights:
General inquiries: privacy@brevetcycling.club
Data Protection Officer: no DPO is currently appointed — see §1 for the documented threshold assessment and its revisit triggers. All data-protection matters reach us at privacy@brevetcycling.club.
Supervisory authority: You have the right to lodge a complaint with your local data protection authority if you believe we are processing your personal data unlawfully.
This privacy policy is written in clear, plain language in accordance with GDPR Art. 12 (transparent information). If any part is unclear, please contact us and we will explain it.