Access is by arrangement. Keys are issued directly while we onboard the first partners, rather than through self-serve signup. Everything on this site describes the product as built — what is actually running is published, and supersedes any page it disagrees with.

Trust

What is true today, what is in progress, and what we deliberately do not do — each with the evidence, so you can check it rather than take it.

The vault

How much we hold, who can open it, and how you find out. The design is that nobody here can open your content alone at any tier, and nobody can open it quietly at any tier. Quietly is already impossible — every unseal lands in your own chain. Alone is what the second and third key holders end, and the box directly below names which of them the running software has.

Implementation status, before the design

The seal runs. Started with --vault-key, the router seals request content at stream close, the receipt for that request reads vault: { mode: "managed", written: true }, and /health publishes a vault.sealed counter that rises with it. The cryptography is AES-256-GCM per record under a fresh data key, the key wrapped per holder with X25519 and HKDF, and a genuine Shamir 2-of-3 split whose share-independence is proved exhaustively over the field rather than asserted.

The rest of the tier table arrives with the holders and the store. Today there is no KMS integration, so the only holder is our own key — the start-up banner says so in as many words, “our_key seals only (no customer or escrow holder configured)”. There is no customer bucket. There is no escrow agent relationship, so zdr_escrow is a mechanism we can show you the proof for, not a service you can buy. The store is on the local filesystem of one process, not a durable multi-region one.

The tier table is the product as designed. The banner your own router prints at start-up is what it does today. What is actually running.

Read the tier table before the mechanism

We do not offer unconditional zero retention, and the reason is contractual. Reselling model access carries a duty to be able to investigate an abuse report — identify the customer, act, and cooperate with the vendor. A gateway that can never see anything cannot discharge that duty, and some obligations, CSAM reporting among them, have no privacy-architecture exemption at all.

So retention is an account-level contracted tier over a minimum-retention floor, and the strongest tiers are things you qualify for rather than toggle. That is also how the model vendors run their own ZDR programmes.

The four tiers

TierWhat we holdWho can open itHow you knowGating
open default30 days, encrypted under our keyus, break-glass, two-personunseal receipt in your chainself-serve
mdr7 days, encrypted under our keyus, break-glass, two-personunseal receipt in your chainverified business
zdr_escrow72 hours, sealed 2-of-3nobody alone — we hold one share of threeunseal receipt, and the absence of one is provablecontract + KYC
zdr_absolutenothing — your bucket onlyonly youthere is nothing to opencontract + indemnity + minimum commitment

The 2-of-3 seal, in detail

On zdr_escrow the data key is not wrapped to one party. It is split into three shares under a 2-of-3 threshold scheme:

  • You hold one share.
  • We hold one share.
  • An independent escrow agent holds the third — named in your contract, and never a party we control.

Four consequences, each a property rather than a promise:

  • We cannot decrypt unilaterally. Not "we have a policy against it" — we hold one share of three. An insider with full production access still cannot open your content.
  • An investigation requires two parties to cooperate, which is a real procedural bar with a paper trail, and it satisfies the vendor obligation to be able to investigate.
  • Every unseal — attempted, refused or completed — is a record in your own receipt chain, carrying the reason, the outcome and how many shares were presented. You cannot be investigated silently.
  • The absence of an unseal is provable. Because the chain is complete and signed, "nobody opened anything last quarter" is something you can verify offline rather than something we assert.

The window is 72 hours by default and the ciphertext is destroyed on expiry, so even the worst case is bounded.

Where the content itself goes

How a request is sealed

  1. The stream closes. We assemble the request, the response and any reasoning text the provider actually emitted.
  2. We mint a fresh 256-bit data key and seal the payload with AES-256-GCM.
  3. We wrap that data key to your KMS key — AWS KMS, GCP KMS, Azure Key Vault or an HSM you control. We never hold anything that unwraps it.
  4. We write the ciphertext, and zero the plaintext before the request arena is released.

customer_bucket mode arrives with the object-store integration: step 4 writes into your own bucket via a role you grant, and we hold nothing at all — not the key, not the ciphertext. It is the mode we expect enterprises to pick and the one we will recommend. The router has no object-store integration today, in any mode, and step 3 wraps to our key because there is no other holder configured.

What we are honest about

KEY LOSS

Lose the key and the data is gone

Permanently. That is the guarantee working, not failing. Use your own cloud KMS so your existing key-management and recovery tooling apply, or enable escrow — a second wrapped copy under a key held by a party you nominate, never us, off by default, and disclosed on every receipt if enabled. A privacy posture with a silent exception is worse than none.

NO SEARCH

