SafeRouter: a verifiable confidential execution layer for LLM routing.
This reference describes SafeRouter’s confidential routing architecture: hardware attestation, cryptographic receipts, and public transparency anchors. Evaluate a deployment using the evidence it actually publishes; reference-build capabilities do not establish its current runtime configuration.
From zero to your first call
Five steps take a new account to a working, billed request: create an account, mint an API key, pick a model, call it, and add credit. Each step lists the console page to click and the raw API call underneath it — use whichever you prefer. The base URL throughout is https://router.sumplus.xyz.
Step 1 · Create your account
Onboarding is invite-gated while we scale access. If you have an invite link, opening it prefills your code; otherwise go to /signup.html and paste your SR-XXXX-XXXX code into the invite_code field. The code credits your account with its attached starter balance, so you can make real calls right away. Sign up with email + password, or with Google, GitHub, or a crypto wallet where the deployment enables them.
# Invite link (code prefills the form):
https://router.sumplus.xyz/signup.html?code=SR-XXXX-XXXX
# API equivalent:
curl https://router.sumplus.xyz/v1/auth/signup \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com","password":"...","invite_code":"SR-XXXX-XXXX"}'
Step 2 · Create an API key
Sign in, open /keys.html, and click + Create new key. The secret (sk-sr-…) is shown once — copy it now and store it somewhere safe. If you lose it, revoke the key and create a new one; there is no way to reveal it again. One account balance backs every key you create, so a key is an access credential, not a separate wallet.
# API equivalent — the secret is in the response, returned only this once:
curl https://router.sumplus.xyz/v1/keys \
-H "Authorization: Bearer sk-sr-EXISTING_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"my-first-key"}'
sk-sr-… secret is shown once. Blur or crop the real secret before publishing.Step 3 · Find a model to call
The catalog is public — no key needed. GET /v1/models lists every model configured on this deployment with its exact id, pricing (input / output USD per million tokens), context window, and availability. You pass the id verbatim as the model field. Only live models can be called; a preview model returns 503 model_not_provisioned. Browse the same catalog with copy-paste snippets at /models.html.
curl https://router.sumplus.xyz/v1/models
# → { "data": [ { "id": "deepseek-v4-flash", "availability": "live",
# "input_per_million": 0.14, "output_per_million": 0.28, ... }, ... ] }
Step 4 · Call the model
SafeRouter speaks the OpenAI API. Point any OpenAI SDK at https://router.sumplus.xyz/v1 with your sk-sr-… key and keep the rest of your code unchanged — streaming, tool calls, and multimodal content all pass through. Billing is per token, settled from your account balance after each call.
from openai import OpenAI
client = OpenAI(
base_url="https://router.sumplus.xyz/v1",
api_key="sk-sr-...",
)
resp = client.chat.completions.create(
model="deepseek-v4-flash", # an id from Step 3
messages=[{"role": "user", "content": "hello"}],
)
print(resp.choices[0].message.content)
# Or plain curl:
curl https://router.sumplus.xyz/v1/chat/completions \
-H "Authorization: Bearer sk-sr-..." -H "Content-Type: application/json" \
-d '{"model":"deepseek-v4-flash","messages":[{"role":"user","content":"hello"}]}'
Every response carries evidence headers (x-saferouter-call-id, x-saferouter-response-sha256, and more) that resolve to a signed receipt. Turning a call into a proof is covered in section 07.
Step 5 · Add credit
Credits are prepaid USD — one balance covers every key and every model. Open /credits.html, choose USDT / USDC under add credit, and enter an amount (minimum $15 per top-up). You are sent to a secure hosted checkout and pay the stablecoin on the chain you prefer — Ethereum, Solana, Polygon, BNB Chain, or Tron. Your balance updates automatically once the payment confirms on-chain. Card payments (Stripe) are coming soon.
# Check your balance any time:
curl https://router.sumplus.xyz/v1/credits -H "Authorization: Bearer sk-sr-..."
# → { "balance_usd": 12.34 }
What SafeRouter is
Every team that routes LLM traffic through a gateway — for model failover, cost control, or unified billing — hands that gateway operator the technical ability to read, log, or alter every prompt. Today the gateway market answers this with a policy promise: "trust us, we don't log."
SafeRouter replaces the promise with evidence. The gateway runs inside an AMD SEV-SNP confidential VM whose memory is encrypted by the CPU, publishes hardware-signed attestation that anyone can re-verify in a browser, and issues a cryptographic receipt for every single API call, chained into an append-only transparency log that is anchored to a public, third-party transparency service (Sigstore Rekor).
What this is not: SafeRouter does not claim to make upstream model providers blind — they necessarily receive plaintext to run inference. The guarantee is scoped to the routing hop we operate; section 09 states the boundaries precisely. Security reviewers respond better to a narrow, checkable claim than a broad, unfalsifiable one.
Who it serves
| Segment | Situation | What SafeRouter changes |
|---|---|---|
| Enterprise AI platform teams | Multiple models in production; blocked or slowed by internal security/legal review of the gateway vendor | Reviewers verify the execution environment instead of auditing vendor intentions — the review gets shorter |
| B2B AI startups with customer-sensitive data | Their customers ask "who can see our data once it leaves the building?" | A demonstrable answer, without building confidential-computing infrastructure themselves |
| Regulated industries | Finance, healthcare, government — long procurement cycles | The verification primitives in these docs were designed with these buyers' audit requirements in mind |
Data retention (reviewed claim)
Why a verifiable gateway
A gateway sits in the most sensitive position in your stack: every prompt and every response passes through it in plaintext. The question a security team actually asks is not whether the vendor has a good privacy policy, but what the operator can technically do with the data, and how anyone would know. An ordinary gateway answers with assurances. SafeRouter answers with evidence you can check.
| Concern | Ordinary LLM gateway | SafeRouter |
|---|---|---|
| Operator access to your prompts | Technically able to read, log, or alter every prompt; you rely on a policy promise | VM memory is encrypted by the CPU and unreadable to the operator, and that is hardware-attested |
| Proof of what actually ran | Vendor dashboards and self-reported logs | Hardware-signed attestation you re-verify in your own browser |
| Evidence per call | Trust the vendor's internal log | A signed receipt for every call, chained into an append-only log |
| Tamper resistance | Internal logs are editable by the operator | Hash-chained and anchored to a public third-party log, so it cannot be silently rewritten or shown differently to different auditors |
| Security review | Auditing vendor intentions, contracts, and policy documents | Verifying the execution environment directly, so the review gets shorter |
| Model coverage | Usually one ecosystem | Western and Chinese frontier models behind one OpenAI-compatible endpoint, with the same guarantees on every call |
Live catalog
USD per million tokens, as charged today. Served from the audited build (GET /v1/models) — these are the prices the billing engine actually uses.
| Model | Context | Input $/M | Output $/M | Notes |
|---|---|---|---|---|
| DeepSeek V4 Flash-0731 | 1M | 0.14 | 0.28 | cache-hit input $0.0028/M |
| DeepSeek V4 Pro-0813 | 1M | 0.435 | 0.87 | cache-hit input $0.003625/M |
| GLM-5.1 | 128k | 1.40 | 4.40 | |
| GLM-5 | 128k | 1.00 | 3.20 | |
| GLM-5 Turbo | 128k | 1.20 | 4.00 | |
| GLM-4.7 | 200k | 0.60 | 2.20 | |
| GLM-4.7 Flash | 200k | free | free | |
| GLM-4.6 | 128k | 0.60 | 2.20 | |
| GLM-4.5 | 128k | 0.60 | 2.20 | |
| GLM-4.5 Air | 128k | 0.20 | 1.10 | |
| GLM-4.5 Flash | 128k | free | free |
New providers from section 04 are added to this table as each integration goes live, with prices wired into the billing engine first — the table and the engine never diverge. The interactive catalog with code snippets lives at /models.html.
One gateway, both model ecosystems
SafeRouter's target lineup spans both the Western and the Chinese frontier-model ecosystems behind one OpenAI-compatible endpoint — with the same verifiable-execution guarantees regardless of which provider serves the call. Status below is current as of 2026-06-11 and updated as integrations land.
| Provider | Models | Status |
|---|---|---|
| DeepSeek (深度求索) | DeepSeek V4 family | live |
| Zhipu AI (智谱) | GLM-4.5 / 4.6 / 4.7 / 5 families | live |
| OpenAI | GPT family (via Azure OpenAI) | integration in progress |
| Anthropic | Claude family | code complete, awaiting upstream credentials |
| Gemini family | planned | |
| Mistral AI | Mistral family | planned |
| Alibaba Cloud (阿里) | Qwen family | planned |
| Moonshot AI (月之暗面) | Kimi family | planned |
| MiniMax (稀宇科技) | MiniMax family | planned |
| Tencent (腾讯) | Hunyuan family | planned |
| StepFun (阶跃星辰) | Step family | planned |
| ByteDance (字节跳动) | Doubao family (Volcano Ark) | planned |
| Xiaomi (小米) | MiMo | under evaluation |
Two tracks, one product
- Pay-as-you-go (live): prepaid USD credits, billed per token at the catalog prices in section 03. One balance covers every model.
- BYOK — bring your own provider keys (roadmap, interest list open): customers with existing provider contracts plug in their own keys, pay a subscription, and get 0% token markup. Key custody will use a vault sealed to the attested enclave; until that ships we capture interest at /providers.html and say clearly that it has not shipped.
Plan tiers (Free / Paid / Enterprise) map to the rate-limit quotas in section 11; enterprise quotas and SLAs are contracted individually.
Drop-in for any OpenAI SDK
Change base_url and the API key; keep your SDK, streaming, and tool-calling code unchanged.
from openai import OpenAI
client = OpenAI(
base_url="https://router.sumplus.xyz/v1",
api_key="sk-sr-...", # issued in the dashboard → keys
)
resp = client.chat.completions.create(
model="deepseek-v4-flash",
messages=[{"role": "user", "content": "hello"}],
)
Optional SafeRouter routing controls travel in a saferouter object in the request body (stripped before the request reaches the provider):
{
"model": "glm-5",
"messages": [...],
"saferouter": {
"provider": "glm", // pin attempts to one provider
"fallback": false, // default true; false disables the retry chain
"session": "run-42" // group a multi-call agent run for tracing
}
}
Onboarding is currently invite-gated (single-use codes, issued by us) — by design while we scale access. Evaluation accounts with preloaded credit are available; sign up at /signup.html?code=<invite>.
Five-minute evaluation script
- Sign up with your invite code → evaluation credit applied automatically.
- Open /chat.html, send a prompt, expand the verify panel → the call's receipt headers resolve to a signed log entry.
- Open /verifier.html → all 7 attestation checks run in your own browser, including the AMD certificate chain.
- Open /explorer.html → browse the transparency chain; re-verify any entry's signature client-side.
curl https://router.sumplus.xyz/v1/rekor→ take thelog_indexto rekor.sigstore.dev and confirm the anchor exists outside our infrastructure.
Console map
Everything in the web console, in one table:
| Page | What it is |
|---|---|
| /chat.html | chat playground with streaming, model picker, and the per-call verify panel |
| /compare.html | one prompt against multiple models side-by-side, with latency per column |
| /models.html | browsable model catalog with pricing and copy-paste code snippets |
| /status.html | provider health and latency |
| /verifier.html | live attestation verifier — 7 checks in your browser |
| /explorer.html | transparency log explorer with client-side signature checks |
| /keys.html | API key management + per-key analytics sign-in |
| /credits.html | balance and top-up history sign-in |
| /activity.html | recent calls with cost, latency, fallback info sign-in |
| /usage.html | usage & billing charts, CSV export sign-in |
| /account.html | account, sign-in methods, sessions sign-in |
| /providers.html | bring-your-own-key program overview and interest form |
| /roadmap.html | engineering-level change history |
Per-call receipts
Every API response carries evidence headers:
| Header | Meaning |
|---|---|
x-saferouter-call-id | unique id for this call; feed it to the proof endpoint below |
x-saferouter-log-index | position of this call's entry in the transparency hash chain |
x-saferouter-response-sha256 | hash of the response bytes — re-hash what you received and compare |
x-saferouter-tee-quote-id | identifies the attestation identity that served the call |
x-saferouter-fallback-from/-to | present only when automatic failover fired — failover is never silent |
Resolving a receipt — no SDK, no account needed beyond the call itself:
curl https://router.sumplus.xyz/v1/calls/<call_id>/proof
# → the signed transparency entry: model, token counts, response hash,
# chain position, Ed25519 signature. Re-hash your response body to match.
100% of calls (not a sample) are appended to an Ed25519-signed hash chain; editing any historical record breaks every subsequent hash. The chain head, build identity, and signing key are anchored into Sigstore Rekor — a public, append-only transparency service run by a third party — so we cannot show different histories to different auditors (GET /v1/rekor returns the Rekor log index for independent lookup).
Hardware attestation
The gateway runs inside an AMD SEV-SNP confidential VM: memory is encrypted by the CPU, unreadable to the host, the cloud operator, and Sumplus ops. The CPU signs an attestation report rooted in AMD's certificate authority (VLEK leaf on AWS).
| Layer | What it proves | How to check |
|---|---|---|
| SEV-SNP confidential VM | Gateway memory is encrypted by the CPU; nobody outside the VM can read it | GET /attestation — mode is sev-snp, not a simulation |
| Hardware-signed quote | Fresh, nonce-bound attestation report signed by an AMD-rooted key; replay-proof | POST /attestation/quote with your own nonce |
| Browser verifier | Full chain re-verified client-side: AMD root → intermediate → leaf certificates, report signature, nonce freshness, measurement, build identity, transparency-chain integrity — 7 independent checks, no server-side "trust me" step | /verifier.html |
| Build identity | The running binary's binary_sha256 + git commit, recorded in the signed chain and publicly anchored — any binary change is visible in history | GET /attestation → build |
| Audit sampling | Asynchronous signed samples of (prompt_hash, response_hash) pairs for spot-audit workflows | GET /v1/audit/samples |
Exact boundaries
We put this section in front of security reviewers verbatim. Overclaiming is the fastest way to fail a review; these are the claims we are prepared to defend.
What SafeRouter proves today
- The gateway runs inside a genuine AMD SEV-SNP VM (hardware-signed report, AMD-rooted certificate chain, nonce-fresh) — memory encrypted at the silicon level against the host and cloud operator.
- The running service self-reports a build identity (
binary_sha256, git commit) that is recorded in the signed transparency chain and anchored publicly — any change of binary is visible in the log history. - Every response you receive hashes to a value recorded in a signed, append-only log at a stated index, with the model id and token counts — re-checkable by you, offline.
- The log cannot be silently rewritten (hash chain) and cannot be forked per-audience without detection (public Rekor anchor).
What SafeRouter does not claim
- Upstream providers see plaintext. Inference requires it. We prove our hop; the provider's conduct is governed by their terms. (Mitigation: provider allow-listing per deployment; BYO-provider-contract via BYOK on the roadmap.)
- The SNP launch measurement covers the VM boot environment, not the application binary. The link from hardware measurement to the exact
saferouterbinary is today a soft assertion (the binary reports its own hash, recorded and anchored). Hardening this into a hardware-rooted binding — reproducible builds plus measured boot — is on the roadmap, and we say so plainly. - TLS terminates at a reverse proxy inside the same confidential VM, in front of (not inside) the router process. Moving TLS termination into the attested process is on the roadmap.
- No defense against compromised client machines, or hardware side-channel attacks against the CPU itself (an open research area industry-wide).
Endpoints
Two credential types: an API key (Authorization: Bearer sk-sr-…) for LLM and account-data endpoints, or the dashboard session cookie for the same data endpoints from the web console.
LLM API api key
| Endpoint | Notes |
|---|---|
POST /v1/chat/completions | OpenAI-compatible; SSE streaming, tools, reasoning_content, multimodal content arrays. Claude models translated to/from the Messages API transparently. Unknown model → 400 with closest-match hint; catalog-listed but unprovisioned → 503 model_not_provisioned |
POST /v1/embeddings | OpenAI-compatible embeddings; billed on input tokens |
Error shape: {"error": {"message": "...", "type": "saferouter_error"}} — 400 bad request · 401 bad key · 402 insufficient credits · 429 rate limit (with Retry-After) · 502 upstream failure after the fallback chain is exhausted · 503 not provisioned.
Catalog & stats public
| Endpoint | Returns |
|---|---|
GET /v1/models | catalog of configured providers' models with context, pricing, tags, availability |
GET /v1/stats/models · /providers · /model/{id} | aggregate call stats per model / provider |
GET /healthz | ok |
Attestation & verification public
| Endpoint | Returns |
|---|---|
GET /attestation | identity card: mode (sev-snp), launch measurement, build identity, providers, uptime |
POST /attestation/quote | fresh attestation bundle bound to your nonce ({"nonce": "<hex>"}), hardware-signed, with the AMD certificate chain |
GET /attestation/transparency · /head · /{index} | signed transparency hash chain: full log / head / single entry |
GET /attestation/audit · /head | signed audit-sampling hash chain you can re-verify in-browser: full chain / head |
GET /v1/calls/{call_id}/proof | signed log entry for one call — model, tokens, response hash, chain position |
GET /v1/rekor | current public anchor in Sigstore Rekor, with log index / uuid for independent lookup |
GET /v1/audit/samples api key | signed (prompt_hash, response_hash) audit samples |
Account & governance api key / session
| Area | Endpoints |
|---|---|
| Auth | /v1/auth/signup (with invite_code), signin, signout, me, sessions, config; Google/GitHub OAuth; email verification & password reset over production email; wallet sign-in (env-gated) |
| Keys | GET/POST /v1/keys, PATCH/DELETE /v1/keys/{id} (rename, tier, group_id, budget_usd, allowance_usd, allowance_add_usd, rpm_limit, tpm_limit, external_customer_id), GET /v1/keys/analytics, /v1/keys/export.csv |
| Credits | GET /v1/credits, POST /v1/credits/topup/crypto (hosted USDC invoice), GET /v1/credits/topups |
| Usage | GET /v1/activity (+/stats), GET /v1/usage/series?days=N, /v1/usage/export.csv |
| Groups | GET/POST /v1/groups, PATCH/DELETE /v1/groups/{id} (budgets with warning + hard-stop), /v1/groups/export.csv |
| Orgs | GET/POST /v1/orgs, members + roles (RBAC), GET /v1/orgs/{id}/overview |
| Sessions | GET /v1/sessions, GET /v1/sessions/{id} — rollups of saferouter.session-tagged agent runs |
| Webhooks | GET/POST /v1/webhooks, DELETE /v1/webhooks/{id}; events: request.completed, balance.low, balance.depleted, budget.warning, budget.exceeded |
Management tokens sk-mgmt-*
Everything in the table above can be driven from a script with a management token instead of a browser session. It is a separate credential from an inference key, it is refused on every model route, it carries explicit scopes and an optional expiry, and every call it makes is audited. Section 11 covers it in full, including the per-key spend and throughput caps.
Sub-accounts platform provisioning
A platform reselling access to its own downstream customers (rather than a single developer managing their own keys) can mint a sub-account key per customer: an ordinary sk-sr-… key tagged with external_customer_id, capped by the per-key budget_usd (a monthly ceiling) or allowance_usd (a one-time amount), or both. All sub-account keys still draw from the platform's own shared balance — there is no separate wallet per customer, just a spend ceiling per key.
# Mint a capped key for one customer, in the same call:
curl https://router.sumplus.xyz/v1/keys \
-H "Authorization: Bearer sk-sr-PLATFORM_OWN_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"customer 42","external_customer_id":"cust_42","budget_usd":5}'
# Look up every key issued to that customer:
curl "https://router.sumplus.xyz/v1/keys?external_customer_id=cust_42" \
-H "Authorization: Bearer sk-sr-PLATFORM_OWN_KEY"
A sub-account key is deliberately scoped out of the full dashboard API — GET /v1/keys, GET /v1/credits, GET /v1/activity, and the rest of the table above all return 403 saferouter_scoped_key for it, so handing that key to a customer never exposes the platform's other keys or account-wide balance. The customer's own app authenticates with its issued key against two narrow, self-describing endpoints instead:
| Endpoint | Returns |
|---|---|
GET /v1/me/key | the calling key's own identity, lifetime totals, this month's budget_usd / budget_period_spent_usd / remaining_budget_usd, and the one-time allowance_usd / allowance_spent_usd / remaining_allowance_usd |
GET /v1/me/activity | recent calls billed to the calling key only — never sibling keys on the same platform account |
The key still calls POST /v1/chat/completions exactly like any other key — only the account-management surface is scoped. Passing external_customer_id as null on PATCH /v1/keys/{id} demotes a key back to an ordinary self-service key.
Running the account from a script
Handing out API keys by hand stops scaling almost immediately. Someone has to open the console for every new teammate, every new customer, every rotation. Spend caps get set from memory, or not at all. And when a key has to be cut off in a hurry, the person who can do it is asleep.
The fix is to let your own code do it. The problem with that fix is which credential you give the code: the key you already have is the one that buys model calls, so putting it in a CI variable puts your spending power there too.
A management token exists for exactly this job. It runs the account and it cannot spend a cent.
Two credentials, two blast radii
| Inference key | Management token | |
|---|---|---|
| Looks like | sk-sr-… | sk-mgmt-… |
| Calls models | yes | refused, always |
| Manages keys and quotas | yes | yes, within its scopes |
| Stored as | the secret itself | a SHA-256 hash |
| Can expire | no | yes |
| Leaves an audit trail | usage records | every call, denials included |
| If it leaks | someone spends your balance | someone reads and reshuffles your keys, and spends nothing |
That last row is the whole point. An account key does both jobs at once, so it has to be guarded to the standard of the more dangerous one. Splitting the two means the credential your automation holds is the one whose worst case you can actually live with.
Mint one
Either from the console, under API Keys, or over the API from a signed-in session. Tick only the permissions the script really needs.
curl https://router.sumplus.xyz/v1/mgmt-tokens \
-H "Content-Type: application/json" \
--cookie "sr_session=YOUR_SESSION" \
-d '{"name":"provisioning bot","scopes":["keys:read","keys:write"],"expires_in_days":90}'
# The raw token is in this response and nowhere else. Store it now.
# {"id":"...","token":"sk-mgmt-...","prefix":"sk-mgmt-ab…7f2c","scopes":[...]}
Minting requires a signed-in session on purpose. A management token cannot create a second token, widen its own scopes, or push out its own expiry. That is what makes revocation final: there is no way for a leaked token to have left itself a spare.
Scopes
| Scope | Grants |
|---|---|
keys:read | read keys, groups, orgs, webhooks, BYOK entries |
keys:write | mint, revoke and retune keys, groups, orgs, webhooks |
credits:read | read the account balance and top-up history |
credits:write | top up and redeem codes. Unticked by default, because it spends money |
usage:read | read activity, usage series, session rollups, per-key analytics |
There is no implicit hierarchy: holding a write scope does not imply the matching read scope. Ask for both if you need both. A request outside the token's scopes is refused with 403 and the response names the scope that was missing, so a misconfigured script tells you what to fix instead of failing vaguely.
{"error":{"type":"saferouter_mgmt_token_scope",
"required_scope":"keys:write",
"granted_scopes":["keys:read","usage:read"]}}
Setting a spend cap per key
Every key can carry two spend caps, and they answer different questions. Set either, both, or neither.
| Field | What it does | When it resets |
|---|---|---|
budget_usd | Monthly ceiling. Caps what this key can spend within a calendar month | Automatically, when the month turns over |
allowance_usd | One-time amount allocated to this key. When it runs out the key stops working | Never. Only a new allocation restores it |
Both are checked before every call. Whichever is reached first returns 402, and the message says which one it was, so a blocked customer knows whether to wait for the month or ask for more.
# A prepaid key: $50 allocated, once.
curl https://router.sumplus.xyz/v1/keys \
-H "Authorization: Bearer sk-mgmt-YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"pilot","allowance_usd":50}'
# It ran out. Allocate $25 more. Spending so far is kept, so this
# buys exactly $25 of headroom.
curl -X PATCH https://router.sumplus.xyz/v1/keys/KEY_ID \
-H "Authorization: Bearer sk-mgmt-YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"allowance_add_usd":25}'
# Or set the total outright. A total below what the key has already
# spent cuts it off immediately; null removes the cap.
curl -X PATCH https://router.sumplus.xyz/v1/keys/KEY_ID \
-H "Authorization: Bearer sk-mgmt-YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"allowance_usd":100}'
Sending both allowance fields in one request is refused with 400. One sets the total and the other adds to it, so guessing which one you meant is worse than asking. Warning notifications fire at 80% of the allocated amount and again when it runs out, by email and webhook, and re-arm each time you allocate more.
Setting a throughput cap per key
Two more fields cap how fast a single key may go, in requests per minute and tokens per minute.
| Field | What it does |
|---|---|
rpm_limit | Requests per minute for this key |
tpm_limit | Tokens per minute for this key |
These only ever narrow. The effective limit is the lower of your plan tier and the value you set, so you can hand a flaky integration 10 requests a minute out of your own allowance and keep the rest for production. A value above your tier ceiling is rejected with a 400 that names the ceiling, rather than being silently clamped and leaving you to work out later why the number you asked for was not the number you got.
A worked example
Provisioning one downstream customer, end to end, with no browser involved.
TOKEN=sk-mgmt-YOUR_TOKEN
API=https://router.sumplus.xyz
# 1. Mint the customer's key: tagged with your own id for them,
# $200 allocated, capped at 60 requests a minute.
KEY=$(curl -s $API/v1/keys \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"name":"Acme Corp","external_customer_id":"acme","allowance_usd":200,"rpm_limit":60}')
echo "$KEY" | jq -r '.secret' # hand this to the customer, once
# 2. Later: how much is left?
curl -s "$API/v1/keys?external_customer_id=acme" \
-H "Authorization: Bearer $TOKEN" \
| jq '.data[] | {name, allowance_usd, allowance_spent_usd, allowance_remaining_usd}'
# 3. They paid for more. Top up.
curl -s -X PATCH $API/v1/keys/$KEY_ID \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"allowance_add_usd":200}'
# 4. They churned. Revoke.
curl -s -X DELETE $API/v1/keys/$KEY_ID -H "Authorization: Bearer $TOKEN"
A key tagged with an external customer id is a sub-account key, and it is scoped out of the account-wide API. Handing it to a customer never exposes your other keys or your balance. That side of the story is in section 10.
Watching and revoking
| Endpoint | Purpose |
|---|---|
GET /v1/mgmt-tokens session | list your tokens: prefix, scopes, expiry, last use, call count |
POST /v1/mgmt-tokens session | mint a new token. Fields: name, scopes, expires_in_days |
PATCH /v1/mgmt-tokens/{id} session | rename, replace the scopes, or change the expiry |
DELETE /v1/mgmt-tokens/{id} session | revoke |
GET /v1/mgmt-tokens/audit session | what your tokens have been doing |
Narrowing a token's scopes applies to its very next request, and revoking it means the next request gets 401. Neither waits for a cache to expire.
The audit trail records refusals as loudly as successes. A token repeatedly bouncing off a scope it does not hold is what someone probing a stolen credential looks like, and a log of successes alone would not show it.
What a management token cannot do
Worth knowing before you paste one into a deployment pipeline.
- It cannot call a model. Every inference route refuses it, so a stolen token cannot be turned into free model calls on your balance.
- It cannot mint, edit or extend a token, its own or anyone else's. That needs a signed-in session.
- It cannot read your other credentials. Key secrets are shown once at creation and never returned again, by any route.
- It cannot exceed its scopes, and it cannot grant itself more.
The static admin token still works for operators who prefer it. Management tokens are the per-person, individually revocable, individually audited alternative.
Quotas and settlement
Rate limits
Token bucket per API key, two dimensions; both must pass. On 429: Retry-After header; responses carry x-ratelimit-limit-rpm / x-ratelimit-limit-tpm. Tier is set per key.
Each key can also carry its own rpm_limit / tpm_limit, set at creation or by PATCH /v1/keys/{id} and clearable with null. These only narrow: the effective limit is the lower of the tier and the override, so you can hand a flaky integration 10 req/min out of your allowance without being able to grant yourself more capacity than the tier carries. A value above the tier ceiling is rejected with a 400 naming the ceiling rather than silently clamped. GET /v1/me/key reports the numbers actually in force, so a downstream customer can size its own concurrency without guessing.
| Tier | Requests/min | Tokens/min |
|---|---|---|
| free | 20 | 60,000 |
| paid | 200 | 600,000 |
| enterprise | 2,000 | 6,000,000 |
Spend caps per key
A key can carry two spend caps, and they answer different questions.
budget_usd is a monthly ceiling. It limits what the key can spend in a calendar month, and it clears itself when the month turns over.
allowance_usd is a one-time amount. It is the total ever allocated to that key, nothing resets it, and when it runs out the key is blocked until more is allocated.
Set either, both, or neither. Both are checked before every call, and whichever is reached first returns 402 with a message naming which one it was.
# Mint a key with $50 allocated to it, once:
curl https://router.sumplus.xyz/v1/keys \
-H "Authorization: Bearer sk-mgmt-YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"pilot","allowance_usd":50}'
# It ran out. Allocate $25 more (spending so far is kept,
# so this buys exactly $25 of headroom):
curl -X PATCH https://router.sumplus.xyz/v1/keys/KEY_ID \
-H "Authorization: Bearer sk-mgmt-YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"allowance_add_usd":25}'
# Or set the total outright. Below what the key has already
# spent cuts it off immediately; null removes the cap:
curl -X PATCH https://router.sumplus.xyz/v1/keys/KEY_ID \
-H "Authorization: Bearer sk-mgmt-YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"allowance_usd":100}'
Sending both fields in one request is rejected with a 400. One sets the total and the other adds to it, so guessing which one you meant is worse than asking.
Warning emails and webhooks fire at 80% of the allocated amount and again when it runs out, re-arming each time you allocate more. Both caps show on the key card in the dashboard, and every field here is reachable with a management token holding keys:write.
Billing
- Account-level USD credits — one balance covers all keys and every model.
- Per-call pre-authorization before the upstream call, settlement from real token usage after (streaming-aware: usage from the final SSE frame).
- Failed calls are automatically refunded; per-call cost is visible in the activity log.
- Provider-side cache pricing passes through where the catalog defines it (e.g. DeepSeek cache-hit input rate).
- Top-ups: USDT / USDC via a hosted checkout — minimum $15 per top-up, paid on Ethereum, Solana, Polygon, BNB Chain, or Tron; balance credits automatically once the payment confirms on-chain. Card payments via Stripe are in progress (see roadmap).
Governance features
| Capability | Detail |
|---|---|
| Accounts & sign-in | Email + password (argon2), email verification and password reset over production email; Google / GitHub OAuth integrated (enabled per deployment policy) |
| API key management | Multiple keys per account, create / rename / revoke, per-key plan tier |
| Usage analytics | Per-key lifetime analytics, durable daily aggregates, charts, CSV/JSON export |
| Groups & budgets | Group API keys; per-group / per-key budgets with warning and hard-stop thresholds enforced before the upstream call |
| Organizations & RBAC | Orgs, member management, role-based access |
| Session attribution | Per-agent/session trace attribution for multi-agent workloads |
| Webhooks | Spend and budget alerts pushed to your endpoint |
| Provider pinning | Per-request: force a specific provider, or disable fallback entirely — for provider allow-list policies |
Committed direction, not yet live
| Item | Status |
|---|---|
| Provider lineup build-out per section 04 (11 of 13 providers pending) | In progress, credential-gated |
| Azure confidential VM support (SEV-SNP + vTPM-based verification path) and Azure OpenAI model integration | In progress |
| BYOK sealed key vault inside the enclave | Designed; interest capture live |
| Reproducible builds + measured boot — hardening build identity from soft assertion to hardware-rooted | Planned (build-reproducibility groundwork shipped) |
| TLS termination inside the attested process | Planned |
| Scheduled Merkle-root anchoring (fixed-cost public anchoring regardless of call volume) | Designed |
| Card payments (Stripe); USDC top-ups (integration built, pending production keys) | In progress |
| SOC 2 / formal compliance program | Not started — stated here so it isn't implied |
Engineering-level change history: /roadmap.html.