Subprocessors
Every third party that receives anything, and what each one gets.
Every row below was verified by reading the code at the file and line named in the last column. Nothing here is from memory or from a vendor's marketing page.
Effective date: 4 September 2026 Last verified against the code: 4 September 2026
This is the companion to privacy-policy.md. If a provider is not on this list, it does not receive your information — there is no fourth analytics vendor, no error-tracking SDK, no advertising network, and no data broker anywhere in CourseLite.
How to read the "role" column
- Processor — handles data on CourseLite's instruction, for CourseLite's purposes.
- Independent controller — decides for itself what to do with the data it receives, under its own policy. You have a direct relationship with them.
- Both — processor for the part we instruct, controller for its own operational and security purposes. Most infrastructure providers are here.
The role assignments below are our reading, not a legal conclusion, and have not been reviewed by a lawyer or confirmed against each provider's DPA. See the note at the end.
1. Providers that receive user data
| Provider | Purpose | Data categories it receives | Role | Privacy policy | Verified in code |
|---|---|---|---|---|---|
| Clerk (Clerk.com, Inc.) | Authentication, sessions, sign-up and sign-in UI. Also sets the sign-in cookie on courselite.org and, in the extension, is the only reader of it | Email address, password or social sign-in, session token, user id, and whatever Clerk's own bot check collects. Our server additionally asks Clerk for a user's email address and role | Independent controller for the account; processor for the id we read back | https://clerk.com/legal/privacy | functions/lib/auth.ts (the only place identity is established); src/main.tsx:33 (ClerkProvider); functions/lib/admin.ts:91-92 and functions/api/courses/ingest.ts:451 (createClerkClient, the only server-side Clerk lookups) |
| OpenAI (OpenAI, L.L.C. / OpenAI Ireland Ltd) | Chat answers, embeddings, image transcription, generated documents and decks | Course material text, retrieved passages, assignment briefs, the student's questions, the student's and instructor's names, and images nothing else could parse. No account identifier is sent | Processor | https://openai.com/policies/privacy-policy · API data: https://developers.openai.com/api/docs/guides/your-data | functions/lib/openai.ts:170 (the single fetch, authorization + content-type only, no user: field); functions/lib/vision.ts |
| Stripe (Stripe, Inc. / Stripe Payments Europe Ltd) | Subscription checkout, billing portal, payment processing | Email address, CourseLite user id (as client_reference_id), plan, and the card details you enter on Stripe's own page | Independent controller (payments); processor for the ids we set | https://stripe.com/privacy | functions/lib/stripe.ts:34 (API base), :196–203 (customer create, sends email), :272 (client_reference_id: input.userId), :133 (the single fetch) |
| Google LLC | Drive/Docs/Slides access under drive.file, the file Picker, and Google Fonts | The drive.file OAuth grant, the files you pick or that CourseLite creates, and the content written into them. Separately, your IP address and user-agent when the Inter webfont loads. Google does not tell CourseLite which account granted the access — drive.file carries no identity and no openid/userinfo.email scope is requested | Independent controller | https://policies.google.com/privacy | Scope: extension/manifest.template.json:29, extension/src/google/auth.ts:47, extension/src/google/token.ts:50, src/google/picker.ts:37. Fonts: index.html:33–36 |
| Cloudflare, Inc. | Hosting the website and the API (Pages + Pages Functions), CDN, TLS, DDoS protection, and the Turnstile bot check | Every request: IP address, URL, headers, and request body. Turnstile additionally receives the challenge token and the IP for one verification | Both | https://www.cloudflare.com/privacypolicy/ | Deploy target throughout functions/; wrangler.toml; public/_headers; Turnstile: functions/lib/turnstile.ts:75, :137, :144 |
| Neon, Inc. | The PostgreSQL database — where everything CourseLite stores actually lives | All stored data: course material, documents, embeddings, generated work, account rows, analytics, anti-abuse hashes, billing ids | Processor | https://neon.tech/privacy-policy | functions/lib/db.ts:90 (connect(), the single connection point); docs/DATABASE.md |
| Crossref (Publishers International Linking Association, Inc.) | Resolving a DOI into a bibliography entry | A DOI, and nothing else. Re-normalised before the call, so a caller holding an essay gets a null instead of a request. A user-agent naming CourseLite is sent; deliberately no email address | Independent controller | https://www.crossref.org/operations-and-sunsetting/privacy/ | functions/lib/sources.ts:222–252 (crossrefByDoi, CROSSREF_ORIGIN), reached from functions/api/sources.ts actions doi and url |
2. Providers involved in operating CourseLite, that do not receive user data
| Provider | Purpose | Why it is not a subprocessor | Verified in code |
|---|---|---|---|
| GitHub (Microsoft) | Source hosting and CI. Typecheck, build, deploy on push to main | The pipeline builds and deploys code. It holds no runtime secret and never touches production data — DATABASE_URL is a Cloudflare Pages runtime secret, never a CI secret | .github/workflows/ci.yml, .github/workflows/deploy.yml |
| npm registry | Build-time dependencies | Build machine only | package.json, package-lock.json |
3. Providers deliberately not used
Worth writing down, because the absence is the disclosure.
| Not used | Would have been for |
|---|---|
| PostHog, Mixpanel, Amplitude, Google Analytics, Plausible | Product analytics. Ours is two Postgres tables (usage_events, usage_daily) written from one file |
| Sentry, Bugsnag, Rollbar, Datadog | Error tracking. Ours is one Postgres table (app_errors) plus Cloudflare's own logs |
| Any advertising network, ad SDK, or attribution provider | Nothing. There are no ads and no advertising code |
| Any data broker, enrichment or identity-resolution provider | Nothing |
| Any device-fingerprinting or bot-scoring vendor beyond Cloudflare Turnstile | Anti-abuse. Ours is five one-way hashes of relationships you chose |
| Any email marketing platform | Nothing. Transactional email only |
| Redis, KV, or any caching service | Nothing. There is no cross-request cache for user-scoped data, deliberately |
| Any object storage (S3, R2, GCS) | Nothing. No file bytes ever reach a CourseLite server |
The outbound destinations, counted by reading rather than by grepping. There are five, and the fifth is the one a grep misses:
| Destination | Where the call is | How the host is chosen |
|---|---|---|
api.openai.com | functions/lib/openai.ts:170 | DEFAULT_BASE_URL in functions/lib/models.ts:36, overridable by OPENAI_BASE_URL |
api.stripe.com | functions/lib/stripe.ts:133 | STRIPE_API, a module constant (:34) |
challenges.cloudflare.com | functions/lib/turnstile.ts:144 | SITEVERIFY, a module constant (:75) |
api.crossref.org | functions/lib/sources.ts:230 | CROSSREF_ORIGIN, a module constant (:252). The call is options.fetchImpl ?? fetch, so a search for fetch( does not find it — an earlier audit missed this row for exactly that reason |
| Clerk's API | Inside @clerk/backend: verifyToken (functions/lib/auth.ts:20) fetches Clerk's signing keys, and createClerkClient(...).users.getUser (functions/lib/admin.ts:91-92, functions/api/courses/ingest.ts:451-452) reads a user | The SDK's own constant, derived from CLERK_SECRET_KEY. No fetch( appears in our source for it at all |
No user-supplied URL is ever fetched server-side, on any of these paths.
4. Where the data physically sits
| Where | |
|---|---|
| Website and API | Cloudflare's global edge network |
| Database | Neon, in the United States (AWS us-west-2, Oregon) |
| Model inference | OpenAI's infrastructure |
| Authentication | Clerk's infrastructure |
| Payments | Stripe's infrastructure |
Assume the United States unless a region is filled in above, and see section 15 of the privacy policy.
5. Changes to this list
If we add a provider that receives user data, we will add it here and update the privacy policy before it starts receiving anything.
6. What is not verified
Kept separate from the table on purpose, so nobody mistakes one for the other.
- The role column is our reading. It has not been checked against any provider's data processing agreement, and none has been signed or reviewed.
- No DPA is in place with any provider named above. Several offer one. If CourseLite ever serves users in the EEA or the UK, a signed DPA with Clerk, Neon, OpenAI, Stripe and Cloudflare is a prerequisite, not a nicety.
- No international transfer mechanism has been confirmed for any provider.
- Neon's point-in-time restore window is Neon's to set, not ours. The region is stated above; the length of the backup window is a schedule Neon operates and we do not control, which is why neither this page nor the privacy policy quotes you a number of days for it.
- Sub-processors of our providers — the people Clerk, Neon, OpenAI, Stripe and Cloudflare use in turn — are listed on their own pages and are not reproduced here. Each provider maintains its own list.
- The legal entity names above are the ones the providers publish. Confirm which entity actually contracts with CourseLite before relying on them in a contract.
- Sub-processor lists change without telling us. This page is a snapshot of what the code does on the date at the top, not a subscription to each provider's own list.
7. Contact
| A question about anything on this page | support@courselite.org |
| Notice that a provider's details here are out of date | support@courselite.org |
| Privacy questions generally | support@courselite.org — see the Privacy Policy |
CourseLite is an online service with no public office, so there is no postal address to write to. Email reaches a person.
If you are a school or a university evaluating CourseLite, this page and the Privacy Policy are the two documents to read together, and the honest summary is the one in section 6: no data processing agreement has been signed with any provider named above, and no international transfer mechanism has been confirmed. If your institution requires either, CourseLite does not meet that requirement today, and we would rather you learn it here than after a procurement review.