Skip to content

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 is GET /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 /torii/door, GET /torii/skill.md, GET /torii/mcp-tools and the mandate_* tools on POST /mcp?profile=full. 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#

  1. Knock on the door. GET /torii/door needs no key. It always answers HTTP 402 with the header X-Torii-Door: mandate and a JSON body: mandate_schema, start (seven numbered steps, each with its tool and HTTP twin, and a ready mandate_propose example), refusals (the API codes and the contract reverts), rate_limits, connect (the config blocks below) and next. Nothing is charged; accepts is an empty list.
  2. Get a key. POST /auth/agent/connect returns an api_key. Send it as header X-NW-User-Key. Your agent id is the user id that key resolves to, as a string (GET /torii/agents/me shows it). It is not nw_agents.id.
  3. Call tools. MCP over HTTP: POST /mcp?profile=full (JSON-RPC tools/list, tools/call) with the same header. Every tool has an HTTP twin, except mandate_propose, which is MCP only.
  4. Find your mandate. mandate_offers (HTTP GET /torii/agents/{agent_id}/offers) lists mandates your owner designated to you. Accept one with mandate_accept. See Offers and accept.
  5. Report. mandate_report_event for what you did; mandate_report_refusal for a call the vault reverted. See Reporting events.
  6. Read skill.md. GET /torii/skill.md is public text/markdown, under 150 lines, and carries the same config blocks as this page.

Client config#

Replace <api-host> with the host you were given and <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-wallet <url> --header "X-NW-User-Key: <key>"):

{
  "mcpServers": {
    "torii-wallet": {
      "type": "http",
      "url": "https://<api-host>/mcp?profile=full",
      "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://<api-host>/mcp?profile=full",
        "--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://<api-host>/mcp?profile=full",
  "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://<api-host>/torii/door
# 2. get a key (send it as X-NW-User-Key from here on)
curl -X POST https://<api-host>/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://<api-host>/torii/agents/me -H "X-NW-User-Key: $KEY"
curl -X POST 'https://<api-host>/mcp?profile=full' -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://<api-host>/torii/agents/<agent id>/offers -H "X-NW-User-Key: $KEY"
curl -X POST https://<api-host>/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://<api-host>/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://<api-host>/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.

ScopeCovers
readEvery GET, plus mandate_list, mandate_get, mandate_offers
txReporting events and refusals, accepting, stopping, policy writes (owner only), transfer reports
signThe 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, with retry_at. The counter is per API process and rolls over at 00:00 UTC.
  • 60 refusal reports per mandate per day. The same tx_hash twice counts once.
  • Writes you may retry carry an Idempotency-Key; see Reporting events.

MCP#

Fifteen tools carry the family. They are listed by tools/list?profile=full and callable by name from any profile once the door is open; they are not in the default connector list.

ToolHintNotes
mandate_list, mandate_get, mandate_offers, mandate_approvalsread-onlySafe 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_balancesread-onlySafe to auto-allow
mandate_execute_call, mandate_unwind_callread-onlyBuild 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_proposewriteCreates a draft for the owner; moves no money
mandate_accept, mandate_report_event, mandate_report_refusal, mandate_stop, transfer_reportwrite, idempotentSafe to repeat; see Reporting events
agent_policy_setwrite, destructiveReplaces the whole policy; the owner's key only

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:

reasonMeaning
TargetNotAllowedthe target is not on the mandate's allowed list
OverPerTxCapthe vault's USDC drop in one call is above the per-action cap
OverDailyCapthe rolling 24-hour spend would pass the daily cap
MandateStoppedthe owner or the stop line stopped the mandate; you are revoked
MandateEndedthe end date has passed
server_guarda 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).

Next#

Offers and accept.