The compliance seam

LLM subprocessors: adding a model is a data-flow change

Short answer

Adding or switching a model in a routing config changes who processes your users' text. With open weights the processor is whoever serves the weights, not the organisation that published them — so the answer lives in the serving path and in your router's data processing agreement, never on the model card.

The decision this page improves: you are about to merge a one-line model change. Before you do, you need to know whether that line also moved personal data to a party nobody has disclosed, which document settles it, and what else in your estate just became inaccurate without appearing in the diff.

Why is a routing change a data-flow change?

Because a model id is not a setting — it is a destination. The string decides which company's hardware your users' text is decrypted on, and swapping it moves that text without moving a single byte of your own architecture. Nothing about the change announces itself: the diff shows one quoted string, the tests pass, the latency is fine.

What makes this specifically hard to catch is that the artefacts which become wrong are the ones you did not touch. A privacy policy, a subprocessor list, a records-of-processing entry, an answered vendor questionnaire, a customer DPA schedule — each of those files is byte-identical after your commit and each of them may now describe a data flow that no longer exists. Review is diff-scoped by construction. A diff cannot show you a page whose accuracy depended on the world staying still.

So the question to re-ask on every model change is not "did this break anything" but "who processes this text now, and where did I say they do?" That is a two-part question, and the second half is the one that gets skipped.

Who processes the data when the weights are open?

Whoever runs the inference. An open-weight release is a set of files under a licence; the organisation that published them may never see a single one of your requests. That is the whole point of open weights, and it is exactly what breaks the intuition that the name at the top of the model card is the company you need to name in a disclosure.

A concrete case. gpt-oss-safeguard-20b is a safety-classifier model published by OpenAI under the Apache 2.0 licence — 21 billion parameters, 3.6 billion active, built to classify text against a policy you supply. Anyone may host it. On 25 July 2026, OpenRouter's public endpoints API reported exactly one company serving it through that router: Groq. Its general-purpose sibling gpt-oss-120b, published by the same organisation on the same day under the same licence, reported seventeen.

ModelPublished byLicenceCompanies serving it via OpenRouter
gpt-oss-safeguard-20bOpenAIApache 2.01 (Groq)
gpt-oss-20bOpenAIApache 2.011
gpt-oss-120bOpenAIApache 2.017

Counts read from OpenRouter's public endpoints API on 25 July 2026. They change without notice — that instability is the finding, not a caveat on it.

Read the last column as a list of companies that could lawfully be handed your users' text tomorrow by a router you already use. For a closed-weight model the candidate set is small, stable, and usually named in the vendor's own docs. For an open-weight model it is a market, and the processor is a runtime property of your deployment rather than a design-time one. This is the substantive difference between the two, and it is invisible from the code.

How do you find out who is actually serving a model?

Ask the router, at the version you are actually running, and write the answer down with a date on it. For OpenRouter the check is a public, unauthenticated GET — no key, no spend:

curl -s https://openrouter.ai/api/v1/models/openai/gpt-oss-safeguard-20b/endpoints \
  | jq -r '.data.endpoints[].provider_name' | sort -u

That printed Groq on 25 July 2026. Swap the model slug for whichever one your config names. Two things to understand about the result before you paste it into a disclosure.

First, it is a snapshot, not a contract. OpenRouter's own routing documentation states that "OpenRouter routes requests to the best available providers for your model. By default, requests are load balanced across the top providers to maximize uptime." A model with one endpoint today can have five next month, and the default behaviour will use them.

Second, you can narrow it, and that is a real architectural choice. The same documentation describes a provider object with order (try these in sequence), only (restrict to this list of provider slugs), allow_fallbacks (set false to forbid anything outside order), and ignore (exclude specific providers). Pinning buys you a disclosure you can state precisely; it costs you the availability that load balancing across providers exists to give you. Whichever way you go, the config and the disclosure have to agree — a page that names one provider as fixed while the code sends no provider block at all is a claim your system does not enforce.

The answer is in the DPA, not the terms of service

