", "model": "gpt-oss:20b"}'
```
Call the endpoint without payment and the server replies `402 Payment Required` with the price, the network, and the `payTo` address. An x402-aware runtime signs a USDC payment payload with the agent's wallet key and retries with the `X-PAYMENT` header. Solrouter's server sends that payload to its facilitator to verify and settle the transfer, then returns 200. The agent never talks to the facilitator.
The paywall charges $0.005 per call. The response body currently reports
`paid.amount: 0.02`
. This is a backend follow-up; the manifest price is the one charged.
This endpoint returns the encrypted reply only. It does not commit an on-chain receipt. Receipts are created for
`POST /tee/process`
, the route the SDK uses. See
[Encryption Proof](/docs/how-it-works/proof)
.
# Agent Tools SDK (/docs/build/agent-tools-sdk)
The @solrouter/agent-tools package is not on npm yet.
`@solrouter/agent-tools` is not published on npm. The package exists in the Solrouter repository at version 1.0.0, but you cannot install it today. The code samples on this page show the planned API. To use the Agent Privacy API now, call `POST /agents/v1/*` over HTTP or use the `umbra_*` tools in the [MCP server](/docs/build/mcp-server).
An AI agent that swaps tokens on Solana leaves a link from payer to destination. To break that link you normally wire up quoting, signing, mixing, and settlement yourself. `@solrouter/agent-tools` will do that work. It gives your agent typed tools for the Agent Privacy API (`/agents/v1`). The agent can quote, run, and settle private swaps, and call encrypted inference.
It ships with a Vercel AI SDK adapter, so you can drop it into an agent without an orchestration layer. Not on the AI SDK? The raw `TOOLS` (JSON Schema definitions) and the `callTool()` function are also exported, so any function-calling framework works.
## What to use today [#what-to-use-today]
* **HTTP.** Call the Agent Privacy API directly. Quotes and anonymity-set reads are live at `GET /agents/v1/quote` and `GET /agents/v1/anonymity-set`. Swap execution routes are Soon.
* **MCP.** The [MCP server](/docs/build/mcp-server) wraps the same routes as `umbra_*` tools for Claude Desktop and Cursor.
## Vercel AI SDK quickstart (Soon) [#vercel-ai-sdk-quickstart-soon]
This will be the fastest path: let an LLM decide when to swap, and let the adapter handle the plumbing. Pass `aiSdkTools(solrouter)` into `generateText` or `streamText`. The adapter wires up the tool schemas, validates the model's arguments, and returns results to the model.
```typescript
import { generateText } from "ai";
import { SolrouterAgentClient } from "@solrouter/agent-tools";
import { aiSdkTools } from "@solrouter/agent-tools/ai-sdk";
const solrouter = new SolrouterAgentClient({
apiKey: process.env.SOLROUTER_API_KEY,
});
const result = await generateText({
model: yourModel, // any AI SDK model provider
tools: aiSdkTools(solrouter),
prompt: "Privately swap 0.01 SOL to USDC and send the USDC to AAA...XXX",
});
```
## Direct usage, no LLM (Soon) [#direct-usage-no-llm-soon]
Sometimes you do not want a model in the loop. You want to drive the privacy pipeline yourself in code. Mode B one-shot swaps do that. Your agent signs the funding transaction with its own wallet. Solrouter then runs the mixer round trip, the Jupiter swap, and the forward to the destination.
```typescript
import { SolrouterAgentClient } from "@solrouter/agent-tools";
const client = new SolrouterAgentClient({ apiKey: "sk_solrouter_..." });
const session = await client.swapOneshot({
payerPubkey: "...",
fromMint: "So11111111111111111111111111111111111111112", // SOL
toMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC
amount: "10000000",
destinationPubkey: "...", // use a fresh address
});
// Sign session.fundingTx with your wallet, broadcast, then:
await client.swapOneshotExecute(session.sessionId, fundingTxSig);
const settled = await client.pollUntilSettled(session.sessionId);
```
## Authentication modes [#authentication-modes]
How your agent proves it can pay shapes how you deploy it, so pick the mode that fits before you build. The Agent Privacy API supports two.
* **API key.** Include `Authorization: Bearer sk_solrouter_...` in every request. Usage bills against your prepaid balance in USDC or `$ROUTER`. Best when your agent is long-lived and you have already funded an account.
* **x402 (keyless).** Pay per call in USDC on Solana mainnet through an x402 facilitator, with no account at all. The live manifest advertises Coinbase (`api.cdp.coinbase.com/x402`). The manifest shows `X402_FACILITATOR_URL` when it is set, or a built-in default when it is not. It does not show which facilitator settles payments. Discover pricing and payment endpoints at [`/.well-known/x402`](https://api.solrouter.com/.well-known/x402).
x402 fits agents that run without a pre-registered API key, for example autonomous agents that start on demand and pay for exactly the calls they make. No balance top-up or account creation is required. The agent still holds a wallet private key to sign payments.
## Available tools [#available-tools]
These are the building blocks your agent will call, through `aiSdkTools()` or the raw `TOOLS` / `callTool()` exports. Each one maps to one step of the private-swap or encrypted-inference flow. The Status column describes the API route behind the tool, not the package.
| Tool | Description | Status |
| ---------------------------- | --------------------------------------------------------------------------------------- | ------ |
| `umbra_quote` | Quote a private swap with anonymity-set sizing | Live |
| `umbra_anonymity_set` | Inspect the current anonymity set for a mint pair | Live |
| `umbra_swap_oneshot` | Open a one-shot swap session. The agent signs the funding transaction | Soon |
| `umbra_swap_oneshot_execute` | Submit the signed funding transaction to start execution | Soon |
| `umbra_session_status` | Poll session state until `settled` | Soon |
| `umbra_create_wallet` | Provision a managed wallet for an agent | Soon |
| `umbra_swap_managed` | Run a swap from a managed wallet | Soon |
| `umbra_encrypt` | Convert a balance to an encrypted balance on the same wallet | Soon |
| `umbra_shield` | Mixer round trip, withdraw, then forward to a fresh address | Soon |
| `umbra_balance` | Read the encrypted balance of a managed wallet | Soon |
| `umbra_attestation` | Read the settlement record of a swap session (`GET /agents/v1/attestations/:sessionId`) | Soon |
| `private_inference_paid` | x402-paywalled encrypted inference, no API key | Live |
# Get an API key (/docs/build/api-key)
Most AI APIs make you sign up with an email, verify your identity, and add a credit card before your first request. Solrouter skips all of that. You authenticate with an API key sent as a bearer token. You get that key by connecting a Solana wallet. There is no email sign-up, no KYC (Know Your Customer identity check), and no card.
Fund a prepaid balance in USDC or $ROUTER, and you can make calls in minutes.
## Getting your API key [#getting-your-api-key]
Here is the full path from zero to your first authenticated request. It has four steps, all in the browser.
### Go to solrouter.com/sdk [#go-to-solroutercomsdk]
Open [solrouter.com/sdk](https://solrouter.com/sdk) in your browser.
### Connect your Solana wallet [#connect-your-solana-wallet]
Connect Phantom, Solflare, or a Privy embedded wallet (any Wallet Standard wallet). Your wallet is your only identity credential. Solrouter collects no email and no personal information.
### Generate an API key [#generate-an-api-key]
Click **Generate API Key**. Your key is issued at once and starts with `sk_solrouter_...`. Copy it and store it somewhere safe. It is not shown again.
### Top up your balance [#top-up-your-balance]
Add funds to your prepaid account in **USDC** or **$ROUTER**. Solrouter meters every API call and deducts the cost from this balance. There is no monthly bill. You pay only for what you use.
## Using your API key [#using-your-api-key]
Once you have a key, you attach it to requests in one of two ways: through the SDK, which handles it for you, or directly over HTTP.
### With the SDK [#with-the-sdk]
Pass your API key when you create the `SolRouter` client. From then on, the SDK attaches it to every request. You never touch the header yourself.
```typescript
import { SolRouter } from '@solrouter/sdk';
const client = new SolRouter({
apiKey: 'sk_solrouter_...',
baseUrl: 'https://api.solrouter.com',
});
```
### Direct HTTP (REST API) [#direct-http-rest-api]
If you are not using the SDK, send the key yourself as a bearer token in the `Authorization` header.
```bash
curl -X POST "https://api.solrouter.com/agent" \
-H "Authorization: Bearer sk_solrouter_..." \
-H "Content-Type: application/json" \
-d '{"prompt": "Hello", "model": "gpt-oss:20b"}'
```
## Authentication tiers [#authentication-tiers]
External callers have two ways to authenticate. Pick the row that matches how your code runs.
| Tier | How it works | Best for |
| ------------------ | -------------------------------------------------------------------------- | ------------------------------------------- |
| **API key** | `Authorization: Bearer sk_solrouter_...`, billed from your prepaid balance | Most applications and development workflows |
| **x402 (keyless)** | Per-call USDC settlement on Solana mainnet | Autonomous agents that do not hold API keys |
x402 settles through a facilitator. The live manifest advertises Coinbase (`api.cdp.coinbase.com/x402`). Service discovery is at [`/.well-known/x402`](https://api.solrouter.com/.well-known/x402). The manifest shows `X402_FACILITATOR_URL` when it is set, or a built-in default when it is not. The manifest does not show which facilitator settles payments. Wire-level facilitator: not determined.
The x402 tier is worth a closer look if you build agents. Instead of storing a long-lived key, an agent pays for each call on the spot in USDC. x402 removes the API key, not the wallet key. The agent still holds a wallet private key to sign payments, so protect that key the same way.
## Keeping your API key safe [#keeping-your-api-key-safe]
Your key can spend real money, so treat it with the same care as a password. The practices below keep it out of the wrong hands.
* **Never commit your API key to source control.** Treat `sk_solrouter_...` like a password. Keep it out of Git history, checked-in `.env` files, and any public repository.
* **Use environment variables.** Store your key in `SOLROUTER_API_KEY` and read it at runtime, so the secret never lives in your code:
```typescript
const client = new SolRouter({
apiKey: process.env.SOLROUTER_API_KEY,
baseUrl: 'https://api.solrouter.com',
});
```
* **Rotate compromised keys at once.** If a key is exposed, go to [solrouter.com/sdk](https://solrouter.com/sdk), delete the affected key, and generate a new one.
Never expose your API key in client-side code or public repositories. Anyone with your key can spend your prepaid balance. If you suspect a key has leaked, rotate it at once at solrouter.com/sdk.
# MCP Server (/docs/build/mcp-server)
The server is on npm. Encrypted chat is live. Swap execution tools are Soon. Read the status word beside each tool.
You already work inside Claude Desktop or Cursor. The Solrouter MCP server lets you run Solrouter's encrypted chat and Agent Privacy API tools right there. You need no separate app.
MCP (Model Context Protocol, an open standard for connecting AI clients to external tools) is the bridge. The server runs on your machine as `npx @solrouter/mcp-server`. It holds your API key and encrypts prompts for the `encrypted_chat` tool and for the AI synthesis step of the research tools. Other calls leave your machine in plaintext. The tables below say which.
## Configuration [#configuration]
This is the one-time setup that registers Solrouter as a tool provider in your client.
Add the following block to your MCP configuration file. For **Claude Desktop**, that file is `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS (or the equivalent path on Windows). For **Cursor**, add it under `mcpServers` in `~/.cursor/mcp.json`.
```json
{
"mcpServers": {
"solrouter": {
"command": "npx",
"args": ["@solrouter/mcp-server"],
"env": {
"SOLROUTER_API_KEY": "sk_solrouter_...",
"SOLROUTER_API_URL": "https://api.solrouter.com"
}
}
}
}
```
Set `SOLROUTER_API_URL`. Without it, the server defaults to a Render host, not `api.solrouter.com`. Set `BRAVE_API_KEY` too: `private_research` and `private_token_analysis` fail without it. `HELIUS_RPC_URL` is optional (default: `https://api.mainnet-beta.solana.com`).
Save the config, then restart Claude Desktop or reload the Cursor window. The Solrouter tools appear in the tool list. There is nothing else to install.
## Privacy and research tools [#privacy-and-research-tools]
Use these tools for encrypted AI inference or on-chain research from inside your client. Only the AI step is encrypted. The data-gathering steps call third parties directly from your machine in plaintext.
| Tool | Description | Leaves your machine in plaintext |
| ------------------------------- | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------- |
| `encrypted_chat` (Live) | Encrypted AI query through `/tee/process` | Nothing. The prompt is encrypted on your machine |
| `private_research` (Live) | Web search, DEX data, and on-chain lookups, then an encrypted AI synthesis | Query to Brave; token symbols to DexScreener; wallet addresses to the Solana RPC |
| `private_token_analysis` (Live) | DEX data, price, and web results for one token, then an encrypted AI synthesis | The token to DexScreener, CoinGecko, and Brave |
| `private_wallet_audit` (Live) | Holdings of one wallet, then an encrypted AI synthesis | The wallet address to the Solana RPC and DexScreener |
| `list_models` (Live) | Models from `GET /api/v1/models` with pricing | Nothing sensitive |
| `account_balance` (Live) | Your credit balance from `GET /api/v1/balance` | Nothing sensitive |
## Agent and swap helper tools [#agent-and-swap-helper-tools]
These tools call the Solrouter backend in plaintext or return text without any network call.
| Tool | Description | Leaves your machine in plaintext |
| ------------------------------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------------- |
| `agent_run` (Live) | Tool-augmented agent completion through `POST /agent` (web search, Solana data, paid APIs) | Your prompt, to the Solrouter backend |
| `umbra_describe` (Live) | Explains private swaps. Makes no network call | Nothing |
| `umbra_anonymity_stats` (Soon) | Deposit count for one denomination bucket from `GET /umbra/anonymity-set` | The bucket and network |
| `umbra_initiate_private_swap_widget` (Soon) | Returns a directive that tells the Solrouter web chat to show the swap widget. Executes nothing | Suggested tokens and network |
## Agent Privacy API tools [#agent-privacy-api-tools]
These tools wrap `POST` and `GET /agents/v1/*` over HTTPS with your API key. Swap parameters travel in plaintext to the Solrouter backend. Swap execution is Soon. Tools marked Soon can move real funds once they go live, so read the session state before you act on it.
| Tool | Description |
| ----------------------------------- | ---------------------------------------------------------------------------------------- |
| `umbra_quote` (Live) | Quote a private swap with anonymity-set sizing |
| `umbra_anonymity_set` (Live) | Inspect the current anonymity set for a mint pair |
| `umbra_swap_oneshot` (Soon) | Open a one-shot swap session. The agent signs the funding transaction |
| `umbra_swap_oneshot_execute` (Soon) | Submit the signed funding transaction to start execution |
| `umbra_session_status` (Soon) | Poll session state until `settled` |
| `umbra_create_wallet` (Soon) | Provision a managed wallet for an agent |
| `umbra_swap_managed` (Soon) | Run a swap from a managed wallet |
| `umbra_encrypt` (Soon) | Convert a balance to an encrypted balance on the same wallet |
| `umbra_shield` (Soon) | Mixer round trip, withdraw, then forward to a fresh address |
| `umbra_balance` (Soon) | Read the encrypted balance of a managed wallet |
| `umbra_attestation` (Soon) | Read the settlement record of a swap session (`GET /agents/v1/attestations/:sessionId`) |
| `private_inference_paid` (Live) | x402-paywalled encrypted inference, no API key. You supply the encrypted prompt yourself |
Not every request through the MCP server is encrypted. `encrypted_chat` and the AI synthesis step of the three research tools use the Privacy SDK path: RescueCipher and Intel TDX. The Solrouter backend never sees that plaintext. Web search (Brave), DexScreener, CoinGecko, and Solana RPC lookups go from your machine to those third parties in plaintext. `agent_run` and the `umbra_*` tools send their inputs to the Solrouter backend in plaintext.
# Privacy SDK (/docs/build/privacy-sdk)
Most AI APIs read your prompts in the clear. The Solrouter Privacy SDK encrypts your prompt before it leaves your machine, so the Solrouter backend cannot read it. The SDK handles the cryptography for you.
Here is what happens when you call `client.chat()`. The SDK fetches the enclave's X25519 public key from `GET /tee/public-key`. It does not fetch or verify the attestation quote. That check is a manual step. See the [attestation guide](/docs/how-it-works/attestation). The SDK then encrypts your prompt on your machine with Arcium's RescueCipher. It sends the encrypted blob through the Solrouter backend, which forwards it without decrypting it. The TEE (Trusted Execution Environment, a CPU-isolated confidential VM) decrypts the prompt and calls the model on a Nosana GPU node. The node runs the model outside the enclave, so it sees the prompt and the reply in plaintext during inference. The reply comes back encrypted, and the SDK decrypts it with your session key.
## Installation [#installation]
Install the SDK from your package manager of choice.
```bash
npm install @solrouter/sdk
```
```bash
yarn add @solrouter/sdk
```
```bash
pnpm add @solrouter/sdk
```
## Basic usage [#basic-usage]
This section walks through the calls you will make most often, starting with the default encrypted chat.
### Encrypted chat (default) [#encrypted-chat-default]
Every call is encrypted unless you opt out. Create a `SolRouter` client with your API key and the production `baseUrl`, then start chatting.
```typescript
import { SolRouter } from '@solrouter/sdk';
const client = new SolRouter({
apiKey: 'sk_solrouter_...',
baseUrl: 'https://api.solrouter.com',
});
// Encrypted on your machine. The Solrouter backend never sees plaintext.
const response = await client.chat('What are the risks of this DeFi protocol?');
console.log(response.message);
```
Set `baseUrl` in every client. Without it, SDK 1.1.0 defaults to a Render host, not `api.solrouter.com`.
### Choosing a model [#choosing-a-model]
Solrouter runs self-hosted open-weight models on Nosana GPU nodes. The backend catalog has three ids. The SDK type allows two strings. At runtime any other string passes through unchanged.
| SDK string | Backend id | Status | Note |
| ------------- | ------------- | -------- | -------------------------------------------------------------------------------------------------------- |
| `gpt-oss-20b` | `gpt-oss:20b` | Live | Default. Use this with SDK 1.1.0. |
| `qwen3-8b` | `qwen3:8b` | Archived | Alias of a retired node. Do not use. |
| none yet | `qwen3.8:27b` | Live | Reachable with a type cast: `model: 'nosana:qwen3.8:27b' as any`. A typed alias needs a new SDK release. |
| none yet | `gemma4:31b` | Soon | Encrypted path not confirmed end to end. |
A new SDK release with the current model map is Soon.
```typescript
const response = await client.chat('Summarize the latest Solana validator outage', {
model: 'gpt-oss-20b', // the only typed live model string in SDK 1.1.0
});
```
After an idle period, a node can answer with a retryable "warming up" error. Wait a moment and send the request again.
### Opt out of encryption (plaintext) [#opt-out-of-encryption-plaintext]
You can turn off client-side encryption for one call. The prompt then goes to the Solrouter backend in plaintext. The backend reads it and routes it to the same self-hosted Nosana models. No proprietary model is reachable this way.
```typescript
const response = await client.chat('Hello', { encrypted: false });
```
In words, with `encrypted: true` (the default):
* Your device encrypts the prompt with RescueCipher and an X25519 shared secret.
* The Solrouter backend forwards the ciphertext. It cannot read it.
* The TEE decrypts the prompt and calls the model at the configured Nosana endpoint URL. The node sees the prompt in plaintext.
* The reply comes back encrypted to your session key.
In words, with `encrypted: false`:
* Your device sends the prompt as plaintext.
* The Solrouter backend reads the prompt and routes it to the same Nosana model.
* The TEE is not used. No on-chain receipt is created.
* The reply comes back in plaintext.
### Guided reasoning (BRAID, agent path) [#guided-reasoning-braid-agent-path]
For questions that need structured analysis, route through the agent endpoint. Setting `reasoning: 'braid'` runs your request through BRAID guided reasoning. BRAID walks a fixed Guided Reasoning Diagram (GRD) of tool steps, then makes one synthesis call to the model. Older material calls this SERV.
This path is plaintext. The SDK sends the prompt to `POST /agent` without encryption and the response reports `encrypted: false`.
```typescript
const response = await client.chat('Compare Marginfi vs Kamino lending on Solana', {
reasoning: 'braid', // plaintext path through the agent endpoint
});
```
### Check balance [#check-balance]
You pay per call from a prepaid balance. Check what is left at any time.
```typescript
const { balance, balanceFormatted } = await client.getBalance();
```
## SDK options reference [#sdk-options-reference]
Each of these options goes in the object you pass as the second argument to `client.chat()`.
On the encrypted path, this leaves your machine: the ciphertext bundle (`ciphertext`, `nonce`, `publicKey`, `version`), plus in plaintext your API key, the model id, `chatId`, and any `systemPrompt`, `useRAG`, `ragCollection`, or `useLiveSearch` you set. The backend forwards only the bundle and the model id to the CVM.
| Option | Type | Default | Description |
| --------------- | ------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `model` | string | `gpt-oss-20b` | Model string. Use `gpt-oss-20b` with SDK 1.1.0. `qwen3-8b` maps to a retired node. Other catalog ids pass through with a type cast. |
| `encrypted` | boolean | `true` | Turn client-side encryption on or off for this call |
| `reasoning` | string | none | Set to `'braid'` for guided reasoning. This path is plaintext. |
| `chatId` | string | none | Sent in plaintext to the backend. Not forwarded to the CVM. |
| `systemPrompt` | string | none | Sent in plaintext to the backend. Dropped before the CVM, so it never reaches the model. |
| `useRAG` | boolean | none | Sent in plaintext to the backend and ignored on the encrypted path. |
| `ragCollection` | string | none | Sent in plaintext to the backend and ignored on the encrypted path. |
| `useLiveSearch` | boolean | none | Sent in plaintext to the backend and ignored on the encrypted path. |
## No KYC required [#no-kyc-required]
Privacy starts at sign-up. You do not need an email address, a credit card, or any personal information to use the SDK. Connect your Solana wallet at [solrouter.com/sdk](https://solrouter.com/sdk), generate an API key, and top up your balance in USDC or `$ROUTER`. Pricing is metered per call from your prepaid balance.
Solrouter runs only self-hosted, open-weight models on the Nosana decentralized GPU network. The catalog ids are `gpt-oss:20b` (Live), `qwen3.8:27b` (Live), and `gemma4:31b` (Soon). There are no third-party model APIs: no OpenAI, Anthropic, or Google. Your prompts never go to an external model provider. The Nosana node that runs the model does see the prompt in plaintext during inference. Solrouter does not control that hardware.
# Quickstart (/docs/build/quickstart)
Most AI APIs can read every prompt you send them. Solrouter does not: your message is encrypted on your machine before it leaves, and the backend relays it without seeing the plaintext. The next five steps set that up end to end. You sign in with a Solana wallet, install the SDK, and send your first encrypted request. No email and no credit card: you need a wallet and a few lines of TypeScript.
### Get an API key [#get-an-api-key]
Your key is how Solrouter authenticates you and bills your usage. It is tied to your wallet, not your identity.
Go to [solrouter.com/sdk](https://solrouter.com/sdk), connect your Solana wallet, and generate an API key. Top up your prepaid balance in USDC or $ROUTER to start making calls.
No email, no credit card, and no KYC required. Your API key is tied to your wallet.
### Install the SDK [#install-the-sdk]
The SDK handles encryption, routing, and decryption for you, so you write normal TypeScript and get privacy by default.
Add `@solrouter/sdk` to your project using your preferred package manager.
```bash
npm install @solrouter/sdk
```
```bash
yarn add @solrouter/sdk
```
```bash
pnpm add @solrouter/sdk
```
### Initialize the client [#initialize-the-client]
One call gets you a configured client. Pass your API key and the API base URL.
The SDK fetches the enclave's published public key from `GET /tee/public-key` before your first encrypted request. That key is the encryption target for your prompts. The SDK does not fetch or verify the attestation quote. To check the enclave yourself, see [Attestation](/docs/how-it-works/attestation).
```typescript
import { SolRouter } from '@solrouter/sdk';
const client = new SolRouter({
apiKey: 'sk_solrouter_...',
baseUrl: 'https://api.solrouter.com',
});
```
Set `baseUrl` explicitly. The SDK default points at a Render host, not at `api.solrouter.com`.
### Send your first encrypted chat [#send-your-first-encrypted-chat]
Here the privacy guarantee pays off. Your prompt travels encrypted through the Solrouter backend. Only the TEE (Trusted Execution Environment: hardware that isolates code and data from the machine's operator) can decrypt it. The enclave then sends the plaintext to an open-weight model on a Nosana GPU node.
Call `client.chat()` to send a message. The SDK encrypts it client-side with Arcium's RescueCipher. It sends the encrypted blob through the Solrouter backend, which cannot read it. Then it decrypts the response for you.
With the current SDK, use the default model `gpt-oss-20b`. Other catalog ids pass through with a type cast; see [Models](/docs/how-it-works/models).
```typescript
// Encrypted end to end: the Solrouter backend never sees plaintext
const response = await client.chat('What are the risks of this DeFi protocol?');
console.log(response.message);
```
If the GPU node was idle, the first reply can say "Nosana GPU node is warming up". Wait a moment and send the request again.
### Check your balance [#check-your-balance]
Solrouter bills against prepaid funds, so you can check your remaining balance at any time. For example, check it before a batch of calls, or show it in your own UI.
Query your prepaid balance:
```typescript
const { balance, balanceFormatted } = await client.getBalance();
console.log(`Balance: ${balanceFormatted}`);
```
## Next steps [#next-steps]
You have sent an encrypted request and checked your balance. That is the core loop. Where you go next depends on what you build: the full SDK surface, keyless payments, or direct HTTP access.
Full `@solrouter/sdk` documentation: model selection, plaintext mode (`encrypted: false`), BRAID reasoning (`reasoning: 'braid'`), and more.
Learn about API key auth, x402 keyless payments, and keeping your credentials safe.
Browse the full REST API, including the agent endpoint and x402 paywall spec.
# Agent reasoning (/docs/how-it-works/agent-reasoning)
`POST /agent` has three paths. The request body picks the path. Older material calls the guided path SERV; the code, the SDK option, and the API value call it BRAID.
## Three paths [#three-paths]
| Path | Trigger | Where it runs | Model calls |
| ------------------------ | -------------------- | ------------------ | ------------------------------ |
| Tool loop (default) | `useTools: true` | Backend | Up to 8 (`MAX_ITERATIONS = 8`) |
| Guided reasoning (BRAID) | `reasoning: 'braid'` | Backend | One synthesis call |
| Encrypted agent mode | `encryptedPrompt` | Inside the enclave | Loop inside the enclave |
The encrypted path is Live for REST callers who send `encryptedPrompt`, and Soon for the SDK. The chat app's agent mode runs the plaintext tool loop.
## The tool loop [#the-tool-loop]
The default path is a standard loop. The model picks a tool, the backend runs it, the model reads the result, and it repeats up to eight times. The last call has no tools, so the model must write the answer. The backend registers 18 tools:
* **Web:** `web_search`, `scrape_url`, `crawl_url`
* **On-chain and markets:** `solana_balance`, `token_price`, `swap_quote`, `trending_tokens`
* **Research:** `deepwiki`, `colosseum_search`, `colosseum_archives`
* **Paid APIs:** `paysh_search_apis`, `paysh_call_api`
* **Connected accounts:** `github_list_repos`, `github_issues`, `github_read_file`, `notion_search`, `notion_get_page`, `notion_query_database`
The encrypted path runs a 5-tool allowlist inside the enclave: `web_search` (SearXNG in the same enclave), `token_price`, `trending_tokens`, `swap_quote`, and `solana_balance`. Every other tool fails closed in that mode.
## Guided reasoning (BRAID) [#guided-reasoning-braid]
A standard loop asks the model what to do at every step, one model call per step. BRAID splits the two jobs an agent does: deciding what data to gather, and writing the answer. It walks a Guided Reasoning Diagram (GRD), a fixed graph that sets which tools run and in what order, then calls the model once at the end to write the reply.
A query passes through four stages:
1. **Intent detection.** Keyword rules map the prompt to one of six GRDs (`comparison`, `defi-analysis`, `general-research`, `market-overview`, `token-research`, `wallet-analysis`). No model call.
2. **GRD execution.** BRAID walks the graph node by node. Branch nodes use a rule first; when no rule applies, the model answers a short one-word question to pick the branch.
3. **Skill-graph injection.** If the prompt matches skill nodes, their notes are added to the synthesis prompt.
4. **Synthesis.** BRAID calls the model once to turn the collected data into a reply.
A swap prompt with two known tokens, or a prompt with three or more known tokens, skips the walk and calls the tools directly before the same single synthesis call.
BRAID spends one synthesis call plus the occasional one-word branch call, instead of one model call per step. That is the whole mechanism behind the cost and latency claim. No benchmark numbers are published.
Send `reasoning: 'braid'` to `POST /agent`, or `client.chat(prompt, { reasoning: 'braid' })` in the SDK. The SDK path is plaintext today (`encrypted: false`). See [POST /agent](/docs/api-reference/agent) for the `braidTrace` envelope.
## The skill graph [#the-skill-graph]
Before the synthesis call, the engine matches your query against 44 knowledge nodes. It scores every node against the prompt, walks the graph from the top three scoring nodes, follows edges to a depth of 2, and stops at 5 nodes. The reached nodes add domain notes to the system prompt. A prompt that matches no node gets no extra tokens, and the response never returns the walked path.
Click a node to see its edges. The highlighted path is a DeFi protocol comparison.
**The 44 nodes, by cluster**
* **Research and analysis (15):** research-core, source-eval, defi-analysis, liquidity-risk, token-economics, market-analysis, on-chain-analysis, wallet-analysis, privacy-research, risk-assessment, smart-contract-risk, comparative-analysis, data-synthesis, colosseum-research, colosseum-archives.
* **Ecosystem (2):** arcium-mpc, solana-ecosystem.
* **DeFi protocols (10):** jupiter-defi, raydium-defi, orca-defi, meteora-defi, kamino-defi, sanctum-staking, pump-fun, lulo-lending, ranger-perps, prediction-markets.
* **Infrastructure and oracles (8):** helius-infra, light-protocol-zk, metaplex-nfts, pyth-oracle, switchboard-oracle, squads-multisig, debridge-cross-chain, coingecko-analytics.
* **Solana development (9):** solana-kit-dev, anchor-dev, pinocchio-dev, framework-kit-frontend, solana-testing, solana-security-audit, token2022-extensions, quicknode-infra, magicblock-gaming.
# Attestation (/docs/how-it-works/attestation)
The quote endpoints and on-chain receipts are live. Reference measurements to compare against are not published yet.
When you send a prompt to a private inference service, how do you know it ran where the service claims? How do you know the code is the published code and not a tampered copy that logs your data? Solrouter answers that with attestation: hardware-signed proof you can check yourself.
The Solrouter TEE (Trusted Execution Environment: hardware that isolates code and data even from the machine's owner) publishes an Intel-signed TDX quote. That quote binds the enclave's public key to the code measurement running inside the Confidential VM. Each `/tee/process` response also carries a quote when the CVM can reach the dStack agent. Otherwise the `attestation.tdxQuote` field is `null` and `attestation.tdxQuoteError` says why. You never have to take Solrouter's word for it: fetch the quote, verify Intel's signature chain, and confirm on-chain that your inference has a receipt.
## Live attestation endpoints [#live-attestation-endpoints]
These two endpoints hand you the raw material for verification. Query them any time to retrieve the current attestation data.
### Get the TEE public key [#get-the-tee-public-key]
```bash
GET https://api.solrouter.com/tee/public-key
```
This returns the X25519 public key currently active inside the Confidential VM. The SDK uses this key to encrypt prompts client-side. The enclave generates it at boot, so only the enclave holds the matching private key. The key changes on every CVM boot. Nobody on the host, including Solrouter, can decrypt traffic sealed to it.
### Get the TDX attestation quote [#get-the-tdx-attestation-quote]
```bash
GET https://api.solrouter.com/tee/attestation
```
This returns the Intel TDX attestation quote, the hardware-signed proof that ties everything together. The response includes `teePublicKey`, `teePublicKeySha256`, `reportDataHex`, and `tdxQuote`. Its `report_data` is `sha256(teePublicKey)`, which binds the public key you fetched above to the enclave that produced the quote. Verify the quote and you confirm two things:
1. The host CPU is a genuine Intel TDX-capable processor.
2. The public key was generated inside that specific enclave instance.
Confirming that the enclave runs the published Solrouter code needs reference measurements. Those are not published yet (see below).
### Two report\_data formulas [#two-report_data-formulas]
Solrouter produces two kinds of quote. They pin different data, so check the right formula for the quote you hold.
| Quote | `report_data` |
| ------------------------------------------------ | ------------------------------------------------ |
| `GET /tee/attestation` | `sha256(X25519 public key)` |
| `attestation.tdxQuote` in a `/tee/process` reply | `sha256(X25519 public key ‖ ed25519 public key)` |
The ed25519 key is a signing key the enclave also generates at boot. The enclave uses it to sign the encryption proof that goes on-chain.
## What you can verify [#what-you-can-verify]
Attestation only matters if you can check the claims independently. Here is what the quote lets you prove on your own today, and what it does not yet.
* **Intel root chain**: the TDX quote is signed by an Intel-issued key. Verifying the signature chain confirms the hardware is a genuine TDX CPU, not a simulated or spoofed environment. Live.
* **Public key binding**: `report_data` commits to the enclave's public key. So you can confirm the key you encrypted to belongs to this enclave instance. An interceptor cannot substitute its own key. Live.
* **Code measurements**: the `tdxQuote` field is the dStack guest agent's response, passed through unparsed. Solrouter does not publish a parser or reference values for the measurements inside it. The product repository is private. Reference measurements: Soon.
Advanced users can verify the full Intel TDX quote chain independently using Intel's DCAP (Data Center Attestation Primitives) libraries or a third-party TEE verification service. The quote Solrouter returns is a standard TDX quote, no proprietary format.
## On-chain attestation anchor [#on-chain-attestation-anchor]
Off-chain verification proves the enclave is genuine, but it lives in a response you have to trust Solrouter to keep. For a record nobody can quietly edit later, Solrouter anchors attestation data to Solana. Cluster: the commit code targets mainnet; the program deployment on mainnet has not been re-measured.
The Solrouter attestation program is deployed at:
```
ATMRatMtsKX4bHax7U4FRdhbE4mjU4NKpDZGqZqAhBKb
```
Each inference sent through `POST /tee/process` gets a **Light Protocol compressed account** on Solana. Solrouter's deployer wallet commits it after the CVM signs the proof. This is automatic; you do not publish anything. The account address is derived from the seeds `attestation_v2` and `sha256(encryptedPrompt)`, so anyone who holds the ciphertext can derive the same address. The `/tee/process` response reports it under `onchainAttestation` with `address`, `signature`, and `explorerUrl`. If the commit fails, the inference still succeeds and `onchainAttestation` is `null`.
To read a receipt back, use the public attestation endpoints. They read from the Solana ledger through a Photon indexer.
```bash
# By the commit transaction signature from onchainAttestation.signature
GET https://api.solrouter.com/attestation/by-tx/:sig
# By sha256 of the encrypted prompt
GET https://api.solrouter.com/attestation/by-hash/:hash
# By the compressed account address from onchainAttestation.address
GET https://api.solrouter.com/attestation/:address
# Derive hash and address from a ciphertext you hold
POST https://api.solrouter.com/attestation/derive
{ "encryptedPrompt": "..." }
```
The `umbra_attestation` MCP tool is a different thing. It returns the settlement record of a private-swap session from `GET /agents/v1/attestations/:sessionId`. It does not read inference receipts.
## Verify it yourself [#verify-it-yourself]
The full check, from the live key through the Intel DCAP signature chain to the on-chain anchor, runs in your browser on [Check a reply yourself](/docs/verify). Auditors will find the by-hand steps there too.
# Encryption (/docs/how-it-works/encryption)
When you send a prompt to an AI provider, you normally trust that provider to read it, store it, and not misuse it. Solrouter removes that trust requirement for its own backend. Your prompt is encrypted on your own device before it leaves, and Solrouter's backend never holds the key to read it.
Solrouter encrypts your prompts and responses with Arcium's `RescueCipher` cipher and `X25519` key exchange. X25519 lets two parties agree on a shared secret without sending it. The ciphertext travels through Solrouter's backend untouched. It is decrypted only inside a hardware-isolated Intel TDX Confidential VM, a TEE. A TEE (Trusted Execution Environment) is hardware that isolates code and data from the machine's owner. Solrouter's backend is a blind relay: it routes encrypted blobs it cannot read, and the private key that could decrypt them never leaves the enclave.
## Encryption components [#encryption-components]
Here are the three building blocks that make the guarantee work, and what each one does.
### Client-side encryption [#client-side-encryption]
The first line of defense is simple: encrypt before you transmit. The SDK encrypts your prompt in the browser or in your server process before it sends anything.
* **`RescueCipher`**: Arcium's field-element symmetric cipher. Arcium chose it for compatibility with MPC, FHE, and ZK computation, so the same encrypted payload can be processed under any of those paradigms as Arcium's network matures.
* **`X25519` key exchange**: your SDK session generates an ephemeral (single-use, per-session) X25519 keypair. The SDK derives the shared secret from your ephemeral private key and the TEE public key. The SDK fetches that key from `GET /tee/public-key`. It does not fetch or verify the attestation quote. Verification is a manual step; see [Attestation](/docs/how-it-works/attestation).
* **TEE-generated keypair**: the TEE's own X25519 keypair is generated inside the Confidential VM at boot time. The private key never leaves the enclave, not even to Solrouter's own infrastructure.
### Inference isolation [#inference-isolation]
Your data has to be decrypted somewhere to run the model. The question is *where*, and who can see it. With Solrouter, decryption happens inside an attested enclave. The model runs on a Nosana GPU node that the enclave calls.
* **Intel TDX Confidential VM**: a hardware-enforced TEE. Memory is encrypted by the CPU and inaccessible to the host OS, hypervisor, and any Solrouter process running outside the enclave.
* **Where plaintext exists**: the enclave decrypts your prompt, then calls the model on a Nosana GPU node (HTTPS per the documented node URL, not re-verified). The prompt and reply exist in plaintext in that node's memory during inference. The node runs outside the TDX enclave. The node operator could read the prompt at that moment; Solrouter does not control that hardware. The request is not linked to your identity on the node. When the reply returns to the enclave, it is encrypted with your session's ephemeral key before it leaves.
* **No backend access**: no Solrouter employee, server process, or privileged operator on the backend can read your prompt or response. The hardware enforces this.
### Transport [#transport]
Encryption only helps if there is no gap where plaintext leaks in transit between you and the enclave. There is none.
* Your prompt and the reply are encrypted end-to-end between your client and the enclave. The request metadata (API key, model id, `chatId`, and any `systemPrompt`) reaches the backend in plaintext.
* The Solrouter backend is a **blind relay**. It forwards encrypted blobs without being able to decrypt them. It never has the keys.
The full request path, hop by hop, is on [How It Works](/docs/how-it-works/request-flow).
## Why RescueCipher? [#why-rescuecipher]
You might wonder why Solrouter does not use a familiar cipher like AES. The answer is about where your data can go next.
Most symmetric ciphers (AES-GCM, ChaCha20) are designed for classical computation. They are efficient on CPUs and GPUs but are not naturally compatible with the algebraic structures that MPC, FHE, and ZK proofs operate over.
RescueCipher is a **field-element cipher**. It operates natively over the same finite-field arithmetic that MPC, FHE, and ZK systems use. That gives you three things:
* The same encrypted payload you send today can, in principle, be processed directly under MPC or FHE computation without re-encryption.
* As Arcium's MXE (Multiparty eXecution Environment) network ships support for more cryptographic compute primitives, Solrouter's encryption layer does not need to change.
* You get a smooth upgrade path: stronger cryptographic compute guarantees over time, zero migration work on your side.
This is why Arcium chose RescueCipher as the cipher for its MXE substrate, and why Solrouter uses it today, even before full MPC/FHE inference is live.
## What encryption does NOT cover (yet) [#what-encryption-does-not-cover-yet]
Privacy claims in this space are often inflated, so here is the honest line on what Solrouter does and does not do today.
Solrouter is **not** running pure FHE (Fully Homomorphic Encryption) inference today, and no production system does. LLM-scale FHE inference is many orders of magnitude away from viable latency. Anyone claiming "FHE LLM inference" in production is overclaiming.
What Solrouter offers today is **client-side encryption + hardware TEE isolation for decryption**, which is a real and meaningful guarantee. Arcium's MXE is a hybrid of MPC + FHE + ZK primitives, and RescueCipher is designed to work with all three. As Arcium's network matures, more of the inference pipeline will move from TEE-isolated plaintext into cryptographic compute: MPC first, then FHE/ZK where they are practical. The client encryption layer stays unchanged throughout.
To be precise about what is and is not guaranteed today:
| Property | Today |
| ------------------------------------------ | ------------------------------------------------------------- |
| Client-side encryption before transmission | Live: RescueCipher + X25519 |
| Plaintext hidden from Solrouter backend | Live: decryption happens only inside the Intel TDX enclave |
| Plaintext hidden from the Nosana GPU node | No: the model runs on plaintext on that node during inference |
| Intel-signed TDX quote you can fetch | Live: `GET /tee/attestation` |
| Published reference measurements | Soon: the repository is private, no reference values yet |
| MPC-based inference | Soon: Arcium MXE roadmap |
| Full FHE inference | Not in production anywhere today |
## Encryption options in the SDK [#encryption-options-in-the-sdk]
Encryption is on by default in `@solrouter/sdk`. You do not have to do anything to get it. You can turn it off for a plaintext path, but you give up every privacy guarantee on this page when you do. The plaintext path sends your prompt to the same self-hosted Nosana models. It does not unlock any other model.
```typescript
// Default: fully encrypted (recommended)
const response = await client.chat('Your prompt', { encrypted: true });
```
```typescript
// Plaintext path: no encryption guarantees
const response = await client.chat('Your prompt', { encrypted: false });
```
When you set `encrypted: false`, your prompt and response travel in plaintext through Solrouter's infrastructure. Use the plaintext path only for non-sensitive workloads.
# Architecture (/docs/how-it-works)
Private swaps are Soon. Every other part of the map is Live.
Solrouter has a handful of moving parts. This map shows all of them at once: what each part holds, what it can see, and where the code lives. Each page in this section zooms into one piece. It gets technical, and every claim points at the code.
Click a node to open its details. Drag to pan. Pinch to zoom.
**In words**
* Chat app (solrouter.com/chat): holds your wallet session and, in Maximum Privacy Mode, encrypts each prompt in the browser. In the default mode it sends plaintext to the backend.
* `@solrouter/sdk`: holds your API key and encrypts by default. Sends the ciphertext bundle plus the API key, model id, and chat id in plaintext.
* `@solrouter/mcp-server` (your machine): holds `SOLROUTER_API_KEY`, `SOLROUTER_API_URL`, and `BRAVE_API_KEY`. Four tools use the encrypted path for the model step. Search and market lookups go to third parties in plaintext.
* REST and x402 clients: send whatever they build. `POST /api/v1/chat/completions` and `/tee/process` require `encryptedPrompt`; `/agent` accepts plaintext or `encryptedPrompt`.
* Solrouter backend (behind api.solrouter.com): checks the key, bills, runs the x402 paywall, relays ciphertext to the enclave, and commits receipts with its deployer wallet. It holds no decryption key on the encrypted path.
* Intel TDX enclave on Phala dStack: generates an X25519 sealing key and an ed25519 signing key at boot, decrypts with RescueCipher, requests TDX quotes from the tappd agent, runs a 5-tool allowlist for encrypted agent mode, and hosts SearXNG in the same enclave.
* Nosana GPU node, one per model: runs the open-weight model in Ollama and sees the prompt and reply during inference. Solrouter does not control that hardware.
* Solana: holds one Light Protocol compressed receipt per private inference, under program `ATMRatMtsKX4bHax7U4FRdhbE4mjU4NKpDZGqZqAhBKb`.
* Umbra mixer plus Jupiter (Soon): the private-swap path. The backend orchestrates it; no mainnet run is confirmed.
* x402 facilitator: the live manifest advertises Coinbase. Which facilitator settles a payment is set on the server and is not visible from outside. The agent never talks to the facilitator.
## Zoom in [#zoom-in]
# Models (/docs/how-it-works/models)
The model table carries a Status column: gemma4:31b is Soon and qwen3:8b is Archived.
In privacy mode Solrouter answers with self-hosted, open-weight models only. It never calls a proprietary API. That choice is what makes the privacy claim hold.
Every model runs on its own [Nosana](https://nosana.io) GPU node. Your encrypted request travels from your device to an Intel TDX enclave (a TEE, a trusted execution environment: a hardware-isolated virtual machine). The enclave decrypts it and calls the model on the Nosana node (HTTPS per the documented node URL, not re-verified). No proprietary model API is in this path.
## Available models [#available-models]
Each row is one model with its ids and its status on 2026-08-26.
| Model | Catalog id | SDK id | Status |
| ------------ | ------------- | ------------- | -------- |
| GPT-OSS 20B | `gpt-oss:20b` | `gpt-oss-20b` | Live |
| Qwen 3.8 27B | `qwen3.8:27b` | none yet | Live |
| Gemma 4 31B | `gemma4:31b` | none | Soon |
| Qwen 3 8B | `qwen3:8b` | `qwen3-8b` | Archived |
`gpt-oss:20b`: default in the chat app and the SDK. Context 8192 tokens.
`qwen3.8:27b`: listed as "Uncensored" in the chat picker. In SDK 1.1.0 it needs a type cast (see below).
`gemma4:31b`: listed by `GET /api/v1/models` with a 262144-token context. The enclave has no endpoint for it yet, so the encrypted path is not confirmed.
`qwen3:8b`: retired node. The chat app folds this id to `qwen3.8:27b`.
The REST API returns catalog ids with a `nosana:` prefix, for example `nosana:gpt-oss:20b`. The backend accepts both forms.
All of these models are open-weight. Their weights are public, so anyone can inspect what runs on your prompt.
## Choosing a model [#choosing-a-model]
Leave the model out and the SDK defaults to `gpt-oss-20b`. To pick one, pass `model` to `client.chat()`:
```typescript
const response = await client.chat('Your prompt here', {
model: 'gpt-oss-20b', // the only typed live model in SDK 1.1.0
});
```
The SDK type lists `gpt-oss-20b` and `qwen3-8b`. The second maps to the retired `qwen3:8b` node, so do not use it. Any other string passes through unchanged, so `nosana:qwen3.8:27b` works with a type cast. A typed alias needs a new SDK release (Soon). The chat app lets you pick `qwen3.8:27b` today.
Call `list_models` on the MCP server to see the models `GET /api/v1/models` returns, with the price per million tokens. Today that list holds `gpt-oss:20b` and `gemma4:31b`, so it differs from the chat picker.
## Node warm-up [#node-warm-up]
A Nosana node goes idle when nobody uses it. The first request after idle time can fail with the error "Nosana GPU node is warming up". The error is retryable. Wait a short time and send the request again.
## Why self-hosted models? [#why-self-hosted-models]
End-to-end privacy only holds if your prompt never reaches a proprietary API. If Solrouter handed your decrypted prompt to OpenAI or Anthropic, that provider would see your plaintext. Client-side encryption and TEE isolation would then buy you nothing.
Running only open-weight models on Nosana nodes closes that gap. It means:
* No proprietary model provider ever sees your query, your documents, or your reply.
* Solrouter's backend never sees your plaintext. It relays ciphertext only.
* The model weights are public, so anyone can inspect what runs on your prompt.
## Where plaintext exists [#where-plaintext-exists]
The enclave decrypts your prompt and then calls the model on a Nosana GPU node (HTTPS per the documented node URL, not re-verified) with `POST /v1/chat/completions` on that node. The node runs Ollama outside the TDX enclave. So your prompt and the reply exist in plaintext in that node's memory during inference. The node operator could read the prompt during inference. Solrouter does not control that hardware. What holds: Solrouter's backend never sees plaintext, and the request is not linked to your identity on the node.
There are no proprietary model APIs in the Solrouter privacy pipeline: no OpenAI, no Anthropic, no Google. `{ encrypted: false }` does not unlock one. It sends your prompt in plaintext to the same self-hosted Nosana models, without TEE isolation. The SDK cannot reach any proprietary model.
# On-chain proof (/docs/how-it-works/proof)
[Attestation](/docs/how-it-works/attestation) proves the enclave is genuine. The **encryption proof** ties *your specific request* to that enclave. Inside the enclave, a key that exists nowhere else signs the hash of your exact ciphertext, and the receipt goes to Solana. The backend relays it and cannot change it without breaking the signature.
Each receipt is a **Light Protocol compressed account**: about 0.000005 SOL each, roughly 400 times cheaper than a normal Solana PDA. That cost gap is why a proof per message is viable.
## What the record stores [#what-the-record-stores]
A v2 attestation lives under the Solrouter program `ATMRatMtsKX4bHax7U4FRdhbE4mjU4NKpDZGqZqAhBKb`. Its address is deterministic: `deriveAddressV2(["attestation_v2", sha256(ciphertext)])`, so a bare ciphertext hash is enough to find it. Alongside `model`, `provider`, `timestamp`, `backend_saw_plaintext`, and `tee_processed`, it stores the proof:
| Field | Meaning |
| --------------------------------- | ------------------------------------------------------------------------- |
| `client_pubkey` | Your ephemeral X25519 key from the request |
| `tee_pubkey` | The enclave's X25519 sealing key your ciphertext was sealed to |
| `nonce` | The RescueCipher nonce |
| `enclave_pubkey` | The enclave's ed25519 signing key, bound inside the per-request TDX quote |
| `enclave_sig_r` / `enclave_sig_s` | The two halves of the ed25519 signature |
| `tdx_quote_hash` | `sha256` of the TDX quote that attests `enclave_pubkey` |
Inside the enclave, the signature covers this exact byte tuple:
```
"SOLR-ATTEST-v2"
‖ sha256(ciphertext) // 32 your encrypted prompt
‖ tee_pubkey // 32 the sealing key
‖ nonce // 16
‖ client_pubkey // 32 your request key
‖ len(model) ‖ model
‖ len(provider) ‖ provider
```
A `POST /tee/process` reply returns this record under `onchainAttestation` (`address`, `signature`, `explorerUrl`), or `null` if the commit failed. The SDK exposes `privacyAttestationId` only; call the REST route for the full object.
## Why a forged receipt is detectable [#why-a-forged-receipt-is-detectable]
The ed25519 signing key is generated inside the enclave at boot and never leaves it. The backend pays for the Solana transaction with its deployer wallet, so it could commit a record carrying any key and signature. The on-chain program stores those fields without checking them. The safeguard is the per-request TDX quote: its `report_data` equals `sha256(tee_pubkey ‖ enclave_pubkey)`, so `enclave_pubkey` is pinned to attested hardware. A verifier who checks that binding can tell a real enclave key from a forged one.
A passing signature check proves the key in the record signed your exact ciphertext, and that the record is on-chain. The per-request quote check proves that key belongs to an attested TDX enclave. The deepest level, Intel's full DCAP chain on the raw quote, needs the live quote from `GET /tee/attestation`.
## Check one yourself [#check-one-yourself]
Paste a lock link, address, transaction, or ciphertext hash into the verifier on [Check a reply yourself](/docs/verify), or read a receipt directly through the [proof-lookup endpoints](/docs/how-it-works/attestation#on-chain-attestation-anchor).
# The TEE request flow (/docs/how-it-works/request-flow)
This page follows one `client.chat()` call through the SDK, the Solrouter backend, the Intel TDX enclave, the Nosana GPU node, and back. Use the step list to walk the map. Each step names its payload and source file.
**In words**
1. `GET /tee/public-key` returns `{publicKey, publicKeySha256, algorithm, teeType}`. The SDK caches it for the life of the process.
2. The SDK makes an ephemeral X25519 keypair and derives the shared secret with the enclave key.
3. RescueCipher encrypts the prompt in packed-31 form. The bundle is `{ciphertext, nonce, publicKey, version: '2.0-packed31'}`.
4. `POST /tee/process` with a Bearer key. What leaves the machine: the ciphertext bundle plus, in plaintext, the API key, model id, `chatId`, and the optional `systemPrompt`, `useRAG`, `ragCollection`, `useLiveSearch`.
5. The backend forwards `{encryptedPrompt, model, privacyAttestationId}` unchanged.
6. The CVM derives the shared secret with its X25519 private key and decrypts.
7. The CVM calls the configured Nosana endpoint at `/v1/chat/completions` with the plaintext prompt. HTTPS per the documented node URL, not re-verified.
8. The CVM encrypts the reply to your key, signs the `SOLR-ATTEST-v2` tuple, and requests a tappd quote with `report_data = sha256(x25519 || ed25519)`.
9. The backend commits the compressed receipt and returns `{success, encryptedResponse, attestation, encryptionProof, requestId, metadata, backendRole: 'BLIND_RELAY', onchainAttestation, privacyProof}`.
10. The SDK decrypts `encryptedResponse` with the session private key.
## The short picture [#the-short-picture]
**In words**
* Your device encrypts. The backend relays ciphertext. The enclave decrypts. The Nosana node runs the model in plaintext. The reply returns encrypted.
## Key custody [#key-custody]
* Your device: an ephemeral X25519 private key per session. Never sent.
* Solrouter backend: no key on this path.
* Enclave: an X25519 sealing key and an ed25519 signing key, generated at boot and never exported.
* Nosana node: no key. It receives plaintext from the enclave.
# What is a TEE? (/docs/how-it-works/what-is-a-tee)
## The question [#the-question]
You type a prompt. A computer somewhere works on it. Who can read the prompt while that happens?
With a normal AI service, the answer is "the company that runs the server". Their staff can read it. Their logs can store it. A TEE changes that answer for one part of the path.
TEE is short for Trusted Execution Environment. In plain words: a sealed part of a computer. The program inside can work on your data. The people who own the computer cannot look in. See the [glossary](/docs/glossary) for the short form of every term on this page.
## The sealed room [#the-sealed-room]
**In words**
* A data center holds many computers. One of them runs Solrouter's private inference program.
* That program lives in a sealed room called a Confidential VM. A VM is a virtual machine: one computer pretending to be a separate, smaller computer.
* The room has one locked slot. Only data sealed to the room's public key can go in. Your device seals your prompt to that key before it leaves your machine.
* The room has one window. Through it, anyone can read a signed note that says which program is running inside. That note is the attestation.
* The operator of the data center stands outside. The CPU encrypts everything in the room's memory, so the operator cannot open the door.
Where the analogy breaks: a real sealed room keeps everything inside. A TEE does not. The program inside can still send data out to other computers. The next sections say where Solrouter's program does that.
## Two layers [#two-layers]
A TEE gives you two separate promises.
### Layer 1: isolation [#layer-1-isolation]
The CPU chip encrypts the memory of the Confidential VM. The cloud operator, the host operating system, and any other program on the same machine see only scrambled bytes. This is why Solrouter can say its own backend and its cloud host never see your prompt in plain text.
### Layer 2: attestation [#layer-2-attestation]
A sealed room could still hold the wrong program, one that copies your prompt somewhere. Attestation closes that gap.
The chip signs a short note. The note says: "This exact program is running in this room right now." Anyone can fetch the note and check the signature against Intel's public records. Solrouter also puts a fingerprint of the room's public key inside the note. When you check the note, you also confirm the key belongs to this room. An impostor cannot fake that.
Think of a tamper-evident seal on a package. The seal shows the package was not opened. It does not tell you what is inside. Attestation proves which program runs. It does not by itself prove the program is a good one. For that you need to compare the note against known reference values. Solrouter does not publish those values yet. Reference measurements: Soon.
## What a TEE does not do [#what-a-tee-does-not-do]
A TEE stops outsiders from looking in. It does not stop the program inside from talking out.
Solrouter's program inside the enclave decrypts your prompt. It then sends the plain-text prompt to a Nosana GPU node, a separate computer that runs the language model. That node is outside the sealed room. The node can see your prompt and the reply during inference. Solrouter does not control that hardware. The request is not linked to your identity on the node.
Solrouter's own deployment file states the same limit. It claims "Solrouter never sees your prompt or searches" and adds "NOT no one sees them".
Solrouter's backend and its cloud host cannot read your prompt. The Nosana GPU node that runs the model can, while it works on it.
The page [What is private here](/docs/use/what-is-private) has the full table of who can see what.
## How Solrouter uses one [#how-solrouter-uses-one]
Solrouter runs its enclave as an Intel TDX Confidential VM on Phala Cloud. TDX is Intel's name for this kind of sealed VM. Phala Cloud is the hosting service that provides the TDX machines.
When the enclave boots, it makes a fresh key pair inside the sealed room. The private half never leaves. The public half is published at `GET https://api.solrouter.com/tee/public-key`. That reply names the enclave type as `INTEL-TDX-PHALA`. The key changes on every reboot, so a copied key from last week is useless.
The signed note comes from `GET https://api.solrouter.com/tee/attestation`. Solrouter's program asks the Phala guest agent inside the same Confidential VM for it, then passes it to you unchanged.
## Check it yourself [#check-it-yourself]
You do not have to take Solrouter's word for any of this.
# Chat App (/docs/use/chat-app)
Most AI chat tools want your email, store your conversations in plaintext, and lock you into a single model. Solrouter Chat at [solrouter.com/chat](https://solrouter.com/chat) works differently. You never create an account. You can turn on Maximum Privacy Mode to encrypt each prompt before it leaves your browser.
You connect a Solana wallet, top up a prepaid balance, and pick one of two open-weight models. You also get file attachments and a RAG knowledge base. Encryption is a toggle. It is off by default.
## Two privacy modes [#two-privacy-modes]
Encryption is a toggle, off by default. Here is what each mode does with your prompt and your history.
| | Persistent (default) | Maximum Privacy (on) |
| --------------------- | ----------------------------------------------------- | -------------------------------------- |
| Prompt to the backend | plaintext | encrypted in your browser |
| Opened where | backend, then the Nosana node | only the enclave, then the Nosana node |
| Chat history | stored, encrypted at rest under a key Solrouter holds | not stored, lost on refresh |
| Best for | everyday chats | sensitive prompts |
For exactly who can read what, see [What is private here](/docs/use/what-is-private).
## Features [#features]
Turn on Maximum Privacy Mode to encrypt each prompt before it leaves your browser. Off by default.
Send images and documents with your question. Attachments are not encrypted, only the text prompt is.
Upload files and ask questions grounded in them. Stored chunks are not encrypted at rest.
Image and video generation is Archived. It is disabled in the chat app.
## Getting started [#getting-started]
Four steps take you from a blank browser tab to your first message.
### Open the chat app [#open-the-chat-app]
Go to [solrouter.com/chat](https://solrouter.com/chat) in your browser.
### Connect your Solana wallet [#connect-your-solana-wallet]
Click **Connect Wallet** and approve the request in your wallet. Your wallet is your identity here. No email address or personal information is required.
### Top up your balance [#top-up-your-balance]
Add credits in **USDC** or **`$ROUTER`**. You pay per call from this prepaid balance, so you only spend on what you use.
### Select a model and start chatting [#select-a-model-and-start-chatting]
Pick a model from the selector and send your first message. To encrypt the prompt in your browser, turn on **Maximum Privacy Mode** first. It is off by default, and history is not kept while it is on.
If the GPU node was idle, the first reply can say "Nosana GPU node is warming up". Wait a moment and send the message again.
## Supported models [#supported-models]
The chat model picker lists two self-hosted open-weight models on the Nosana GPU network: `gpt-oss:20b` (Default, Live) and `qwen3.8:27b` (Uncensored, Live). No proprietary model is reachable in the chat app. Maximum Privacy Mode works with both models.
For model ids and their status, see the [Supported Models](/docs/how-it-works/models) page.
In Maximum Privacy Mode, Solrouter's backend cannot read your prompt. Plaintext exists on your device, inside the Intel TDX enclave, and on the Nosana GPU node that runs the model during inference. Solrouter does not control that node's hardware.
# Pricing (/docs/use/pricing)
Prepaid billing and x402 are Live. Buyback and burn is Soon: the configured ratios are not published.
Most AI platforms make you commit before you build: a monthly subscription, a credit card on file, an email to verify. Solrouter does none of that. You pay per API call from a balance you fund yourself, so you only ever spend what you use.
The setup is short. Connect a Solana wallet at [solrouter.com/sdk](https://solrouter.com/sdk), fund your balance with USDC or `$ROUTER`, generate an API key, and start building. Every product (the Privacy SDK, Agent Privacy API, MCP server, and chat app) draws from that same prepaid balance.
You pay per call from your prepaid balance. `GET /payments/pricing` returns the live rate table.
## Feature status [#feature-status]
| Feature | Status |
| ------------------------------------------------------------ | ------ |
| Prepaid balance in USDC or `$ROUTER` | Live |
| Per-token metering (tables below) | Live |
| x402 keyless payment on `POST /api/v1/x402/chat/completions` | Live |
| `$ROUTER` buyback and burn | Soon |
## Payment methods [#payment-methods]
You can fund your balance two ways. The difference is what each one means for you and for the token.
Pay with USDC from your Solana wallet. As a stablecoin pegged to the dollar, its value stays put, so you carry no price risk between top-ups. Top up at any time from [solrouter.com/sdk](https://solrouter.com/sdk).
Pay with the native `$ROUTER` token. Fees paid in `$ROUTER` feed the buyback-and-burn mechanism. See [$ROUTER buyback and burn](#router-buyback-and-burn) below.
## Per-call rates [#per-call-rates]
Two rate tables exist in code. Table A bills the chat app and `POST /agent`. Table B bills the REST route `POST /api/v1/chat/completions`. `GET /payments/pricing` is the live source for table A.
**Table A: chat and `/agent` billing.** Rates are USD per 1M tokens, before a 20% margin. The balance is held as app tokens at 1,000 app tokens per USD. Each billed direction has a floor of 10 app tokens ($0.01), so a normal inference costs at least $0.02. Charges settle in `$ROUTER` first and fall back to USDC.
| Model | Input | Output |
| ------------- | ----- | ------ |
| `gpt-oss:20b` | $0.10 | $0.20 |
| `qwen3.8:27b` | $0.15 | $0.30 |
| `gemma4:31b` | $0.15 | $0.30 |
**Table B: `POST /api/v1/chat/completions`.** Rates are USD per 1M tokens. No margin and no floor apply. Token counts are estimated from character counts (about 4 characters per token). The cost is debited from your USDC balance only. The route answers 402 when your USDC balance is zero, even if you hold `$ROUTER`.
| Model | Input | Output |
| ------------- | ----- | ------ |
| `gpt-oss:20b` | $0.15 | $0.30 |
| `qwen3.8:27b` | $0.15 | $0.30 |
| `gemma4:31b` | $0.15 | $0.30 |
The two tables disagree on `gpt-oss:20b`. Unifying them is a backend follow-up. x402 route prices come from the manifest at `https://api.solrouter.com/.well-known/x402`.
## x402 keyless payments [#x402-keyless-payments]
An autonomous agent often has no human around to sign up for an account or manage an API key. x402 solves that: it lets an agent pay for each call on its own, with no registration at all.
x402 is a standard for HTTP-native micropayments (payments built into the web request itself), settled in USDC on Solana mainnet. The live manifest advertises Coinbase (`api.cdp.coinbase.com/x402`) as the facilitator. The manifest shows `X402_FACILITATOR_URL` when it is set, or a built-in default when it is not. It does not show which facilitator settles payments. Any x402-aware agent can discover the payment manifest and start paying immediately.
* **Discovery:** `https://api.solrouter.com/.well-known/x402`, the x402 paywall manifest listing available endpoints and pricing.
* **Endpoint:** `POST /api/v1/x402/chat/completions`. Arcium-encrypted prompt in, encrypted response out.
* **Price:** $0.005 per call. Solrouter's server verifies and settles the USDC payment through its facilitator and then returns the reply.
* **Best for:** autonomous agents that self-fund their own inference costs without a human managing API keys.
Going keyless costs you no privacy. The x402 path runs the same end-to-end encrypted inference as the API-key path: your prompt is still encrypted with Arcium's RescueCipher before it reaches Solrouter's backend.
The paywall charges $0.005 per call. The response body of this endpoint currently reports `paid.amount: 0.02`. This is a backend follow-up; the manifest price is the one charged.
## Managing your balance [#managing-your-balance]
You can read your current balance straight from the SDK, so an agent or app can check funds before it spends and top up when it runs low.
```typescript
const { balance, balanceFormatted } = await client.getBalance();
console.log(`Balance: ${balanceFormatted}`);
```
To add funds, visit [solrouter.com/sdk](https://solrouter.com/sdk) and connect your Solana wallet. Deposit USDC or `$ROUTER`. Deposit minimum: not determined.
## $ROUTER buyback and burn [#router-buyback-and-burn]
Status: Soon. The mechanism exists in code. The ratios are runtime settings, and the worker skips its run while they are zero.
* A buyback worker reads the USDC inflow for each window and buys `$ROUTER` with a configured share of it (`BURN_USDC_BPS`).
* A configured share of the `$ROUTER` bought back is burned (`BURN_OUTPUT_TOKEN_BPS`). The rest stays in treasury.
* A configured share of fees paid directly in `$ROUTER` is burned on receipt (`BURN_TOKEN_BPS`). The rest stays in treasury.
Configured ratios: not published. `GET /payments/buyback/log` returns the recent buyback worker ticks, including skipped ones. See [/docs/token](/docs/token) for the token supply schedule and vesting details.
# What is private here (/docs/use/what-is-private)
Each row states its own Live or Soon status.
## The question [#the-question]
"If I type something private into Solrouter, who can read it?"
With encryption on, Solrouter's own servers cannot read your prompt or the reply. The prompt is opened only inside a sealed computer (a TEE) and on the rented GPU machine that runs the AI model. Your attached files, your knowledge base, and your saved chat history do not get that protection, and the tables below show exactly where each one is readable.
Encryption is a toggle in the chat app. It is off by default. The Privacy SDK encrypts by default. The page [Chat app](/docs/use/chat-app) explains the toggle. This page explains what each setting exposes.
## Who is who [#who-is-who]
The matrix shows five parties. You are the sixth: your own device always reads your own words. Here is each party in plain words.
* **You.** Your browser, or the program that uses the SDK.
* **Network observer.** Anyone who watches the connection between you and Solrouter. For example, your internet provider or the owner of a public Wi-Fi.
* **Solrouter backend.** Solrouter's own servers. They check your login, take payment, store your history, and pass messages along.
* **CVM cloud host (Phala).** The company that owns the physical machine where the sealed computer runs. The sealed computer is a Confidential Virtual Machine (CVM). The processor encrypts its memory, so the machine owner cannot read it. See [What is a TEE?](/docs/how-it-works/what-is-a-tee).
* **Nosana GPU host.** The operator of the graphics-card machine that runs the AI model. Solrouter rents it from the Nosana network. Solrouter does not control that hardware.
* **Solana observer.** Anyone who reads the public Solana blockchain. Solrouter posts a receipt there for each encrypted request.
Each cell says what that party can see. The legend under the matrix explains every word.
## Who can see what [#who-can-see-what]
Rows here describe the encrypted path: the Privacy SDK with its default settings, or the chat app with Maximum Privacy Mode on. Amber cells mark the only places a party can read your words. Every row is Live.
**Notes**
* **Nosana GPU host, readable.** The model runs there in plaintext for the length of one request. Solrouter rents the machine and does not control it. The request is not linked to your identity.
* **Chat history, at rest.** Persistent mode stores each message encrypted with AES-256-GCM under a key the backend holds. That protects against a stolen database copy, not against Solrouter. On this path the backend also reads the prompt in plaintext on the way in.
* **Knowledge-base documents.** Files are split and embedded on the server as plain text, not encrypted at rest. Whether the deployed app isolates collections per user is not determined.
* **Attached files.** The encrypted path sends the text prompt only. Attachments travel on the default path below.
* **Web searches** is the encrypted agent path: a REST call to `/agent` with `encryptedPrompt`, Live for REST and Soon for the SDK. Search runs through SearXNG inside the enclave, which then queries public engines. Those engines receive the search text.
* The enclave logs the first 50 characters of each reply. Who can read that log is not determined.
## Default chat (toggle off) [#default-chat-toggle-off]
This is the chat app with the toggle off, the SDK with `encrypted: false`, and guest chat. There is no client-side encryption, so the backend and the model node read your words. Only the rows that change from the matrix above are shown. Status: Live.
On this path attachments and knowledge-base files reach the backend and the model node in plaintext. Documents upload to Cloudflare R2 through a short-lived link, and the backend extracts their text. Live search and the `web_search` tool use Brave, with DuckDuckGo and Wikipedia as fallbacks, so those services receive the search text.
## Where your words are readable [#where-your-words-are-readable]
The strip below follows one encrypted request from your device to the Solana receipt. Green zones hold your words in readable form. Grey zones hold only ciphertext or a hash. The amber zone is the rented GPU machine.
**In words**
* Your device: your words are readable here. Your browser or program scrambles them before they leave.
* Network: ciphertext only. A watcher sees size and timing.
* Solrouter backend: ciphertext only. It checks your login, bills you, and passes the blob along.
* TDX CVM: your words are readable here, inside memory that the processor encrypts. The machine owner cannot open it.
* Nosana GPU node: your words are readable here while the model runs. Solrouter does not control this machine. The request is not linked to you.
* Solana: hash only. A receipt proves a request happened and names the model. It does not hold your words.
Solrouter does not run fully homomorphic encryption (FHE) inference. FHE means a computer works on scrambled data without ever unscrambling it. No production system runs AI models of this size under FHE in 2026. The compute cost is many orders of magnitude away from usable speed. Anyone who claims "FHE LLM inference" in production is overclaiming.
What Solrouter provides is encryption on your device, a hardware-isolated CVM that unscrambles the prompt, a model that runs on a rented Nosana GPU node, and a receipt on Solana for each encrypted request. That is a real and checkable guarantee. It is not FHE, and we will not claim otherwise.
## What we keep [#what-we-keep]
Retention periods are not published. Each row states what is stored, in what form, who holds the key, and how to remove it. Rows are Live unless marked.
| What | Where | Format | Key holder | How to delete | Retention |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
| Chat rows (`enc:v1:`) | Solrouter's database, tables `chat_messages` and `chats` (message text, chat title, search and knowledge-base context, pitch-deck cards) | Scrambled with AES-256-GCM, stored as text with the prefix `enc:v1:` | Solrouter backend, from `CHAT_CONTENT_KEK` or `WALLET_VAULT_KEK` in the server settings | Deleting a chat in the app marks it archived. The rows stay in the database. A hard delete path: not determined | not published |
| Memory envelope (`umem:v1:`) | Solrouter's database, table `user_memory`, one row per user | Scrambled in your browser with AES-256-GCM, stored as text with the prefix `umem:v1:`. Solrouter cannot read it | You. The key comes from your wallet signature and is never stored | "Forget all" in the app removes the row (`DELETE /memory`) | not published |
| Knowledge-base chunks | Files on the backend server disk, one JSON file per collection under `data/vectors/` | Plain text chunks plus embedding vectors. Not encrypted | none | Delete the whole collection (`DELETE /rag/collections/:name`). Delete of one document: not determined | not published |
| Uploaded files (R2) | A Cloudflare R2 bucket, key `documents/` plus a timestamp and a random id | The original file. Not encrypted by Solrouter | none | not determined. The code has upload and download, no delete | not published |
| Usage log | Solrouter's database, table `api_usage` | Plain rows: key id, user id, model, token counts, cost, request id, time. No prompt text | none | not determined | not published |
| Swap session rows (Soon) | Solrouter's database, table `agent_swap_sessions` | Plain rows: mode, state, tokens, amount, destination address, transaction ids, payer id, webhook URL. The one-shot signer key is scrambled and set to null when the swap ends | Solrouter backend, `WALLET_VAULT_KEK`, for the signer key only | not determined. The API has read and webhook routes, no delete | not published. Pending sessions expire after 7 days |
| Guest per-IP counter | Backend process memory, not a database | Your internet address, a message count, and a reset time | none | No route. The entry resets 24 hours after first use and vanishes when the server restarts | not published |
Guest chat sends your prompt in plaintext to the backend and then to the model node. It stores no chat rows.
## Short answers [#short-answers]
* "Can Solrouter read my prompt?" With the toggle on, or with the SDK default, no. With the toggle off, yes.
* "Can Solrouter read my history?" In Persistent mode, yes. It holds the key. In Maximum Privacy mode there is no history.
* "Can Solrouter read my memory?" No. Only your wallet can unlock it.
* "Can Solrouter read my uploaded documents?" Yes. They are stored as plain text and plain files.
* "Can anyone else read my prompt?" The operator of the Nosana GPU node could, while the model runs. No one else.
* "Is my wallet address public?" It is not on the Solana receipt. Solrouter's backend knows it.
## Next [#next]
Where the Privacy Mode toggle is and what each mode keeps.
Paste the lock link from a reply and see the receipt check pass.
The sealed-room picture and where it breaks.
Every term on this page in one line each.
# GET /tee/public-key (/docs/api-reference/tee/public-key)
This endpoint returns the X25519 public key that is active inside the Solrouter Intel TDX enclave (a TEE, a trusted execution environment: a hardware-isolated virtual machine). Before you send a prompt, your client encrypts it to this key with Arcium's RescueCipher. Only the enclave can decrypt it. The Solrouter backend receives an opaque ciphertext blob and relays it to the enclave. It cannot read the contents.
## Endpoint [#endpoint]
```
GET https://api.solrouter.com/tee/public-key
```
No authentication is required for this endpoint. The backend proxies the call to the enclave and returns the enclave's body unchanged.
## Response [#response]
| Field | Type | Description |
| --------------- | ------ | -------------------------------------------------------------------------------------------------------------------- |
| publicKey | string | The enclave's current X25519 public key, base64 encoded. Use it as the recipient key for client-side encryption. |
| publicKeySha256 | string | Hex sha256 of the raw 32-byte public key. `GET /tee/attestation` pins this same digest in the quote's `report_data`. |
| algorithm | string | Always `x25519` (lower case). |
| teeType | string | Always `INTEL-TDX-PHALA`. |
This response has no top-level `success` field.
## Example [#example]
```bash
curl "https://api.solrouter.com/tee/public-key"
```
Example response (shape checked against the live endpoint on 2026-08-26; values shortened):
```json
{
"publicKey": "base64...=",
"publicKeySha256": "hex...",
"algorithm": "x25519",
"teeType": "INTEL-TDX-PHALA"
}
```
## Errors [#errors]
When the backend cannot reach the enclave, it answers with status 502 and this body:
```json
{
"error": "tee_unreachable",
"message": "...",
"teeEndpoint": "..."
}
```
## When to use this [#when-to-use-this]
In most cases you do not need to call this endpoint yourself.
* **`@solrouter/sdk`**: the SDK fetches the key once per process and caches it. You never handle the key yourself.
* **`@solrouter/agent-tools`** (Soon): the package is not on npm yet.
* **Custom client**: if you write your own encryption layer (for example in a language with no Solrouter SDK), fetch this endpoint first. Then use `publicKey` as the X25519 recipient key in your RescueCipher key-exchange flow.
## Key lifetime and caching [#key-lifetime-and-caching]
The enclave generates a new X25519 keypair on every CVM boot. The SDK fetches the key once per process and keeps it until you call `clearSession()`. A long-lived process can hold a stale key after the enclave restarts. When a request fails with `tee_unreachable`, or a reply fails to decrypt, call `clearSession()` and retry. The next request fetches the current key. Custom clients should do the same: cache the key, and fetch it again after a failure.
The X25519 keypair is generated inside the Confidential VM at boot. The private key never leaves the enclave: not to the Solrouter backend, not to any host process, and not to Solrouter staff. You can check this claim yourself. `GET /tee/attestation` returns a TDX quote whose `report_data` equals `sha256(publicKey)`. See the [attestation guide](/docs/how-it-works/attestation).
# Provision a managed Umbra wallet (Mode A) (/docs/api-reference/agent-privacy/managed-wallets/agents/v1/wallets/post)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Quote a private swap (/docs/api-reference/agent-privacy/swaps/agents/v1/quote/get)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Pool depth for a denomination bucket (/docs/api-reference/agent-privacy/swaps/agents/v1/anonymity-set/get)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# On-chain attestation for a settled session (/docs/api-reference/agent-privacy/sessions/agents/v1/attestations/sessionid/get)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Poll a swap session's state (/docs/api-reference/agent-privacy/sessions/agents/v1/sessions/id/get)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Begin a one-shot ephemeral-wallet private swap (Mode B) (/docs/api-reference/agent-privacy/swaps/agents/v1/swaps/oneshot/post)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Cryptographically private AI inference (Arcium-encrypted, x402-paywalled) (/docs/api-reference/agent-privacy/inference/api/v1/x402/chat/completions/post)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Wallet operation audit log (/docs/api-reference/agent-privacy/managed-wallets/agents/v1/wallets/id/audit/get)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Build an unsigned funding tx for the agent to sign (/docs/api-reference/agent-privacy/managed-wallets/agents/v1/wallets/id/fund-intent/post)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Run a private swap from a managed wallet (Mode A) (/docs/api-reference/agent-privacy/managed-wallets/agents/v1/wallets/id/swap/post)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Encrypted-balance snapshot for given mints (/docs/api-reference/agent-privacy/private-balance/agents/v1/wallets/id/balance/get)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Encrypt a public balance into an encrypted balance (/docs/api-reference/agent-privacy/private-balance/agents/v1/wallets/id/encrypt/post)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Shield + unlink: mixer round-trip to a fresh address (/docs/api-reference/agent-privacy/private-balance/agents/v1/wallets/id/shield/post)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Withdraw from encrypted balance to a public address (/docs/api-reference/agent-privacy/private-balance/agents/v1/wallets/id/withdraw/post)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Submit fundingTxSig and start the swap orchestrator (/docs/api-reference/agent-privacy/swaps/agents/v1/swaps/oneshot/id/execute/post)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}