batchwatch

batchwatch › Data Processing

Data Processing

Companion to PRIVACY.md.

This is a controller-to-controller data sharing statement, not an Art. 28 processor agreement — §1 explains why that distinction is the right one for this service. Every technical claim was checked against the running code (src/) and the schema (schema.sql). If your process requires a counter-signed agreement, write to hello@batchwatch.dev and we will handle it as a contract rather than as a web page.

Companion to PRIVACY.md.


1. Read this first: we are usually not your processor

The front page links to a "data processing agreement", and customers' procurement teams will ask for one. But the honest analysis is that for almost everything batchwatch does, we are a controller in our own right, not a processor acting on your instructions.

The reasoning:

So the instrument you actually need from us is a controller-to-controller data sharing statement, which is §§2–8 below. §9 is a fallback Art. 28 processor annex that engages only in the narrow case described there.

Getting this the wrong way round matters. Signing a standard Art. 28 processor DPA would describe a relationship that does not exist, oblige us to "process only on documented instructions" while we plainly do not, and oblige us to "delete or return all personal data at the end of the provision of services" — which directly contradicts the aggregate licence the terms take, even in its terminable form: a withdrawal ends the licence going forward but does not recall an aggregate already published (§9, and TERMS.md §7).


2. What personal data is actually involved

Most of what we hold is not personal data at all: token counts, model names, timestamps, and durations describing machine behaviour.

The parts that are, or may be:

