
TrustedRouter.com repo for secure LLM proxying
# TrustedRouter
[](https://github.com/Lore-Hex/quill-router/actions/workflows/ci.yml)
[](https://github.com/Lore-Hex/quill-router/actions/workflows/deploy.yml)
[](https://github.com/Lore-Hex/quill-router/actions/workflows/prod-smoke.yml)
[](https://github.com/Lore-Hex/quill-router/actions/workflows/trust-drift.yml)
[](https://status.trustedrouter.com)
[](https://trust.trustedrouter.com)
[](https://www.npmjs.com/package/@lore-hex/trusted-router)
[](https://pypi.org/project/trusted-router-py/)
[](LICENSE)
# End-to-end encrypted LLMs. One API. Privacy with proof.
Stop worrying about who can see your prompts. Tell your coding agent to move
your project over, pick how private you want to be, pick a model, drop in a
key — done. Same API, 30+ models, one key. The gateway runs in hardware
enclaves and you can cryptographically verify it never logs you.
### Move your project over in one prompt
Paste this into Codex, Claude Code, or Cursor — it does the migration for you:
```text
Migrate this project to TrustedRouter, a privacy-first LLM router
(https://trustedrouter.com). Repoint my LLM client to base_url
"https://api.trustedrouter.com/v1" (or "https://api.trustedrouter.com" for the
Anthropic SDK), read the key from the TRUSTEDROUTER_API_KEY env var, and keep
all my existing calls working.
For a hard provider-side confidential-compute and end-to-end-encryption
requirement, add {"provider": {"min_privacy": "confidential"}}. TrustedRouter
fails closed when the selected model or provider cannot satisfy both controls.
Then tell me to sign up at trustedrouter.com, add a card, and paste my sk-tr
key into TRUSTEDROUTER_API_KEY.
```
Then:
1. **Pick your privacy level or region** — use
`{"provider": {"min_privacy": "zdr"}}` for a hard zero-retention floor, or
`{"provider": {"min_privacy": "confidential"}}` for the stronger hard
confidential-compute + E2EE floor. The convenient `trustedrouter/zdr` and
`trustedrouter/e2e` (`trustedrouter/confidential`) aliases select those
pools. Use `trustedrouter/eu` with
`https://api-europe-west4.quillrouter.com/v1` for EU-focused routing.
2. **Pick a model** — any of hundreds, or `trustedrouter/auto` for automatic
fallback when provider breadth matters more than the strictest privacy
filter.
3. **Sign up, add a card, get your key** at https://trustedrouter.com.
4. **Ship** — your prompts now run through a path you can verify.
<details>
<summary>Prefer to wire it by hand?</summary>
```bash
# Codex
export OPENAI_BASE_URL="https://api.trustedrouter.com/v1"
export OPENAI_API_KEY="sk-tr-v1-..."
# Claude Code
export ANTHROPIC_BASE_URL="https://api.trustedrouter.com"
export ANTHROPIC_API_KEY="sk-tr-v1-..."
```
```python
# Any OpenAI SDK
client = OpenAI(base_url="https://api.trustedrouter.com/v1", api_key="sk-tr-v1-...")
```
</details>
- **Get a key / take my money:** https://trustedrouter.com
- **Try it first (no signup):** https://trustedrouter.com/chat
- **The technical details (for the nerds):** https://trustedrouter.com/security
- **Generate images:** [docs/image-generation.md](https://github.com/lore-hex/quill-router/blob/main/docs/image-generation.md)
- **Generate video:** [docs/video-generation.md](https://github.com/lore-hex/quill-router/blob/main/docs/video-generation.md)
- **Why we built it:** https://jperla.com/blog/attestation-is-all-you-need
---
<details>
<summary><strong>For the nerds: how the privacy is provable</strong></summary>
TrustedRouter's gateway runs inside GCP Confidential Space. The platform signs
a measurement of the running container. Verify that signed evidence and compare
its image digest with the published release measurement. Source review and the
release build provenance establish what that measured image does; a matching
digest alone does not prove the absence of bugs or logging.
Inspect the GCP evidence, no account:
```bash
NONCE=$(openssl rand -hex 16)
curl --fail --silent --show-error \
"https://api-us-central1.quillrouter.com/attestation?nonce=$NONCE" \
--output attestation.jwt
# GCP returns a raw Google-signed JWT, not a JSON response.
# Decoded claims include eat_nonce and submods.container.image_digest.
# GCP evidence has no pcrs field; PCRs belong to AWS Nitro evidence.
```
Saving or decoding a JWT is **not verification**. Use the
[attestation verifier](https://github.com/Lore-Hex/quill-cloud-proxy/blob/main/tools/verify-attestation.py)
from a reviewed checkout of `quill-cloud-proxy` to verify the issuer/signature,
fresh nonce, TLS certificate/exporter binding, and expected release measurement.
For a GCP endpoint, run from that checkout:
```bash
DIGESTS=$(curl --fail --silent --show-error \
https://trust.trustedrouter.com/trust/accepted-image-digests-gcp.txt)
test -n "$DIGESTS" && uv run --script tools/verify-attestation.py \
--api-host api-us-central1.quillrouter.com --expect-digest "$DIGESTS"
```
The verifier fetches fresh evidence on the TLS connection it verifies. That
check does not attest a later, separate `curl` or SDK connection. These examples
use the published US Central GCP hostname to select the GCP evidence format. The global
endpoint can select another cloud; use its cloud-specific verification policy
and measurements from [the trust site](https://trust.trustedrouter.com/).
| | trust model |
|---|---|
| OpenRouter, hosted providers | "We don't log." A policy you can't check. |
| Portkey, Cloudflare AI Gateway | Log everything for observability. |
| LiteLLM | Self-host, but the running proxy is unverified. |
| **TrustedRouter** | **Open source + hardware attestation. Verify the code path; it logs nothing.** |
Honest scope: attestation proves the running binary is the published binary on
hardware you can challenge with a nonce. It does not defeat a nation-state with
physical host access, and it does not prove the open-source binary is bug-free.
The trust anchor is Google Confidential Computing's hardware-backed attestation
chain. Upstream providers handle prompts per
their own policies — each provider's posture is published on the model pages.
</details>
---
## Repository layout
This repo implements the control-plane contract: route coverage, auth/key
management, billing ledger semantics, usage metadata, prompt/output logging
prohibitions, Sentry scrubbers, and provider abstractions. The attested gateway
implementation lives in `quill-cloud-proxy`.
Trust boundary: `api.trustedrouter.com` is the attested prompt path and must
terminate TLS inside Confidential Space. `trustedrouter.com` is the control
plane and must never serve a production inference fallback.
`api.quillrouter.com` remains a permanent working alias (same attested
gateway and cert), so existing integrations keep working with no migration.
## Local
```bash
uv sync
uv run pytest
uv run uvicorn trusted_router.main:app --reload
```
End-to-end smoke against a running instance:
```bash
TR_SMOKE_BASE_URL=http://127.0.0.1:18080/v1 uv run python scripts/smoke_e2e.py
```
For production, set `TR_SMOKE_BASE_URL=https://api.trustedrouter.com/v1` and
`TR_SMOKE_INTERNAL_TOKEN` if the internal gateway routes are token-protected.
Set local operator/provider keys in:
```text
/Users/jperla/claude/.quill_cloud_keys.private
```
That file is never committed. It is expected to be dotenv-style:
```text
ANTHROPIC_API_KEY=...
OPENAI_API_KEY=...
GEMINI_API_KEY=...
CEREBRAS_API_KEY=...
DEEPSEEK_API_KEY=...
MISTRAL_API_KEY=...
STRIPE_SECRET_KEY=...
STRIPE_WEBHOOK_SECRET=...
SENTRY_DSN=...
```
The deploy script also accepts local aliases already used in some operator
files: `CLAUDE_API_KEY` for Anthropic, `CHATGPT_API_KEY` for OpenAI, and
`STRIPE_KEY` for `STRIPE_SECRET_KEY`.
Vertex is different from the other provider platforms: production GCP deploys
use the Cloud Run or Confidential Space service account and short-lived Google
access tokens from metadata/ADC. Do not put a long-lived Vertex key in this
file for the first-party prepaid Vertex route; grant the runtime service
account Vertex permissions instead.
## License
Business Source License 1.1. The source is public so anyone can read, build,
and verify the exact code behind TrustedRouter's privacy and attestation
claims (https://trust.trustedrouter.com) — that is what it is here for.
Non-production use (security review, audit, local evaluation) is free.
Production use requires a commercial license from Lore Hex Corp:
[email protected]. Each version converts to the Apache License 2.0
four years after publication. Code published before July 3, 2026 remains
Apache-2.0.
## Security Defaults
- Prompt and output content is never logged. Ordinary synchronous and streaming
inference does not retain it. The opt-in Batch API temporarily retains
enclave-encrypted artifacts for up to 30 days.
- Usage logs contain metadata only.
- API keys are stored as salted SHA-256 hashes with opaque key IDs.
- User-submitted BYOK provider keys are stored as envelope-encrypted ciphertext
rows, not one Secret Manager object per key. In production, Cloud KMS wraps
the per-key DEK; external `env://...` references remain supported for
operator-managed keys.
- Gateway authorizations include a non-secret `byok_cache_key` for encrypted
BYOK envelopes. Attested gateways use it for short TTL, memory-only decrypted
key caching; BYOK rotation changes the key and delete stops returning the
envelope.
- BYOK raw keys are one-time input only; public/control-plane responses expose
a short first/last key hint and encrypted reference metadata, never plaintext.
- Production config fails closed without an internal gateway token, signed
Stripe webhook secret, and a non-memory storage backend.
- Production control-plane apps do not register `/chat/completions`,
`/messages`, `/responses`, or `/embeddings`; those belong on the attested API
plane.
- Sentry is control-plane-only and scrubs request bodies, auth headers, API
keys, BYOK keys, prompt messages, and output text. A client-side Sentry flood
gate caps repeated issues per fingerprint and total events per process/window
so a single noisy integration cannot consume the whole error budget again.
- No Sentry configuration belongs in the attested enclave.
## Broadcast Observability
Workspace owners can configure Broadcast destinations at
`/v1/broadcast/destinations` or in the console under Broadcast. Supported
destinations are PostHog and OTLP JSON webhooks. Broadcast is metadata-only by
default: model, provider, token counts, latency, cost, route type, region, and
custom trace metadata. Prompt/output content is exported only when a destination
explicitly enables `include_content`; those content-enabled encrypted
destinations are returned only to the attested gateway, not normal management
responses. Metadata-only deliveries are written to a persistent Broadcast
outbox first and drained asynchronously by `/internal/broadcast/drain`, so a
PostHog/webhook outage does not block inference or lose already-settled
metadata on process restart.
## Fixed routing tiers
These aliases use an explicitly ordered model pool: the first member is preferred
and the rest are fallbacks. They run one model per attempt, with no synthesis,
orchestration, or implicit privacy floor. Set `provider.min_privacy` explicitly
when needed. The live `/v1/models` catalog publishes each pool in `auto_candidates`.
- `trustedrouter/frontier`: fixed frontier model pool.
- `trustedrouter/advanced`: fixed advanced model pool.
- `trustedrouter/professional`: fixed professional model pool.
- `trustedrouter/efficient`: fixed efficient model pool.
## Synthetic Monitoring
TrustedRouter has a separate synthetic monitoring plane for public uptime.
Synthetic workers run outside the enclave, send tiny real requests into the
public attested API, and store only metadata. The monitor model aliases are:
- `trustedrouter/free`: OpenRouter-style free pool. Useful for users, not an
SLA signal.
- `trustedrouter/cheap`: cheapest paid pool with provider diversity.
- `trustedrouter/eu`: EU-focused provider pool. It prefers European,
EU-regionable, and privacy-forward providers, especially when paired with
`https://api-europe-west4.quillrouter.com/v1`. This is a routing policy, not
a blanket data-residency guarantee.
- `trustedrouter/monitor`: internal uptime pool for PONG and fallback checks.
It is visible in the catalog for transparency, but authorization requires
the configured `TR_SYNTHETIC_MONITOR_API_KEY`; normal API keys receive 403.
Workers should run from `us-central1` and `europe-west4`, using a dedicated
`trustedrouter-synthetic-monitoring` workspace/key with hard spend caps and
auto-refill. Raw samples are append-only Bigtable rows; public status pages
read compact rollups exposed at `/status`, `/status.json`, and
`/status/history?window=5m|24h|daily`. Synthetic generations use the
`TrustedRouter Synthetic` app label and are excluded from customer/app
analytics.
Every monitor invocation also makes a `stream: true` chat completion and fails
unless it receives an SSE data event, a terminal chunk, and `[DONE]`. The
recurring Stage D job adds `--expect-stage-d`, then looks up that request's
typed authorization and requires a bound `stage_d_boot_kid` plus
`heartbeat_seq > 0`. Its workspace is intentionally separate: provision it
with `scripts/provision_stage_d_probe_workspace.py` and add its id to
`TR_STAGE_D_PILOT_WORKSPACE_IDS`. The key must be a
heartbeat-capable local-typed key; the deploy reads it from
`trustedrouter-stage-d-probe-api-key` and uses `trustedrouter/cheap` so it is
independent of the ordinary monitor-only key. The normal rollout leaves Stage D
eligibility disabled, so creating the key alone does not enable Stage D.
Provider measurement uses two independent probe classes:
- Short PONG probes randomly cover the full active catalog and measure uptime,
TTFB, TTFT, and upstream API drift.
- A sustained 512-token stream covers the 200 most important provider/model
routes in deterministic rotation from `us-central1`. It measures output
tokens per second after the first token. It runs in a separate Cloud Run Job,
so slow streams cannot delay uptime probes. Long-probe failures never count
against provider uptime or API-drift alerts.
The current two-minute schedule gives each sustained route about 25 samples per
week and 108 per 30 days. CI calculates a full-cap spend estimate from the live
catalog and fails if it exceeds the reviewed monthly ceiling.
Status separates two service SLO classes instead of blending them with
upstream-provider behavior:
- `router_core`: attested API reachable, key authorization works, route
candidates/fallback are available, and settle/refund is durable.
- `control_plane`: dashboard, billing UI, keys, credits, docs, trust, and
status surfaces.
Deploy watchdogs and internal burn-rate alerts default to `router_core`.
Provider-only failures are measured per provider on `/status` and
`/leaderboard`; they do not consume the router-core error budget when fallback
remains available.
## Public Positioning
- Pricing: prepaid and BYOK usage is tracked as integer microdollars, not
floating point dollars, so tiny token costs remain auditable in the ledger.
- Uptime target: `trustedrouter/auto` is a real chat model alias in local/test
control-plane inference and rolls to the next configured provider on upstream
provider failures. `trustedrouter/eu` prefers the EU-focused provider pool,
`trustedrouter/zdr` forces a zero-retention provider floor with Anthropic
first, and `trustedrouter/e2e` forces confidential + E2EE routes with
Tinfoil first. Chat requests also honor OpenRouter-style `models` and
`provider` routing filters (`order`, `only`, `ignore`, `allow_fallbacks`,
`min_privacy`, `data_collection`, `sort`, `max_price`, `require_parameters`,
and `zdr`) so clients can request explicit
fallback chains or provider preferences. `provider.order` is a preference
while fallbacks are enabled, and unlisted providers remain eligible after the
preferred list. With `allow_fallbacks=false`, routing is restricted to the
ordered provider set and only its first available route is authorized.
`provider.only` is always a hard provider allowlist. Setting
`allow_fallbacks=false` either at the request top level or as
`provider.allow_fallbacks` also prevents substituting another model.
`min_privacy="confidential"` is a
hard provider-side confidential-compute + E2EE requirement and fails closed;
the request-value aliases `e2e` and `e2ee` select the same tier
rather than falling back to a weaker route.
Unknown request or provider options return `400` with the exact field name.
Recognized OpenRouter controls that are not implemented return a stable
`501 not_supported_in_alpha`; they are never accepted as router no-ops.
- Billing: prepaid credits and BYOK first; no subscription is required.
- Trust: hosted open source, with the running API's source commit, image
reference, image digest, and attestation policy published at
`trust.trustedrouter.com`.
- Signup: email signup creates a one-time management key for the workspace.
- Wallet/crypto: stablecoin checkout is wired through Stripe Checkout's Crypto
payment method when requested. Card/default Checkout remains the default path.
## Scale Target
The goal is to support OpenRouter-class scale:
- 1 trillion tokens/day, or about 11.6 million tokens/second averaged over a
day.
- 1-4 million developer accounts.
- 300+ actively routable models.
- 60+ providers.
- Global routing overhead competitive with edge-deployed routers.
The current production deployment does **not** meet that target yet. It runs
the control plane in four GCP regions behind a global LB with per-region
Serverless NEGs and four live attested API regions. Capacity scales
horizontally as more attested pools come online; correctness, trust, billing,
and SDK compatibility are in steady-state.
Request volume depends heavily on average generation size. At 1 trillion
tokens/day:
| Average tokens/request | Requests/day | Average request rate |
| ---: | ---: | ---: |
| 1,000 | 1.0B | 11.6k rps |
| 2,500 | 400M | 4.6k rps |
| 10,000 | 100M | 1.2k rps |
The architecture can be evolved to this scale, but only if the hot path avoids
per-request global bottlenecks. That means regional stateless gateway fleets,
regional provider pools, sharded quota leases, append-only metadata writes, and
asynchronous aggregation.
## Current Latency
Measured from this development machine to the centralized GCP `us-central1`
attested API on May 2, 2026:
| Probe | p50 | p95 | Notes |
| --- | ---: | ---: | --- |
| Unauthenticated `/v1/chat/completions` rejection | 174 ms | 184 ms | Includes DNS, TCP, public TLS, enclave request handling. |
| TCP connect | 55 ms | 59 ms | Network path to `us-central1` from this machine. |
| TLS handshake complete | 112 ms | 124 ms | Public ACME cert terminates inside the enclave. |
| `/attestation` | 1.06 s | 1.12 s | Includes GCP attestation token generation, so not representative of normal routing overhead. |
The centralized network overhead is much higher than OpenRouter's reported
edge overhead, but model latency usually dominates interactive requests. The
first production scaling step should be multi-region rather than building a
custom global edge immediately.
## Horizontal Scale Shape
The production path is designed to scale by keeping the prompt gateway
stateless:
- `api.trustedrouter.com` instances can be replicated behind TCP passthrough.
They authorize, reserve, and settle through the control plane, but prompt
bytes never leave the attested path.
- Spanner stores strongly consistent control-plane and billing state: users,
workspaces, keys, BYOK metadata, payment event idempotency, balances,
aggregates, active reservations, and a 30-day terminal request audit window.
- Bigtable stores bounded high-volume activity metadata keyed by workspace and
date. Activity and provider benchmarks retain 30 days, raw synthetic samples
retain 14 days, and compact status rollups retain 24 months. Prompt, output,
and tool-call arguments are not stored.
- API-key verification uses a high-entropy lookup hash for point reads; it does
not scan keys.
- Rate limits are enforced before route handlers and use the configured store,
so production counters are shared across Cloud Run instances.
At OpenRouter-scale traffic, the next bottleneck is not the enclave binary; it
is the synchronous billing/authorization path. The architecture needs sharded
reservations, regional Bigtable clusters, Cloud Armor edge limits, and multiple
gateway replicas before public traffic is allowed to ramp.
## Multi-Region Plan
Multi-region is feasible while preserving the trust boundary, but it has to be
done carefully:
São Paulo (`southamerica-east1`) remains a live Cloud Run control plane:
`trusted-router` continues deploying and serving through the global load balancer.
Only its enclave gateway MIG was retired (quill-cloud-proxy #309); its stale
regional gateway DNS record has been deleted. Control-plane deployment regions
remain `us-central1`, `us-east4`, `europe-west4`, and `southamerica-east1`.
Oregon (`us-west1`) is the opposite case: a gateway-only region. It runs an
attested gateway pool at `api-us-west1.quillrouter.com` and has no Cloud Run
control plane. Its gateway reaches the control plane through the global load
balancer, which sends it to the nearest control-plane region (`us-central1`).
Nothing in `scripts/deploy` creates a Cloud Run service, scheduler job, or NEG
there, and it has no entry in the warm-region or min-instances defaults.
- Run independent warm attested gateway pools in `us-central1`, `us-east4`,
`europe-west4`, and `us-west1`. The enclave inventory is
`src/trusted_router/enclave_regions.py`, shared by synthetic probes, provider
smoke checks, the watchdog defaults, and `TR_REGIONS` deploy defaults. The
same module names the gateway regions that have no local control plane
(`ENCLAVE_REGIONS_WITHOUT_LOCAL_CONTROL_PLANE`).
- Keep TLS private keys inside each regional Confidential Space workload.
- Keep certificate issuance inside the attested workload. Certificates are
shared between replicas through the encrypted cache. A first regional
hostname is exposed to one node only after every node passes canonical
attestation and a settled PONG; it is expanded only after the regional
certificate and the same gates pass on every node.
- Keep regional hostnames such as `api-us-central1.quillrouter.com`,
`api-us-east4.quillrouter.com`, `api-europe-west4.quillrouter.com`, and
`api-us-west1.quillrouter.com` for deterministic attestation,
smoke tests, and SDK failover.
- Put `api.trustedrouter.com` behind latency/geo DNS or TCP passthrough that does
not terminate TLS. Cloudflare orange-cloud proxying remains incompatible
with the prompt-path trust claim.
- Authorize through sharded billing rows, not a single global Spanner counter
mutation for every request. New workspaces and keys start with 16 exact
billing shards. Regional quota leases remain future work: the 2026-09
regional-quota and spend-lease pilots were removed.
- Write generation metadata to regional Bigtable clusters, then aggregate into
global activity views asynchronously.
- Keep provider routing regional, with provider-specific circuit breakers,
fallback policy, and per-provider rate limits.
The key design rule: a regional outage can fail closed or route to another
attested region, but it must never silently degrade to a non-attested prompt
handler.
## Router-Core Four-Nines Target
The target is an internal SLO, not a contractual SLA. 99.99% allows about
52 minutes 36 seconds of downtime per year. Public status labels this number
as a target until at least 30-60 days of measured 99.99% router-core uptime
exists.
Router-core availability means:
- attested TLS is reachable;
- API-key validation and gateway authorization work;
- route candidates are returned and fallback can choose a healthy provider;
- settlement/refund is durable or safely repairable;
- no prompt request ever falls back to a non-attested path.
The code paths that support this roadmap today are:
- `/status.json` exports `slo_classes.router_core`,
`slo_classes.control_plane`, and burn-rate alerts for 5m, 1h, 6h, and 24h
windows.
- The deploy watchdog reads `router_core` by default, so provider-only outages
do not automatically roll back a control-plane deploy.
- SDKs are expected to retry connection failures and 502/503/504 across
regional attested endpoints before surfacing failure.
- Bigtable activity writes are repairable from the durable settlement outbox.
Settlement uses deterministic generation IDs, so retries overwrite the same
index rows and cannot double charge or duplicate activity.
Before describing four nines as measured availability rather than a target,
require three warm GCP attested regions, tested paging, router-core chaos
tests, staged regional deploys with rollback gates, and at least 30 days of
measured router-core uptime at or above 99.99%.
## Internal Gateway Contract
The attested API plane can reserve and settle usage without sending prompt or
output content to the control plane:
- `POST /v1/internal/gateway/authorize`: validates the API key hash, reserves
credits/key limits, and returns provider/BYOK routing metadata, route
candidates derived from `model`, `models`, and `provider` request filters,
and configured regional endpoints.
- `POST /v1/internal/gateway/settle`: settles successful usage and appends
metadata-only activity rows.
- `POST /v1/internal/gateway/refund`: releases reservations after provider
failures or client disconnects.
Set `TR_INTERNAL_GATEWAY_TOKEN` outside local development.
## Production Storage
Production uses:
```text
TR_STORAGE_BACKEND=spanner-clickhouse
TR_SPANNER_INSTANCE_ID=trusted-router-nam6
TR_SPANNER_DATABASE_ID=trusted-router
TR_OPERATIONAL_ANALYTICS_CLICKHOUSE_URL=http://<clickhouse-ilb>:8123
TR_OPERATIONAL_ANALYTICS_CLICKHOUSE_PASSWORD=<control-read secret>
```
`scripts/deploy-gcp.sh` enables the APIs, creates the Spanner table
`tr_entities`, deploys Cloud Run, and
wires the current GCP trust metadata into the trust page.
## Billing
`POST /v1/billing/checkout` creates a Stripe Checkout session when
`TR_STRIPE_SECRET_KEY` is configured and otherwise returns a deterministic local
mock response. Stripe webhooks credit workspaces idempotently using the
workspace ID in Checkout metadata. Card payments settle immediately. ACH
payments use `{"payment_method":"ach"}` and are credited only after Stripe sends
`checkout.session.async_payment_succeeded`; Checkout completion while the debit
is processing never grants credits. `POST /v1/billing/portal` follows the same
Stripe-or-mock pattern for billing management.
For stablecoin checkout, send `{"payment_method":"stablecoin"}`. When
`TR_STABLECOIN_CHECKOUT_ENABLED=true`, the Checkout session is created with
Stripe's `crypto` payment method and still credits the workspace from the signed
`checkout.session.completed` webhook.
ACH uses Stripe Checkout's `us_bank_account` payment method. The default
processing schedule is 0.8% capped at $5 and can be overridden with
`TR_STRIPE_ACH_FEE_BASIS_POINTS`, `TR_STRIPE_ACH_FEE_FIXED_CENTS`, and
`TR_STRIPE_ACH_FEE_MAX_CENTS`. Saved-card auto refill remains card-only.