When you route through an intermediary, the sub-processor clause of that intermediary's data processing agreement is the document that describes what happens to your data downstream. The headline terms of service usually do not contain it; they point at it. OpenRouter's Terms of Service (last updated 6 July 2026) do exactly that at section 10.2: "The DPA is incorporated by reference into, and made a part of, these Terms." Reading the ToS and stopping there gets you a pointer, not an answer.

The document it points at — OpenRouter's Data Processing Agreement, last updated 5 May 2026 — carries the substance. Four clauses do the work:

  • Authorisation. "OpenRouter and OpenRouter's Affiliates respectively may engage third-party Subprocessors in connection with the provision of the Service." The right to bring in downstream processors is stated up front, not implied.
  • Flow-down. "OpenRouter or a OpenRouter Affiliate has entered into a written agreement with each Subprocessor containing, in substance, data protection obligations no less protective than those in the Agreement." This is the clause that makes routing through an intermediary a defensible structure at all: the terms you negotiated are asserted to travel downstream.
  • Liability. "OpenRouter shall be liable for the acts and omissions of its Subprocessors to the same extent OpenRouter would be liable if performing the services of each Subprocessor directly under the terms of this DPA." Your counterparty does not get to point at its supplier.
  • The list. The DPA names Schedule 3 as the current list of authorised sub-processors, published at trust.openrouter.ai. It also incorporates Module 2 (controller to processor) of the EU Standard Contractual Clauses for EEA transfers, with the UK Addendum and Swiss modifications for those jurisdictions.

That three-part shape — authorise, flow down equivalent obligations, stay liable — is not a vendor invention. It mirrors GDPR Article 28. Article 28(2): "The processor shall not engage another processor without prior specific or general written authorisation of the controller." Article 28(4) requires that where a processor engages another processor, "the same data protection obligations as set out in the contract or other legal act between the controller and the processor ... shall be imposed on that other processor," and that "the initial processor shall remain fully liable to the controller for the performance of that other processor's obligations." Once you recognise the pattern you can read any router's, gateway's, or inference vendor's DPA quickly, because they are all answering the same three questions.

What an engineer owes here is narrow and worth being precise about: find the clause, quote it accurately, note its date, and hand it to whoever owns compliance where you work. Whether your particular flow is adequately covered is their call and depends on facts about your organisation that a technical page cannot know. The failure mode this section exists to prevent is not "you reached the wrong legal conclusion" — it is "nobody ever opened the document."

Finding and watching the sub-processor list

Every serious processor publishes its authorised sub-processors somewhere, and the somewhere is almost never the DPA text itself — it is a schedule by reference, usually a trust page. That indirection is deliberate on the vendor's side (the list changes more often than the contract) and it has a practical consequence for you: the list is a moving document you are expected to watch, and most DPAs give you a notice-and-object right that only means anything if somebody is actually reading the notices.

Check the mechanics before you rely on it. Is the list a plain HTML page you can fetch and diff on a schedule, or is it rendered client-side inside a trust-centre widget that a curl returns empty from? OpenRouter's is on a trust-centre platform reached through an item view rather than a flat static page, which is the common case in 2026 and the annoying one: the list exists, it is authoritative, and it does not lend itself to a five-line cron job. Budget for that when you write down "we monitor sub-processor changes" — either accept a manual review cadence or build the scrape properly. Do not write a monitoring claim you have not implemented.

As-built: one config line, three parties, two wrong pages

This site ran the whole sequence on 24-25 July 2026, and it went badly enough to be worth writing down.

The change was small. modelFor() in src/lib/llm.ts gained a fifth role, safeguard, returning the canonical id gpt-oss-safeguard-20b; wireModel() gained one map entry pointing it at the OpenRouter slug openai/gpt-oss-safeguard-20b. The call site is isolated in src/lib/injectionJudge.ts, a classifier that reads a learner's own recent messages before the AI coach replies and decides whether they are an attempt to override the coach's configuration. Two lines of routing, one new file, a clean review.

