documentation

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.

live servicerouter.sumplus.xyz
public verifier/verifier.html — 7 checks, re-run in your own browser
transparency log/explorer.html — every call, hash-chained & signed
api styleOpenAI-compatible (/v1/chat/completions, /v1/embeddings)
reference hardwareAMD SEV-SNP confidential VM (EPYC Milan), reference build
docs versionreference sections against the 2026-06-11 build; getting-started walkthrough added 2026-07-08
get started · walkthrough

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"}'
SafeRouter sign-up page with the invite code field filled in
The sign-up page at /signup.html with an invite code entered.

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"}'
Keys page showing a freshly created key with the sk-sr secret revealed once
The keys page at /keys.html — the new key's 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, ... }, ... ] }
Model catalog page showing model ids, pricing, and live/preview badges
The catalog at /models.html — model ids, pricing, and live / preview badges.

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.

Chat playground with a streamed response and the per-call verify panel expanded
The chat playground at /chat.html with the per-call verify panel expanded — the quickest way to see a call and its receipt in one view.

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 }
Credits page with the add-credit panel and the USDT/USDC amount field
The credits page at /credits.html — the add credit panel with the USDT / USDC amount field.
That is the whole loop. From here, verify a call to see the receipt chain, or read rate limits & billing before you scale up traffic.
01 · overview

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

SegmentSituationWhat SafeRouter changes
Enterprise AI platform teamsMultiple models in production; blocked or slowed by internal security/legal review of the gateway vendorReviewers verify the execution environment instead of auditing vendor intentions — the review gets shorter
B2B AI startups with customer-sensitive dataTheir customers ask "who can see our data once it leaves the building?"A demonstrable answer, without building confidential-computing infrastructure themselves
Regulated industriesFinance, healthcare, government — long procurement cyclesThe verification primitives in these docs were designed with these buyers' audit requirements in mind

Data retention (reviewed claim)

The gateway does not persist prompt or response bodies. Stored per call: model id, token counts, latency, billing cost, status, key/owner attribution — plus SHA-256 hashes of prompt/response in the signed audit log. Chat-playground history lives in the user's own browser storage.
02 · why saferouter

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.

ConcernOrdinary LLM gatewaySafeRouter
Operator access to your promptsTechnically able to read, log, or alter every prompt; you rely on a policy promiseVM memory is encrypted by the CPU and unreadable to the operator, and that is hardware-attested
Proof of what actually ranVendor dashboards and self-reported logsHardware-signed attestation you re-verify in your own browser
Evidence per callTrust the vendor's internal logA signed receipt for every call, chained into an append-only log
Tamper resistanceInternal logs are editable by the operatorHash-chained and anchored to a public third-party log, so it cannot be silently rewritten or shown differently to different auditors
Security reviewAuditing vendor intentions, contracts, and policy documentsVerifying the execution environment directly, so the review gets shorter
Model coverageUsually one ecosystemWestern and Chinese frontier models behind one OpenAI-compatible endpoint, with the same guarantees on every call
The short version: an ordinary gateway asks your security team to trust it. SafeRouter is built to be checked. Every claim in these docs resolves to something you can re-run against the live deployment.
03 · models & pricing

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.

ModelContextInput $/MOutput $/MNotes
DeepSeek V4 Flash-07311M0.140.28cache-hit input $0.0028/M
DeepSeek V4 Pro-08131M0.4350.87cache-hit input $0.003625/M
GLM-5.1128k1.404.40
GLM-5128k1.003.20
GLM-5 Turbo128k1.204.00
GLM-4.7200k0.602.20
GLM-4.7 Flash200kfreefree
GLM-4.6128k0.602.20
GLM-4.5128k0.602.20
GLM-4.5 Air128k0.201.10
GLM-4.5 Flash128kfreefree

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.

04 · provider lineup

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.

