# Watch your bot

> Send your live bot's orders, fills and heartbeats to Langbot with a report-only token, or link a Polymarket wallet, and see its trades, positions and P&L next to the test it passed, with alerts.

Source: https://langbot.ai/docs/monitoring

Your bot runs on your machine, on your own exchange keys. Langbot watches it: you send what the bot did (orders, fills, cancels, positions, heartbeats, halts), and Langbot shows its live trades, positions and P&L, compares them with the [Sim](https://langbot.ai/docs/backtesting) run it was tested on, and alerts you when something is wrong.

**Langbot never receives your exchange keys, never places, changes or cancels an order, and never trades for you.** Reporting is one way: your bot tells Langbot what happened. The numbers are measurements of what your bot reported, not advice.

## How it works

1. **Create a bot** in the dashboard or with `lb.bots.create`. A bot is a name, the venues it trades on (`kalshi`, `polymarket`, `polymarket_us`) and, optionally, the Sim run you tested it with.
2. **Create a report token** for it (`lbr_…`). It can send that bot's events and nothing else.
3. **Send events** from the bot: with the SDK's `BotReporter`, from `LiveGuard` automatically, or with plain HTTP. For Polymarket you can instead link the bot's wallet and Langbot reads its fills from the public chain.
4. **Watch**: status, P&L, positions, fills, the equity curve, live against the test, and alerts by email or a signed webhook.

## Report from Python

```python
from langbot.monitor import BotReporter

rep = BotReporter("bot_01M6…", token="lbr_…", venue="kalshi")

rep.order("KXMENTION-26NOV03-TARIFF", "yes", "buy", 0.41, 20, client_order_id="c-1")
rep.fill("KXMENTION-26NOV03-TARIFF", "yes", "buy", 0.41, 15, fee=0.11, liquidity="taker")
rep.heartbeat()

rep.close()  # sends what is queued, waits at most 5 s
```

Every call only puts the event on a queue and returns its `client_event_id`; it never blocks and never raises into your bot. A background thread sends batches of up to 500 every second, retries with backoff when the API is busy or unreachable, and spills to a file on disk if the queue (10,000 events) fills up, so a slow network never slows your trading. It logs `bot reporting is behind: N queued, oldest Xs` (at most once a minute) when it falls behind.

If you trade through `LiveGuard`, pass the reporter and every order, fill, cancel, halt and resume it records is sent for you, with a heartbeat every 30 seconds:

```python
from langbot.live import LiveGuard, load_limits
from langbot.monitor import BotReporter

guard = LiveGuard(
    venue,
    load_limits(),
    strategy="mention-sniper",
    reporter=BotReporter("bot_01M6…", token="lbr_…", venue="kalshi"),
)
```

TypeScript has the same reporter: `lb.bots.reporter(botId, "lbr_…")` or `new BotReporter(botId, { token })` from `@langbotai/sdk`. Any other language can [POST the events](https://langbot.ai/docs/api/monitoring/report-events) directly.

## Events

Each event has a `client_event_id` (your idempotency key, unique per bot), a `type` and a `ts` in milliseconds. Market events carry `venue`, `ticker`, `side` (`yes` or `no`), `action` (`buy` or `sell`), `price` (dollars per contract of that side, 0 to 1) and `count`; fills can carry `fee` and `liquidity`. The full table is on [Report events](https://langbot.ai/docs/api/monitoring/report-events).

- **Send the same batch again** after a timeout, a `429` or a `503`. Events already stored come back as `duplicate` and change nothing; an id reused with different content is refused as `idempotency_conflict`.
- **Each event stands alone.** One bad event is refused with a code (`invalid`, `credential_field`, `venue_not_enabled`, `too_old`, `future_ts`) and the rest of the batch is stored.
- **Never send keys.** An event whose `meta` holds anything that looks like a credential (`api_key`, `secret`, `private_key`, `password`, a private key block, a Langbot token) is refused and not stored.

## Polymarket: link a wallet instead

Polymarket settles every fill on the Polygon chain, and Langbot records those fills. [Link the bot's wallet](https://langbot.ai/docs/api/bots/link-wallet) and its fills are attached from that recording, within about a minute, with no key involved and nothing to send. Link the address that holds the positions (the wallet your Polymarket profile shows), not a signing key. A wallet's trades are public on the chain: linking reads nothing private and grants nothing, and anyone can read the same fills.

While a wallet is linked, Polymarket fills your bot reports itself are kept but do not move P&L, so nothing is counted twice. On-chain fills carry no fee.

## What you see

| | |
|---|---|
| **Status** | `ok`, `stale` (no heartbeat inside the heartbeat window, 5 minutes by default), `halted` (the bot reported a halt and no resume since) or `new` |
| **P&L** | realized, fees, realized net, marked (open positions at the settled result, the last recorded trade, or the bot's last fill), today's net, max drawdown |
| **Positions** | computed from the fills, beside what the bot last reported holding, with a `mismatch` flag when they differ |
| **Fills and events** | every event, newest first, with where it came from (`client` or `chain`) |
| **Equity** | cumulative realized net by hour or day, plus the current marked point |

P&L is per market in Yes terms with average cost: buying No at p counts as selling Yes at 1 − p.

## Live against the test

Link the bot to the Sim run you tested it with and [Compare with the test](https://langbot.ai/docs/api/monitoring/get-vs-test) puts four numbers side by side: fill rate, fee per contract, P&L per contract, and slippage against the prices the test filled at. Every number comes with the sample size behind it; no significance test is claimed, so read a small sample as a small sample. Slippage needs markets traded both live and in the test; a test usually replays a past event, so it is often empty.

## Alerts

[Set alert rules](https://langbot.ai/docs/api/monitoring/set-alerts) per bot:

- **Heartbeat missing** for N minutes.
- **Daily loss**: today's net P&L at or below −X dollars.
- **Drift from the test**: a live number (P&L per contract, fill rate or fee per contract) differs from the test by more than a threshold, once enough fills exist.
- **Halted**: the bot reported a halt.

Alerts go to your account's email and, if you set one, to a webhook signed like every Langbot webhook (`X-Langbot-Signature: sha256=…`). Each problem is one incident: you hear once when it starts and once when it recovers, never repeatedly in between.

## Security

- **Report tokens are least privilege.** An `lbr_…` token can send events for its bot and nothing else; anything else it is sent to answers `403 scope_denied`. It cannot read your account, your bots or your P&L. Bind it to one bot, revoke it any time, and only its hash is stored.
- **Your data is your account's.** Every read and write is scoped to your account; a bot of another account is simply not found.
- **Audited.** Creating and revoking report tokens, linking and unlinking wallets, and changes to bots and alerts are kept in an append-only [audit log](https://langbot.ai/docs/api/bots/get-audit-log).
- **No keys, ever.** Langbot never asks for, accepts or stores exchange credentials.

## Plans

| | Free | Builder | Standard | Desk |
|---|---|---|---|---|
| Bots | 1 | 5 | 50 | custom |
| Events kept | 7 days | 90 days | 1 year | custom |
| Events per minute, per bot | 6,000 | 60,000 | 300,000 | custom |

P&L stays exact when old events are removed: the ledger keeps what they added up to.