There is no server-side search, and never will be

It is not on a roadmap. Search runs in prism export, on your machine, against the copy you decrypted. Anyone offering semantic search over “your private logs” is holding a readable copy of them — that is not a criticism, it is arithmetic.

IN MEMORY

We do see plaintext while proxying

Unavoidable for any gateway, load balancer or TLS-terminating proxy. What is avoidable is writing it anywhere, and no code path does — including the error paths, which is where this normally leaks. A CI guard traces body-typed values and fails on any reaching a writer.

FAILURES

A vault write can fail, and never silently

It cannot fail your request: the seal runs after the answer has been produced, and a copy we could not keep never becomes an answer you do not get. It increments vault_write_failures, publishes on the status page, and marks the receipt vault_written: false, so a gap in your export is visible from the chain rather than discovered a quarter later.

Retention postures

Four values. Each one names the class of document and the verification date that would back it, and the rate card records, per endpoint, whether that document has been read — the “Evidence we hold” column below is the shape of the evidence, not a claim that we hold it. Your policy sets a floor; endpoints below it are removed from the candidate set rather than ranked lower. A fallback may never lower your posture.

PostureMeansEvidence that would back itRe-checked
zdr_contractualThe vendor contractually retains nothing.DPA clause reference and dateon contract renewal
zdr_configuredThe vendor supports zero retention and it is enabled on our account.org setting plus API verificationevery 30 days, automatically
transientRetained N days for abuse monitoring only.published policy and the windowevery 30 days
retainingMay be retained indefinitely or used for training.published policyevery 30 days
The published claim and the routing filter are one fact

One posture field, read by the eligibility filter and published by the catalogue. That is the design, and drift between the two is the usual way a retention promise quietly becomes false. It is now one field in one file. Every one of the 25 cards in router/data/rate-card.json and every one of the 10 unpriced endpoints carries the schema's retention block, the loader installs those postures into the routing filter at startup, and a bundle in which any card, any unpriced declaration or any catalogue endpoint has no block fails the load and the router exits rather than starts. The compiled table that used to answer this question survives only as a named fallback, and the boot banner prints which of the two answered.

The evidence still has to arrive. All 35 of those blocks read verification_state: unverified, with evidence_url and verified_at null and a written reason in unverified_reason, because we hold no DPA clause reference, no zero-retention addendum and no verified organisation setting for any endpoint — and there is no vendor contract for one to live in. The loader refuses a block that carries an evidence URL while claiming nobody read it, and refuses one that claims verification and shows nothing, so the two states cannot be blurred. What changed is that the posture became accountable, not that it became evidenced, which is why the catalogue still publishes no posture per endpoint.

“ZDR on all tiers where possible” is the honest phrasing, because it is not always possible: some models are served only on retaining substrates. We list those endpoints in the catalogue rather than hide them, and a policy with a ZDR floor simply cannot see them. A smaller catalogue that tells the truth is the entire pitch.

Security architecture

AreaWhat we do
AuthenticationEnforced. Started with --tenants <register> the router authenticates every request against a credential register; started with neither that flag nor an explicit --no-auth it refuses to boot rather than serve an open port. Keys are sk_prism_<kid>_<secret>, the plaintext is shown once at mint and never stored, the register keeps a SHA-256 verifier, and an unrecognised or malformed key is refused 401 before any table is consulted. Receipts carry org_id and key_ref, both inside the signature. Credentials have a lifecycle over the wire, on a separate admin credential type: /admin/orgs, /admin/keys and /admin/revoke, the last of which withdraws a leaked key on a running router — measured, the next request with it is refused 401 without a restart. Two things this row must not overclaim: the key hash is SHA-256, not argon2id — an earlier version of this row said argon2id and no such code has ever existed here — and there is no request-signature scheme, so a bearer token does cross the wire. Per-organisation rate limiting is also not enforced.
IsolationPer-request arena memory, freed at close. No cross-request shared mutable state on the hot path.
TransportTLS 1.3 to clients and to every upstream. HSTS. No plaintext hop anywhere, including internally.
SecretsUpstream vendor credentials are read from the process environment and never written to disk or to a log; started with neither credentials nor an explicit mock flag the router refuses to boot rather than pretend. There is no KMS integration — that is a design we have written down, not a system we run — and no BYOK arrangement, so there are no per-organisation provider credentials to seal. A pre-push hook blocks any commit containing a credential pattern, installed before the first commit rather than after the first leak.
Receipt logAppend-only files with the kernel append-only flag set, one write per record, rotated daily. A stray redirect or a careless cleanup script cannot destroy history. Rotation clears the flag deliberately.
Conversation groupingA keyed hash under a per-install secret — never a raw session id and never a hash of your text. A short hash of real user text is brute-forceable, and an audit log written to scope a privacy incident must not become one.
AccessLeast privilege, MFA required, and production access is audited. Support cannot read your content because there is no readable copy to read.

