Skip to content

Docs

Refusals#

Every refusal tells you what to change. Branch on reason.

Refusals carry the standard nightwatch block (nightwatch.refusal.v1): reason, error, retry_safe, who_fixes, next[] (executable calls that fix it) and, where it applies, remaining and retry_at.

  • retry_safe: true means the same request, sent again unchanged, can succeed later (wait for retry_at when it is given).
  • retry_safe: false means change something first: a field, a credential, or ask the owner.
  • A refused call wrote nothing, so a repeat is harmless either way.
  • remaining is what is still allowed in the unit of the refusal's other fields (for insufficient_idle, the idle you have; for mandate_budget_exceeded, the Cherry left today).
  • approval_url and approval_required do not exist yet: there is no out-of-band approval step. Do not wait for one.

Codes#

reasonHTTPretry_safeWho fixes itMeaning and what to do
auth_required401falseyouNo key. POST /auth/agent/connect
auth_invalid401falseyouAn unknown key. POST /auth/agent/connect
scope_denied403falsekey ownerYour key lacks the scope (read, tx or sign), or you tried to write a policy. Ask the owner to widen the key
spend_limit_reached403truekey ownerThe key's daily spend limit would be exceeded. Wait for the day to roll over, or ask the owner
not_owner403falsenobodyThe route is the owner's, or you are not this mandate's agent, or you tried to report a chain or owner kind
not_found404falseyouNo such mandate for this key, or no such agent. List your ids first
wrong_status409falseyouThe mandate is not in the phase this needs (no vault yet, not live, not closed; an open-board mandate that is already signed). agent_conflict arrives as wrong_status with detail.error: agent_conflict
invalid_parameter400falseyouBad kind or amount, a target that is not allowed, approve_amount above the cap, a bad Idempotency-Key, a reserved payload key, a payload over 8,192 bytes, a why over 280 characters, a tx_hash that is not 0x followed by 64 hex digits (events and refusals). fields.field or message names it
invalid_number400falseyouA number in your report is not finite (NaN, Infinity), is above 1e12, has more than 6 decimals, or is negative where it must not be. Send a plain decimal such as 50.00; nothing was written
agent_not_designated409falsenobodyThe mandate names no agent and the owner did not open the board. Ask the owner to designate you and opt in with accepts_mandates; read mandate_offers for what is open
agent_not_accepting409falsekey ownerThe owner's call: the agent they named has not opted in, or has no wallet address on file
agent_owner_mismatch403falseyoumandate_propose named an owner_user_id that is not your owner. Leave it out or send the owner's id; nothing was written
tw_over_order_cap403falsecallerOrder above the per-order limit (counted at the signed price bound); max_notional_usd
tw_over_daily_cap403truetimeDay or order-count limit used up; retry_at is 00:00 UTC
tw_leverage_cap403falsecallerLeverage above the product, house or own limit; max_leverage
tw_below_minimum400falsecallerBelow Hyperliquid's 10 USDC minimum (a reduce-only close is exempt)
tw_signer_mismatch403falsecallerThe signature does not recover to the session wallet
tw_nonce_replay409falsecallerThis signed order was already submitted; sign again
tw_mode_changed409falsecallerPaper or live changed between screen and order; mode_in_force
tw_price_moved409truecallerSigned price too far from the live mark; read again and sign again
tw_wallet_missing409falsecallerThe session has no wallet on file
tw_limit_raise400falsecallerA limit can be lowered or reset, never set above the house cap
tw_fee_tag400falsecallerBuilder tag not the published one or above what the account approved
tw_no_position409falsecallerNo open position this close can reduce
tw_insufficient_margin403falsecallerNot enough free margin (paper account or xyz trading balance)
tw_invalid_number400falsecallerA signed size or price is not finite, is above 1e12 or has more than 8 decimals; field is s or p
tw_wallet_role_unsupported403falsecallerThe session wallet is a Hyperliquid agent, vault or sub-account, not a normal user account. Checked (and cached 10 minutes) before an order or close is relayed. Trade from the wallet that owns the funds
tw_feature_disabled404falseoperatorThe HL leg is not switched on for this deployment (TW_HL_LEG_ENABLED); there is nothing to call. Answers before authentication
tw_hl_not_approved409falsecallerHyperliquid does not list this mandate's Torii key as an approved agent on the owner's account (or the approval has expired). Read hl-leg/approve-call, sign it with the owner's wallet, post it to Hyperliquid, then bind again
tw_hl_leg_owner_busy409falsecallerThe owner's Hyperliquid account is already the live leg of another mandate; one account serves one mandate at a time. Wait until that mandate has ended, or use another wallet
tw_hl_leg_not_bound409falsecallerThe mandate has no bound Hyperliquid leg, so hl-order signs nothing. The owner approves the key and binds it first
tw_hl_over_cap403falsecallerAn opening hl-order is above the per-action cap, would pass today's daily cap, or would take the open notional on the leg past TW_HL_LEG_MAX_OPEN_NOTIONAL_MULT (default 2) times the daily cap; which is per_tx, daily or open_notional, with cap_usd, notional_usd, remaining_usd. A reduce-only close is never capped
tw_hl_account_mode_unsupported409falsecallerBind: the owner's Hyperliquid account is a unified or portfolio-margin account (or not a plain user account). Those show every balance in the spot state, so the leg's worth would be counted twice. Use a dedicated plain account. The same condition on a later read makes the keeper withhold the mark (hl_account_mode)
tw_hl_house_positions_open409falsecallerBind: the mandate already placed orders through the house relay, so its positions are not attributable beside a leg. Bind a mandate that starts on the leg
tw_hl_positions_open409falsecallerBind: the owner's Hyperliquid account already holds open positions. The leg needs a dedicated account that starts flat
tw_hl_builder_fee_required409falsecallerBind or an opening hl-order: the owner's account has not approved Torii's builder fee. The owner approves it (the card does it before the key), then retry. Closes need no tag
tw_hl_bound_to_mandate409falsecallerTrade door: this wallet's Hyperliquid account is the live leg of a mandate, so live opens are paused until the mandate ends. Closes and paper stay allowed
tw_leg_min_deposit400falsecallerLeg funding only: the first deposit of a vault with a funded Hyperliquid leg must be at least 6 USDC (create-call?deposit_usdc=); it also becomes the leg's lifetime cap. Ask the owner for more, or create the vault without a leg
tw_leg_unreconciled409falsecallerLeg funding only: Torii's reconcile found the leg's money does not add up (ledger, Hyperliquid and gateway differ by more than 1 USDC or 0.5 %) on two runs. Opening hl-orders are paused; reduce-only closes stay allowed. Read GET /torii/mandates/{id}/money, retry later
tw_leg_positions_open409falsecallerLeg funding only: return-call closes the open positions with the mandate key (reduce-only) and re-reads the account; a position is still open (a partial fill). remaining_notional_usd says how much; read return-call again
tw_mandate_not_closed409falsecallerLeg funding only: POST .../hl-leg/return-call is for a stopped or ended mandate; a working one keeps its leg working. Stop it first
tw_hl_leg_money_out409falsecallerLeg funding only: hl-leg/unbind is refused while the vault still has money out on the leg or a transfer is under way. Bring it back first, then release the leg
tw_leg_return_amount409falsecallerLeg funding only: the free balance on the Hyperliquid account is zero, or Hyperliquid did not accept the transfer. Read return-call again and sign exactly its typed data
tw_leg_nothing_out409falsecallerLeg funding only: no money was lent to the owner's Hyperliquid account for this mandate, so there is nothing to bring back
tw_paper_market409falsecallerThe mandate has no on-chain targets (paper market): mandate_execute_call builds nothing. Report each paper action with mandate_report_event; the server checks your limits. Read how_to_act on the mandate
tw_paper_not_funded409falsekey ownerA paper mandate's vault holds no deposit yet: there is no paper book (it starts at the deposited amount, never negative). Nothing was written. Wait for the owner's deposit (progress.committed above 0), then report again
tw_paper_over_limit403falsecallerA paper action is above a limit (per action, per day, budget, end date, or the stop line on the paper book). It was not booked; the refused attempt is already on the record (recorded: true), so do not report it again. Read limits.used.left_today and send a smaller action
tw_rate_limited429truetimeMore than 10 orders or closes a minute (60 an hour) from this wallet. Wait Retry-After seconds (also retry_at), then send the same request again
insufficient_idle409falseyouAn outbound transfer report larger than idle. remaining is your idle. Report the missing deposit first, or the owner allows overdraft (allow_negative: true)
exchange_no_wallet409falseyoudeposit-address needs a wallet on file for this agent. Link a self-custody wallet first
mandate_budget_exceeded402truekey ownerThe policy's daily Cherry budget is used up. remaining is what is left. Wait for the UTC day to roll over, or the owner raises it
mandate_budget_busy503truetimeThe budget check could not take its per-agent lock in 3 seconds. Nothing was charged or recorded. Send the same request again in a moment
signature_invalid400falseyouThe owner's signature does not match the terms (owner side)
idempotency_key_reused409falseyouThis key was used for a different request. Send a new key
idempotency_in_flight409truetimeA request with this key is still running. Wait, then send the identical request
rate_limited429truetimeToo many requests. Retry after retry_at
temporarily_unavailable503truetimeA dependency is down. Retry shortly
schema_not_applied503trueoperatorThe tables for this feature are not created on this deployment yet

A rule break is not a refusal#

A rule break you report is stored, flagged and answered 202 with violation: true, violation_detail.codes and retry_safe: false. See Reporting events.

MCP#

MCP tools answer a refusal as {error: {reason, message, retry_safe}}.

Retrying#

  1. retry_safe: true: wait (retry_at if present) and resend the identical request.
  2. retry_safe: false: fix the cause named in who_fixes, then send a new request.
  3. After a timeout on a write, send the same Idempotency-Key again, or read GET /torii/mandates/{id} first. Never retry a write without a key.