ProviderModelsStatus
DeepSeek (深度求索)DeepSeek V4 familylive
Zhipu AI (智谱)GLM-4.5 / 4.6 / 4.7 / 5 familieslive
OpenAIGPT family (via Azure OpenAI)integration in progress
AnthropicClaude familycode complete, awaiting upstream credentials
GoogleGemini familyplanned
Mistral AIMistral familyplanned
Alibaba Cloud (阿里)Qwen familyplanned
Moonshot AI (月之暗面)Kimi familyplanned
MiniMax (稀宇科技)MiniMax familyplanned
Tencent (腾讯)Hunyuan familyplanned
StepFun (阶跃星辰)Step familyplanned
ByteDance (字节跳动)Doubao family (Volcano Ark)planned
Xiaomi (小米)MiMounder evaluation
A provider appears as live only when its upstream credentials are provisioned, its models and prices are wired into the audited catalog, and calls verify end-to-end through the proof chain. We do not list aspirational capacity as available.
05 · commercial model

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.

06 · quick start

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

  1. Sign up with your invite code → evaluation credit applied automatically.
  2. Open /chat.html, send a prompt, expand the verify panel → the call's receipt headers resolve to a signed log entry.
  3. Open /verifier.html → all 7 attestation checks run in your own browser, including the AMD certificate chain.
  4. Open /explorer.html → browse the transparency chain; re-verify any entry's signature client-side.
  5. curl https://router.sumplus.xyz/v1/rekor → take the log_index to rekor.sigstore.dev and confirm the anchor exists outside our infrastructure.
If any of those five steps fails to verify, we want to know about it. That is the product working as designed.

Console map

Everything in the web console, in one table:

PageWhat it is
/chat.htmlchat playground with streaming, model picker, and the per-call verify panel
/compare.htmlone prompt against multiple models side-by-side, with latency per column
/models.htmlbrowsable model catalog with pricing and copy-paste code snippets
/status.htmlprovider health and latency
/verifier.htmllive attestation verifier — 7 checks in your browser
/explorer.htmltransparency log explorer with client-side signature checks
/keys.htmlAPI key management + per-key analytics sign-in
/credits.htmlbalance and top-up history sign-in
/activity.htmlrecent calls with cost, latency, fallback info sign-in
/usage.htmlusage & billing charts, CSV export sign-in
/account.htmlaccount, sign-in methods, sessions sign-in
/providers.htmlbring-your-own-key program overview and interest form
/roadmap.htmlengineering-level change history
07 · verify a call

Per-call receipts

Every API response carries evidence headers:

HeaderMeaning
x-saferouter-call-idunique id for this call; feed it to the proof endpoint below
x-saferouter-log-indexposition of this call's entry in the transparency hash chain
x-saferouter-response-sha256hash of the response bytes — re-hash what you received and compare
x-saferouter-tee-quote-ididentifies the attestation identity that served the call
x-saferouter-fallback-from/-topresent 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).

08 · attestation

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).

LayerWhat it provesHow to check
SEV-SNP confidential VMGateway memory is encrypted by the CPU; nobody outside the VM can read itGET /attestation — mode is sev-snp, not a simulation
Hardware-signed quoteFresh, nonce-bound attestation report signed by an AMD-rooted key; replay-proofPOST /attestation/quote with your own nonce
Browser verifierFull 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 identityThe running binary's binary_sha256 + git commit, recorded in the signed chain and publicly anchored — any binary change is visible in historyGET /attestation → build
Audit samplingAsynchronous signed samples of (prompt_hash, response_hash) pairs for spot-audit workflowsGET /v1/audit/samples
09 · trust model

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

  1. 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.
  2. 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.
  3. 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.
  4. The log cannot be silently rewritten (hash chain) and cannot be forked per-audience without detection (public Rekor anchor).

What SafeRouter does not claim

  1. 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.)
  2. The SNP launch measurement covers the VM boot environment, not the application binary. The link from hardware measurement to the exact saferouter binary 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.
  3. 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.
  4. No defense against compromised client machines, or hardware side-channel attacks against the CPU itself (an open research area industry-wide).
10 · api reference

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

EndpointNotes
POST /v1/chat/completionsOpenAI-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/embeddingsOpenAI-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

EndpointReturns
GET /v1/modelscatalog of configured providers' models with context, pricing, tags, availability
GET /v1/stats/models · /providers · /model/{id}aggregate call stats per model / provider
GET /healthzok