Compliance position

Read this before you read anything else on the page

Prismux is pre-launch and has no customers. We hold no third-party attestations, and we do not display badges for audits that have not happened. Everything below is marked with what is actually true today.

ItemStatusDetail
GDPR / UK GDPR processor termsdraftDPA published as a draft pending counsel review.
Subprocessor listpublishedIt is the catalogue. One document, so it cannot drift from the routing filter.
Records of processingby designGenerated from the receipt log rather than maintained by hand.
Data residency enforcementby designA routing filter that fails closed, not a preference.
Deletion / erasure requestsby designTrivial in off mode (nothing exists) and in customer_bucket mode (your lifecycle policy). A ciphertext delete in managed mode.
SOC 2 Type Inot startedPlanned before the first regulated-enterprise engagement.
SOC 2 Type IInot startedRequires a 6–12 month observation window. We will publish the window start date when the clock begins.
ISO 27001not started
Penetration testnot startedSummary will be published; full report under NDA.
HIPAA / BAAnot offeredWe will not sign a BAA we cannot honour end to end across every upstream.

When any row changes we will date the change here rather than quietly swap the chip. A trust page whose history you cannot see is a marketing page.

Subprocessors

Under GDPR Article 28 you are entitled to know every party that processes your data, and to be told when the list changes. For a model gateway the subprocessor list is the set of endpoints traffic can reach — so we publish one document, not two that can drift.

The catalogue is the subprocessor list. Every row is a model vendor and a hosting substrate, with the jurisdiction that serves it and the retention terms we hold them to.

Infrastructure subprocessors — the parties that touch metadata rather than content — are separate and short:

PartyPurposeDataRegion
Cloud compute and object storagedata plane, receipt log, managed vault ciphertextmetadata; ciphertext we cannot readyour chosen residency
Managed Postgresaccounts, keys, journal, receipt indexmetadata and money, never contentyour chosen residency
Payment processorbilling and fraud signalsbilling details; never request dataglobal
Email deliveryalerts, invoices, noticescontact detailsglobal

Notice of any change: 30 days before it takes effect, with a right to object. A policy restricting posture or residency also restricts your subprocessor set — and every receipt names the endpoint that actually served you.

Resale standing

A question most gateways do not invite, so here it is directly. Reselling model API access is not automatically permitted: vendor terms vary on onward resale, some paths are explicitly designed for it, and others require a named agreement.

PathStandingWhat it means for you
Cloud marketplace substrates (Bedrock, Vertex, Azure)designed for itThese paths exist to be resold and are contracted accordingly.
Direct vendor endpoints, managed billingper vendorWe will state our standing per vendor here, with the date, once agreements are in place. We hold none today, which is why there is no launched managed-billing product to buy — and why no request this router has ever served went to a real vendor.
BYOK is not a product here

There is no bring-your-own-key option on this platform. It does not exist and it is not planned: BYOK means the customer holds the vendor relationship, which is the opposite of a business whose whole basis is that we hold the agreements.

So our resale standing, stated per vendor above, is the question this business turns on, and we are not hedging it with a second product.

Service levels — two numbers, never one

Our uptime cannot exceed the union of our upstreams' for a model you have pinned. Publishing one blended figure would hide exactly that, so we publish two.

Both take their values from traffic, and there is none yet. Nothing has been measured because nothing is deployed — the 99.99% and 99.9% this page once printed were measurements of nothing. Neither is a contractual target either: we name an SLO once we can show the history behind it, and not before. The shape of the measurement is the part that is settled.

Router availability
no data
We accepted, routed, metered and receipted correctly, given a healthy upstream. This is the part we control and the part we will contract on.
Effective availability
no data
Your request got a usable answer. Bounded by the union of your eligible endpoints — and therefore by your policy, which is why we report it per model and per policy.

A policy allowing rung-1 fallback has a materially higher effective availability than one pinned to a single endpoint. You should be able to see that number for your own configuration before choosing, so the dashboard shows it — an honest SLA that doubles as the argument for a better policy.

Live figures and history: status.

Enforcement without reading your content

Metadata alone is not sufficient to enforce an acceptable use policy, and it is worth being exact about where it works and where it does not.

What metadata catches well: fraud, credential abuse, stolen-card credit purchases, and industrialised abuse operations — which have a traffic shape nothing else does. What it catches poorly: a low-volume, well-funded customer generating prohibited content. That is the case that ends companies, and no amount of concurrency-curve analysis finds it.

