The short version: Prep99 works without knowing who you are.
There is no account, no advertising and no profile of you anywhere. Your study data stays on your device. What leaves it is small and listed here item by item: pseudonymous usage statistics you can turn off, the question reports you choose to send, a proof of purchase when you unlock a pack, and the technical metadata every server receives. Nothing is sold, and nothing is used for advertising.
Prep99 has no account, so there is no account to delete. Almost everything the app knows is on your device, where you delete it yourself. Here is each kind of Prep99 data: what you do, what is deleted, and what is kept, by whom and for how long.
Prep99 is operated by Julien Courbebaisse, a self-employed operator (autónomo) registered in Spain (NIE: Y8924027H); a postal address is provided on request. That person is the “controller” of the data this policy describes. Because the controller is established in the European Union, the EU General Data Protection Regulation (GDPR) and Spain’s data-protection law apply to everything described here, wherever you live. Where the law of your own state or country gives you further rights, such as the Texas Data Privacy and Security Act, we honour those too (see your rights).
For anything about this policy or your data: support@prep99.app, or the support page.
When you write to us. If you email support@prep99.app, we use your address, your message and anything you choose to include in it only to answer you and to follow the matter up, under our legitimate interest in giving support. We keep the exchange as long as the matter needs, then delete it. The mailbox is hosted by Apple, through iCloud Mail on our own domain. Apple may store messages in its own data centers or in those of providers it uses, which may be outside the European Union, including in the United States; Apple states that its transfers of personal data out of the European Union are governed by the European Commission’s standard contractual clauses.
The app never asks you to create an account: no email address, no password, no name, no phone number. There is no user database on our side. Nothing our server holds can be matched to a person, and a purchase is attested by the store you bought from, not by us. The one exception is an email you choose to send us (see when you write to us).
Everything the app knows about your studying lives on your phone or tablet:
None of this is sent to us as such. A question report waiting on the device goes out once a connection is available (see question reports), and the usage statistics described below carry the random install identifier and only the few values their list names (your exam and the app language, for example, and the readiness band, never the score). Me › Reset progress deletes the answer log, every mock, your place in every lesson and every flashcard’s schedule on that device, the latest readiness bands and today’s study plan as the day goes; your settings stay. Deleting the app deletes everything above, including the install identifier (every step, backups included: delete your data).
Backups. On iPhone and iPad, your device backup (iCloud Backup or a computer backup, under your own Apple Account settings) may include your answer log, your profile and your lesson and flashcard progress; the install file and the downloaded paid content are marked to stay out of it. On Android, if backup is turned on in your phone’s settings, Android’s automatic backup copies your profile, lesson and flashcard progress and answer log into your Google Account’s backup and restores them on a new phone with the same account; the install file (identifier, statistics choice, purchase marker), pending reports and paid content are never part of it. Both backups are your own space with Apple or Google, which we cannot access; neither passes through our servers. This version of the app does not sync between devices. If a later version adds syncing through your own iCloud, this policy is updated first.
Reminders. If you set an exam date and allow notifications, the reminders around that date are scheduled and shown by your device itself. No notification service, no device token, no server: nothing is sent or received. Turn them off in the app or in your device’s settings.
Nothing else. The app contains no advertising software, reads no advertising identifier, does not track you across other apps or websites, asks for no location and no contacts, and never sends what you type.
To learn which parts of the app help and where people get stuck, the app sends usage events to PostHog, processed in the European Union (Frankfurt, Germany). The app talks to PostHog directly over an encrypted connection with no third-party analytics kit inside it, so no event reaches PostHog that is not named in the list below.
Every event carries a random install identifier, created on the first launch and never derived from your device, your Apple or Google account or anything about you. It lets us count installs and follow a single install’s path through the app. It is not linked to your identity, because we hold none; still, because it is persistent for the life of the install, the GDPR treats it as personal data, which is why we call these statistics pseudonymous rather than anonymous. No person profile is created; there is no “identify”, no session recording, no automatic capture of taps or screens. PostHog is configured to discard the IP address of each request on arrival and to keep no location beyond the country.
Every event also carries its timestamp in universal time (UTC), a random per-event identifier (uuid) so a repeated send is counted once, the name of the sending app ($lib) and PostHog’s $process_person_profile: false flag, which forbids a person profile; nothing else.
The properties attached to events are only counts, yes/no values and closed choices, never the identifier of a question, never a date of yours (only ranges), never free text, never your age, never an email. The one exception is the error event (app_error, under “Errors” below), which names the kind of error and where in the app’s code it happened. Every event also carries these five, except the error event, which carries only channel and app_version:
channel — how the app was installed — one of six closed values: App Store, TestFlight (our own test builds), Google Play, sideload (an install made outside a store), a development build, or unknown — so our own test runs can be kept out of the statistics.app_language — the app language in use (English or Spanish).exam — the exam pack you chose (absent before you choose one).entitled — whether a pack is unlocked on this install (yes or no).app_version — the app version.The complete list of events, with every property each one carries:
app_installed — the first launch of an install; nothing beyond the properties every event carries.app_opened — every cold start, with the age of the install as a range (listed below), never the install date. Properties: days_since_install.onboarding_started — the setup began.language_chosen — the language you chose and whether it was the device default or changed by hand. Properties: language, source.state_chosen — the state you chose (Texas). Properties: state.state_requested — a “coming soon” state you tapped (California or Florida), the count that decides the next state. Properties: state.exam_chosen — the exam pack you chose. Properties: exam.exam_date_set — you set an exam date: only how far away it is, as a range (listed below), and from which screen (setup, the end of a practice session, Settings, an exam outcome or Home) — never the date. Properties: days_until, source.exam_date_ask_dismissed — you dismissed the exam-date question, and from which screen. Properties: source.attempt_history_chosen — first attempt or retake. Properties: history.diagnostic_started — the diagnostic began.diagnostic_completed — the diagnostic ended, with the number of correct answers (listed below). Properties: correct.diagnostic_skipped — you skipped the diagnostic.onboarding_completed — the setup finished.heard_from — your answer to “How did you hear about Prep99?”, if you answer: one of twelve closed choices (the app store, a search engine, TikTok, Instagram, Facebook, YouTube, an AI assistant, a friend or family member, an employer or sponsor, a course or school, a forum or group, other). It is the app’s only attribution tool; there is no advertising install tracking. Properties: source.heard_from_dismissed — you skipped that question. Skipping sends no answer at all.paywall_viewed — the unlock screen was shown, from which door in the app and for which pack. Properties: placement, pack.purchase_started — you tapped to buy a pack. Properties: pack.purchase_completed — the purchase completed. Never the price paid, never a transaction identifier. Properties: pack.purchase_cancelled — you closed the store’s payment sheet without buying. Properties: pack.purchase_pending — the purchase is waiting for an approval (for example a family approval). Properties: pack.purchase_failed — the purchase failed, with the cause from a closed list of eight (payments restricted on the device, store unavailable, product not found, network, server busy, server error, verification refused, a purchase already pending). Never the error message itself. Properties: pack, reason.purchase_recovered — an interrupted purchase completed at a later launch. Properties: pack.restore_tapped — you tapped “Restore purchases”, with the outcome (restored, nothing to restore, or failed). Properties: outcome, pack.lesson_started — a lesson opened, with its blueprint section. Properties: section.lesson_completed — a lesson read to the end, with its section. Properties: section.practice_started — a practice session began: the mode, the section (or all), and the length you chose. Properties: mode, section, length.practice_completed — a practice session ended: how many questions you answered and the share of first-seen questions you got right, both as ranges (listed below), never the exact figures. Properties: answered_bucket, first_seen_correct_bucket.free_pool_exhausted — the free questions ran out.drill_started — a drill began, with its kind (weak topic, trap family, contrast). Properties: kind.mistake_review_completed — a mistake review ended, with how many items as a range (listed below). Properties: count_bucket.flashcards_session — a flashcard session.item_language_toggled — you switched one question’s language, and to which (English or Spanish). Properties: to.language_switched — you changed the app language in Settings, from which to which. Properties: from, to.question_reported — you reported a question, with the closed reason only; the question itself travels in the report to our server (see the reports section), never in the statistics. Properties: reason.mock_started — a mock began: which exam format and under which conditions (exam conditions, untimed, or the extended-time setting). Properties: format, conditions.mock_completed — a mock ended, with the verdict against Prep99’s own bar (above, close to, or below). Never the score. Properties: format, conditions, verdict.mock_abandoned — a mock left unfinished. Properties: format.readiness_viewed — you opened the readiness detail.readiness_band_changed — your band moved, for the overall reading or the Texas-law one, from which band to which. Never the score. Properties: scope, from, to.how_readiness_works_viewed — you opened “How readiness works”.short_horizon_entered — your exam date came within the short-horizon plan.repeater_path_entered — the retake path began, from onboarding, from an exam outcome or from Settings. Properties: source.reminder_permission — the device asked you to allow notifications, and your answer (yes or no). Properties: granted.reminders_scheduled — reminders were scheduled on your device, with how many. Properties: count.reminder_opened — you opened a reminder, and which of the four slots (seven days, three days or one day before, or the day after). Never the time. Properties: kind.exam_outcome_reported — the exam result you report yourself, if you choose to (passed, not passed, or not taken), with the readiness bands the app showed at exam time. This is how the readiness method is calibrated. Never your exam score, never the date. Properties: outcome, band_at_exam, texas_band_at_exam.rating_prompt_eligible — one of four good moments to ask for a rating was reached (a good practice session, a mock above the bar, Ready reached, a pass reported), and which. Properties: trigger.rating_prompt_shown — the app asked the system to show its rating prompt. Neither event says whether you rated the app: the system does not tell us.share_tapped — you tapped share on a mock result. Only the gesture: never where you shared it or what. Properties: surface.sources_viewed — you opened the Sources screen.official_link_opened — you copied one of the two official links (TDI or Pearson VUE), and which. Properties: target.app_error — the app hit an error it did not catch. The event is built on the device from the app’s own code, never from the error’s message: the kind of error by its name in the app’s code (in a store build this can be a scrambled name), a short fingerprint of where in the app’s code it happened, the app’s build number, the system (iOS, Android or other) and how many errors this launch has had, as a range (listed below). Never the error’s message, a file path, a web address, a question identifier or anything you typed. Properties: error_type, stack_digest, app_build, platform, errors_this_launch.The ranges mentioned above, verbatim: days since install 0 · 1 · 2-7 · 8-30 · 31-90 · 90+; days until the exam 0-7 · 8-30 · 31-90 · 90+; diagnostic answers correct 0 to 10; questions answered in a practice session 1-9 · 10-19 · 20-39 · 40+; share of first-seen questions correct 0-49 · 50-69 · 70-79 · 80-89 · 90-100; items in a mistake review 1-5 · 6-10 · 11-20 · 21+; errors this launch 1 · 2-3 · 4-10 · 11+.
What the statistics keep on your device, and why. For the statistics, the app keeps on your device the random install identifier, the latest readiness bands for each exam and the exam date whose final week was already counted, all in the install file, plus the events not yet sent, in the app’s cache. The bands and that date are kept even while the statistics are off, so turning them back on never reports an old change. Only the app itself writes and reads all this, and the events go only to our own PostHog project, which processes them for us and for no purpose of its own. They serve a statistical purpose only: improving the app and calibrating the readiness method. We read them in aggregate, as counts and paths across installs; a single install’s path is looked at only to see where people get stuck, never to act on that install, and never for advertising, a profile of you or a decision about you. The statistics are on from the first launch, and the switch described below turns them off.
PostHog keeps these events for 24 months, then deletes them. We rely on our legitimate interest in understanding and improving the app (see legal bases), balanced by everything above and by your switch:
You can turn the statistics off at any time: in the app, tap the person button at the top of Home to open Me and turn off “Usage statistics”. Collection stops at once, any events still waiting to be sent are deleted from the device, and nothing is sent on later launches until you turn it back on. The install identifier stays in the install file, no longer sent, until you delete the app. Your choice stays on the device; it is not reported to us. A request already on its way when you flip the switch cannot be recalled.
When the app hits an error it did not catch, it can send the error event described above (app_error), through the same channel and under the same switch as the usage statistics. What it sends is built on the device from the app’s own code, never from the error itself: when neither the kind of error nor its place in the code can be read that way, nothing is sent. The same error is sent once per launch, and a launch sends at most 10 error events.
If you use “Report a question” in the app, we receive exactly four things: the identifier of the question, the language you were reading it in (the Spanish text may be the fault), the version of the content on your device, and the reason you picked from a closed list (the answer looks wrong, the wording is unclear, the law may have changed, a translation issue, a display problem). There is no free-text field, and no device or personal identifier travels with it. The report waits on your device until a connection is available, then is sent to our server. Reports are kept for up to 24 months, so the correction can be made and dated, then deleted. We rely on our legitimate interest in the accuracy of the content (see legal bases).
Packs are bought through your device’s store, the App Store on iPhone and iPad or Google Play on Android. Apple or Google is the seller (the merchant of record) and handles the payment under its own privacy policy, as an independent controller: we never receive your name, address or payment details, and restoring a purchase also goes through the store (Me › Restore purchases). A purchase does not carry over from one store to the other.
To unlock a pack, the app presents our server with a proof of purchase, which contains no name, address or payment data:
Our server answers with a short-lived access token (about 10 minutes) that the app uses to download the pack’s content in your language; the token is kept in the app’s memory only, never written to disk. Neither the proof nor the token is logged or stored. There is no record on our side of who bought what.
To prevent abuse (a proof of purchase copied and shared would let anyone download paid content), the server keeps a minimal trace of each verification: a keyed cryptographic fingerprint (HMAC) of the transaction number, the same kind of fingerprint of the network address the request came from (the whole address for IPv4, the /64 network for IPv6; never the address itself), whether it was a real purchase or a store test environment, a counter, and first and last seen dates. The fingerprints cannot be turned back into the number or the address, let alone into a person; the key lives outside the database. They are deleted 90 days after the last verification. The legal basis is the contract with you (delivering what you bought) and our legitimate interest in preventing fraud (see legal bases).
Prep99 is built for adults preparing for a professional licensing exam and is not directed at children under 13. We hold nothing that identifies anyone, so there is nothing of a child’s to hold; if you tell us a child has used the app, we help you remove what is on the device.
Under the Texas App Store Accountability Act, the store is the one that knows your age category. On iPhone and iPad, when you tap to buy a pack (even if you then cancel), the app first checks, through Apple’s Declared Age Range feature, whether a law of this kind applies to your account. Only if it does, as for an account in Texas, does the app ask for your age range; Apple may show its own screen for this, and the app reduces the answer to adult, minor or unknown. Otherwise, and on versions of iOS before 26.2, it asks for nothing. Whatever the answer, the purchase continues under the store’s own rules; where this law applies, the store itself asks a parent or guardian to approve a minor’s purchase (Ask to Buy on the App Store). The answer changes nothing about the purchase; it is read for that step and discarded at once: never saved, logged, sent to our server or included in the statistics. If it says the account belongs to a minor, the app also turns the usage statistics off on that install, as the switch in Me would. The app never asks about your age for the free content.
On Android, when you tap to buy a pack (even if you then cancel), the app first reads your Google Play account’s country on your device, without keeping it. If it is the United States, or if Google Play cannot give it at that moment, the app asks Google Play, through its Age Signals feature, whether your age range is shared with the app; for an account in any other country, it asks nothing. Where a law of this kind applies to your account, as for an account in Texas, Google Play answers without showing anything; for other accounts, depending on your Google Play settings, it may first show its own screen asking whether to share your age range. Only if Google Play shares it does the app read your age range, reduced to adult, minor or unknown as on iPhone; otherwise it reads nothing. The rest is as on iPhone: the purchase continues under Google Play’s own rules, which ask a parent or guardian to approve a supervised minor’s purchase where this law applies, and the answer is handled the same way.
Like any online service, our server (used only for purchase verification, content downloads and question reports) sees the IP address of each request. It appears in short-lived technical logs and in rate-limiting counters that stop abuse; those traces expire automatically within hours to days and are never joined to anything about your studying. Proofs of purchase, tokens and request bodies are kept out of the logs. The server runs on Fly.io and its database on Neon, both in Frankfurt, Germany; the database holds only question reports, rate-limiting counters and the verification fingerprints described under purchases.
prep99.app is a static website: no cookies, no analytics scripts, no fonts loaded from a third party. It is served by Cloudflare, which handles the network requests needed to deliver the pages, as any web host does. Links from this site to the stores may carry a campaign tag that Apple or Google counts in aggregate; nothing is stored on or read from your device.
Apple hosts the support mailbox under its iCloud terms; each other processor acts on our instructions under a data-processing agreement. Our server and its database stay in the European Union, so none of their data is transferred to a third country; the one call our server makes to Google in the United States is covered by Google’s standard contractual clauses. An email you send us may be stored by Apple outside the European Union (see when you write to us). The stores process your purchase under their own policies. No other third party receives data. Nothing is sold, rented or shared for advertising.
| Data | Why we may process it | Kept |
|---|---|---|
| Study data, profile, settings | Not processed by us: it never leaves your device (and your own backups) | On your device, under your control |
| Usage statistics | Legitimate interest: understanding and improving the app, with the switch as your objection | 24 months |
| Question reports | Legitimate interest: accuracy of the content | 24 months |
| Proof of purchase and access token | Contract: delivering the pack you bought | Not stored; the token expires after about 10 minutes |
| Verification fingerprints | Legitimate interest: fraud and abuse prevention | 90 days after the last verification |
| Request logs and rate-limiting counters | Legitimate interest: security and operation of the service | Hours to days |
| Age category | Legitimate interest: complying with the age-assurance law that applies to your store account, such as Texas’s (a US state law, not a “legal obligation” in the GDPR’s sense), with the least data: when you tap to buy, read on your device and discarded at once; on Android, the Google Play country read to decide whether to ask is covered here too | Not stored: discarded once the purchase step has it |
| Messages you send us | Legitimate interest: answering you and following the matter up (for a request about your rights, our legal obligation to answer it) | As long as the matter needs, then deleted |
Where we rely on a legitimate interest, we have assessed it in writing against your interests and rights; the assessment is available on request. We process no sensitive data, make no automated decisions about you, do no profiling and do not sell personal data.
Under the GDPR you may ask for access to the personal data we hold about you, its correction, deletion, restriction or portability, and you may object to processing based on a legitimate interest. Texas residents have comparable rights under the Texas Data Privacy and Security Act, including to confirm whether we process their data, to access, correct and delete it, to obtain a copy, and to opt out of targeted advertising, the sale of data and profiling, none of which Prep99 does. Prep99 is designed so that most of these rights are in your own hands:
For anything else, write to support@prep99.app. We answer within 30 days. If we cannot act on a request because nothing we hold can be identified as yours, we say so and explain why; we never ask you to create an account or to give us more data in order to exercise a right. If we refuse a request, you may appeal by replying to our answer; we decide within 60 days and, if we still refuse, tell you how to reach the Texas Attorney General. If you believe we handled your data wrongly, you may complain to the Spanish data-protection authority (Agencia Española de Protección de Datos) or to the authority of your own country; Texas residents may also contact the Texas Attorney General.
If this policy changes, the new version is published here with its update date, and a change in what the app sends is described here before a version that sends it is released. Material changes are also announced in the app’s release notes.
This policy exists in English and Spanish. The two say the same things; should they ever differ, the English text prevails. The terms of use complete this policy.
Prep99 is an independent study app. It is not affiliated with, endorsed by, or approved by the Texas Department of Insurance (TDI), Pearson VUE, or any government agency. Official exam information: https://www.tdi.texas.gov/agent/general-life-apply.html and https://www.pearsonvue.com/us/en/tx/insurance.html
The exam facts and every official source we rely on are on the sources page.