Attestation & verification public

EndpointReturns
GET /attestationidentity card: mode (sev-snp), launch measurement, build identity, providers, uptime
POST /attestation/quotefresh 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 · /headsigned audit-sampling hash chain you can re-verify in-browser: full chain / head
GET /v1/calls/{call_id}/proofsigned log entry for one call — model, tokens, response hash, chain position
GET /v1/rekorcurrent public anchor in Sigstore Rekor, with log index / uuid for independent lookup
GET /v1/audit/samples api keysigned (prompt_hash, response_hash) audit samples

Account & governance api key / session

AreaEndpoints
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)
KeysGET/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
CreditsGET /v1/credits, POST /v1/credits/topup/crypto (hosted USDC invoice), GET /v1/credits/topups
UsageGET /v1/activity (+/stats), GET /v1/usage/series?days=N, /v1/usage/export.csv
GroupsGET/POST /v1/groups, PATCH/DELETE /v1/groups/{id} (budgets with warning + hard-stop), /v1/groups/export.csv
OrgsGET/POST /v1/orgs, members + roles (RBAC), GET /v1/orgs/{id}/overview
SessionsGET /v1/sessions, GET /v1/sessions/{id} — rollups of saferouter.session-tagged agent runs
WebhooksGET/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:

EndpointReturns
GET /v1/me/keythe 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/activityrecent 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.

11 · management tokens

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 keyManagement token
Looks likesk-sr-…sk-mgmt-…
Calls modelsyesrefused, always
Manages keys and quotasyesyes, within its scopes
Stored asthe secret itselfa SHA-256 hash
Can expirenoyes
Leaves an audit trailusage recordsevery call, denials included
If it leakssomeone spends your balancesomeone 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

ScopeGrants
keys:readread keys, groups, orgs, webhooks, BYOK entries
keys:writemint, revoke and retune keys, groups, orgs, webhooks
credits:readread the account balance and top-up history
credits:writetop up and redeem codes. Unticked by default, because it spends money
usage:readread 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.

FieldWhat it doesWhen it resets
budget_usdMonthly ceiling. Caps what this key can spend within a calendar monthAutomatically, when the month turns over
allowance_usdOne-time amount allocated to this key. When it runs out the key stops workingNever. 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.

FieldWhat it does
rpm_limitRequests per minute for this key
tpm_limitTokens 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

EndpointPurpose
GET /v1/mgmt-tokens sessionlist your tokens: prefix, scopes, expiry, last use, call count
POST /v1/mgmt-tokens sessionmint a new token. Fields: name, scopes, expires_in_days
PATCH /v1/mgmt-tokens/{id} sessionrename, replace the scopes, or change the expiry
DELETE /v1/mgmt-tokens/{id} sessionrevoke
GET /v1/mgmt-tokens/audit sessionwhat 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.

12 · rate limits & billing

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.

TierRequests/minTokens/min
free2060,000
paid200600,000
enterprise2,0006,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).
13 · enterprise controls

Governance features

CapabilityDetail
Accounts & sign-inEmail + password (argon2), email verification and password reset over production email; Google / GitHub OAuth integrated (enabled per deployment policy)
API key managementMultiple keys per account, create / rename / revoke, per-key plan tier
Usage analyticsPer-key lifetime analytics, durable daily aggregates, charts, CSV/JSON export
Groups & budgetsGroup API keys; per-group / per-key budgets with warning and hard-stop thresholds enforced before the upstream call
Organizations & RBACOrgs, member management, role-based access
Session attributionPer-agent/session trace attribution for multi-agent workloads
WebhooksSpend and budget alerts pushed to your endpoint
Provider pinningPer-request: force a specific provider, or disable fallback entirely — for provider allow-list policies
14 · roadmap

Committed direction, not yet live

ItemStatus
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 integrationIn progress
BYOK sealed key vault inside the enclaveDesigned; interest capture live
Reproducible builds + measured boot — hardening build identity from soft assertion to hardware-rootedPlanned (build-reproducibility groundwork shipped)
TLS termination inside the attested processPlanned
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 programNot started — stated here so it isn't implied

Engineering-level change history: /roadmap.html.