Skip to content

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#

FieldMeaning
allowedper_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_24hSpend 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_todayper_day minus spent_24h, never below zero
used.left_totalbudget minus all spend, never below zero
used.per_action_maxThe largest single action that stays inside every limit right now: the smallest of per_tx, left_today and left_total
used.window_frees_atWhen 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_totalFacts from the same ledger
breachescount, 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}.

  • value is a 6-decimal string, or null when unknown. Never "0.000000" for unknown.
  • source says who knows it: chain, events, terms or none.
  • basis says 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_today and allowed.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_est and fees.net_after_fees_realized are separate figures. Never add them.
  • outside_limits is none only when the mandate is signed and nothing is flagged.
  • A refused or flagged attempt counts in breaches where 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.