---
title: "Events"
description: "Read MachineMail's replayable event log for the organization, a pod or an inbox, or follow events live over Server-Sent Events or a WebSocket."
canonical: "https://machinemail.dev/docs/reference/api/events"
---

# Events

> Read MachineMail's replayable event log for the organization, a pod or an inbox, or follow events live over Server-Sent Events or a WebSocket.

Source: https://machinemail.dev/docs/reference/api/events

| Method | Path                                       | Summary                                           |
| ------ | ------------------------------------------ | ------------------------------------------------- |
| `GET`  | `/v0/events`                               | [List Events](#list-events)                       |
| `GET`  | `/v0/events/stream`                        | [Stream Events (SSE)](#stream-events-sse)         |
| `GET`  | `/v0/events/{event_id}`                    | [Get Event](#get-event)                           |
| `GET`  | `/v0/inboxes/{inbox_id}/events`            | [List Events in Inbox](#list-events-in-inbox)     |
| `GET`  | `/v0/inboxes/{inbox_id}/events/{event_id}` | [Get Event in Inbox](#get-event-in-inbox)         |
| `GET`  | `/v0/pods/{pod_id}/events`                 | [List Events in Pod](#list-events-in-pod)         |
| `GET`  | `/v0/pods/{pod_id}/events/{event_id}`      | [Get Event in Pod](#get-event-in-pod)             |
| `GET`  | `/v0/ws`                                   | [WebSocket Event Stream](#websocket-event-stream) |

## List Events

```http
GET /v0/events
```

The replayable event log, newest first. Pass `after` (an event ID or a time) to read forward from a point, oldest first. Events are kept without a retention limit. Only the events the credential may read are listed, as on the live streams: `message.*` needs `message_read`, `inbox.*` `inbox_read`, `domain.*` `domain_read`, `draft.*` `draft_read`, `webhook.test` `webhook_read`, and the audit events (agent registrations, members) `metrics_read`. `event_types` cannot add others.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `metrics_read`.

#### Query parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `limit` | `integer` |  | Maximum number of items to return. Default 50, maximum 500; larger values are capped. |
| `page_token` | `string` |  | next_page_token from the previous page. |
| `ascending` | `boolean` |  | Sort oldest first. Defaults to true when after is set. |
| `after` | `string` |  | An event ID (evt_...): only events stored after it, in commit order. Or an ISO 8601 time: only events created after it. Either one makes the default order oldest first. |
| `before` | `string (date-time)` |  | Only events created before this time. |
| `event_types` | `("message.received" \| "message.received.spam" \| "message.received.quarantined" \| "message.received.unauthenticated" \| "message.received.blocked" \| "message.sent" \| "message.delivered" \| "message.bounced" \| "message.complained" \| "message.rejected" \| "domain.verified" \| "domain.updated" \| "inbox.created" \| "inbox.expired" \| "draft.sent" \| "webhook.test" \| "trusted_domain.created" \| "trusted_domain.updated" \| "trusted_domain.deleted" \| "message.released")[]` |  | Only these event types (repeat the parameter, or comma-separate). Omit for every type. |
| `inbox_id` | `string` |  | Only events of this inbox. |
| `pod_id` | `string` |  | Only events of this pod. |

#### Response

**200** A page of events (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `count` | `integer` | required | Number of items returned. |
| `limit` | `integer` |  | Maximum number of items per page. Default 50, maximum 500; larger values are capped. |
| `next_page_token` | `string` |  | Page token for pagination. Absent on the last page. |
| `events` | `Event[]` | required | Newest first; oldest first when after is set or ascending is true. |

Each item of `events` is a [Event](https://machinemail.dev/docs/reference/api/objects#event):

| Field | Type | | Description |
| --- | --- | --- | --- |
| `event_id` | `string` | required | ID of the event (evt_...). Sorts by creation time. |
| `event_type` | `"message.received" \| "message.received.spam" \| "message.received.quarantined" \| "message.received.unauthenticated" \| "message.received.blocked" \| "message.sent" \| "message.delivered" \| "message.bounced" \| "message.complained" \| "message.rejected" \| "domain.verified" \| "domain.updated" \| "inbox.created" \| "inbox.expired" \| "draft.sent" \| "webhook.test" \| "trusted_domain.created" \| "trusted_domain.updated" \| "trusted_domain.deleted" \| "message.released" \| "registration.created" \| "token.issued" \| "agent.claimed" \| "agent.revoked" \| "member.invited" \| "member.role_changed" \| "member.removed"` | required | Type of event: an events_EventType, or an audit event for agent registrations and members (registration.created, token.issued, agent.claimed, agent.revoked, member.invited, member.role_changed, member.removed), which is logged but never delivered to webhooks or streams. |
| `organization_id` | `string` | required | ID of the organization. |
| `pod_id` | `string` |  | ID of the pod. Absent for organization-level events. |
| `inbox_id` | `string` |  | ID of the inbox. Absent for events without an inbox (for example domain.verified). |
| `data` | `Record<string, unknown>` | required | Event payload, as delivered to webhooks: message.* events (and message.released) carry message and thread, delivery events their send, delivery, bounce, complaint or reject object, inbox.* the inbox, domain.verified and domain.updated the domain, trusted_domain.* the trusted_domain. |
| `timestamp` | `string (date-time)` | required | When the event happened (same as created_at). |
| `created_at` | `string (date-time)` | required | When the event was stored. |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid request: error.type is validation and error.param names the field.                                                                                                                                                             |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl "https://api.machinemail.dev/v0/events" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
```

```json title="Response 200"
{
  "count": 1,
  "limit": 50,
  "next_page_token": "eyJvIjo1MH0",
  "events": [
    {
      "event_id": "evt_01k6x3g4pt0000k6t2wa8e1n3q",
      "event_type": "message.received",
      "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e",
      "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
      "inbox_id": "agent@machinemail.dev",
      "data": {
        "key": "value"
      },
      "timestamp": "2026-10-01T12:00:00.000Z",
      "created_at": "2026-10-01T12:00:00.000Z"
    }
  ]
}
```

## Stream Events (SSE)

```http
GET /v0/events/stream
```

Server-Sent Events of everything in the credential scope that its permissions allow, narrowed by the query filters.

Each event is sent only when the credential holds the read permission of what it describes: `message.*` needs `message_read`, `domain.*` needs `domain_read`, `inbox.*` needs `inbox_read`, `draft.*` needs `draft_read`, `webhook.*` needs `webhook_read`, `trusted_domain.*` needs `trusted_domain_read`. Other events are skipped, without an error, and `event_types` cannot add them. The credential is re-checked every 30 seconds: when it is revoked or expired, or its permissions or pod/inbox scope change, the stream ends with an error (reconnect to get the events the credential allows now).

Frames are `id: <event_id>`, `event: <event_type>`, `data: <JSON>`, where the JSON is the webhook event envelope (`type: "event"`, `event_id`, `event_type`, `timestamp`, `data`, and the `data` keys repeated at the top level), not the `Event` object of `GET /v0/events`. The first frame is `event: ready` with `data: {"type":"subscribed"}`. A `: ping` comment is sent after 15 seconds without events.

With a `Last-Event-ID: evt_...` header, up to 500 missed events are replayed first. The replay applies `event_types`, the credential scope and the per-event permissions, not the `inbox_id` or `pod_id` filters. When the API runs on several servers, the replay also repeats events from the few seconds before `Last-Event-ID` (an event from another server can reach a stream after a later one): dedupe by event id.

An `event: resync` frame (`data: {"type":"resync"}`) means the stream may have missed events; the stream then ends. Its `id:` is a cursor from before the gap, which an `EventSource` takes as its last event ID: reconnect with it as `Last-Event-ID` to replay what was missed, some events again (dedupe by event id).

A stream opened with a dashboard session cookie also receives that person's session events (`session.agent.claimed`, `session.cli.device`, `session.oauth.request`): live notices that a hand-off they started (an agent claim, a CLI sign-in, an OAuth consent) was completed, so their other tabs can finish too. Their frames have no `id:` line and their `data` is `{"type":"session","event_type","timestamp","data"}`; they are never stored, replayed or sent to API keys, OAuth tokens, webhooks or the WebSocket. They need no permission: they go only to the session of the person they are about.

**Authentication:** `Authorization: Bearer <api key>` or `?token=<api key>` or the dashboard `mm_session` cookie. Permission: `metrics_read`.

#### Query parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string[]` |  | Only events of these inboxes (repeat the parameter, or comma-separate). |
| `inbox_ids` | `string[]` |  | Same as inbox_id. |
| `pod_id` | `string[]` |  | Only events of these pods (repeat the parameter, or comma-separate). |
| `pod_ids` | `string[]` |  | Same as pod_id. |
| `event_types` | `("message.received" \| "message.received.spam" \| "message.received.quarantined" \| "message.received.unauthenticated" \| "message.received.blocked" \| "message.sent" \| "message.delivered" \| "message.bounced" \| "message.complained" \| "message.rejected" \| "domain.verified" \| "domain.updated" \| "inbox.created" \| "inbox.expired" \| "draft.sent" \| "webhook.test" \| "trusted_domain.created" \| "trusted_domain.updated" \| "trusted_domain.deleted" \| "message.released")[]` |  | Only these event types (repeat the parameter, or comma-separate). Omit for every type. |

#### Headers

| Field | Type | | Description |
| --- | --- | --- | --- |
| `Last-Event-Id` | `string` |  | Resume after this event (evt_...): missed events are sent first. |

#### Response

**200** The event stream (`text/event-stream`)

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid request: error.type is validation and error.param names the field.                                                                                                                                                             |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl "https://api.machinemail.dev/v0/events/stream" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
```

```text title="Response 200"
id: evt_01k6x3g4pt0000k6t2wa8e1n3q
data: {"event_id":"evt_01k6x3g4pt0000k6t2wa8e1n3q", ...}

```

## Get Event

```http
GET /v0/events/{event_id}
```

One event of the log. An event the credential may not read (see `GET /v0/events`) is a `404`, like one outside its scope.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `metrics_read`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `event_id` | `string` | required | ID of the event (evt_...). |

#### Response

**200** The event (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `event_id` | `string` | required | ID of the event (evt_...). Sorts by creation time. |
| `event_type` | `"message.received" \| "message.received.spam" \| "message.received.quarantined" \| "message.received.unauthenticated" \| "message.received.blocked" \| "message.sent" \| "message.delivered" \| "message.bounced" \| "message.complained" \| "message.rejected" \| "domain.verified" \| "domain.updated" \| "inbox.created" \| "inbox.expired" \| "draft.sent" \| "webhook.test" \| "trusted_domain.created" \| "trusted_domain.updated" \| "trusted_domain.deleted" \| "message.released" \| "registration.created" \| "token.issued" \| "agent.claimed" \| "agent.revoked" \| "member.invited" \| "member.role_changed" \| "member.removed"` | required | Type of event: an events_EventType, or an audit event for agent registrations and members (registration.created, token.issued, agent.claimed, agent.revoked, member.invited, member.role_changed, member.removed), which is logged but never delivered to webhooks or streams. |
| `organization_id` | `string` | required | ID of the organization. |
| `pod_id` | `string` |  | ID of the pod. Absent for organization-level events. |
| `inbox_id` | `string` |  | ID of the inbox. Absent for events without an inbox (for example domain.verified). |
| `data` | `Record<string, unknown>` | required | Event payload, as delivered to webhooks: message.* events (and message.released) carry message and thread, delivery events their send, delivery, bounce, complaint or reject object, inbox.* the inbox, domain.verified and domain.updated the domain, trusted_domain.* the trusted_domain. |
| `timestamp` | `string (date-time)` | required | When the event happened (same as created_at). |
| `created_at` | `string (date-time)` | required | When the event was stored. |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl "https://api.machinemail.dev/v0/events/evt_01k6x3g4pt0000k6t2wa8e1n3q" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
```

```json title="Response 200"
{
  "event_id": "evt_01k6x3g4pt0000k6t2wa8e1n3q",
  "event_type": "message.received",
  "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e",
  "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
  "inbox_id": "agent@machinemail.dev",
  "data": {
    "key": "value"
  },
  "timestamp": "2026-10-01T12:00:00.000Z",
  "created_at": "2026-10-01T12:00:00.000Z"
}
```

## List Events in Inbox

```http
GET /v0/inboxes/{inbox_id}/events
```

Same as List Events (`GET /v0/events`), limited to the inbox.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `metrics_read`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox. |

#### Query parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `limit` | `integer` |  | Maximum number of items to return. Default 50, maximum 500; larger values are capped. |
| `page_token` | `string` |  | next_page_token from the previous page. |
| `ascending` | `boolean` |  | Sort oldest first. Defaults to true when after is set. |
| `after` | `string` |  | An event ID (evt_...): only events stored after it, in commit order. Or an ISO 8601 time: only events created after it. Either one makes the default order oldest first. |
| `before` | `string (date-time)` |  | Only events created before this time. |
| `event_types` | `("message.received" \| "message.received.spam" \| "message.received.quarantined" \| "message.received.unauthenticated" \| "message.received.blocked" \| "message.sent" \| "message.delivered" \| "message.bounced" \| "message.complained" \| "message.rejected" \| "domain.verified" \| "domain.updated" \| "inbox.created" \| "inbox.expired" \| "draft.sent" \| "webhook.test" \| "trusted_domain.created" \| "trusted_domain.updated" \| "trusted_domain.deleted" \| "message.released")[]` |  | Only these event types (repeat the parameter, or comma-separate). Omit for every type. |
| `inbox_id` | `string` |  | Only events of this inbox. |
| `pod_id` | `string` |  | Only events of this pod. |

#### Response

**200** A page of events (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `count` | `integer` | required | Number of items returned. |
| `limit` | `integer` |  | Maximum number of items per page. Default 50, maximum 500; larger values are capped. |
| `next_page_token` | `string` |  | Page token for pagination. Absent on the last page. |
| `events` | `Event[]` | required | Newest first; oldest first when after is set or ascending is true. |

Each item of `events` is a [Event](https://machinemail.dev/docs/reference/api/objects#event):

| Field | Type | | Description |
| --- | --- | --- | --- |
| `event_id` | `string` | required | ID of the event (evt_...). Sorts by creation time. |
| `event_type` | `"message.received" \| "message.received.spam" \| "message.received.quarantined" \| "message.received.unauthenticated" \| "message.received.blocked" \| "message.sent" \| "message.delivered" \| "message.bounced" \| "message.complained" \| "message.rejected" \| "domain.verified" \| "domain.updated" \| "inbox.created" \| "inbox.expired" \| "draft.sent" \| "webhook.test" \| "trusted_domain.created" \| "trusted_domain.updated" \| "trusted_domain.deleted" \| "message.released" \| "registration.created" \| "token.issued" \| "agent.claimed" \| "agent.revoked" \| "member.invited" \| "member.role_changed" \| "member.removed"` | required | Type of event: an events_EventType, or an audit event for agent registrations and members (registration.created, token.issued, agent.claimed, agent.revoked, member.invited, member.role_changed, member.removed), which is logged but never delivered to webhooks or streams. |
| `organization_id` | `string` | required | ID of the organization. |
| `pod_id` | `string` |  | ID of the pod. Absent for organization-level events. |
| `inbox_id` | `string` |  | ID of the inbox. Absent for events without an inbox (for example domain.verified). |
| `data` | `Record<string, unknown>` | required | Event payload, as delivered to webhooks: message.* events (and message.released) carry message and thread, delivery events their send, delivery, bounce, complaint or reject object, inbox.* the inbox, domain.verified and domain.updated the domain, trusted_domain.* the trusted_domain. |
| `timestamp` | `string (date-time)` | required | When the event happened (same as created_at). |
| `created_at` | `string (date-time)` | required | When the event was stored. |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid request: error.type is validation and error.param names the field.                                                                                                                                                             |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl "https://api.machinemail.dev/v0/inboxes/agent@machinemail.dev/events" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
```

```json title="Response 200"
{
  "count": 1,
  "limit": 50,
  "next_page_token": "eyJvIjo1MH0",
  "events": [
    {
      "event_id": "evt_01k6x3g4pt0000k6t2wa8e1n3q",
      "event_type": "message.received",
      "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e",
      "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
      "inbox_id": "agent@machinemail.dev",
      "data": {
        "key": "value"
      },
      "timestamp": "2026-10-01T12:00:00.000Z",
      "created_at": "2026-10-01T12:00:00.000Z"
    }
  ]
}
```

## Get Event in Inbox

```http
GET /v0/inboxes/{inbox_id}/events/{event_id}
```

Same as Get Event (`GET /v0/events/{event_id}`), limited to the inbox.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `metrics_read`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox. |
| `event_id` | `string` | required | ID of the event (evt_...). |

#### Response

**200** The event (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `event_id` | `string` | required | ID of the event (evt_...). Sorts by creation time. |
| `event_type` | `"message.received" \| "message.received.spam" \| "message.received.quarantined" \| "message.received.unauthenticated" \| "message.received.blocked" \| "message.sent" \| "message.delivered" \| "message.bounced" \| "message.complained" \| "message.rejected" \| "domain.verified" \| "domain.updated" \| "inbox.created" \| "inbox.expired" \| "draft.sent" \| "webhook.test" \| "trusted_domain.created" \| "trusted_domain.updated" \| "trusted_domain.deleted" \| "message.released" \| "registration.created" \| "token.issued" \| "agent.claimed" \| "agent.revoked" \| "member.invited" \| "member.role_changed" \| "member.removed"` | required | Type of event: an events_EventType, or an audit event for agent registrations and members (registration.created, token.issued, agent.claimed, agent.revoked, member.invited, member.role_changed, member.removed), which is logged but never delivered to webhooks or streams. |
| `organization_id` | `string` | required | ID of the organization. |
| `pod_id` | `string` |  | ID of the pod. Absent for organization-level events. |
| `inbox_id` | `string` |  | ID of the inbox. Absent for events without an inbox (for example domain.verified). |
| `data` | `Record<string, unknown>` | required | Event payload, as delivered to webhooks: message.* events (and message.released) carry message and thread, delivery events their send, delivery, bounce, complaint or reject object, inbox.* the inbox, domain.verified and domain.updated the domain, trusted_domain.* the trusted_domain. |
| `timestamp` | `string (date-time)` | required | When the event happened (same as created_at). |
| `created_at` | `string (date-time)` | required | When the event was stored. |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl "https://api.machinemail.dev/v0/inboxes/agent@machinemail.dev/events/evt_01k6x3g4pt0000k6t2wa8e1n3q" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
```

```json title="Response 200"
{
  "event_id": "evt_01k6x3g4pt0000k6t2wa8e1n3q",
  "event_type": "message.received",
  "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e",
  "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
  "inbox_id": "agent@machinemail.dev",
  "data": {
    "key": "value"
  },
  "timestamp": "2026-10-01T12:00:00.000Z",
  "created_at": "2026-10-01T12:00:00.000Z"
}
```

## List Events in Pod

```http
GET /v0/pods/{pod_id}/events
```

Same as List Events (`GET /v0/events`), limited to the pod.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `metrics_read`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `pod_id` | `string` | required | ID of the pod. |

#### Query parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `limit` | `integer` |  | Maximum number of items to return. Default 50, maximum 500; larger values are capped. |
| `page_token` | `string` |  | next_page_token from the previous page. |
| `ascending` | `boolean` |  | Sort oldest first. Defaults to true when after is set. |
| `after` | `string` |  | An event ID (evt_...): only events stored after it, in commit order. Or an ISO 8601 time: only events created after it. Either one makes the default order oldest first. |
| `before` | `string (date-time)` |  | Only events created before this time. |
| `event_types` | `("message.received" \| "message.received.spam" \| "message.received.quarantined" \| "message.received.unauthenticated" \| "message.received.blocked" \| "message.sent" \| "message.delivered" \| "message.bounced" \| "message.complained" \| "message.rejected" \| "domain.verified" \| "domain.updated" \| "inbox.created" \| "inbox.expired" \| "draft.sent" \| "webhook.test" \| "trusted_domain.created" \| "trusted_domain.updated" \| "trusted_domain.deleted" \| "message.released")[]` |  | Only these event types (repeat the parameter, or comma-separate). Omit for every type. |
| `inbox_id` | `string` |  | Only events of this inbox. |
| `pod_id` | `string` |  | Only events of this pod. |

#### Response

**200** A page of events (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `count` | `integer` | required | Number of items returned. |
| `limit` | `integer` |  | Maximum number of items per page. Default 50, maximum 500; larger values are capped. |
| `next_page_token` | `string` |  | Page token for pagination. Absent on the last page. |
| `events` | `Event[]` | required | Newest first; oldest first when after is set or ascending is true. |

Each item of `events` is a [Event](https://machinemail.dev/docs/reference/api/objects#event):

| Field | Type | | Description |
| --- | --- | --- | --- |
| `event_id` | `string` | required | ID of the event (evt_...). Sorts by creation time. |
| `event_type` | `"message.received" \| "message.received.spam" \| "message.received.quarantined" \| "message.received.unauthenticated" \| "message.received.blocked" \| "message.sent" \| "message.delivered" \| "message.bounced" \| "message.complained" \| "message.rejected" \| "domain.verified" \| "domain.updated" \| "inbox.created" \| "inbox.expired" \| "draft.sent" \| "webhook.test" \| "trusted_domain.created" \| "trusted_domain.updated" \| "trusted_domain.deleted" \| "message.released" \| "registration.created" \| "token.issued" \| "agent.claimed" \| "agent.revoked" \| "member.invited" \| "member.role_changed" \| "member.removed"` | required | Type of event: an events_EventType, or an audit event for agent registrations and members (registration.created, token.issued, agent.claimed, agent.revoked, member.invited, member.role_changed, member.removed), which is logged but never delivered to webhooks or streams. |
| `organization_id` | `string` | required | ID of the organization. |
| `pod_id` | `string` |  | ID of the pod. Absent for organization-level events. |
| `inbox_id` | `string` |  | ID of the inbox. Absent for events without an inbox (for example domain.verified). |
| `data` | `Record<string, unknown>` | required | Event payload, as delivered to webhooks: message.* events (and message.released) carry message and thread, delivery events their send, delivery, bounce, complaint or reject object, inbox.* the inbox, domain.verified and domain.updated the domain, trusted_domain.* the trusted_domain. |
| `timestamp` | `string (date-time)` | required | When the event happened (same as created_at). |
| `created_at` | `string (date-time)` | required | When the event was stored. |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid request: error.type is validation and error.param names the field.                                                                                                                                                             |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl "https://api.machinemail.dev/v0/pods/pod_01k6x2m9qd0001b8j4mr0w3d5f/events" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
```

```json title="Response 200"
{
  "count": 1,
  "limit": 50,
  "next_page_token": "eyJvIjo1MH0",
  "events": [
    {
      "event_id": "evt_01k6x3g4pt0000k6t2wa8e1n3q",
      "event_type": "message.received",
      "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e",
      "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
      "inbox_id": "agent@machinemail.dev",
      "data": {
        "key": "value"
      },
      "timestamp": "2026-10-01T12:00:00.000Z",
      "created_at": "2026-10-01T12:00:00.000Z"
    }
  ]
}
```

## Get Event in Pod

```http
GET /v0/pods/{pod_id}/events/{event_id}
```

Same as Get Event (`GET /v0/events/{event_id}`), limited to the pod.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `metrics_read`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `pod_id` | `string` | required | ID of the pod. |
| `event_id` | `string` | required | ID of the event (evt_...). |

#### Response

**200** The event (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `event_id` | `string` | required | ID of the event (evt_...). Sorts by creation time. |
| `event_type` | `"message.received" \| "message.received.spam" \| "message.received.quarantined" \| "message.received.unauthenticated" \| "message.received.blocked" \| "message.sent" \| "message.delivered" \| "message.bounced" \| "message.complained" \| "message.rejected" \| "domain.verified" \| "domain.updated" \| "inbox.created" \| "inbox.expired" \| "draft.sent" \| "webhook.test" \| "trusted_domain.created" \| "trusted_domain.updated" \| "trusted_domain.deleted" \| "message.released" \| "registration.created" \| "token.issued" \| "agent.claimed" \| "agent.revoked" \| "member.invited" \| "member.role_changed" \| "member.removed"` | required | Type of event: an events_EventType, or an audit event for agent registrations and members (registration.created, token.issued, agent.claimed, agent.revoked, member.invited, member.role_changed, member.removed), which is logged but never delivered to webhooks or streams. |
| `organization_id` | `string` | required | ID of the organization. |
| `pod_id` | `string` |  | ID of the pod. Absent for organization-level events. |
| `inbox_id` | `string` |  | ID of the inbox. Absent for events without an inbox (for example domain.verified). |
| `data` | `Record<string, unknown>` | required | Event payload, as delivered to webhooks: message.* events (and message.released) carry message and thread, delivery events their send, delivery, bounce, complaint or reject object, inbox.* the inbox, domain.verified and domain.updated the domain, trusted_domain.* the trusted_domain. |
| `timestamp` | `string (date-time)` | required | When the event happened (same as created_at). |
| `created_at` | `string (date-time)` | required | When the event was stored. |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl "https://api.machinemail.dev/v0/pods/pod_01k6x2m9qd0001b8j4mr0w3d5f/events/evt_01k6x3g4pt0000k6t2wa8e1n3q" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
```

```json title="Response 200"
{
  "event_id": "evt_01k6x3g4pt0000k6t2wa8e1n3q",
  "event_type": "message.received",
  "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e",
  "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
  "inbox_id": "agent@machinemail.dev",
  "data": {
    "key": "value"
  },
  "timestamp": "2026-10-01T12:00:00.000Z",
  "created_at": "2026-10-01T12:00:00.000Z"
}
```

## WebSocket Event Stream

```http
GET /v0/ws
```

Upgrades to a WebSocket (send `Connection: Upgrade` and `Upgrade: websocket`). The socket starts subscribed to every event in the credential scope; query parameters are not read.

Client messages are JSON: `{"type":"subscribe","inbox_ids":[...],"pod_ids":[...],"event_types":[...]}` replaces the subscription (the server answers `{"type":"subscribed","inbox_ids","pod_ids","event_types"}`), `{"type":"ping"}` is answered with `{"type":"pong"}`, anything else with `{"type":"error","message"}`. Event messages are the webhook event envelope (`type: "event"`, `event_id`, `event_type`, `timestamp`, `data`, and the `data` keys repeated at the top level), not the `Event` object of `GET /v0/events`.

Each event is sent only when the credential holds the read permission of what it describes: `message.*` needs `message_read`, `domain.*` needs `domain_read`, `inbox.*` needs `inbox_read`, `draft.*` needs `draft_read`, `webhook.*` needs `webhook_read`, `trusted_domain.*` needs `trusted_domain_read`. Other events are skipped, without an error, and `event_types` cannot add them. The credential is re-checked every 30 seconds: when it is revoked or expired, or its permissions or pod/inbox scope change, the stream ends with an error (reconnect to get the events the credential allows now).

Opening the socket needs at least one of those permissions; a credential with none is refused with `403`. A request without the upgrade headers is a `404`.

The server closes the socket with code `1012` when it may have missed events from other servers (its notification connection to the database was interrupted): reconnect and refetch, since the WebSocket has no replay.

**Authentication:** `Authorization: Bearer <api key>` or `?token=<api key>` or the dashboard `mm_session` cookie. Permission: `metrics_read` or `message_read`.

#### Response

**101** Switching Protocols: the connection is now a WebSocket.

#### Errors

| Status | Meaning                                                                                 |
| ------ | --------------------------------------------------------------------------------------- |
| `400`  | The WebSocket handshake failed (plain text WebSocket upgrade failed).                   |
| `401`  | Missing, invalid, expired or revoked credential.                                        |
| `403`  | The credential holds no permission the stream serves events for (plain text Forbidden). |
| `404`  | Not found. Also returned for resources outside the credential scope.                    |
| `500`  | Unexpected server error.                                                                |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl "https://api.machinemail.dev/v0/ws" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
```