So enforcement runs on four content-blind signals plus a retention floor:

SignalWhat it seesWorks at tier
Blind matchingA perceptual hash of content checked against known-bad fingerprint sets. Learns matched: true, never the content — the principle image platforms have used for over a decade.all, including zdr_absolute
Vendor filter signalsThe model vendor's own filters still run. A key accumulating content_filter refusals is actionable without our seeing anything.all
Traffic shapeRequest distributions, token ratios, refusal rates, model mix, concurrency curves, key-creation velocity.all
Payment and identityPrepaid first, spend velocity earned rather than granted, business verification above a threshold, KYC for the contracted tiers.all
Sealed content, on causeThe minimum-retention window, or the 2-of-3 unseal. The path that exists precisely because the four above are not sufficient on their own.open, mdr, zdr_escrow

At zdr_absolute the last row does not exist, which is why that tier requires you to accept the enforcement duty contractually: AUP flow-down to your end users, a named responsible officer, and indemnity. The duty does not evaporate because the ciphertext did — it moves.

What we will not do at any tier: read prompts to moderate them as a matter of course, or retry a vendor's content-filter refusal on a different vendor to get around it. Routing around a filter would make us a laundering service for the thing the filter exists to prevent. Full policy: acceptable use.

An operator running their own router can add a content classifier on top of all of this. It is off unless they switch it on, and what it is allowed to write into your receipt is deliberately almost nothing — the classifier layer.

The classifier layer, and what a receipt may say about it

Everything above is content-blind. This is the one control that is not, and it is switched off unless an operator switches it on. A classifier inspects the request before it is routed, or the answer before it is returned, and the router obeys what it says — allow, flag, transform, or block. It is pluggable: the built-ins exist for testing and for the one policy that needs no model, and the real one is a process you supply, spoken to in one JSON line each way. Nothing is recompiled to change a policy.

There is no default stance, and the router will not pick one

A classifier that cannot run — a timeout, a crash, an unparseable answer, an oversize payload — leaves exactly one question: does the request go through unclassified, or is it refused? Both answers are defensible, which is precisely why this software will not choose. fail_closed spends availability to keep the policy true. fail_open lets the request proceed and the receipt records that it was never inspected. A router told to classify and not told which does not start.

Nor will it start without knowing where the prompt goes once the classifier has it. A classifier reads every byte the model reads, and a hook is free to be one line of curl to somebody else's host — so a retention floor enforced against the model and not against the inspector is an overclaim, and the receipt would attest it. The declaration must name a posture and a jurisdiction, and either an evidence URL for a document somebody has read, or forty characters of written reason why nobody has. An unverified declaration may claim only the weakest posture: evidence gates the claim rather than accompanying it.

Measured on 2026-09-04 — the shipped router, told to classify, given a stance, and given no retention declaration:

$ prismux-router --classifier length --classifier-id demo.len \
      --classify-inbound --classifier-stance fail_closed
FATAL: the classifier configuration is incoherent: PostureUndeclared

Exit 78, before a socket is opened. A safety control whose configuration is incoherent must not serve one request before anybody finds out.

What the receipt records, and what it must never record

A classifier's whole job is to look at content and say something about it. Recording what it said is therefore recording a function of your content — into a chain that is signed, durable and published. So every candidate field had to survive one question: does this tell a receipt-holder something about the content that the caller did not already observe from their own request and response? The caller has the content. A third party reading the chain does not, and that asymmetry disqualifies almost everything.

Two things are recorded. The classifier's id, which is configuration — identical on every request under one deployment, so it carries no bits about any particular one. And a mark per point, from a closed 9-variant list, every variant named after what the router did rather than what the classifier found:

absent off allowed flagged transformed blocked error_open error_closed skipped

This is what the block looks like on a real leg — measured on 2026-09-04, from a router running the built-in length classifier at the inbound point:

"classification": {"classifier_id": "demo.length",
                   "classify_inbound": "allowed",
                   "classify_outbound": "off",
                   "coverage": "complete"}

blocked is in that list. blocked_for_pii is not, and that distinction is the entire design. Also refused, each for its own reason:

