Written for directors and for the district IT or privacy reviewer they hand this to. It answers one question in detail — how do you keep one school's students out of another school's data? — and then says plainly what we do not claim.
Every school is a separate tenant. A user's school is decided by our server when their account is created, written into their sign-in token, and checked on every single database read and write. The app never asks the browser which school someone belongs to, so there is nothing for a browser to lie about.
These stack. Defeating one does not get you anything, because the next one still applies.
Account creation runs through a server-side check before the account exists. We look up the email domain — and for Google sign-in, the verified hosted-domain claim Google itself attests to — against our registry of schools. If no school has registered that domain, the account is never created.
This runs on our servers as part of account creation, not in the browser. There is no client-side code path that can skip it.
When that check passes, the server stamps the school and the role (student or director) into the user's authentication token as signed claims. The browser receives the token but cannot mint or alter one — only our server can issue it.
This is the part that matters most: the school a request belongs to is never taken from data the client sent. It comes from the signed token every time.
Access rules are enforced by the database itself, not by application code that could be bypassed by calling the API directly. Every collection holding school data — profiles, practice entries, goals, lesson plans, rosters — requires the record's school to equal the school in the caller's token.
Some collections are denied to every client outright, including our own app: the schools registry, per-school private configuration, AI usage counters, and inbound access requests. Those are reachable only by our server-side administrative code.
A subtlety worth stating, because it's where multi-tenant systems usually leak: it is not enough to filter results after fetching them. Our rules require a query to declare its school up front. A search that doesn't restrict itself to one school is rejected by the database before any records are read — it does not return a filtered list, it returns an error.
Isolation between schools is absolute. Inside a school it is deliberately not, because the product wouldn't work otherwise. Specifically:
| Who | Can see |
|---|---|
| A student | Their own practice entries, notes, and goals — plus other students' names and total practice minutes on their school's leaderboards. Never another student's practice notes. |
| A director | Everything for their own program: entries, notes, goals, and roster. |
| A parent | Nothing in the app — parents have no account. They receive only the monthly summary their director chooses to send about their own child. |
| Another school | Nothing. No names, no totals, no existence. |
If a director would rather students not see each other's totals at all, tell us — that's a reasonable request and worth us knowing.
On Google Cloud infrastructure in the United States. Authentication, database, and our server-side code all run there. Data is encrypted in transit and at rest by the platform.
Our full subprocessor list — Google Cloud, the Google Gemini API, and Stripe — is in the privacy policy, including exactly which AI feature sends what.
PracticeXP runs entirely on Google Cloud, so the hosts it needs are ones a district running Google Workspace already permits — the same infrastructure behind Gmail, Drive and Classroom. In most cases nothing has to change.
If your web filter works from an explicit allowlist, these are the destinations. There are no others; the app makes no request to any host outside this list.
firestore.googleapis.com — the database. Everything a student logs and
every dashboard a director opens goes here.identitytoolkit.googleapis.com — sign-in.securetoken.googleapis.com and securetoken.google.com —
session token refresh. Blocking these signs users out roughly every hour.us-central1-practicexp.cloudfunctions.net and *.a.run.app —
our server-side code: access requests, school settings, billing, and the optional AI
features.accounts.google.com and apis.google.com — the Google
account picker used to sign in.content-firebaseappcheck.googleapis.com — client attestation.practicexp.firebasestorage.app — file storage.app.practicexp.com — only for the browser version. The
iOS and Android apps never request it; see below.The iOS and Android apps carry their own interface on the device rather than downloading it. A domain block cannot leave a student staring at a blank screen: the app opens, draws, and shows the practice log it already has. Only new data needs the network. Because the apps keep an offline copy of that student's own log, a practice entry made on a filtered network is stored on the phone and syncs when it next has a connection.
This is worth knowing if you are evaluating us mid-year. A newly registered domain is commonly filtered by default for its first month regardless of content — ours was, at our first school — and the apps were unaffected throughout.
One caveat, so it isn't a surprise: the practice-resources section links out to about a hundred third-party music education sites we don't control. Those open in the phone's browser and are subject to your filter like any other site. A blocked one affects only that link.
Test it over plain http://, not https://. Several appliances
kill a filtered HTTPS connection with a bare TCP reset, which a browser can only report as
a generic connection error — the page that names the actual category is served over HTTP.
Send us what it says and we will pursue a re-rating; a static allowlist entry on your side
is the faster fix.
Every AI feature in PracticeXP is optional, and the switch belongs to your director — not to us. In the Teacher Dashboard there is an AI Features panel with one master switch and one switch per feature. Each row states what that feature sends, and the two that transmit a student's own writing are labelled as such.
The setting is stored on your school's record and re-checked by our server every single time an AI feature runs. Switching something off is not a hidden button — the request is refused at the server, so nothing is sent to Google even for someone calling our API directly. It takes effect immediately, for everyone at your school.
Two honest limits. Turning a feature off stops new data being sent; it does not retroactively delete AI responses already saved against past practice entries — ask us and we will purge those for your school. And a director can change the setting, so if your district requires AI to stay off permanently, put that in the agreement and we will lock it at our end.
Practice logging, goals, streaks, badges, reports, CSV export, and the monthly parent summaries all work identically with AI switched off entirely. Nothing about the product depends on it.
A vendor who answers a security review with only good news isn't worth trusting. So:
If you believe you've found a way for one school to reach another's data, or any other security issue, email support@practicexp.com with "Security" in the subject. We will confirm receipt, and we won't pursue anyone who reports something in good faith and doesn't access data beyond what's needed to demonstrate it.
support@practicexp.com
PracticeXP · 421 N Elizabeth Ave, Apt 1, Jefferson, WI 53549 · United States