<!-- https://zunderlabs.com/docs/reference/veto-codes · Markdown version of the page -->

# Veto and reason codes

Every code Guard, the risk engine and the browser tools use to say why an entry was refused or resized.

A refusal always carries a code (stable, for programs) and a reason (a sentence, for people). Codes are `snake_case` and never change meaning; new ones may be added.

_On the web page, each code has a "show me" that runs an example through the real engine._

## Risk engine: in code today

`Veto` in `crates/zunder-risk/src/engine.rs`. The codes are the variant names in `snake_case`, as the WebAssembly build serialises them (`VetoCode` in `crates/zunder-risk-wasm/src/api.rs`).

| Code | Rule | Reason (the engine's own) | Example |
|---|---|---|---|
| `halted_for_day` | (h) | trading is halted for the day | down 6% since 00:00 UTC |
| `stopped` | (i) | trading is stopped until a manual review | 25% below the peak |
| `stop_on_wrong_side` | (g) | the stop is not on the losing side of the entry price | buy at 100, stop at 101 |
| `open_risk_exhausted` | (e) | the open-risk budget is used up | stops already risk 6% |
| `leverage_exhausted` | (c) | the leverage cap is reached | positions already worth 5× equity |
| `below_minimum` | (g) | the sized quantity is below the venue minimum | the allowed size is worth less than the minimum order |
| `unprotected_position` | (e) | an open position has no protective stop, or its price is at or through it | an ETH position without a stop blocks a BTC entry |
| `invalid_request` | (g) | the sizing request contains a negative or non-positive amount | a price of 0 |
| `overflow` | (g) | the numbers are too large or too small to size safely | absurd inputs |

## Policy: in the website's judge (being built)

`crates/zunder-risk-wasm/src/judge.rs`. Planned to be the same in Guard's policy.

| Code | Rule | Example reason |
|---|---|---|
| `coin_not_allowed` | (a) | HYPE is not on the market allowlist |
| `no_protective_stop` | (b) | no stop order protects the ETH position |
| `unbounded_risk` | (b) | neither a stop nor a liquidation price bounds the loss |
| `liquidation_too_close` | (d) | liquidation is 7% from the price; the minimum is 10% |
| `position_cap_reached` | (f) | a position may be worth at most 200% of account value |
| `equity_not_positive` | (g) | the account has no positive equity |

**Resizes** carry the rule that bound the size instead of a code: `max_leverage`, `max_open_risk`, `max_position_size` or `max_loss_per_trade`, with a reason such as "a stop-out may lose at most 2% of equity".

## Watch: account warnings (being built)

`crates/zunder-risk-wasm/src/account.rs`. Not refusals: warnings about the account as a whole.

| Code | Meaning |
|---|---|
| `halted_for_day` | the daily loss stop is reached |
| `stopped` | the drawdown halt is reached |
| `position_without_stop` | a position has no protective stop |
| `open_risk_over_cap` | open risk is over your cap |
| `leverage_over_cap` | leverage is over your cap |

## Guard only: planned

| Code | Meaning |
|---|---|
| `killed` | the kill switch is pulled; nothing opens until a person releases it |
| `client_not_allowed` | the request was signed by a key that is not a client of this Guard |
| `nonce_reused` | the nonce was already used, or is too old |
| `action_not_allowed` | an action Guard never forwards: withdrawals, transfers, key or builder approvals |
| `stop_loosening_ignored` | not a refusal: the stop stayed where it was ([Stops only tighten](https://zunderlabs.com/docs/concepts/stops-only-tighten)) |
| `not_ready` | Guard is reconciling, or waiting out its start-up wait |
| `margin_not_set` | isolated margin could not be confirmed before the entry |
| `no_safe_leverage` | no leverage of 1x or more keeps liquidation beyond the stop |

The last two follow `SessionError::MarginNotSet` and `SessionError::NoSafeLeverage` in Zunder's session, which exist today.

## How a refusal reaches your bot

:::note[Planned]
In Hyperliquid's own error format, so a tool that handles Hyperliquid errors handles Guard's:

```json
{"status": "err", "response": "zunder-guard: no_protective_stop: no stop order protects the ETH position"}
```

The exact shape is checked against each integration before release.
:::
