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

# Event schema for the monitor

The events a running Guard publishes on its local API, for the monitor page, Telegram alerts and your own tools.

:::note
The events of Guard 1.0 as its code writes them (`zunder-guard-core`, `event.rs`, schema version 1). Fields may be added within a version, never removed or changed in meaning.
:::

{/* GENERATED:events:start — replaced at build time from zunder-guard-core's event types; do not edit by hand */}

## Where

Guard's own address, local only (`127.0.0.1:8547` by default), over HTTP:

- `GET /guard/events?since=N`: the events after sequence number `N`, oldest first (the last 1,000 are kept in memory). Poll it; pass the last `seq` you saw.
- `GET /guard/decision?nonce=N`: Guard's decision on one request, by the nonce the bot signed it with, and what was sent for it. Read from the journal on disk, so it reaches back to Guard's first day.
- `GET /guard/status`: the state now (mode, kill switch, risk state, alerts, rules).

Read-only: nothing sent to them changes Guard. The one write path is the kill switch (`POST /guard/kill`, signed with a client key): it can only pull, never release.

## Envelope

Every event has:

| Field | Type | Meaning |
|---|---|---|
| `seq` | integer | 1, 2, 3, … over the journal's life; a gap means you missed events |
| `at_ms` | integer | UTC epoch milliseconds |
| `kind` | string | one of the kinds below |

The schema is version 1 (the `started` event and the status carry `schema: 1`). Fields may be added within a version, never removed or changed in meaning. Money is a decimal **string**, never a JSON number: `"0.03144"`, not `0.03144`.

## Kinds

### `started`

Guard started: `schema`, `version`, `mode` (`paper`, `testnet`, `mainnet`), `account`, `rules` (the `zr1_` code), `clients` (the client addresses).

### `decision`

One per request to `/exchange` (HTTP or WebSocket), also when it was refused before it could be judged.

```json
{"seq":42,"at_ms":1791000000000,"kind":"decision","via":"http",
 "client":"0x14791697260e4c9a71f18484c9f997b308e59325","signed_as":"testnet","nonce":1791000000000,
 "action":"order","verdict":"resize","code":"resized",
 "text":"a loss at the stop within 2% of equity and open risk within 6%",
 "changes":["limit price 3150 pulled in to 3015, 0.5% from the mid 3000",
            "size 10 cut to 2.5537 ETH for a loss at the stop within 2% of equity and open risk within 6%",
            "a stop loss at 2940 attached (2% from the price)"],
 "request":{"type":"order","orders":[…],"grouping":"na"},
 "pre":[{"type":"updateLeverage","asset":1,"isCross":false,"leverage":4}],
 "forward":{"type":"order","orders":[…],"grouping":"normalTpsl"},
 "post":[]}
```

- `verdict`: `allow`, `resize` (something changed: see `changes`) or `veto`. `code`: [Veto and reason codes](https://zunderlabs.com/docs/reference/veto-codes).
- `client`, `signed_as`, `nonce` are null for a request whose signature did not check out.
- `pre`, `forward`, `post`: what Guard sends, or would send in paper mode: the leverage first, the action, then cancelling Guard's own stop once a tighter one of the bot's rests.

### `sent`

What went to Hyperliquid and what came back (never in paper mode): `decision` (the decision's `seq`), `nonce` (Guard's own), `ok`, `reply` (the venue's answer).

### `risk`

Whenever the risk engine's state changes: `state` (`active`, or `halted_for_day` / `stopped` with their details), `equity`, `open_positions`, `discrepancies`, `journal_error`.

### `flatten`

A daily loss stop, drawdown halt or the kill switch closed everything: `reason`, `actions` (the cancels and closes), `problems` (what could not be priced), `sent` (false in paper mode).

### `protect`

Positions without a market stop covering them in full: Guard placed its own stop (`actions`), closed a position whose stop the venue refused, or in paper mode would have. `coins`, `actions`, `problems`, `sent`.

### `kill`

The kill switch latched: `reason` (from `zunder-guard kill`, the kill file, or `POST /guard/kill` with the client that pulled it).

### `error`

`text`: something went wrong outside a request (reading the account, a refused close). Never contains a key; addresses only.

### `stopped`

Guard stopped: `reason` (a signal).

{/* GENERATED:events:end */}

## Example: a day in events

```text
seq 41  started   paper, rules zr1_…
seq 42  decision  order → resize (size 10 cut to 2.5537 ETH, stop 2940 attached)
seq 43  sent      ok, filled 2.5537 @ 3015
seq 44  decision  order → veto [market_not_allowed]
seq 45  decision  modify → veto [stop_loosened] (2940 to 2900 would loosen it)
seq 46  protect   ETH: Guard's stop at 2940 for a position opened in the app
seq 47  risk      halted_for_day, equity 1880
seq 48  flatten   daily loss stop: 1 cancel, 1 close
```
