# Report events

Source: https://langbot.ai/docs/api/monitoring/report-events

`POST /v1/bots/{bot_id}/events`

Send up to 500 of your bot's orders, fills, cancels, positions, heartbeats and halts in one call. Idempotent per `client_event_id`; each event is accepted or refused on its own.

**Auth.** A report-only token for this bot (`Authorization: Bearer lbr_…`, [Create a report token](https://langbot.ai/docs/api/bots/create-report-token)), or an account token. Never an exchange key.

**Limits.** Events per minute, per bot and per account: 6,000 and 6,000 on Free, 60,000 and 120,000 on Builder, 300,000 and 600,000 on Standard. A batch that does not fit is refused whole (`429`, `Retry-After`). Not counted against your API requests.

Each event:

| Field | Type | Rule |
|---|---|---|
| `client_event_id` | string | required; 1 to 128 of `A-Z a-z 0-9 . _ : -`; unique per bot (your idempotency key) |
| `type` | string | `order`, `fill`, `cancel`, `position`, `heartbeat`, `halt`, `resume` or `note` |
| `ts` | integer | required; ms epoch; at most 5 minutes ahead, not older than your plan keeps |
| `venue` | string | one of the bot's venues; required for `order`, `fill`, `cancel`, `position` |
| `ticker` | string | required for `order`, `fill`, `cancel`, `position` |
| `side` | string | `yes` or `no`; required for `order`, `fill`, `position` |
| `action` | string | `buy` or `sell`; required for `order`, `fill` |
| `price` | number | 0 to 1, dollars per contract of `side`; required for `order` (the limit) and `fill`; on `position` the average cost |
| `count` | number | contracts; above 0 for `order` and `fill`; on `position` what is held after |
| `fee` | number | dollars, fills only |
| `liquidity` | string | `maker` or `taker`, fills only |
| `order_id`, `client_order_id` | string | up to 128 characters |
| `text` | string | up to 500 characters (a note, a halt reason) |
| `meta` | object | up to 4 KiB and 4 levels deep |

**Never send keys.** An event whose `meta` has a key that looks like a credential (`api_key`, `secret`, `private_key`, `password`, `token`, `signature`, …) or a value holding a private key block or a Langbot token is refused with `credential_field` and not stored.

Send the same batch again after a timeout, a `429` or a `503`: events already stored come back as `duplicate` and change nothing.

## Parameters

### Path parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `bot_id` | string | yes | The bot id (`bot_…`). |

### Body

| Name | Type | Required | Description |
|---|---|---|---|
| `events` | array of object | yes | The events, 1 to 500, at most 1 MiB in all. Each is described below. Up to 500 items. |

## Request

**curl**

```bash
curl -X POST "https://api.langbot.ai/v1/bots/bot_01M6G72RFQRQ8JGNG8T1447GGP/events" \
  -H "Authorization: Bearer $LANGBOT_REPORT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"events": [{"client_event_id": "s1:4", "type": "heartbeat", "ts": 1793693146653}]}'
```

**Python**

```python
from langbot import LangbotClient

async with LangbotClient() as lb:  # reads LANGBOT_API_TOKEN
    from langbot.monitor import BotReporter

    rep = BotReporter("bot_01M6G72RFQRQ8JGNG8T1447GGP", token="lbr_…", venue="kalshi")
    rep.fill("KXMENTION-26NOV03-TARIFF", "yes", "buy", 0.41, 15, fee=0.11)  # never blocks
    rep.heartbeat()
    rep.close()  # flushes what is queued
```

**TypeScript**

```ts
import { LangbotClient } from "@langbotai/sdk";

const lb = new LangbotClient(); // reads LANGBOT_API_TOKEN
const rep = lb.bots.reporter("bot_01M6G72RFQRQ8JGNG8T1447GGP", "lbr_…");
rep.fill({ venue: "kalshi", ticker: "KXMENTION-26NOV03-TARIFF", side: "yes", action: "buy", price: 0.41, count: 15 });
await rep.close();
```

## Response

`200 OK`

```json
{
  "accepted": 4,
  "duplicates": 0,
  "rejected": 1,
  "results": [
    {
      "index": 0,
      "client_event_id": "s1:1",
      "status": "accepted"
    },
    {
      "index": 1,
      "client_event_id": "s1:2",
      "status": "accepted",
      "applied": true
    },
    {
      "index": 2,
      "client_event_id": "s1:3",
      "status": "accepted",
      "applied": true
    },
    {
      "index": 3,
      "client_event_id": "s1:4",
      "status": "accepted"
    },
    {
      "index": 4,
      "client_event_id": "s1:5",
      "status": "rejected",
      "error": {
        "code": "credential_field",
        "detail": "`meta` looks like it carries a credential (api_key); Langbot never accepts keys or secrets: remove it"
      }
    }
  ]
}
```

`200` whenever the batch itself is well formed, even if some events are refused: read `results[].status` (`accepted`, `duplicate`, `rejected`) and `error.code` (`invalid`, `credential_field`, `venue_not_enabled`, `too_old`, `future_ts`, `idempotency_conflict`, `duplicate_in_batch`). `applied: false` on a fill that does not move P&L (a Polymarket fill while a wallet is linked).

## Errors

| Status | `code` | When |
|---|---|---|
| 403 | `scope_denied` | The report token is bound to another bot. |
| 404 | `not_found` | No bot with that id on your account (or it was deleted). |
| 413 | `too_large` | More than 500 events or more than 1 MiB; nothing was stored. |
| 429 | `rate_limited` | The bot's or account's events per minute are used; resend the same batch after `Retry-After` seconds. |
| 503 | `unavailable` | Ingestion is busy; resend the same batch after `Retry-After` seconds. |
| 401 | `auth_required` | No token was sent. |
| 401 | `token_invalid` | The token was not recognised (mistyped, revoked or expired). |
| 422 | `validation_error` | A parameter failed validation; `detail[].loc` names it. |

Every error body is `{"detail": …, "code": "…"}`; branch on `code`. All codes: [Errors](https://langbot.ai/docs/api#errors).
