KindCircl Data Retention Policy
Owner: Ali Salomi · Effective: October 20, 2026 · Review: at least once a year
Systems: Firebase project circl-pilot, Firestore database in us-west2 (Los Angeles); Cloud Storage bucket circl-pilot.firebasestorage.app in US-WEST1 (Oregon), used only for signed consent forms.
Principles
- Keep children's personal information only as long as reasonably necessary for the specific purpose it was collected for, and not indefinitely.
- Delete it using reasonable measures to prevent unauthorized access during deletion (Firestore document deletion, after which Google purges it under its deletion process).
- When a parent deletes a child's account, delete the child's personal information promptly, except for the safety records listed below.
Retention schedule
| Data (location) | Purpose | Business need | Retention | Mechanism (as built) |
|---|---|---|---|---|
Chat messages and stickers (circls/*/messages, dms/*/messages) |
Deliver chat; parent review of DMs | Recent conversation history | 90 days from sending | Firestore TTL on expireAt |
| Messages stopped by the filter | None | None | Not stored | sendMessage returns before writing |
Safety reports (reports), incl. up to 300-character excerpt and kid-typed details |
Safety review; legal reporting | Detect repeat abuse; NCMEC duties | About 1 year from creation | Firestore TTL on expireAt (1 year); daily cleanup also deletes reviewed reports older than 1 year. Reports a kid filed are deleted with that kid's account; reports about a kid are kept until they expire. |
Parent activity feed (parents/*/activity) |
Alerts and approvals for parents | Recent safety history | 1 year | Firestore TTL on expireAt |
Admin audit log (auditLog) |
Accountability for safety-team actions | Review of moderation decisions | 2 years | Firestore TTL on expireAt; daily cleanup backstop |
Consent sessions (consentSessions): parent uid, Stripe Checkout session id, payment intent id, refund id and status, amount (50 cents). Never card details. |
Match Stripe results to parents; confirm the refund | Short-term | 30 days | Firestore TTL on expireAt |
Card check summary on the parent record (parents/{uid}.consentCard): session id, amount, refund status |
Proof of consent (COPPA 312.5(b)(2)(ii)) | Demonstrate that verifiable consent was obtained | While the parent account exists | deleteParentAccount |
Processed Stripe webhook event ids (stripeEvents) |
Process each Stripe event once | Short-term | 90 days | Firestore TTL on expireAt |
Signed consent form files (Cloud Storage consentForms/{parentUid}/...): photo or PDF showing parent name, signature, date, account email, child usernames |
Verify consent (COPPA 312.5(b)(2)(i)); proof of consent | Demonstrate that verifiable consent was obtained | Approved: while the parent account exists. Sent back: deleted immediately. | decideConsent deletes rejected files; deleteParentAccount and the 30-day unverified cleanup delete consentForms/{uid}/; submitConsentForm deletes an earlier waiting file when a new one is sent |
Consent review record (consentReviews/{parentUid}): email, typed signer name, child usernames, file path, submission time, decision, reviewer, note |
Admin review workflow | Track decisions | 1 year from submission, or sooner with the parent account | Firestore TTL on expireAt; deleteParentAccount |
Pairing codes (pairingCodes) |
Sign a kid's device in | 10 minutes | Deleted on use; expired codes removed by daily cleanup | redeemPairingCode; dailyCleanup (3:15 a.m. Pacific) |
| Pairing rate-limit records: HMAC-SHA256 of IP with a secret salt, plus counter | Stop code guessing | Short-term abuse prevention | Up to 1 day (removed by the next daily cleanup) | dailyCleanup deletes records older than 1 day. The raw IP is never stored. |
Per-kid message rate-limit records (rateLimits) |
Abuse prevention | Minutes | Up to 1 day; deleted with the kid's account | dailyCleanup; deleteKid |
Kid profile: username, age group, avatar and avatar look, friend code, CirBux, ledger, owned items, card collection with Buddy levels and XP (kids/*/cards), the chosen Buddy, free packs and the Founding Member number, settings, kindness counts (kids/*, profiles/*, usernames/*, friendCodes/*) |
Run the account | While the account is in use | Until the parent deletes the kid or their own account | deleteKid, deleteParentAccount |
Squads (squads/*, squadInvites/*): name, emoji, color, members, parents, read markers |
Run Squads | While the Squad exists | When the owner closes it or their account is deleted (the Squad and its chat are deleted). A member who leaves or is deleted is removed at once. | leaveSquad, deleteKid |
Live game rooms (rooms/*, rooms/*/hands/*, roomSecrets/*, roomCodes/*, roomInvites/*): players' usernames, looks and Buddies, answers, picks, card hands, letters and wagers, scores, reactions |
Run live games | 30 minutes after the last activity | Firestore TTL and the daily cleanup delete idle rooms; empty rooms are deleted at once; a deleted kid is removed from rooms at once | TTL policy in firestore.indexes.json (also on the hands collection group), cleanup, leaveRoom, deleteKid |
Game Hub best scores (kids/{uid}.best): the kid's highest solo score for each game |
Show "Your best" in the Game Hub | While the account exists | Deleted with the kid's account | deleteKid |
Teams (teams/*): team name, type, organizer display name, organizer parent id, age groups, member kid ids and usernames, member parent ids and the first letter of each parent's email, captains, milestones paid |
Run Team Circls | While the team exists | When the organizer ends it or deletes their account; a member is removed at once when removed, taken out by their parent, deleted, or paused by withdrawn consent | endTeam, removeFromTeam, deleteKid, revokeConsent, deleteParentAccount |
Team chat and announcements (teams/*/messages) |
Deliver team chat | 90 days | Firestore TTL; deleted with the team; a deleted kid's messages are removed at once | TTL policy, endTeam, deleteKid |
Team codes (teamCodes/*, teams/*/org/info) |
Let parents join | While the code is current | Deleted when the organizer makes a new code or ends the team | regenerateTeamCode, endTeam |
Team weekly challenges and trophies (teams/*/challenges/*, teams/*/trophies/*) |
Weekly team challenge | While the team exists | Deleted with the team; a removed kid's id is removed at once | endTeam, removeFromTeam |
Squad daily activity (squads/*/stats/*): games played per day, who played, and which members chatted that day (ids only, no message text) |
Squad streaks and Squad activity | While the Squad exists | Deleted with the Squad | leaveSquad, deleteKid |
Squad streak (squads/{id}.streak): day count, best, last counted day, monthly freeze, flame flair |
Squad streaks | While the Squad exists | Deleted with the Squad | leaveSquad |
Weekly challenges and trophies (squads/*/challenges/*, squads/*/trophies/*): which challenge, how much each member helped, who got the reward |
Weekly Squad Challenge and trophy shelf | While the Squad exists | Deleted with the Squad; a deleted kid's id is removed from them at once | leaveSquad, deleteKid |
Squad chat (squads/*/messages) |
Deliver chat | 90 days | Firestore TTL deletes after 90 days; a deleted kid's messages are removed at once | TTL policy, deleteKid |
Invite records (inviteRewards/*, inviteCaps/*, parents/*.pendingInvite) |
Give the free packs once per pair and enforce the monthly cap | While the accounts exist | With the parent or kid account | deleteParentAccount |
| Friendships, friend requests, join requests, memberships | Run friendships and clubs | While the account is in use | Account deletion | deleteKid |
| Circls the kid created | Run the club | Other members rely on it | Deleted if the kid is the only member; otherwise kept with no owner | deleteKid |
| DM threads with a deleted kid | Let the other kid keep their own messages | The other kid's content | Deleted kid's messages removed immediately; thread marked closed; the rest expire at 90 days | deleteKid |
| Blocks made by the kid, and other kids' block entries about the kid | Safety | While the account is in use | Account deletion | deleteKid |
| Kid login record (Firebase Auth: UID, display name = username, creation and last sign-in time) | Authentication | While the account is in use | Account deletion | auth.deleteUser in deleteKid |
| Parent record, email, login and consent record | Parent account; proof of consent | While the parent uses KindCircl | Until the parent deletes their account | deleteParentAccount (deletes all kids, parent record, activity, consent sessions and login) |
| Unverified parent accounts with no kids | Request consent | Only while consent is pending | 30 days (parents with a form waiting for review are skipped) | dailyCleanup (also deletes their consent form files and review record) |
| Google Cloud Logging | Security and troubleshooting | Detect abuse | _Default bucket about 30 days; _Required admin audit logs about 400 days (set by Google) |
Cloud Logging retention |
Withdrawn consent
revokeConsent sets the parent's status to revoked, clears the consented claim, and pauses every kid account immediately, so no further information is collected. Data remains until the parent deletes the accounts or re-verifies (which unpauses them). Attorney to confirm whether revoked accounts should also be auto-deleted after a set period.
Remaining gaps
- Inactivity deletion (24 months) is not built yet. Build it with a parent email warning 30 days before deletion.
- Legal holds: there is no automatic hold flag. If a report is sent to NCMEC or law enforcement requests preservation, the coordinator must export the records to restricted storage before the 1-year TTL deletes them, and keep them for the required period (1 year after a CyberTipline report under the REPORT Act).
Annual review
The Ali Salomi reviews this schedule at least once a year and whenever a feature adds new data, and updates Privacy Policy section 8 to match.