Rejected fieldWhy it is not there
Category labelsA statement about the subject matter of your prompt, made durable and published. Over a chain it builds a topic profile of an organisation out of receipts that were sold as content-free. The most tempting field and the most damaging one.
ScoresA per-request real number derived from content is a covert channel — and worse, a gradient. Anyone who can submit and read watches the score move as they edit, which is a tuning signal against the operator's own policy.
A hash of the matched spanThe field that looks safest and is not. Hashing is not privacy over a low-entropy domain, and a matched span is low-entropy by construction — a name, an address, a card number. Anyone holding the receipt can guess a value, hash it, and confirm. Salting does not help: whoever reads the chain usually reads the salt.
Match counts, offsets, lengthsContent-derived integers with far more range than a verdict needs.
Per-request classifier durationA timing side channel that correlates with input size and sometimes with what was found. The receipt already leaks input size more strongly through its timestamps, so the marginal cost is small — and the product value of this field on a customer's receipt is zero, because an operator measures classifier latency from their own metrics. A field with no value and a nonzero leak should not exist.

Two limits worth knowing before you rely on this

An outbound classifier cannot run on a streaming response. The client has already rendered bytes before the last one arrives from upstream, so there is no moment at which the whole answer exists and nothing has been delivered, and nothing can un-send what was already sent. Under fail_open the receipt says skipped rather than implying a check that did not happen. Under fail_closed the request is refused outright, before anything is forwarded — measured on 2026-09-04, that refusal reads classification_impossible and tells the caller to drop stream or ask the operator to run the classifier fail-open. This mattered: the mark alone used to be the answer regardless of stance, which meant a fail-closed deployment served uninspected answers to any client that asked for streaming — fail-open behaviour wearing a fail-closed configuration, recorded honestly and therefore easy to miss.

A request blocked at the inbound point has no leg, so its receipt does not name the classifier. The classification block lives on a routing attempt, and a request refused before routing made none. You still get a signed receipt — measured on 2026-09-04 it read delivery: none, posture: refused and charged: 0.000000, and the error names classifier as the binding constraint — but the chain line for that refusal carries no classifier_id. It is a real gap and it is stated here rather than discovered.

A caller may send X-Prism-Classify: on to switch classification on for their own request. off does nothing: a policy the caller can disable from their own request is not a policy, it is a suggestion. An organisation can carry a standing requirement that forces both points on and forces fail_closed, and there is no expressible value of it that switches a point off or relaxes a stance — the same direction every other axis here travels.

Signing keys

Receipts are signed with Ed25519, over the receipt's own content hash. Started with --signing-key, the router prints one line at boot and tells you to pin it:

  signing     7d6d9778b3d96b3f ed25519pub:wD3QWek9ka1XmFWRPl2AmGu+oYAFMPAN0+BlP0chNxw=
              ^ pin this line; `prism verify <chain> --keys <file>` reads it verbatim

That is the whole key distribution story today, and it is deliberately the smallest one that works: the id is the same value every receipt carries as signing_key_id, and the file format the verifier reads is that line, unedited. The line you pin is the line the verifier consumes, with nothing to convert in between.

What the signature does and does not tell you

The signature covers the receipt's content hash, and the content hash covers 44 canonical fields — including org_id, key_ref, the whole promises block and the whole charge breakdown. So an edited receipt still carries a signature that verifies against the hash it was signed with, and what catches the edit is that the hash no longer matches the record. The verifier reports the two independently and says so on the line between them, rather than letting a green signature imply an intact record.

Chaining tells you a record was not altered relative to the chain. The signature tells you the chain is ours. You need both, and you now have both.

Key publication and rotation land with the first production deployment: /.well-known/prismux-signing-keys.json, and a rotation history beside it. Today there is one key, no deployment to serve it from, and no published rotation history because nothing has rotated. A rotated key still checks old receipts. If a signing key is ever compromised we publish the fact, the affected window, and a re-attestation of every receipt in it — the chain makes the scope of such an event computable rather than a matter of assurance.

Incident policy

  • Notification within 72 hours of becoming aware of a personal data breach, to affected customers, with what we know and what we do not yet.
  • Scope from the receipt log, not from guesswork. Records carry an interval, not a timestamp, precisely so “which requests were in flight concurrently on endpoint X between T1 and T2” is a query. A point-in-time log cannot express concurrency, and that is what makes an incident unscopeable.
  • A public post-mortem for anything affecting integrity of the receipt chain, retention guarantees, or billing correctness — including what we got wrong and what we changed.
  • Counters, not adjectives. Degradations already appear as published numbers before anyone writes a status update.

Reporting a vulnerability

Write to security@prismux.com. We will acknowledge within one business day and give you a substantive response within five.

We do not require an NDA to report, we will not threaten legal action against good-faith research, and we will credit you unless you prefer otherwise. If you find a way to make the receipt log contain content, or to make a fallback lower a retention posture without it being counted, we especially want to hear from you — those are the two claims this whole system rests on.