Sub-processor List
Version 1.0.0-draft · drafted 2026-08-16
This is a working draft. It has not been reviewed or approved by a lawyer, and it does not yet name a contracting entity or a governing law. 6 questions in it are still open for legal review, and each one is marked in the text below.
# Sub-processor List
> **DRAFT — NOT LEGALLY REVIEWED.**
> Every entry is evidenced by this repository: a dependency, an environment
> variable, or a shipped adapter. Nothing is listed because it is a normal thing
> for a service to use.
>
> **[LEGAL REVIEW REQUIRED — sub-processor characterisation.]** Whether the
> advertising and commerce platforms a customer connects are sub-processors,
> independent controllers, or neither is a legal question. Data flows from those
> platforms *to* the service on the customer's own authorisation, which is not
> the usual sub-processor direction. This list records what the software talks
> to; it does not characterise any relationship.
>
> **[LEGAL REVIEW REQUIRED — change notification.]** How much notice customers
> receive before a new sub-processor is engaged.
## In use today
### Anthropic
**Purpose.** Generates the AI analyses, recommendations and creative suggestions.
**Data involved:**
- Aggregated advertising performance figures for the requesting workspace
- Campaign, ad set and ad names
- Audience segment labels and their measured figures
**Evidence in the codebase.** `@anthropic-ai/sdk` is a runtime dependency; `ANTHROPIC_API_KEY` is in the environment schema; `src/server/ai/providers/anthropic.ts` is the shipped provider.
Prompt input is redacted before the call and cross-checked afterwards (§8.4). The AI layer is fenced from the database and from platform adapters by check:arch, so a model response has no path to a write. Customer email addresses and OAuth tokens are never included.
### Meta (Meta Platforms)
**Purpose.** The advertising platform a customer connects. Campaign structure and performance metrics are read FROM Meta on the customer's own authorisation.
**Data involved:**
- OAuth authorisation granted by the customer
- Advertising account, campaign and metric data read from their account
**Evidence in the codebase.** `META_APP_ID` / `META_APP_SECRET` in the environment schema; `src/server/integrations/meta/**` is the shipped adapter.
The direction of flow is unusual for a sub-processor: data moves from Meta to us, not the other way. Whether that makes them a sub-processor is a legal characterisation and is in `LEGAL_REVIEW_REQUIRED`.
### Shopify
**Purpose.** The commerce platform a customer connects. Products and orders are read FROM Shopify on the customer's own authorisation.
**Data involved:**
- OAuth authorisation granted by the merchant
- Product and order data read from their store
**Evidence in the codebase.** `SHOPIFY_API_KEY` / `SHOPIFY_API_SECRET` in the environment schema; `src/server/integrations/shopify/**` is the shipped adapter.
Customer identity from orders is reduced to a peppered one-way hash at ingest (R8/R12) — no email address, name or address is stored. Same characterisation question as Meta.
### Resend
**Purpose.** Delivers transactional email — verification, password reset, invitations, digests.
**Data involved:**
- Recipient email address
- Message content generated by the product
**Evidence in the codebase.** `EMAIL_DRIVER=resend` and `EMAIL_FROM` are set on the production application, and `assertProductionRequirements()` in `src/lib/env.ts` requires `EMAIL_DRIVER=resend` together with `RESEND_API_KEY` and `EMAIL_FROM` under `NODE_ENV=production`. `src/server/email` sends through the Resend API.
Transactional email — address verification, password reset, invitations and digests — is delivered through Resend in production. The console driver, which writes rendered emails to disk, remains the development default.
### DigitalOcean Spaces (object storage)
**Purpose.** Stores uploaded creative assets and generated exports.
**Data involved:**
- Files a customer uploads
**Evidence in the codebase.** `STORAGE_DRIVER=s3` is set on the production application, with `STORAGE_S3_ENDPOINT`, `STORAGE_S3_REGION` and `STORAGE_S3_BUCKET` naming a DigitalOcean Spaces bucket in the `fra1` region. `assertProductionRequirements()` in `src/lib/env.ts` requires `s3` under `NODE_ENV=production`, and `src/server/storage/s3.ts` is the driver that sends to it.
Files a customer uploads are stored in that bucket, under keys scoped to the workspace that owns them (`org/{organizationId}/...`). The driver itself is provider-neutral — Spaces, R2, MinIO and S3 differ only in configuration — but DigitalOcean Spaces is what production is configured to use. Object versioning is enabled on the bucket, so a version of a deleted or replaced file is retained until it is explicitly removed; the erasure paths in `src/server/modules/privacy/purge.ts` and `src/server/modules/files` delete every version rather than only the current one. [LEGAL REVIEW REQUIRED — whether adding an active sub-processor requires customer notice, and how storage in `fra1` should be described.]
### DigitalOcean (hosting and database)
**Purpose.** Runs the application and holds the database.
**Data involved:**
- Everything the product stores
**Evidence in the codebase.** A DigitalOcean Managed PostgreSQL cluster exists in the `fra1` region and holds the production database `ads_ai`, with all migrations applied. `docs/runbooks/database-provisioning.md` is the procedure that created it, and `scripts/do-db-setup.sql` is the bootstrap that was executed against it.
Recorded as IN_USE because the infrastructure is provisioned and holds the schema. No customer data has reached it: the application is not deployed and no account exists. The application tier (DigitalOcean App Platform) is selected but not yet created. Whether this is a sub-processor, and what transfer mechanism applies to a datacentre in Germany, are questions for counsel — see LEGAL_REVIEW_REQUIRED.
## No provider chosen
### Error tracking provider
**Purpose.** Would receive application error reports.
**Data involved:**
- Error messages and stack traces, scrubbed of payload contents
**Evidence in the codebase.** ARCHITECTURE §14 names Sentry, and deliverable 7 found it **not installed** — no `@sentry/*` dependency, no configuration, no DSN. §14 was corrected to say so.
### Monitoring and paging provider
**Purpose.** Would evaluate the alert catalogue and notify an on-call engineer.
**Data involved:**
- Operational metrics; no customer data by design
**Evidence in the codebase.** Deliverable 7 built a declarative alert catalogue precisely because none is chosen.
## Deliberately not implemented
### Payment processor / merchant of record
**Purpose.** Would process subscription payments.
**Data involved:**
- Billing contact and payment details, none of which the product stores today
**Evidence in the codebase.** `PROVIDER_ID` is unset and `hasBillingProvider()` is false. Phase 11 is gated on L0.1, L1.4 and L1.6, and no processor SDK is a dependency.
Nothing is charged and no payment data exists. Listed so its absence is explicit.
---
## Why the distinction between these sections matters
A provider the code can call today is not the same as one the code merely has a
driver for, and neither is the same as a category where nothing has been chosen.
Collapsing the three would make this list wrong in the direction that matters —
naming providers that receive no data, or implying a choice that has not been
made.
Two categories remain unresolved: error tracking, and monitoring and paging. No
provider has been selected for either, and nothing is sent to one.
**[LEGAL REVIEW REQUIRED — processing location and international transfers.]**
The application, its database and its object storage all run in DigitalOcean's
`fra1` region. What that means for processing location, and for transfers
out of it, is a legal question that this list does not answer.