DataWhy it may be personal data
trial.identity = ip:<salted SHA-256>Pseudonymised IP address. We hold the salt, so re-identification is trivially possible for us. See PRIVACY.md §4
usage.identity = newkey:<salted SHA-256>Same construction, used for the key-creation limit
api_key.labelFree text chosen by the key holder. Intended for prod-pipeline-style notes, but nothing stops a personal name
call.model, call.endpointFree text from the submitter. Intended for model names and API paths; stored verbatim
api_key + call rows togetherWhere a key belongs to a sole trader or an individual developer, the pattern of that key's activity is data about them
call.regionCountry code only. Not identifying alone; an attribute of a key's record when combined
api_key.verified_email and email_verification.emailEmail address of a contributor who has opted in to verify one, via POST /v1/verify/start (#145). Directly identifying. Held only for keys whose holder chose to confirm an email — it unlocks the contributor_verified tier and lets us contact the key holder. A key that has not verified holds no email. See PRIVACY.md §1
api_key.recovery_emailEmail address of a paying customer, taken from the Stripe receipt at checkout.session.completed and only when the buyer left key recovery ticked in the subscribe dialog (#309). Directly identifying. Its single purpose is to issue a replacement API key: because we store only a hash of the token, a paying customer who lost their key otherwise had no way back into what they had bought. Never used for marketing. Not held for free, trial or contributor keys, and removable at any time with DELETE /v1/keys/recovery-email. See PRIVACY.md §1

Categories of data subject: developers and operators who create API keys or call the API; visitors to the dashboard (at the Cloudflare edge only). No end users of your applications — nothing about them ever reaches us.

No special categories (Art. 9) and no criminal-offence data are involved, intended or foreseeable.

What is never received: prompts, completions, system prompts, tool definitions, function schemas, provider API keys, end-user identifiers, request IDs. The client library reads timing and token counts off the job and never touches the payload; it is open source, ~90 lines, and that is the point of publishing it.


3. Nature, purpose and duration

Nature: collection, storage, aggregation and publication of queue-time measurements; authentication of API keys; counting of trial calls and quota.

Purpose: operating batchwatch — building a public dataset of LLM batch API queue times, answering queries against it, and commercialising access to the aggregate.

Duration: for as long as the service operates. No automatic deletion is implemented in the code today — verified: there is no TTL, no scheduled purge, and no DELETE statement anywhere in src/. Rolling query windows (30 days, 7 days, 1 hour) mean older rows stop influencing answers, but they are retained. Setting and implementing retention periods for trial and usage is an open item, recorded in PRIVACY.md §6.


4. Security measures actually in place

Stated as what exists, not as what a template would claim.

In place:

Not in place — state these honestly to any customer who asks:


5. Sub-processors

Sub-processorRoleLocation
Cloudflare, Inc.Compute (Workers), database (D1), CDN and edge terminationUS-headquartered, global network. Database region: see below
Google LLC (Google Analytics 4)Website analytics on the marketing pages only, and only for visitors who have actively consentedUS-headquartered, global network
Stripe Payments Europe, Ltd. (with Stripe, Inc.)Payment processing, subscription billing and tax calculation. Receives name, email, billing address, card data and country for paying customers only. Card data never touches our systems — checkout is hosted by StripeIE-established, US parent; global network
Resend (Plus Five Five, Inc.)Transactional email delivery. Receives an email address and the message body — only for the two messages we ever send: a contributor's opt-in verification link (#145) and a paying customer's key-recovery link (#309). No measurement data, no marketing, no mailing listUS-headquartered

Cloudflare is the only sub-processor in the request path of the measurement service. No customer measurement data reaches anyone else: the API (/v1/…) loads and reports nothing to any third party.

Google Analytics is a sub-processor for website visitors, not for API callers, and only on consent. Nothing is loaded before a visitor clicks Allow; declining loads nothing, ever. See PRIVACY.md §9a.

Stripe is a sub-processor only for people who pay. It is engaged when a customer buys a subscription: checkout is hosted by Stripe, so card data goes straight to Stripe and never touches our systems. Anyone who has not paid is not in Stripe at all — free and contributor keys involve no payment processor whatsoever.

Resend is a sub-processor only for people we have an address for, which is the opt-in verified contributors and the paying customers who left key recovery on. It is not in the request path of the API and receives no measurement data.

Changes to this list. Google Analytics 4 was added on 2026-08-26, Stripe on 2026-08-30 when subscription billing went live, and Resend on 2026-08-31 — recorded when paid key recovery shipped, though the email tier (#145) had already been using it. Every addition is announced under the 30-day notice below, and this section is the authoritative record of what it says today.

The D1 region is not pinned in our configuration. wrangler.toml declares the binding and the database id but no location_hint; the primary region was chosen by Cloudflare at creation time. It still needs to be looked up in the Cloudflare dashboard and recorded here — and until it has been, we do not assert an EU region. If an EU primary is required for a customer, D1 supports a location hint at database creation and moving would mean recreating the database.

Sub-processor change notice period: 30 days, consistent with the 30-day notice already promised in TERMS.md §9.

Our prober calls OpenAI, Anthropic and Google using our own API keys and our own toy jobs (8 tokens in, 5 out). Those providers are not sub-processors of anyone's data: no customer data is sent to them, ever.


6. International transfers

Cloudflare is a US company operating a global network, so personal data may be processed outside the EEA. The basis is Cloudflare's DPA and SCCs, which must be executed. A transfer impact assessment should be done as part of that, and is not done yet.


7. Data subject requests

We handle requests directly, as controller — see PRIVACY.md §7, which sets out exactly what can and cannot be deleted and why.

The one point worth repeating here, because it surprises people: we cannot action a deletion request for measurements submitted without an API key. key_id is NULL on those rows and nothing links them to a person. Art. 11 GDPR applies. If deletability matters to you, submit with a key.

There is no self-service route for export or deletion. Both are manual operations by the operator. Key revocation is the one thing you can do yourself, and it does not by itself delete measurements.


8. Personal data breach

If we become aware of a breach affecting personal data we hold, we will notify affected controllers and, where required, Datatilsynet, without undue delay and in any event within 72 hours of becoming aware.

Realistically, the breach scenarios worth naming are:

  1. Compromise of the Cloudflare account. This is the whole system.
  2. Disclosure of TRIAL_SALT, which would make the stored IP hashes brute-forceable back to addresses — the exact risk the salt exists to prevent.
  3. Database exfiltration. Contents would be measurement metadata, token hashes (not usable to authenticate), key labels, salted IP hashes and the email addresses of contributors who opted in to verify one (§2). No prompts, no completions, no provider credentials, no card data and no passwords — none of those are ever collected.

9. Fallback: Art. 28 processor terms

These terms apply only if, and only to the extent that, we in fact process personal data on your documented instructions — realistically only where you have placed personal data into the free-text model, endpoint or key label fields, which ACCEPTABLE-USE.md §2.9 asks you not to do.

Where they apply, we will:

  1. Process that data only on your documented instructions, including as to transfers, unless required otherwise by EU or Member State law.
  2. Ensure persons authorised to process it are bound by confidentiality.
  3. Apply the measures described in §4.
  4. Not engage a sub-processor other than those in §5 without prior notice and an opportunity to object.
  5. Assist you, so far as reasonably possible, with data subject requests and with Arts. 32–36 obligations.
  6. Notify you without undue delay on becoming aware of a breach affecting that data.
  7. At your choice, delete or return that data at the end of the provision of the service, and delete existing copies unless retention is required by law.
  8. Make available the information reasonably necessary to demonstrate compliance and allow audits — see the limits below.

Audit: as a single-operator service, we cannot host an on-site audit. We will answer a written security questionnaire and provide the Cloudflare DPA and any Cloudflare compliance reports we are permitted to pass on.

Scope limit that must be preserved in any negotiated version: clause 7 above (delete or return) applies only to data processed on your instructions under this §9. It does not apply to the measurements you contributed. Those are covered by the terminable aggregate licence in TERMS.md §7, which is a controller-to-controller arrangement, not a processing instruction. That licence can be ended going forward by writing to us, and once we act on it we stop using those measurements in new aggregates — but an aggregate already published stands, because a released statistic cannot be recalled. Any customer redline that extends the §9 deletion obligation across that boundary — or that tries to reach back into already-published aggregates — changes the product's foundation and must be escalated, not accepted.


10. Contact

hello@batchwatch.dev · Stenager Consulting ApS, CVR 44456923, Stenagervaenget 45, 7100 Vejle, Denmark.

No Data Protection Officer is appointed, and none is required. The Art. 37 trigger is "regular and systematic monitoring of data subjects on a large scale". We monitor queues, not people: we hold per-person data only as bounded, salted hashes used for abuse control, and we do not profile or track individuals anywhere in the service.