# Events

> The Events operations of the Kabu API: one ordered, resumable stream; also delivered by webhook.

One ordered, resumable stream; also delivered by webhook.

Generated from the contract, version `2.0.0-draft.2` ([OpenAPI](/openapi.yaml)). Base URL `https://usekabu.com/api`. Errors are problem details, each [code](/docs/errors) with its own page.

## `GET /v2/events/checkpoint` · The current checkpoint (take it before scanning state)

Accepts the same filters as events.list and events.stream and returns a checkpoint bound to them. Recovery recipe: 1) take this checkpoint, 2) list current state, 3) page or stream events after it with the same filters, applying each only if its resource_version is newer than what you hold. Reads during recovery are primary-consistent, never from a lagging replica.

- **Auth:** an API key, or a signed-in human's token (the Kabu apps).
- **Scopes:** `events:read`.
- **Signed-in humans:** `audience`.

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `types` | query | array of string | no | Comma-separated event types. |
| `project` | query | string | no |  |
| `session` | query | string | no |  |
| `agent` | query | string | no |  |
| `Kabu-Org` | header | string | no | Select a live membership (default personal org); non-member is 404. Human tokens are live-only. Caller user id is distinct from billing owner. |

| Status | Means | Body |
|---|---|---|
| `200` | OK | [Checkpoint](#schema-Checkpoint) |
| `4XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |
| `5XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |

```sh
curl "https://usekabu.com/api/v2/events/checkpoint" \
  -H "Authorization: Bearer $KABU_API_KEY"
```

## `GET /v2/events` · Page through events

Every page returns a checkpoint, even when empty. A checkpoint past retention returns 410 checkpoint_expired.

- **Auth:** an API key, or a signed-in human's token (the Kabu apps).
- **Scopes:** `events:read`.
- **Signed-in humans:** `audience`.

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `limit` | query | integer, from 1 to 100 | no |  |
| `after` | query | string | no | Exclusive checkpoint. Omit to start at the oldest retained event. A checkpoint is bound to the filters it was issued with: reusing it with different filters is 400 checkpoint_filter_mismatch. |
| `wait` | query | integer, from 0 to 50 | no | Hold up to N seconds for the state to settle. 0 or absent returns at once. The kabu CLI and the MCP tools wait 25. |
| `types` | query | array of string | no | Comma-separated event types. |
| `project` | query | string | no |  |
| `session` | query | string | no |  |
| `agent` | query | string | no |  |
| `Kabu-Org` | header | string | no | Select a live membership (default personal org); non-member is 404. Human tokens are live-only. Caller user id is distinct from billing owner. |

| Status | Means | Body |
|---|---|---|
| `200` | OK | [EventList](#schema-EventList) |
| `4XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |
| `5XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |

```sh
curl "https://usekabu.com/api/v2/events" \
  -H "Authorization: Bearer $KABU_API_KEY"
```

## `GET /v2/events/stream` · Stream events (SSE, or NDJSON)

SSE: id is the checkpoint, event is the type, data is an Event; 15 s comment heartbeats and a `retry:` hint; resume with Last-Event-ID, which takes precedence over `after`. Accept application/x-ndjson for one StreamFrame per line. A consumer more than 10,000 events behind is disconnected and resumes from its checkpoint.

- **Auth:** an API key, or a signed-in human's token (the Kabu apps).
- **Scopes:** `events:read`.
- **Signed-in humans:** `audience`.

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `after` | query | string | no |  |
| `types` | query | array of string | no | Comma-separated event types. |
| `project` | query | string | no |  |
| `session` | query | string | no |  |
| `agent` | query | string | no |  |
| `Last-Event-ID` | header | string | no |  |
| `Kabu-Org` | header | string | no | Select a live membership (default personal org); non-member is 404. Human tokens are live-only. Caller user id is distinct from billing owner. |

| Status | Means | Body |
|---|---|---|
| `200` | A stream of events | `text/event-stream`: string, or `application/x-ndjson`: [StreamFrame](#schema-StreamFrame) |
| `4XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |
| `5XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |

```sh
curl "https://usekabu.com/api/v2/events/stream" \
  -H "Authorization: Bearer $KABU_API_KEY"
```

## `GET /v2/events/{event_id}` · Get one event

- **Auth:** an API key, or a signed-in human's token (the Kabu apps).
- **Scopes:** `events:read`.
- **Signed-in humans:** `audience`.

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `event_id` | path | string | yes | An `evt_…` id. |
| `Kabu-Org` | header | string | no | Select a live membership (default personal org); non-member is 404. Human tokens are live-only. Caller user id is distinct from billing owner. |

| Status | Means | Body |
|---|---|---|
| `200` | OK | [Event](#schema-Event) |
| `4XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |
| `5XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |

```sh
curl "https://usekabu.com/api/v2/events/$EVENT_ID" \
  -H "Authorization: Bearer $KABU_API_KEY"
```

## Schemas

### Checkpoint

| Property | Type | Required | Description |
|---|---|---|---|
| `object` | `"checkpoint"` | yes |  |
| `checkpoint` | string | yes |  |

### EventList

| Property | Type | Required | Description |
|---|---|---|---|
| `object` | `"list"` | yes |  |
| `data` | array of [Event](#schema-Event) | yes |  |
| `has_more` | boolean | yes |  |
| `checkpoint` | string | yes | Always present, even on an empty page. Resume with after=checkpoint. |

### Event

At-least-once; dedupe by id. Ordered per resource and per session, not across an org. Events are stored thin (type, resource, resource_version, routing fields); `data.object` is the resource rendered when the event is delivered to you, at a version ≥ resource_version, with your projection, retention and erasure applied — so webhook retries and replays re-render, and erased content is never resent (an erased message renders its redacted skeleton, as GET does; a purged resource renders as a Tombstone). Use `previous` and resource_version for what changed at the event.

| Property | Type | Required | Description |
|---|---|---|---|
| `object` | `"event"` | yes |  |
| `id` | string | yes |  |
| `type` | [EventType](#schema-EventType) | yes | Open enum. Known values are `EventTypeKnown`; accept and ignore unknown values. |
| `livemode` | boolean | yes |  |
| `project` | string | yes |  |
| `resource` | string | yes | The id of the resource this event is about. |
| `resource_version` | integer | yes | That resource's version after this event; apply only if newer than what you hold. |
| `session` | string or null | yes |  |
| `agent` | string or null | yes |  |
| `traceparent` | string or null | yes |  |
| `created_at` | [Timestamp](/docs/api/messages#schema-Timestamp) | yes |  |
| `data` | [EventData](#schema-EventData) | yes |  |

### EventType

Open enum. Known values are `EventTypeKnown`; accept and ignore unknown values.

Type: [EventTypeKnown](#schema-EventTypeKnown) or string.

### EventTypeKnown

Event types. Ignore types you do not know.

Type: string, one of `"message.created"`, `"message.delivered"`, `"message.resolved"`, `"message.expired"`, `"message.cancelled"`, `"message.acked"`, `"session.created"`, `"session.updated"`, `"session.ended"`, `"agent.paused"`, `"presence.updated"`, `"ping"`.

### EventData

| Property | Type | Required | Description |
|---|---|---|---|
| `object` | [EventPayload](#schema-EventPayload) or [UnknownObject](#schema-UnknownObject) | yes |  |
| `previous` | [Previous](#schema-Previous) or null | yes |  |

### EventPayload

Type: [Message](/docs/api/messages#schema-Message) or [Session](/docs/api/sessions#schema-Session) or [Agent](#schema-Agent) or [Media](/docs/api/messages#schema-Media) or [Ping](#schema-Ping) or [Tombstone](#schema-Tombstone) or [RuntimePresence](/docs/api/sessions#schema-RuntimePresence).

Chosen by `object`: `"message"`: [Message](/docs/api/messages#schema-Message), `"session"`: [Session](/docs/api/sessions#schema-Session), `"agent"`: [Agent](#schema-Agent), `"media"`: [Media](/docs/api/messages#schema-Media), `"ping"`: [Ping](#schema-Ping), `"tombstone"`: [Tombstone](#schema-Tombstone), `"presence"`: [RuntimePresence](/docs/api/sessions#schema-RuntimePresence).

### Agent

Members see no settings or metadata (empty objects); `listening` is derived from steering claims and `presence` from the leases of the agent's sessions.

| Property | Type | Required | Description |
|---|---|---|---|
| `object` | `"agent"` | yes |  |
| `id` | string | yes |  |
| `version` | integer | yes |  |
| `slug` | string | yes |  |
| `name` | string or null | yes |  |
| `description` | string or null | yes |  |
| `normalized_key` | string | no |  |
| `icon` | string | no | A `med_…` avatar media id (or SF Symbol fallback when not `med_…`). |
| `symbol` | string | no | An avatar glyph: `sf:NAME`, `emoji:CHAR` or `art:ID` (a bare SF Symbol name is also read). |
| `color` | [Accent](/docs/api/messages#schema-Accent) | no | Open enum. Known values are `AccentKnown`; accept and ignore unknown values. |
| `avatar_url` | string (uri) or null | yes |  |
| `status` | string | yes | active or paused (the loop breaker pauses; open). |
| `settings` | [SettingsView](#schema-SettingsView) | yes | Layered: org → project → agent → recipient. Unset fields inherit. |
| `metadata` | [Metadata](/docs/api/messages#schema-Metadata) | yes | Your own key/value pairs (≤ 50 keys, values ≤ 500 chars), returned verbatim on every object and event. |
| `created_at` | [Timestamp](/docs/api/messages#schema-Timestamp) | yes |  |
| `project` | string or null | no |  |
| `listening` | boolean | no |  |
| `pause_reason` | string or null | no |  |
| `last_activity_at` | [Timestamp](/docs/api/messages#schema-Timestamp) or null | no |  |
| `presence` | [AgentPresenceRollup](#schema-AgentPresenceRollup) | no | An agent's sessions rolled up: the strongest state among them (`paused` when the agent is paused), with counts. Read-only; it moves without Agent.version, as `listening` does. |

### SettingsView

Layered: org → project → agent → recipient. Unset fields inherit.

| Property | Type | Required | Description |
|---|---|---|---|
| `appearance` | [AppearanceView](/docs/api/messages#schema-AppearanceView) | no | Cosmetics only. Resolved org → project → agent → recipient, then this override; recipient restrictions always win. |
| `delivery` | [DeliverySettingsView](#schema-DeliverySettingsView) | no |  |
| `extensions` | [Extensions](/docs/api/messages#schema-Extensions) | no | Reverse-domain namespaced extension data (e.g. `com.example.ticket`), ≤ 8 KiB total. Cannot change core semantics. |

### DeliverySettingsView

| Property | Type | Required | Description |
|---|---|---|---|
| `quiet_hours` | object | no |  |
| `quiet_hours.start` | string, matching `^[0-2][0-9]:[0-5][0-9]$` | no |  |
| `quiet_hours.end` | string, matching `^[0-2][0-9]:[0-5][0-9]$` | no |  |
| `quiet_hours.time_zone` | string | no |  |
| `digest_notify_after` | integer, from 1 to 200 | no | Notifies per 10 min before FYIs are digested. |
| `remind_every` | integer, from 0 to 86400 | no | Seconds between reminders of a pending ask: 0 turns them off, otherwise 300 (5 min) to 86400 (24 h). Unset inherits: the recipient's, then the org's, then 600 (10 min). An ask is reminded at most 3 times and never within 2 min of its expiry. One whose life (expiry minus its first push) is under this plus 2 min is reminded once, at the midpoint of its life, if it lives at least 3 min (a 300 s ask at 150 s). Set by recipients and orgs, not agents. |

### AgentPresenceRollup

An agent's sessions rolled up: the strongest state among them (`paused` when the agent is paused), with counts. Read-only; it moves without Agent.version, as `listening` does.

| Property | Type | Required | Description |
|---|---|---|---|
| `state` | [PresenceState](/docs/api/sessions#schema-PresenceState) | yes | Open enum. Known values are `PresenceStateKnown`; accept and ignore unknown values. |
| `counts` | [PresenceCounts](#schema-PresenceCounts) | yes | The agent's sessions with presence, per state; ended sessions are not counted. |
| `last_seen_at` | string (date-time) | yes | The latest report from any of the agent's sessions. |

### PresenceCounts

The agent's sessions with presence, per state; ended sessions are not counted.

| Property | Type | Required | Description |
|---|---|---|---|
| `waiting` | integer | yes |  |
| `blocked` | integer | yes |  |
| `working` | integer | yes |  |
| `idle` | integer | yes |  |
| `online` | integer | yes |  |
| `offline` | integer | yes |  |

### Ping

| Property | Type | Required | Description |
|---|---|---|---|
| `object` | `"ping"` | yes |  |
| `at` | [Timestamp](/docs/api/messages#schema-Timestamp) | yes |  |

### Tombstone

What an event renders when its resource no longer exists at all (purged or past retention). An erased message that keeps its digest, receipt and audit skeleton renders that skeleton, exactly as GET returns it, not a Tombstone.

| Property | Type | Required | Description |
|---|---|---|---|
| `object` | `"tombstone"` | yes |  |
| `id` | string | yes |  |
| `resource_object` | string | yes |  |
| `version` | integer | yes | The resource's final version; newer than any version it replaces. |
| `reason` | string | yes | purged or retention_expired (open). |

### UnknownObject

Fallback for future resources: keep or ignore, never fail.

| Property | Type | Required | Description |
|---|---|---|---|
| `object` | string | yes | A resource type this client does not know yet. |

### Previous

Whitelisted previous values. Never content.

| Property | Type | Required | Description |
|---|---|---|---|
| `status` | string or null | yes |  |
| `version` | integer or null | yes |  |
| `delivery_status` | string or null | yes |  |

### StreamFrame

One NDJSON line: resume with after=checkpoint.

| Property | Type | Required | Description |
|---|---|---|---|
| `checkpoint` | string | yes |  |
| `event` | [Event](#schema-Event) | yes | At-least-once; dedupe by id. Ordered per resource and per session, not across an org. Events are stored thin (type, resource, resource_version, routing fields); `data.object` is the resource rendered when the event is delivered to you, at a version ≥ resource_version, with your projection, retention and erasure applied — so webhook retries and replays re-render, and erased content is never resent (an erased message renders its redacted skeleton, as GET does; a purged resource renders as a Tombstone). Use `previous` and resource_version for what changed at the event. |
