# Data policy

The marketplace sits between your application and model providers. Its data
posture is simple: **content-free by design**. Your prompts and the
model's responses pass through the gateway on their way to and from the
provider — they are never written down. What the marketplace records about a
request is token *counts* and metadata: ids, timings, prices, error codes.

This is not a configuration option or a retention window. There is no code path
in the gateway that persists request or response content, so there is
nothing to opt out of and nothing to delete later. One product is different by
design, and says so: [Optimize](/docs/model-search) stores the eval cases and
candidate answers of a search, for your organization only, until you delete the
search. Nothing else holds content.

## The content-free rule

Every store on the request path holds metadata only:

| Store | What it holds | Content? |
|---|---|---|
| Process logs | request id, surface, model, outcome, HTTP status — every buyer-controlled string sanitized first | Never |
| Telemetry (analytics) | ids, org/key ids, provider, deployment, model, region, attempt ordinal, outcome, error code, statuses, timestamps, latency, five token counts, request/response *byte sizes*, cost, provider request id (house routes only; see below) | Never |
| Billing ledger | per-attempt row: the prices in force, reserved maximum, settled cost, token counts, HTTP status, error code, provider request id (house routes only; see below), timestamps, route and admission context | Never |
| Rendered pages | model directory, model pages, console, status — all render from metadata | Never |

Some details worth knowing:

- **Model names appear in logs** (they are routing metadata), but every
  buyer-controlled string is sanitized before it touches a log line: control
  characters are stripped and length is capped, so request fields cannot forge
  log entries.
- **Error bodies never echo your input.** Every error body is the gateway's
  own typed shape; a provider's raw error body is never relayed. When a refusal
  is translated across dialects or surfaced mid-stream, the gateway emits only
  the typed error class.
- **Byte sizes are recorded, bodies are not.** Telemetry keeps
  `request_bytes` and `response_bytes` so throughput is measurable without
  storing a single byte of what was said.
- **Image bytes and revised prompts are content.** On
  [`/v1/images/generations`](/docs/api-images) the prompt, the base64 image and
  any `revised_prompt` are never logged, never journaled and never stored — only
  the response byte size is.
- **Videos are content too.** On [`/v1/videos`](/docs/api-videos) the prompt and
  the video file are never logged, journaled or stored. The gateway keeps the job's
  id, model, length, shape, status and charge, so you can read the job and fetch
  the file later; the file itself streams through from the provider when you
  download it. A provider's error text is reduced to a code before anything keeps it.
- **The playground keeps your conversations, images and videos in your browser**:
  conversations in local storage, images and videos in IndexedDB, filed by your
  sign-in, the conversation and the reply. They are never uploaded. Deleting a
  chat, or **Clear all**, deletes them.
- **A playground decision is content-free too.** The state and the answers pass
  through the control plane and the gateway to the decision model's provider,
  and are never logged or stored. The gateway keeps what it keeps for every
  request: model, provider, token counts and cost.
- **Raw API keys never appear anywhere** — not in logs, not in the ledger, not
  in error bodies. The gateway authenticates by comparing a SHA-256 hash.
- **The provider request id is kept on house routes only.** It is the reference
  number a provider gives one call, so a failed call can be traced with that
  provider. A house route runs on the marketplace's own provider account. A call
  through a provider credential you connect (BYOK) never keeps the id. A provider
  can put the API key into that header, so the gateway drops an id that contains
  the key it sent, or any 16 characters of the key in a row. It also drops an id
  longer than 256 characters.

> [!NOTE]
> One debug exception exists, and it is disclosed rather than hidden: setting
> `TM_UNSAFE_LOG_BODIES=1` prints request bodies to stdout for local debugging.
> The flag hard-refuses to activate outside `local`/`demo` regions — a
> production deployment that sets it gets a logged warning and no body logging.
> It never touches telemetry or the ledger in any region, and an image prompt
> never prints even under it.

The rule is enforced by code, review and tests: the integration suite plants
sentinel content in playground chats, image and video prompts, and asserts it
never appears in the control plane's or the gateway's logs, or in Postgres.

## What we do store

Running an account requires a small amount of real data:

