Privacy

What the site collects, where it's stored, and how to request deletion. Updated 2026-04-23.

Who runs this

Solo project, run by me out of Brussels. The legal data controller for GDPR is DropBars Consulting SComm (my one-person company, transitioning to SRL). Privacy questions to privacy@bikepowermodel.fit. Ask what's on file, fix it, delete it. No justification needed.

Cookies

Two cookies, both first-party, both HttpOnly/Secure/SameSite=Strict. Neither identifies you across sites.

NamePurposeTTLConsent
bpm-sessionAnti-abuse: signed token that proves the caller solved an ALTCHA proof-of-work. Payload: <ts>.<sha256(ip)[:32]>.<hmac>1 hourStrictly necessary, no consent required (ePrivacy 5(3))
bpm-accountSigned-in session. Only set after you click your magic link. Payload: <ts>.<sha256(email)[:16]>.<hmac>7 days (rolling, 60-day cap)Strictly necessary for the feature you requested

Each authenticated request extends your session by 7 days from now; after 60 days from sign-in you're asked to magic-link again, regardless of activity. Opening someone else's shared plan link doesn't count as activity: share endpoints are anonymous and never extend the session.

Email

If you download a Power Guide FIT, analyze a ride, or donate one, the site emails you a one-time magic link to confirm the address. Delivery goes through Scaleway Transactional Email (EU-hosted). Your address is sent to Scaleway's send API and not stored on my side: the database keeps only a one-way hash (sha256(email)[:16]) so a return visit with the same email works. Scaleway has its own retention policy on sending logs.

Analytics

PostHog EU Cloud (eu.i.posthog.com, Frankfurt), routed through this site's own origin at /api/m/log so ad blockers don't skew the stats. Two modes, picked by your signal:

  • You haven't touched the banner, or clicked Decline → cookieless server-hash pageview. Nothing stored on your device, no cross-session identifier, no IP. Just a "visit happened" count.
  • Accepted → regular capture with a visitor id persisted in localStorage so I can see returning visitors vs new ones. Page paths, click targets, web vitals. Geo-IP off on my side.

The browser's Do-Not-Track header is ignored: it's deprecated (MDN) and orthogonal to cookie consent anyway (a DNT user can still actively click Accept). You see the same banner either way.

You can change your mind later by clearing localStorage.bpm.analytics.consent.

What leaves your browser

Beyond the cookies above, the following reach a server when you use the corresponding feature:

  • Planner: your slider values (FTP, weight, CdA, effort) + the route id. No IP logging beyond web-server access logs, which rotate on 7 days.
  • Download Power Guide: only the job id and your account cookie. The bytes come back to you; nothing extra is logged.
  • Analyzer / donation: the FIT you upload. The server runs it through a whitelist redactor before writing anything to storage: GPS first/last 500 m trimmed, device serials and rider-profile name stripped. A one-way hash of your email keys the submission so repeated uploads from the same person can be grouped when useful for model-calibration review. Your IP and user-agent do not get stored.
  • Operator notification email: when you donate a ride, I get a digest email summarising what came in (donor hash, when, planning fields like effort and intent, ride-level metrics like distance and duration). Health-derived fields you enter (FTP, weight, height, gender, CdA) are deliberately excluded from the email body and stay only inside the auth-gated S3 sidecar.
  • Planner: bring your own course. A different posture. The FIT is parsed in your browser; only GPS + elevation survive (rider name, device serial, HR, power, cadence all stripped client-side). That stripped course is then encrypted in your browser with AES-256-GCM under a key generated for this tab. The ciphertext is what gets stored on S3 (Scaleway, Paris); the key never leaves your browser tab except for the instant during a plan request when the server decrypts, runs the model, and drops both key and plaintext when the response returns. The server is a blind operator for this flow: no copy of the key, no mirror to a sync service, no recovery path. Sign out, close the tab, lose the laptop: the upload becomes unreadable ciphertext. This is deliberate; it's also why there's no multi-device view of your uploads. Shared links use a second, per-share key that rides in the URL fragment (browsers don't send fragments to the server), so recipients decrypt locally too. Shares expire when the parent upload expires: 7 days of no use, 60 days hard maximum.
  • One honest footnote on the bring-your-own flow.At upload time the server applies DEM-based elevation correction against a SRTM mirror that lives in my own Scaleway bucket (Paris). No third-party data hosts are involved; the tile lookups never leave Scaleway. Your filename stays in your browser as a label and never leaves the tab; a constant internal name is stored inside the encrypted payload, so even if the decryption key were ever compromised, no filename surfaces alongside the coordinates.

Third parties

Hosting is Scaleway (Paris). Email is Scaleway TEM. Analytics is PostHog EU. Map tiles are proxied through this server from a private Scaleway bucket: your browser never talks to a third- party tile host. No Google Fonts, no ad networks, no Facebook pixel, no Cloudflare, no customer-data brokers.

Your rights

You can request access, correction, or deletion of anything associated with your email hash. Email privacy@bikepowermodel.fit. Single operator, no databases of profiles, so I usually respond within a day. Complaints about data handling can go to the Belgian data protection authority (APD/GBA).

If you're a rider whose name appears in the public training dataset on GitHub (pro_weights.json, rides_inventory.json) and want your row removed or pseudonymised, email the same address: same response, no justification needed.

Delete all my uploads. Wipes every encrypted course you've uploaded and every share link you've created. Signing out does this automatically; this button is the same thing while staying signed in.

Two things this doesn't reach. Donations you sent through the Analyzer are stored without your account on them: to remove one, save the receipt code from the donation panel and email privacy@bikepowermodel.fit. And once a recipient opens a share, the plaintext is in their browser: I can't reach across to delete that.

bikepowermodel.fit · open-source race-time prediction · per-split surface Crr · Garmin / Wahoo / Hammerhead / Bryton