Data policy
Content-free by design; what is and is not stored.
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 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_bytesandresponse_bytesso throughput is measurable without storing a single byte of what was said. - Image bytes and revised prompts are content. On
/v1/images/generationsthe prompt, the base64 image and anyrevised_promptare never logged, never journaled and never stored — only the response byte size is. - Videos are content too. On
/v1/videosthe 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.
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.
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.
Account authentication
Public signup and sign-in use Clerk in the browser at Sign up and Sign in. 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 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:
{
"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
zdrflag:
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). 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:
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.
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 for account and API-key access, and Errors for the error taxonomy referenced above.
Markdown source for agents: /docs/data-policy.md · index at /llms.txt