| Data | Why | Form |
|---|---|---|
| Email address | Sign-in and billing receipts | Stored canonicalized (lowercased; gmail dots and `+tags` collapsed, `+tags` stripped elsewhere) so aliases share one account and promotional-credit eligibility |
| Members' emails | Organization membership and roles | Canonicalized the same way |
| API keys | Gateway authentication | SHA-256 hash plus the first 12 characters (so the console can show you *which* key). The raw key is shown exactly once, at creation |
| Legacy and operator-issued email-link tokens | Redeeming previously issued sign-in links | SHA-256 hash only, single use, 30-minute expiry |
| Clerk identity and session references | Connect verified sign-ins to the same marketplace account and support sign-out | Clerk issuer, user/session ids, verified canonical email, organization id and session timestamps |
| Authentication email delivery IDs | Prevent duplicate sends when the Clerk email relay is enabled | Message id, outcome and timestamp only; no email body, sign-in link or recipient in this table |
| Per-attempt metadata | Billing and the usage you read back | The ledger row described above |
| Provider credentials you connect (BYOK) | Routing through your own OpenAI, Anthropic, Azure or Bedrock account | Envelope-encrypted; never returned by any API or page. A Bedrock connection stores a role ARN, never AWS keys |
| Braintrust API key (Optimize) | Reading your evals | Encrypted; only its last four characters are ever shown |
| Optimize searches | Comparing models on your evals | The eval cases and the candidates' answers, scoped to your organization, deleted with the search |
| Video jobs | Following a render across reloads | Job id, model, length, shape, status and charge — never the prompt or the file |
| App attribution | Optional `HTTP-Referer` / `X-Title` headers you send | Stored as-is in telemetry — send them only if you want your app identified |