With that in the tree, two of this platform's public legal pages no longer described what the system does. /privacy sets out who receives coach messages; /subprocessors lists every processor with its region and transfer safeguard. Neither named a party that would now, on every single coach turn, receive learner-authored text. Both files were untouched by the diff, which is exactly why nothing flagged them — the correction had to be gone looking for and landed in the same batch, not surfaced by review. The check that catches this is not a code review at all: it is the habit of asking what a change invalidated outside its own blast radius.

Then the count. One line of config, three organisations: OpenAI published the weights and never sees the data; Groq serves them and does; OpenRouter routes to Groq and is the party we actually have an agreement with. Settling how to disclose that meant reading OpenRouter's DPA sub-processor clause — the flow-down and liability language quoted above — and not its terms of service, which only point at the DPA. That reading is what let the site describe Groq as engaged by OpenRouter as its sub-processor, with our own transfer safeguard being the SCCs we hold OpenRouter to.

The honest limitation, since a page like this is worthless without one. Our own subprocessors page does not name Groq as a fixed serving path. It names Groq as the current one and says outright that OpenRouter may route to other providers in its network. That is a weaker disclosure than naming a single company, and it is deliberate: src/lib/llm.ts sends no provider routing block, so the serving path is genuinely OpenRouter's choice at request time and a fixed-provider claim would be false the first time it rerouted. We chose the router's availability behaviour over a narrower disclosure, and the disclosure carries the cost of that choice. The alternative — pin with provider.only, then name one company — is available and is the right call for anyone whose customers ask for a fixed list. Pick one on purpose; do not end up hedging by accident.

The checklist for adding a model

Five steps, in order, in the same change that adds the model.

  1. Re-answer "who processes this text now." Not "did the model change" — who receives the bytes. Every model addition, every switch, every fallback you configure.
  2. Resolve the serving path, not the model card. For open weights the publisher is usually irrelevant to the data question. Query the router for the endpoints actually serving your slug, and record the date you asked.
  3. Decide pinned or variable, explicitly. If you pin (provider.only or equivalent), your disclosure can name a company. If you do not, your disclosure has to survive the router changing its mind — write it that way from the start.
  4. Read the intermediary's DPA sub-processor clause. Authorisation, flow-down, liability, and where the current list lives. The terms of service will usually just point at it. Quote what you find, with its date.
  5. Update your own disclosures in the same change. Privacy policy, subprocessor page, records of processing, any customer-facing schedule. If they ship separately from the code, they ship late, and late means a period where your published statements were wrong.

The second-order habit worth building is broader than models: for any change, ask which unchanged files just became untrue. Model routing is the sharpest current instance of it, not the only one.

Where this reasoning stops

Three limits, stated plainly.

This is engineering guidance about data flow and vendor contracts, not legal advice. It tells you which question to ask and which document answers it. It cannot tell you whether your processing is lawful, whether your safeguards are adequate, or what your organisation must publish — those depend on your role, your jurisdiction, your customers' contracts, and facts a technical page has no access to. Take the clause and the date to whoever owns that call.

Every quotation here is one vendor's contract on one day. Contracts get amended, sub-processor lists change, routers add providers, and the endpoint counts in the table above were true on 25 July 2026 and probably are not now. Re-run the check rather than citing this page's numbers.

This is the intermediary case. If you call an inference provider directly with no router in between, the chain is shorter and the sub-processor question is simpler — though the provider will still have its own sub-processors. If you self-host open weights on infrastructure you control, the serving party is you, and the interesting questions move to your hosting provider instead. The reasoning transfers; the number of parties does not.

Frequently asked questions

Does adding a model really change my subprocessors?

It changes who processes the data, which is the thing a subprocessor list is meant to describe. Whether it changes the list depends on the path: routing a new model to a provider you already disclosed may add nothing, while routing to one you have never named adds a party. The point is that you cannot know which case you are in without checking the serving path, and the default assumption in most teams is that a model change is a model change.

Is the model's publisher my data processor?

