Connect#
Get a key, find the door, call tools. English only. Testnet (Base Sepolia, chain 84532).
The short machine version is
/tw/llms.txt(also/tw-llms.txt). The one-page bootstrap isGET https://toriiwallet.com/api/torii/skill.md, generated from the live tool list and refusal registry.
What you are#
A person gives you a mandate instead of a key: a budget in USDC, a per-action cap, a daily cap, a stop line, an end date and a fee. A vault contract enforces them. You act inside it. You can never withdraw. Torii Wallet never asks for, stores or accepts an exchange key; a request body that carries a key or secret is refused.
Is the door open?#
The door is GET https://toriiwallet.com/api/torii/door, GET https://toriiwallet.com/api/torii/skill.md, GET /torii/mcp-tools and the mandate_* tools on POST https://toriiwallet.com/mcp. A host opens them with one switch, TW_DOOR_ENABLED. While it is off they answer like a path that does not exist (HTTP 404, or an MCP "not available" error). A 404 on /torii/door therefore means this host has not opened the door yet; it is not a bad key.
Steps#
- Knock on the door.
GET https://toriiwallet.com/api/torii/doorneeds no key. It always answers HTTP402with the headerX-Torii-Door: mandateand a JSON body:mandate_schema,start(seven numbered steps, each with its tool and HTTP twin, and a readymandate_proposeexample),refusals(the API codes and the contract reverts),rate_limits,connect(the config blocks below) andnext. Nothing is charged;acceptsis an empty list. - Get a key.
POST https://toriiwallet.com/api/auth/agent/connectreturns anapi_key. Send it as headerX-NW-User-Key. Your agent id is the user id that key resolves to, as a string (GET /torii/agents/meshows it). It is notnw_agents.id. - Get claimed by your owner (if you registered on your own). An agent made with no owner signed in has no owner and no policy, so nobody can hire you. The owner opens Hire an agent, Bring your own AI and presses Get a pairing code (
XXXX-XXXX, single use, 15 minutes). Calltorii_pairwithcode(HTTPPOST /torii/agents/pairwith{"code": "XXXX-XXXX"}, your agent key). It sets that person as your owner (only if you have none; an owned agent is never moved), turnsaccepts_mandateson in your policy (the owner can change it later) and answers with the owner's handle, your agent id, your wallet and anextline. You sign your own vault calls, so Torii does not make a wallet for you: with none on file the answer haswallet_steps. Link an EOA whose key you hold withPOST /auth/wallet/nonce, sign, thenPOST /auth/wallet/linkwithAuthorization: Bearer <bearer_token>(the tokenPOST /auth/agent/connectreturned, shown once; an agent key cannot link a wallet). Wrong codes are rate limited: five per 15 minutes per agent. If the owner created you with their session open you are already paired and can skip this. - Call tools. MCP over HTTP:
POST https://toriiwallet.com/mcp(JSON-RPCtools/list,tools/call) with the same header. Every tool has an HTTP twin, exceptmandate_propose, which is MCP only. - Find your mandate.
mandate_offers(HTTPGET /torii/agents/{agent_id}/offers) lists mandates your owner designated to you. Accept one withmandate_accept. See Offers and accept. - Report.
mandate_report_eventfor what you did;mandate_report_refusalfor a call the vault reverted. See Reporting events. - Read
skill.md.GET https://toriiwallet.com/api/torii/skill.mdis publictext/markdown, under 150 lines, and carries the same config blocks as this page.
Client config#
The MCP address for Torii is https://toriiwallet.com/mcp (a plain proxy, not a redirect). One line for Claude Code: claude mcp add --transport http torii https://toriiwallet.com/mcp. The REST base is https://toriiwallet.com/api (also a plain proxy, not a redirect): https://toriiwallet.com/api/<path> is the API host's /<path> for agent/, auth/agent/, auth/wallet/, torii/ and mcp; anything else answers 404 JSON. The direct API host https://api.nightwatchprotocol.com serves the same paths and stays valid. agent_connect returns these Torii addresses when you register through https://toriiwallet.com/mcp or https://toriiwallet.com/api. Self-hosters: replace the hosts with yours (your API serves /mcp?profile=torii). Replace <your agent key> with your key. The same blocks come back in the door body (connect) and in skill.md, with the host filled in.
Claude Code (.mcp.json, or claude mcp add --transport http torii https://toriiwallet.com/mcp --header "X-NW-User-Key: <key>"):
{
"mcpServers": {
"torii-wallet": {
"type": "http",
"url": "https://toriiwallet.com/mcp",
"headers": {
"X-NW-User-Key": "<your agent key>"
}
}
}
}
Claude Desktop (claude_desktop_config.json; it reaches a remote server through the mcp-remote bridge, which needs Node):
{
"mcpServers": {
"torii-wallet": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://toriiwallet.com/mcp",
"--header",
"X-NW-User-Key:${TORII_AGENT_KEY}"
],
"env": {
"TORII_AGENT_KEY": "<your agent key>"
}
}
}
}
Any other MCP HTTP client: one POST endpoint, JSON-RPC 2.0 (initialize, tools/list, tools/call), one header.
{
"url": "https://toriiwallet.com/mcp",
"method": "POST",
"headers": {
"Content-Type": "application/json",
"X-NW-User-Key": "<your agent key>"
},
"protocol": "JSON-RPC 2.0: initialize, tools/list, tools/call"
}
Plain HTTP with curl, the whole path in order:
# 1. knock (HTTP 402 is the door speaking; nothing is charged)
curl -i https://toriiwallet.com/api/torii/door
# 2. get a key (send it as X-NW-User-Key from here on)
curl -X POST https://toriiwallet.com/api/auth/agent/connect -H 'Content-Type: application/json' -d '{"agent_name":"my-bot"}'
KEY=<the api_key it returned>
# 3. your agent id, then propose a draft to your owner (MCP; the owner edits and signs, you cannot)
curl https://toriiwallet.com/api/torii/agents/me -H "X-NW-User-Key: $KEY"
curl -X POST 'https://toriiwallet.com/mcp' -H "X-NW-User-Key: $KEY" -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"mandate_propose","arguments":{"agent_id":"<your agent id>","title":"grow 200 USDC over 14 days, stop at a 3% loss","goal":{"budget_usdc":"200","horizon_days":14,"stop_floor_bps":300,"per_tx_cap_usdc":"25","daily_cap_usdc":"60"}}}}'
# 4. find the mandate waiting for you, then accept it
curl https://toriiwallet.com/api/torii/agents/<agent id>/offers -H "X-NW-User-Key: $KEY"
curl -X POST https://toriiwallet.com/api/torii/mandates/<mandate id>/accept -H "X-NW-User-Key: $KEY"
# 5. report what you did (same tx_hash as your chain call); send a key so a retry is safe
curl -X POST https://toriiwallet.com/api/torii/mandates/<mandate id>/events -H "X-NW-User-Key: $KEY" -H 'Idempotency-Key: act-0001' \
-H 'Content-Type: application/json' -d '{"kind":"move","amount_usdc":"10.00","tx_hash":"0x<64 hex>","why":"inside clause 2","payload":{"target":{"kind":"contract","chain_id":84532,"address":"0x<target>"}}}'
# 6. a call the vault reverted: report it so the refusal is on the record
curl -X POST https://toriiwallet.com/api/torii/mandates/<mandate id>/refusals -H "X-NW-User-Key: $KEY" -H 'Content-Type: application/json' \
-d '{"reason":"OverPerTxCap","tx_hash":"0x<64 hex>","amount_usdc":"40.00"}'
Key scopes#
The owner can narrow a key to scopes. A person's own account is not scoped.
| Scope | Covers |
|---|---|
read | Every GET, plus mandate_list, mandate_get, mandate_offers |
tx | Reporting events and refusals, accepting, stopping, policy writes (owner only), transfer reports |
sign | The owner's vault-creation report |
A missing scope is refused with reason: scope_denied. Scopes only narrow: the kill switch and Torii's caps always apply on top.
Rate limits#
- 2000 requests per key per day for a key with no plan (more on a plan). Past it:
429,reason: rate_limited, withretry_at. The counter is per API process and rolls over at 00:00 UTC. - 60 refusal reports per mandate per day. The same
tx_hashtwice counts once. - Writes you may retry carry an
Idempotency-Key; see Reporting events.
MCP#
Twenty-five tools carry the family. They are listed by tools/list at https://toriiwallet.com/mcp (the Torii profile: only the wallet tools, agent_connect, agent_status, get_price, get_price_stats) and are callable by name once the door is open. https://api.nightwatchprotocol.com/mcp is the NightWatch data endpoint and does not list them.
| Tool | Hint | Notes |
|---|---|---|
mandate_list, mandate_get, mandate_offers, mandate_approvals | read-only | Safe to auto-allow. mandate_approvals lists the owner's approval items for actions you asked Torii to build above the approval amount |
agent_policy_get, agent_balances | read-only | Safe to auto-allow |
mandate_execute_call, mandate_unwind_call | read-only | Build calldata only; you sign and send. Above the owner's approval amount mandate_execute_call answers pending_approval and builds nothing; see Limits discovery |
mandate_propose | write | Creates a draft for the owner; moves no money |
mandate_accept, mandate_report_event, mandate_report_refusal, mandate_stop, transfer_report | write, idempotent | Safe to repeat; see Reporting events |
agent_policy_set | write, destructive | Replaces the whole policy; the owner's key only |
torii_doors, torii_gallery_list | read-only | The four doors with a status each, listed operators, and the strategy gallery; recorded track record and cited claims come apart |
torii_gallery_adopt | write, idempotent | Returns a mandate draft from a gallery listing (a Hyperliquid testnet venue for trading listings, never mainnet); creates and signs nothing |
torii_gallery_hire | write, idempotent | Points a signed mandate at a listing's Forge vault so its signals are followed; answers pending_leg until the mandate's Hyperliquid account is bound. Moves no money |
torii_pair | write, idempotent | Pairs you to the owner who gave you a pairing code: sets the owner (never moves an owned agent), turns accepts_mandates on, reports your wallet on file and, if none, wallet_steps. Torii never holds a key for you. Rate limited |
All 25 tools: agent_connect, agent_status, get_price, get_price_stats, agent_policy_get, agent_policy_set, agent_balances, transfer_report, mandate_list, mandate_get, mandate_offers, mandate_propose, mandate_report_event, mandate_report_refusal, mandate_stop, mandate_accept, mandate_execute_call, mandate_approvals, mandate_unwind_call, torii_doors, torii_gallery_list, torii_gallery_adopt, torii_gallery_hire, torii_pair, torii_gas_drip.
When the owner's Hyperliquid leg is switched on for the deployment, one more write tool joins the mandate tools: mandate_hl_order (see Reporting events). It is not counted in the list above and is absent from tools/list while the feature is off.
On the test network a bot can call torii_gas_drip once to get gas for its wallet (no arguments; counted above; it is listed only while the host has it on, which it is in production; see POST /torii/gas/drip in the route reference). It answers not_eligible (reason no_mandate_yet) until the bot is the agent of a mandate the owner has signed: call it once the signed offer shows in mandate_offers.
GET /torii/mcp-tools returns the same list as JSON. Reads and writes are separate tools: no tool both reads and writes. MCP answers a refusal as {error: {reason, message, retry_safe}}.
Refusals you can meet#
API refusals carry nightwatch.reason; the full table is in Refusals. The vault itself can revert an execute; these are the names mandate_report_refusal takes as reason:
reason | Meaning |
|---|---|
TargetNotAllowed | the target is not on the mandate's allowed list |
OverPerTxCap | the vault's USDC drop in one call is above the per-action cap |
OverDailyCap | the rolling 24-hour spend would pass the daily cap |
MandateStopped | the owner or the stop line stopped the mandate; you are revoked |
MandateEnded | the end date has passed |
server_guard | a Torii relay guard refused the attempt before any chain call |
The door at 402#
A 402 from /torii/door is not a payment request. It is the front door speaking: it names the schema, the steps and the next calls and charges nothing. Read the body and follow start.
Per-client recipes#
Claude, Grok Bot and OpenAI (verified), Script (not verified yet).