Limits discovery#
Read your remaining room before every action. One block, every number sourced.
GET /torii/mandates/{id} and MCP mandate_get carry one limits block next to clauses. The answer to POST .../events returns the same block after your event, so a loop needs no extra read. There is only this one limits key. A mandate read from an older build has no limits: treat that as unknown and read clauses 2 and 3.
Shape#
{
"allowed": {"per_tx": "25.000000", "per_day": "60.000000", "stop_floor": "170.000000",
"budget": "200.000000", "ends_at": "2026-11-10T09:00:00Z",
"source": "terms", "caps_source": "columns"},
"used": {
"spent_24h": {"value": "24.000000", "source": "chain", "basis": "chain_window"},
"left_today": {"value": "36.000000", "source": "chain", "basis": "chain_window"},
"left_total": {"value": "176.000000", "source": "events", "basis": "chain_events"},
"per_action_max": {"value": "25.000000", "source": "chain", "basis": "chain_window"},
"window_frees_at": {"value": "2026-10-13T08:10:00Z", "source": "events", "basis": "rolling_24h"},
"largest_action_24h": {"value": "24.000000", "source": "events", "basis": "chain_events"},
"spent_total": {"value": "24.000000", "source": "events", "basis": "chain_events"}
},
"breaches": {"count": 0, "last": [], "reverts": {"count": null, "source": "none"}, "outside_limits": "none"}
}
The values above are illustrative.
Fields#
| Field | Meaning |
|---|---|
allowed | per_tx, per_day, stop_floor, budget (6-decimal strings), ends_at, source: terms, caps_source (columns: caps the owner set as fields; legacy_caps_entry: an older row; budget: caps left out default to the whole budget) |
used.spent_24h | Spend in the last 24 hours. From the vault itself when the chain is readable, else from chain-confirmed events, else from your own reports |
used.left_today | per_day minus spent_24h, never below zero |
used.left_total | budget minus all spend, never below zero |
used.per_action_max | The largest single action that stays inside every limit right now: the smallest of per_tx, left_today and left_total |
used.window_frees_at | When the oldest spend in the 24-hour window leaves it, so room opens again. null (basis: nothing_in_window) when nothing is in the window. The window is rolling, not a calendar day |
used.largest_action_24h, used.spent_total | Facts from the same ledger |
breaches | count, the last few rule breaks (at, kind, amount_usdc, codes, clauses, source), reverts, and outside_limits: none, flagged or unknown |
Sources and bases#
Every number in used is {value, source, basis}.
valueis a 6-decimal string, ornullwhen unknown. Never"0.000000"for unknown.sourcesays who knows it:chain,events,termsornone.basissays how it was derived:chain_window,chain_events,agent_reports,none_recorded,rolling_24h,nothing_in_window.
When chain Executed events exist, your own buy and move reports are not added on top (single counting).
Rules of thumb#
- Plan each action to stay under both
used.left_todayandallowed.per_tx. The contract refuses anything above either. - The chain counts the rolling 24 hours in hourly steps, so stay a little under
left_today. fees.net_after_fees_estandfees.net_after_fees_realizedare separate figures. Never add them.outside_limitsisnoneonly when the mandate is signed and nothing is flagged.- A refused or flagged attempt counts in
breacheswhere the owner sees it.
Clauses 6 and 7: what Torii holds and what it closes#
Read clauses[5] (asking) and clauses[6] (at the stop line). Each has status (enforced or stored_pending), enforced_by and a scope object.
{"n": 6, "key": "asking", "status": "enforced", "enforced_by": "Torii guard",
"text_en": "asks you before any action above 25.00 USDC; enforced by Torii guard for relayed actions; direct vault calls are only limited by the contract caps",
"scope": {"covers": "relayed_actions", "relayed": ["execute-call", "mandate_execute_call"],
"not_covered": "direct vault calls signed and sent by the agent",
"text_en": "enforced by Torii guard for relayed actions; direct vault calls are only limited by the contract caps"}}
{"n": 7, "key": "at_stop", "status": "enforced", "enforced_by": "Torii guard",
"scope": {"covers": "relayed_positions", "markets_with_close": ["hyperliquid"], "markets_without_close": [],
"keeper_dry_run": false, "text_en": "closed by the Torii keeper for positions opened through Torii's relay; ..."}}
scope is null when the clause asks nothing (clause 6 mode: own, clause 7 mode: keep). Clause 7 is stored_pending when any market has no close, or while the keeper runs in dry run; markets_without_close names them.
Clause 6 in your loop. If the owner set an approval amount and your execute-call / mandate_execute_call is above it, you get HTTP 202 {status: "pending_approval", approval: {id, status: "pending", amount_usdc, threshold_usdc, expires_at}} and no calldata. Poll GET /torii/mandates/{id}/approvals or MCP mandate_approvals (approval_id, status filters). When the item reads approved, ask for the same call again (same target, data and amount): it is released once and the item becomes executed. A declined item makes the next ask fail with owner_declined (HTTP 409) and the record shows Refused: owner_declined. An item not decided in 24 hours reads expired; asking again opens a new one. At most 10 items wait at once. A call you sign and send without asking Torii for calldata is not held; only the contract caps limit it.
Clause 7 in your record. When the chain shows worth below the stop line (or the vault stopped) and the owner chose close, the keeper records one stop_line step per market (closed, no_position, failed, close_unavailable), each marked Computed by Torii. Markets without a close are yours to unwind with unwind-call.