Privacy Policy
How Progression Racing League (“PRL”, “we”, “us”) collects, uses and protects your personal data when you use the PRL app or website.
Effective date: 2026-05-11
Version: 2026-10-16
1. Who’s responsible
Progression Racing League is operated by Jonathan Baagø, a sole proprietor based in Denmark. Jonathan is the data controller under EU Regulation 2016/679 (GDPR) for all personal data described below.
We do not currently have a designated Data Protection Officer because our processing operations do not meet the Art. 37 GDPR triggers. The controller acts as the single point of contact — see Contact.
2. What we collect, why, and on what legal basis
Identity and account
| Data | Purpose | Legal basis |
|---|---|---|
| Email address, password (hashed), display name, profile photo, language, location, short bio | Identify you, sign you in, render your profile, attribute your race results | GDPR Art. 6(1)(b) — performance of contract |
| Your Zwift ID, if you choose to link it | Match Zwift race results to your PRL account | Art. 6(1)(b) |
Consent records
| Data | Purpose | Legal basis |
|---|---|---|
| Record of when you accepted these Terms and this Privacy Policy, version strings, IP address at the time | Demonstrate consent was given under Art. 7 GDPR | Art. 6(1)(c) — legal obligation |
League activity
| Data | Purpose | Legal basis |
|---|---|---|
| Race signups, stage line-ups, stage results, season standings, jersey results, team rosters | Run the league: pair you to races, calculate standings, settle results disputes, render the public standings | Art. 6(1)(b) + Art. 6(1)(f) — legitimate interest in keeping league history intact |
Zwift data (from ZwiftPower and ZwiftRacing.app)
| Data | Purpose | Legal basis |
|---|---|---|
| Your ZwiftPower category, your ZwiftRacing.app rating (vELO) and how many races you rode in the last 90 days, read for your checked Zwift ID, with the date and the site each number came from | Show you, your team (its riders and staff) and league admins your current category and rating without you typing them | Art. 6(1)(a) — consent, given when you link your Zwift ID; withdraw it in one tap under Settings → Zwift account (that deletes what we read) |
| The same category and rating, shown to everyone in the league — only if you switch "Show my category and rating to the whole league" on (off by default) | Let other teams see your level, for example when you are looking for a team | Art. 6(1)(a) — a separate consent; switching it off hides them from other teams at once |
| Your FTP and weight as ZwiftPower shows them — only if you switch "Power numbers" on (off by default) | Let you and your current team's staff see your FTP and W/kg | Art. 6(1)(a) — a separate consent; switching it off deletes the numbers at once |
| Your finishing place in ZRL rounds your team races, from the ZwiftPower results page of the event | Show your places on the round page, in the season grid and in your history | Art. 6(1)(f) — legitimate interest in keeping the team's ZRL record; riders who are not on PRL are shown only as "Not on PRL" |
| A name you ask us to look up on Zwift ("Is this you?") and up to five Zwift riders we found for it | Help you find and link your Zwift ID | Art. 6(1)(b) — you asked for it; deleted after 24 hours |
Once your Zwift ID is checked, PRL reads your public ZwiftPower category and ZwiftRacing.app rating about once a week so your team sees them here. FTP and weight are read only if you switch that on. You can switch it all off under Settings → Zwift account. ZwiftPower and ZwiftRacing.app are sources we read from, not processors of your data: we send them nothing about you but your Zwift ID. A number is always shown with the site and the date it came from, and nothing here sets a price or a value on you.
Live race data
| Data | Purpose | Legal basis |
|---|---|---|
| During a race you start in: your power, W/kg, heart rate, cadence and draft, about once a second, and a W′ balance estimate worked out from your power — only if you switch "Share live race numbers with my team" on (off by default). Your position and gaps are the team's race and are shown without it. | Let your team's race staff — the DS and the captain (whoever holds race calls) and that race's road captain — follow the race live and call it; you see your own numbers on your race screen | Art. 6(1)(a) and, for heart rate, Art. 9(2)(a) — your explicit consent, given with the switch; withdraw it with the same switch |
| One summary per race: average / normalised / max power, average and max heart rate, average cadence, the lowest W′ balance, time in five zones, distance, duration and your finishing place | Let you and your team's race staff look back at a race | Art. 6(1)(a) / Art. 9(2)(a) — the same consent; kept only for races you rode while sharing |
Live race data is off unless you switch it onin Settings → Training data (“Share live race numbers with my team”). While it is on, your numbers go only to your own team’s race staff for the race you are riding, and to you. The live frames are never stored: they are passed on and forgotten. One short summary per race is kept for 12 months. Switching it off works immediately— from the next live update your team sees only your position, never your body numbers again — and you can still read your own summaries. Nothing here is ever shown on a public page, a stream overlay or Discord.
Training data (Intervals.icu)
| Data | Purpose | Legal basis |
|---|---|---|
| If you connect Intervals.icu: daily fitness, fatigue and ramp rate; per activity its date, type, name, duration, distance, power, training load, intensity, FTP and heart rate (average and max); your best power for 1 s to 1 h | Show you your training; only if you also switch on sharing: your PRL fitness and fatigue come from it (everyone in the league sees those two numbers) and your team's officers see your training, including heart rate | Art. 6(1)(a) and, for heart rate, Art. 9(2)(a) — explicit consent at connect, and a second one for sharing; disconnecting deletes your key and everything copied |
Your team’s public page
| Data | Purpose | Legal basis |
|---|---|---|
| Your display name and two-letter country on your team's public web page — only if your team turned its public page on AND you said yes under Privacy & Data → Public team page (off by default) | Show who rides for the team to anyone with the page's link (it is on the open internet) | Art. 6(1)(a) — your consent; switching it off removes your name from the page at once. Nothing else about you is ever on that page |
Communications and social
| Data | Purpose | Legal basis |
|---|---|---|
| Direct messages, shoutbox posts, forum threads, polls, news posts, moderation actions | League-internal communication, team coordination, abuse prevention | Art. 6(1)(b) + Art. 6(1)(f) for moderation |
| Voice channel metadata (who joined when, per-user volume settings). Voice traffic itself is not recorded. | Real-time voice during races | Art. 6(1)(b) |
| If you link Discord: your Discord user id and name; while you are in your team's Team Radio or Radio 1:1 channel, that you are there, muted or deafened, and — while a DS has the live cockpit open — whether you are talking (no audio is ever recorded, listened to or stored) | Show your team and league staff who is "on radio" on My Team and on race day | Art. 6(1)(f) — legitimate interest in running team radio; on unless you switch "Show me as on radio to my team" off in Settings → Mail & Discord |
Payments (post-banking launch)
| Data | Purpose | Legal basis |
|---|---|---|
| Stripe customer ID, payment metadata, subscription / contract / transfer records | Process payments, settle transfers, prove contractual obligations, comply with Danish bookkeeping law | Art. 6(1)(b) + Art. 6(1)(c) — Danish bogføringsloven §10, 5-year retention |
AI-assisted features
| Data | Purpose | Legal basis |
|---|---|---|
| Prompts you trigger from AI-assisted features (e.g. the post-race "coach take" button): the action you ask for plus the small set of race numbers it summarises (race name, finishing position, points). We never send your messages, DMs, or contact list. | Generate the requested summary or coaching text via a large language model hosted on PRL infrastructure. Each call is logged with token counts so we can enforce a per-rider burst rate limit. | Art. 6(1)(b) — performance of contract (you triggered the feature). Your prompt and the generated reply never leave PRL infrastructure. |
AI-assisted features (e.g. the post-race “coach take” button) use a large language model that runs on PRL-owned infrastructure. There is no third-party AI sub-processor— your prompts and the generated replies never leave our servers. Only the small numeric context you triggered (race name, finish position, points) is fed into the model; we never forward your messages, contact list, or browsing history. Each rider is capped by a short-burst rate limit enforced server-side.
Notifications
Push notification tokens from your device, your notification preferences, and a 90-day log of notifications sent to you. Used to deliver in-app and push notifications. Legal basis: Art. 6(1)(b) for in-app, Art. 6(1)(a) consent for push (you can mute any category in Settings).
Pre-launch waitlist
If you sign up on the home page to be notified when the public beta opens, we store your email, a record of when you ticked the consent box, and a hash of your IP (kept 30 days for abuse triage). We send you a one-time confirmation email; you’re only added to the list once you click the confirm link. Every email contains a one-click unsubscribe link. Legal basis: Art. 6(1)(a) consent.
Operational
Audit logs of selected actions, rate-limit counters, data-export job records. Used to keep the league secure and to fulfil your Art. 15 access rights. Legal basis: Art. 6(1)(f) legitimate interest in security and abuse prevention.
3. How long we keep it
Different categories of data have different retention rules. We keep data no longer than necessary for the purpose it was collected for, in line with Art. 5(1)(e) GDPR.
| Category | Retention |
|---|---|
| Identity + account | Lifetime of your account, then 30-day grace, then erasure |
| Consent records | 3 years after your account is erased |
| Race history | Indefinite while you have an account; pseudonymised on erasure |
| Financial records | 5 years from the end of the financial year (Danish bookkeeping law) |
| Direct messages | 24 months from last activity, then archive 12 months, then permanent delete |
| Shoutbox | Feature retired on 2026-09-30: its remaining data is archived, access-locked, and deleted before public launch |
| Team chat, forum and polls | Retired on 2026-09-30 (moved to Discord): the remaining data is archived, access-locked, and deleted before public launch |
| Notifications | League mail: 180 days after its last update (starred mail until you unstar it); message alerts: 90 days. Alerts of the chat features retired on 2026-09-30 are archived, access-locked, and deleted before public launch |
| Moderation log | Feature retired on 2026-09-30: its remaining data is archived, access-locked, and deleted before public launch |
| Audit log | Indefinite (security forensics) |
| Voice metadata | In-app voice retired on 2026-09-30 (voice now runs on Discord): the remaining metadata is archived, access-locked, and deleted before public launch |
| Zwift data (category, rating, FTP / weight if shared) | While your Zwift ID is checked and you have not switched it off; each earlier reading 12 months. Switching it off, or losing the check, deletes the current numbers at once |
| ZRL places of riders not on PRL | Their Zwift ID and ZwiftPower name 30 days at most (or until they join and are matched), then only the anonymous place |
| "Is this you?" lookups | 24 hours |
| Live race numbers (heart rate, power, W′ balance) | Never stored: each frame is passed to your team's race staff and forgotten |
| Live race summaries | 12 months, then deleted; kept only for races you rode while sharing |
| Intervals.icu training data | While you stay connected; disconnecting deletes your key and everything copied |
| Your name on a public team page | While both you and your team keep it switched on; removed at once when either switches it off |
| Discord radio presence | Who is in a radio channel: 10 minutes at most, and at once when you opt out or unlink; "talking" is never written down |
| Pre-launch waitlist | Until you unsubscribe or beta opens + 30 days, then erased |
These rules are enforced by automated nightly jobs — there is no manual step where someone forgets to delete things. The one exception is the in-app features we retired on 30 September 2026 when chat and voice moved to Discord (shoutbox, team chat, forum, polls, moderation log, voice): their remaining data is kept in an access-locked archive that no app user can reach, is still covered by your data export and erasure rights, and is deleted in full before public launch.
4. Who else sees it
Your data lives in our systems and the systems of the third-party processors below. We sign a Data Processing Agreement (DPA) with every processor before they handle any production data, and we never sell or rent personal data to anyone.
| Processor | What they touch | Region |
|---|---|---|
| Supabase | Database, authentication, file storage, edge functions | EU Ireland (eu-west-1) |
| Resend | Transactional email (verification, password reset, etc.) | EU (Ireland) |
| Cloudflare | DNS, captcha (Turnstile), DDoS protection, edge CDN for this site | Global edge with EU presence |
| PostHog | Product analytics (pseudonymous unless signed in) | EU (Cloud EU region) |
| LiveKit (self-hosted) | Real-time voice. On our own equipment — not a third party. | Denmark |
| Apple APNs / Google FCM | Push notification delivery (when you opt in) | Global (their infrastructure) |
| Stripe (post-banking) | Payments. Added once payments go live. | EU residency configured |
We may also disclose your data when required by Danish or EU law (for example: a court order, a tax investigation under the bookkeeping retention rules, or a lawful request from Datatilsynet).
5. International transfers
All primary processing is within the European Economic Area. Apple APNs and Google FCM may receive your push token outside the EEA; both providers have their own EU adequacy or Standard Contractual Clauses in place. When Stripe is enabled, EU customer-data localisation will be configured before any production traffic flows.
6. Your rights
Under GDPR Articles 15–22, you have the right to:
- Access — download all the personal data we hold about you. Use Settings → Privacy & Data → Request data export in the app. Your ZIP arrives within minutes.
- Rectify— fix anything that’s wrong. Edit your profile in Settings, or email us.
- Erase— delete your account and all associated personal data. Available from the same Privacy & Data screen. Erasure has a 30-day grace period so accidental requests can be undone, after which everything PII-shaped is removed and your historical contribution to league history is pseudonymised.
- Restrict processing — pause use of your data while a dispute is open.
- Object— to processing based on legitimate interest (Art. 6(1)(f)). We’ll stop unless we have a compelling reason to continue.
- Data portability — the same ZIP export contains your data in machine-readable JSON.
- Withdraw consent— for anything we process on consent (Zwift data and who sees it, power numbers, live race data, Intervals.icu, the public team page, marketing emails, push notifications). Withdrawal is as easy as opt-in: a toggle in Settings. Settings → Privacy & Data lists what you have agreed to and when.
All requests are free of charge and we’ll respond within 30 days as required by Art. 12(3). If a request is manifestly unfounded or excessive (e.g. a flood of duplicate requests), we may charge a reasonable fee or refuse — and we’ll tell you which and why.
7. Security
- All connections to PRL use TLS 1.2 or higher.
- Passwords are hashed with bcrypt by Supabase Auth; we never see your plaintext password.
- Database access is row-level-security-gated — even an attacker with the public API key cannot read other users’ data.
- Sensitive operations (e.g. data export, account deletion) re-verify the session before running.
- We monitor for known-bad passwords against the HaveIBeenPwned breach list at signup time.
- We use Cloudflare Turnstile on authentication endpoints to defend against scripted abuse.
- We will notify Datatilsynet within 72 hours of becoming aware of any breach that meets the Art. 33 threshold, and notify you directly if the breach is high-risk to your rights and freedoms (Art. 34).
8. Cookies and similar tech
This website uses only essential cookies (none currently — this site is fully static). The PRL app stores a session token locally so you stay signed in across launches.
Product analytics (PostHog) uses a pseudonymous identifier in local storage. You can opt out via your device’s “Limit Ad Tracking” / “Reset Advertising ID” setting; we honour those signals.
9. Changes to this policy
If we change this policy in a material way (new categories of data, new processors, new lawful bases), we’ll notify you in-app and by email and ask you to acknowledge the new version before the change takes effect. The version string at the top of this page bumps for every change.
10. Complaints
If you think we’re handling your data unlawfully, please contact us first — most complaints can be resolved that way. You also have the right to lodge a complaint directly with the Danish supervisory authority:
Datatilsynet
Carl Jacobsens Vej 35, 2500 Valby, Denmark
Phone: +45 33 19 32 00
Email: [email protected]
Web: www.datatilsynet.dk
Reviewed and last published 2026-05-11. Last operational changes reflected: GDPR data-export feature live; auth flows protected by captcha; retention purges automated; PII split into a separate table with admin-only access.