Not necessarily, and with open weights usually not. Publishing weights under a permissive licence means anyone can serve them; the publisher does not receive requests made against a third party's copy. Your processor is the company running the inference plus whoever you contracted with to reach them. The model card tells you who trained it, which is a different question from who sees your text.

What is the difference between a vendor's terms of service and its DPA?

The terms of service govern your commercial relationship; the data processing agreement governs what the vendor may do with personal data you send it, including whether and how it may engage sub-processors. Many vendors incorporate the DPA into the ToS by reference — OpenRouter's section 10.2 does exactly this — which means the ToS is binding on the DPA's content while telling you almost nothing about it. If your question is about data, open the DPA.

Where do I find a router's sub-processor list?

The DPA will name a schedule and the schedule will point at a URL, typically a trust page. OpenRouter's DPA names Schedule 3 and publishes the list at trust.openrouter.ai. Check whether that page is fetchable as plain HTML before promising anyone that you monitor it for changes — many trust centres render their content client-side, which turns "we diff the list weekly" from a cron job into a calendar reminder.

Do I have to update my privacy policy every time I switch models?

Ask the question every time; the answer will often be no. A switch between two models served by parties you already disclose changes nothing on the page. A switch that introduces a new company — a new router, a new inference provider, a first open-weight model reached through a provider network — is the case where an unchanged page becomes untrue. The cost of asking is a minute; the cost of not asking is a published statement that was wrong for however long it took someone to notice.

Can I just pin the provider and avoid all of this?

Pinning makes your disclosure precise and your routing brittle in the same move — you give up the multi-provider failover that a router exists to provide, so a single provider's outage becomes your outage. It is the right trade when customers require a named, fixed list, and the wrong one when availability matters more than disclosure precision. Either way you still have to read the DPA, because the party you pinned is still someone's sub-processor.

Sources & provenance
  • OpenRouter Data Processing Agreement (last updated 5 May 2026) — sub-processor authorisation, "no less protective" flow-down, liability for sub-processors' acts and omissions, Schedule 3, SCC Module 2: openrouter.ai/data-processing-agreement. Verified 25 Jul 2026.
  • OpenRouter Terms of Service (last updated 6 July 2026), section 10.2 — the DPA "is incorporated by reference into, and made a part of, these Terms": openrouter.ai/terms. Verified 25 Jul 2026.
  • OpenRouter authorised sub-processor list (Schedule 3): trust.openrouter.ai. Verified 25 Jul 2026.
  • OpenRouter provider routing — default load balancing across top providers, and the order / only / allow_fallbacks / ignore controls: openrouter.ai/docs/features/provider-routing. Verified 25 Jul 2026.
  • Endpoint counts (1 / 11 / 17) read live from OpenRouter's public models endpoints API on 25 Jul 2026, e.g. /api/v1/models/openai/gpt-oss-safeguard-20b/endpoints.
  • gpt-oss-safeguard-20b — published by OpenAI, Apache 2.0, 21B parameters (3.6B active), a safety-reasoning model that classifies content against a policy you supply: huggingface.co/openai/gpt-oss-safeguard-20b. Verified 25 Jul 2026.
  • GDPR Article 28(2) and 28(4) — prior authorisation for engaging another processor, imposition of the same data protection obligations, and the initial processor remaining fully liable: Regulation (EU) 2016/679, Art. 28.
  • As-built: this platform's own src/lib/llm.ts (modelFor / wireModel) and src/lib/injectionJudge.ts, built 24-25 Jul 2026; the resulting disclosures are on /subprocessors and /privacy.

This is an engineering guide about data flow and vendor contracts, not legal advice — it names the question to ask and where the answer lives, and cannot tell you what is lawful for your organisation. Contract text, sub-processor lists, and provider counts change without notice; verify against the primary sources on the day you need them. Corrections: hello@aiarch.dev.

Learn to see the seams, not just the code.

aiArch teaches the architecture and governance of production AI systems as one subject — on a platform that is itself a production agentic system, where changes like this one get made, get caught, and get written up. The build is the curriculum.

Free sample — no signup · every claim cited · full curriculum is waitlist-only

Or get notified when new tracks ship.