The marketplace does not store passwords. Clerk handles passwords, email
verification and Google sign-in. Google sign-in requests
basic identity information (OpenID, email and profile); it does not request access
to Gmail, Drive or Calendar. The marketplace uses the verified email to find your
existing account, including accounts originally created through an email link.
Clerk processes authentication data under its
[privacy policy](https://clerk.com/legal/privacy).

Authentication emails can be delivered through Resend, including Clerk-generated
email links. The relay verifies the sender's signature and does not log or persist
the email body or sign-in link. Resend processes delivery data under its
[privacy policy](https://resend.com/legal/privacy-policy).

## Account authentication

Public signup and sign-in use Clerk in the browser at
[Sign up](https://app.routerplus.com/signup) and [Sign in](https://app.routerplus.com/login). Complete the
email-verification and authentication steps shown there. The local magic-link
submission and programmatic signup routes have been removed. Existing accounts
and API keys are preserved; Clerk connects a verified email to the same
marketplace account.

After Clerk verifies a sign-in, the marketplace issues its own seven-day signed
session cookie (`HttpOnly`). Marketplace **Sign out** revokes that cookie and the
linked Clerk session. Clerk-backed sessions are rechecked during authenticated
requests, using cached proof for at most 30 seconds. A revoked or expired Clerk
session can no longer authorize new marketplace requests after that cache expires.
Other devices stay signed in.

Beside the session cookie the browser holds a sign-in hint (`tm_in`, the same
expiry, readable by script) that says only "signed in", and a display cache
(`localStorage["tm-acct"]`: your email and your balance as the last page showed
them). They let every page draw your account menu and credits at once. Neither
opens anything, and both are cleared on sign out.

Previously issued marketplace email links and operator-issued links use stored
SHA-256 token hashes; a leaked database row is not a working link. These links
are single-use and expire. They do not provide a public account-creation route.

See [Authentication](/docs/authentication) for browser onboarding and API keys.

## What providers do with your prompts

The marketplace not storing content does not mean the *provider* serving your
request stores nothing. Every provider in the catalog must declare its prompt
retention in its manifest before it can serve traffic:

```json
{
  "provider": {
    "id": "anthropic",
    "privacy_policy_url": "https://www.anthropic.com/legal/privacy",
    "prompt_logging": "retained"
  }
}
```

`prompt_logging` is a closed enum — `"none"` or `"retained"` — and a manifest
without it (or without a privacy policy URL) fails conformance and never goes
live. The flag is surfaced where you make decisions:

- On every **model page**: the Data policy card says the marketplace's logs are
  content-free and how many of the model's providers offer zero data retention,
  and the providers table has a **Retention** column per provider.
- In the machine-readable catalog, per provider of each model, and per host
  behind an aggregator as a `zdr` flag:

```bash
curl -s https://app.routerplus.com/api/models.json | python3 -c \
  'import json,sys; [print(m["id"], "—", [(p["id"], p["prompt_logging"]) for p in m["providers"]]) for m in json.load(sys.stdin)["models"]]'
```

Every provider in the catalog today declares `"retained"`, per their published
policies, except Fastino, Cloudflare Workers AI, Perplexity and RouterPlus. Levanto, which serves the
decision model Sage, keeps no ordinary request, but keeps the full request of a
failed or slow one for 30 days to debug it, so it declares `"retained"` too.
Fastino, which serves the decision
model GLiNER-2.5-Decide, declares `"none"`. Fastino keeps an inference unless the
request says `store: false`, and the gateway sends `store: false` on every
request. Fastino's terms let it train on inputs unless a team has Zero Data
Retention on; our Fastino team has it on. So Fastino keeps nothing and trains on
nothing. Cloudflare Workers AI, which serves the decision models Clef and Clef-flash,
declares `"none"`, on Cloudflare's own statements. Of Clef, Cloudflare says: "we don't
read, store, or train on your requests or responses (unless you want to use our
fine-tuning product)". We do not use that product. Cloudflare's Workers AI data-usage
page says that Cloudflare does not use a customer's content to train any AI model on
Workers AI, or to improve any Cloudflare or third-party service, without the customer's
explicit consent. It also says that Cloudflare stores the content only if the customer
uses a Cloudflare storage service (R2 or KV, for example) with Workers AI. We use none.
The gateway calls the Workers AI REST API directly. It never sends Cloudflare's AI
Gateway header (`cf-aig-gateway-id`), so our requests do not go through AI Gateway,
whose logs are on by default and can hold the full prompt and response. Cloudflare's
replies to our calls carry none of AI Gateway's `cf-aig-*` headers.
Perplexity, which serves the decision model Perplexity Decider v1 27B, declares
`"none"`, on Perplexity's own statements. Its API FAQ says: "We do not retain any query
data sent through the API and do not train on any of your data." It also says that the API
has zero day retention of user prompt data by default, never used for AI training, and that
its compute is hosted on Amazon Web Services in North America. Perplexity keeps the usage
metadata it bills from, such as the number of requests and tokens (its API FAQ). The gateway
calls Perplexity's API directly, with our one Perplexity key.
Bespoke Labs, which serves the decision model Bespoke Nimble v3, declares
`"retained"`: Bespoke keeps API inputs, outputs and request logs for up to 30 days,
to debug the service and check for abuse, and then deletes them. It also keeps the
metadata it bills from (token counts, times, key id, status). Bespoke does not
train on API content unless an organization opts in, and ours does not.
OpenRouter publishes no retention terms for the free endpoint that serves the
decision model Mercury Decide, so that deployment (`openrouter-decisions-free`) declares
`"retained"`, like `openrouter-decisions`. For a model served through
OpenRouter, `served_by` lists the hosts that actually run it and which of them
OpenRouter classifies as zero data retention; a request can pin a host with
`provider.upstream` (see [Routing policies](/docs/routing-policies)). We report
the flags; we do not editorialize them.

### RouterPlus

RouterPlus is our own decision-model service. It serves our models Decider 2B and Kev 4B,
and this page is its data policy. Its GPUs run in the United States.
RouterPlus writes no request logs. It keeps a cache of its answers: each answer and its
usage, for 600 s (10 minutes). The cache key is the RouterPlus API key, the
model and the exact request body, byte for byte, so only an identical request gets a cached
answer. A cache entry is never shared across RouterPlus keys, but the gateway sends every
request with our one RouterPlus key: an identical request from any of our buyers within
600 s can get the cached answer, which repeats your option names. The cache is all it
keeps, so the listing declares `"none"`. RouterPlus
does not use your states, questions or answers for training. The marketplace side is the
same as for every provider: content-free, as this page describes.

## Supply policy: where your requests can land

Models from PRC-domiciled labs (DeepSeek, Moonshot, Z.ai, MiniMax, Tencent,
Xiaomi) are listed through OpenRouter, not through those labs' own APIs: the
catalog has no first-party deployment for them. Which host runs a given request
is OpenRouter's choice among the hosts `served_by` lists for the model, unless
you pin one with `provider.upstream`. This gets revisited only if a buyer needs
first-party pricing or features the hosts lack — and it would be revisited
openly, not quietly.

## Reading your own metadata back

Everything the marketplace knows about your requests, you can read back with
your API key:

```bash
curl -s -H "Authorization: Bearer $TM_API_KEY" https://api.routerplus.com/v1/usage
curl -s -H "Authorization: Bearer $TM_API_KEY" "https://api.routerplus.com/v1/generation?id=<request_id>"
```

Both endpoints read the billing ledger — balance, attempts, token counts,
settled cost. Neither can return message content, because none was stored.

> [!TIP]
> If you need to audit a specific request, keep the `x-request-id` response
> header from the original call — it is the lookup key for `/v1/generation`,
> and the id the marketplace will use if you write in about a billing dispute.

See [Authentication](/docs/authentication) for account and API-key access, and
[Errors](/docs/errors) for the error taxonomy referenced above.
