Draft — pending legal review. This text has not been reviewed by a lawyer and may change.
Data processing agreement
Version 2026-10-23-draft
Version: 2026-10-23-draft
Draft — pending legal review. This template has not been reviewed by a lawyer. Do not sign it as is. The open questions a lawyer must settle are in docs/legal/README.md. Every description of what ScoutSheet does is written from the code at the time of drafting; anything not built yet is marked (planned).This agreement is between:
- The League: [league legal name], [address], represented by [name, title] ("League"), and
- The Operator: [operator legal name], [address], who runs ScoutSheet ("Operator").
It takes effect on [date] and covers the League's use of ScoutSheet at https://scoutsheet.org.
1. Roles
1.1 The League decides which players and which information go into ScoutSheet and why. The League is the controller of that information (in California terms, the business; under COPPA, the League is the party that collects children's information from parents).
1.2 The Operator processes that information only for the League and only on the League's documented instructions, which are this agreement and the League's use of ScoutSheet's features. The Operator acts as the League's processor (in California terms, its service provider).
1.3 Children do not have accounts. ScoutSheet collects nothing directly from children; player information comes from the League's registration records and from what the League's volunteers record at tryouts.
2. What is processed, and why
2.1 Purpose. Running the League's player evaluations: tryout setup, check-in, scoring by the League's evaluators, rankings and reports, and the League's draft export. The Operator will not use League data for any other purpose: no sale, no sharing for advertising, no advertising or analytics trackers, no profiling, no training of models, and no combining with other leagues' data.
2.2 Players (minors). Name; birth date or birth year; gender where the League collects it; positions; any registration fields the League maps into the import; parent or guardian email, phone, text-message and WhatsApp numbers (the email is also used for the signup emails in 2.5); Eval Group signup links (stored as one-way hashes with their status, send times and the email provider's message id; never an address) and the Eval Group a family chose; tryout placement (division, group, tryout number, league age at placement); check-in time and volunteer; whether an age document was checked, with an optional note; check-in photos (retakes included); evaluators' scores and notes.
2.3 League volunteers (adults). Name and email from sign-in; League roles and invitations; evaluator assignments; the scores and notes they enter and their timestamps; entries in the League's administration log; network addresses of failed tryout access-code attempts (kept one day).
2.4 Who inside the League sees what is set by roles the League assigns:
| Role | Sees |
|---|---|
| Admin | Everything in the League: players, parent contacts, photos, every score and note, every report and export, the administration log |
| Check-in | The tryout roster at the check-in desk (names, birth dates, tryout numbers, photos); no contacts, no scores |
| Evaluator | Only players placed and numbered at the tryout they joined (tag, name, birth year, positions, league age, photo); their own scores and notes; their coaching partner's notes if paired; division results after an Admin releases them, which never include notes |
Parent contacts are shown only to Admins and never reach evaluators, the check-in desk, reports or exports.
2.5 Signup email to parents. When a League Admin opens Eval Group signup for a tryout and presses send, the Operator's service emails the parent addresses the League imported:
- *Purpose:* an invite, a reminder to families who have not chosen, and a confirmation to the child's addresses each time a group is picked or switched. The Operator sends nothing on its own and uses the addresses for nothing else.
- *Content:* the League's name, each child's first name and division, a private link per child, the signup deadline, and the League's reply-to address; a confirmation also names the chosen group, time and place. Never a last name, birth date, photo, score or note. Open and click tracking are switched off.
- *Links:* each link is a random 256-bit code opening one child's signup page without an account; anyone holding it can change that child's group until it expires, which is 30 days after the signup deadline, or when a newer email or an Admin's "Send a new link" replaces it. Only a hash of the code is stored. Links are deleted with the child, tryout or league, and with a closed season's scores at the data purge (5.5).
- *Handling:* the Operator reads the addresses only to send and does not store them anywhere else. Test, preview and rehearsal tryouts never send real email.
- *Not yet built* (planned): recording bounces and complaints and suppressing those addresses. Until then the League sees only whether a send was accepted.
3. Subprocessors
3.1 The League authorizes these subprocessors:
| Subprocessor | What it does | Data it holds | Location |
|---|---|---|---|
| Vercel Inc. [confirm contracting entity] | Runs the application; stores photos in a private Blob store | Everything passes through it in transit; photos at rest; request logs | Portland, Oregon (pdx1) |
| Neon [confirm contracting entity] | Postgres database | All League data except photo files | AWS us-west-2 (Oregon) |
| Resend, Inc. [confirm contracting entity] | Sends signup emails (invites, reminders, confirmations) for the League | Each recipient parent email address and the message content in 2.5; delivery records kept per Resend's data processing agreement [confirm retention period]. No other player data | United States [confirm Resend's processing locations] |
| Clerk, Inc. [confirm contracting entity] | Volunteer sign-in | Volunteers' names, emails, sign-in history. No player data | United States [confirm Clerk's processing locations] |
3.2 The Operator will give the League at least 30 days' notice before adding or replacing a subprocessor that receives League data, and the League may end this agreement under section 8 if it objects.
3.3 The Operator will hold each subprocessor to data protection terms no weaker than this agreement, through that subprocessor's published data processing terms. [Lawyer: confirm each vendor's DPA is accepted for the Operator's account and adequate; see README.md.]
4. Security measures
The Operator maintains the following measures. Each is in the ScoutSheet code today unless marked (planned); the file paths are for the League's or its reviewer's verification.
4.1 Access control
- Every page and API request is authorized through one module (
src/lib/auth/) against the person's own League role or tryout evaluator record; nothing trusts a role, evaluator or name sent by the browser. - Tryout access for evaluators comes from their own evaluator record on that tryout, which the Admin can remove; removal ends access.
- Every id in a request must belong to the League or tryout in the address, or the request is refused. An automated test (
src/lib/isolation.test.ts) attempts cross-League access against every route and page and fails the build if any succeeds. - The database itself refuses links between one League's or tryout's records and another's (composite foreign keys, migration
0006_league_constraints). - Signed-in write requests must come from ScoutSheet's own origin, which blocks cross-site request forgery.
4.2 Minimizing exposure
- Parent contacts sit in a separate table that only one module may read, and only for a League Admin; automated tests (
src/lib/privacy.test.ts) fail if any other code reads it. - Evaluators' notes never appear in division results; individual and pair reports load only their authors' notes.
- Photos are stored in a private store, never in public files; they are served only through an authorized ScoutSheet address, with
Cache-Control: private, no-store, and storage addresses are never exposed. - Responses holding a viewer's data are marked
private, no-storeso shared caches do not keep them. The offline scoring cache on evaluators' phones holds no photos and no contacts, and is cleared on sign-out. - Every exported spreadsheet neutralizes formula cells.
4.3 Logging without personal data
- Unexpected errors are reduced to a code, a route and a request id before they reach logs; request bodies, player fields, contacts and notes are never logged (
src/lib/errors/report.ts). - League administration actions (role changes, invitations, access-code changes, report releases and exports, imports, erasures, tryout closes and deletions, form edits) are recorded in an append-only log that the League's Admins can read. It holds ids, actions and counts, never names or values.
- Every score change is recorded in an append-only score history.
4.4 Transport and browser protections
- HTTPS only, with HSTS for two years including subdomains; a content security policy limiting scripts, frames and connections to ScoutSheet, Clerk and Clerk's bot protection (Cloudflare Turnstile); framing by other sites forbidden; a strict referrer policy; search engines told not to index.
- Requests are rate limited per person and per network address; wrong tryout access codes are throttled.
4.5 Operations
- Production and preview environments are separate: previews use a separate database project holding only demo data, a separate photo store and separate sign-in keys. Production refuses to start unless it is pointed at the production database and origins.
- Database changes are applied only through a release workflow that first takes a restorable snapshot.
- Encryption at rest is provided by Neon and Vercel. [Confirm each provider's current encryption-at-rest statement before signing.]
- (planned) Production database API keys scoped to the production project only; an outside security review before a second league's data goes in.
5. Retention and deletion
5.1 Player records are never erased automatically. A League Admin erases a player from the Players page; this removes, in one step, the player's record, tryout entries, check-in history, every photo ever taken of them (retakes included) and the stored files, every score and note, and parent contacts. The administration log keeps only that an erasure happened, who did it, and counts (src/lib/players/erase.ts).
5.2 Parent requests. Parents make requests to the League. The League handles them in ScoutSheet (view, correct, merge duplicates and erase directly). The Operator will help the League answer any request within [10] business days of the League asking, and will pass to the League, without answering it, any request it receives directly.
5.3 Closing a season. A League Admin closes a season once its tryouts are closed. Closing is final: the season's tryouts become a read-only archive (they cannot be reopened and their scores cannot be corrected), and the retention periods in 5.4 and 5.5 run from that moment. Players and parent contacts carry over to the next season (src/lib/seasons/close.ts).
5.4 Photos. A daily job deletes a closed season's tryout photos, retakes included, and the stored files a League-set period after the season closed: 60 days by default, from 1 day to 10 years. A photo still shown for the player in a later season's tryout is kept. Until a season closes, its photos are kept unless the player is erased. Photo files that never became attached to a player are deleted after 24 hours (src/lib/retention/run.ts, src/lib/photos/cleanup.ts).
5.5 Scores and notes. The same daily job deletes a closed season's scores, evaluators' notes and score history a League-set period after the season closed: 12 months by default, from 1 to 120 months. Player records, parent contacts and tryout entries are not deleted by this job (see 5.1).
5.6 Legal hold. A League Admin can place a legal hold from the League's settings page. While it is on, the daily job deletes nothing of the League's; deletion resumes, on the League's settings, when it is lifted. Changes to the retention settings and the hold, season closes and each deletion run are recorded in the administration log, as counts only.
5.7 Backups. The database keeps point-in-time history for up to 7 days, so erased data can remain there for up to 7 days. A snapshot is taken before each database upgrade; snapshots older than 7 days are deleted except the newest, which is kept until the next upgrade. The Operator will not restore erased data from history except to recover from an incident, and will re-apply erasures made since the restore point.
5.8 At termination. Within [30] days after this agreement ends, the Operator will, at the League's choice, give the League an export of its data (reports and draft exports in CSV) and then delete all League data: every player (as in 5.1), tryouts, forms, seasons, memberships, invitations, import history and the administration log. Deletion from backups follows 5.7. The Operator will confirm completion in writing. (planned) The operator tool that deletes a whole league (npm run delete-league) refuses any league but a rehearsal league today; until a termination path ships, the Operator erases each player through the same code as 5.1 and deletes the remaining League records by hand, and records what was deleted.
6. Security incidents
6.1 The Operator will notify the League's named contact without undue delay and within 72 hours after confirming a security incident affecting League data, by email and phone, with what is known: what happened, which data and which players or volunteers, when, and what is being done. It will update the League as it learns more.
6.2 The Operator will help the League meet its own notice duties, including California's requirement (Civil Code § 1798.82 as amended by SB 446) to notify affected California residents within 30 calendar days of discovery, and to send a sample notice to the California Attorney General when more than 500 residents are notified. The League decides whether and how residents are notified; the Operator drafts and supplies the facts.
6.3 The Operator's procedure is docs/runbooks/incident.md.
7. The League's duties
7.1 The League has a lawful basis and any required parental notice or consent for every player it puts in ScoutSheet, including telling parents that their child's information and photo will be used for tryout evaluation.
7.2 Photos. The League obtains each parent's or guardian's permission before a check-in photo is taken, consistent with Little League International's guidance on photographs of players and on sharing player information [cite the current guidance; see README.md], and does not take a photo of a player whose parent has declined. Photos are for identifying players at tryouts only.
7.3 The League assigns roles only to vetted adult volunteers (consistent with its Little League volunteer screening), removes access when a volunteer leaves or a tryout ends, and keeps the tryout access code within its evaluators.
7.4 The League imports only the registration fields it needs for tryouts. It does not import medical, financial, government-id or similar data.
7.5 The League names a privacy contact: [name, email, phone], and keeps it current.
8. Term and termination
This agreement lasts while the League has an account. Either party may end it with [30] days' written notice; the League may end it at once if the Operator breaches sections 2, 4 or 6. Section 5.8 applies on any ending.
9. Audit
On reasonable notice, once a year or after an incident, the Operator will answer the League's written questions about these measures and show the League the relevant code, configuration and the League's administration log.
10. Signatures
A new League asks for access in ScoutSheet at /start and must accept the version of this agreement published at /legal/dpa before it can send the request. ScoutSheet records the version accepted, the signed-in person who accepted it and when; the Operator creates the League only after approving that request. [Confirm whether that acceptance binds the League or whether this signed copy is still required.]
| League | Operator |
|---|---|
| Name: | Name: |
| Title: | Title: |
| Date: | Date: |
| Signature: | Signature: |