> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.runtype.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.runtype.com/_mcp/server.

# Watch product events

> Poll a cursor-ordered log of failed runs, approval requests, eval results, and other product events through the REST API, MCP, or the CLI.

Product events report state changes in your Runtype organization that someone might need to act on, such as a failed run or an Agent that waits for approval. You read them by polling `GET /v1/events` with a cursor. A script, a coding agent, or a terminal monitor can react to them without a public webhook endpoint.

## Event types

Runtype records the following event types. Each one accepts its own set of filters:

| Event                                 | Recorded when                                                                                                                           | Filters                                        |
| ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
| `runtype.execution.failed`            | An Agent or flow run fails or is interrupted. `errorClass` is `interrupted` for an interrupted run and the run's stop reason otherwise. | `agentId`, `flowId`, `minIntervalMs`           |
| `runtype.approval.requested`          | An Agent pauses for a person to approve a tool call.                                                                                    | `agentId`, `productId`                         |
| `runtype.schedule.run_failed`         | A scheduled run of a flow or Agent fails.                                                                                               | `scheduleId`, `flowId`, `agentId`, `productId` |
| `runtype.eval.run_completed`          | An eval suite run ends with the status `completed` or `failed`.                                                                         | `suiteId`, `onlyRegressions`                   |
| `runtype.conversation.human_takeover` | A person takes over a messaging conversation from the Agent.                                                                            | `surfaceId`, `productId`                       |
| `runtype.skill.proposal_created`      | A deployed Agent proposes a Skill that waits for review.                                                                                | `agentId`                                      |
| `runtype.task.input_required`         | A delegated task stops to ask a question. `question` holds the question.                                                                | `taskId`                                       |
| `runtype.task.completed`              | A delegated task finishes. `summary` holds a short digest, not the full result.                                                         | `taskId`                                       |
| `runtype.task.failed`                 | A delegated task fails.                                                                                                                 | `taskId`                                       |
| `runtype.task.cancelled`              | A delegated task is cancelled before it finishes.                                                                                       | `taskId`                                       |

Each event has an `eventId`, a `name`, a `timestamp`, a `cursor`, and a `data` object. The `data` object holds resource IDs and short summaries, not full content. Error messages have secrets redacted and are truncated to 500 characters.

A failed run's `data` carries `runType`, which is `agent` or `flow`.

Delegated task events let an MCP host or script wait for a task that it started with `delegate_to_runtype` without holding a `check_delegation` call open. Treat each one as a wake-up signal: the event carries the `taskId`, the task page as `taskUrl`, and the question, summary, or error truncated as described above. To read the full result, call `check_delegation` or `GET /v1/delegate-tasks/{taskId}`. An API key needs `DELEGATE_TASKS:READ` or `DELEGATE_TASKS:WRITE` to read these events.

A completed eval run's `data` carries `passRate` and `previousPassRate`, both from 0 to 1. Its `regressed` field is `true` when `passRate` is lower than the pass rate of the suite's previous completed run.

## How the event log works

Runtype keeps one event log for each organization. Every event gets a cursor: a decimal string that increases with each event in the organization. You pass the last cursor that you received back to the API, and the API returns the events after it in order. If you store the cursor, a restarted poller resumes where it stopped and receives each event once.

The log keeps events for 7 days. Product events require an organization workspace. A request from an account without one returns `403`.

## Before you begin

To call the endpoints, you need a Runtype API key. For steps to create one, see [Authentication](/developer-guides/getting-started/authentication).

An API key reads only the event types that its scopes cover. Each type lists these scopes in `requiredScopes`. For `runtype.execution.failed`, a key with `AGENTS:READ` gets failed Agent runs, and a key with `FLOWS:READ` gets failed flow runs. A key with both scopes gets both.

## Discover event types

To list the event types with their schemas, send a request to `GET /v1/events/types`:

**`cURL`**

```bash title="cURL"
curl https://api.runtype.com/v1/events/types \
  -H "Authorization: Bearer YOUR_API_KEY"
```

Replace `YOUR_API_KEY` with your Runtype API key. Each entry in `events` has a `name`, a `description`, a `delivery` array (`["poll"]`), an `inputSchema`, and a `payloadSchema`. The `inputSchema` is the JSON Schema of the filters that the event accepts. The `payloadSchema` is the JSON Schema of its `data`.

## Poll for events

To follow failed runs of one Agent, follow these steps:

1. Get the current cursor. Omit `after`, so the read starts from now:

   **`cURL`**

   ```bash title="cURL"
   curl "https://api.runtype.com/v1/events?name=runtype.execution.failed&agentId=YOUR_AGENT_ID" \
     -H "Authorization: Bearer YOUR_API_KEY"
   ```

   Replace `YOUR_AGENT_ID` with the ID of the Agent. The response returns no backlog, only the cursor for the current position:

   ```json
   { "events": [], "cursor": "1042", "truncated": false, "hasMore": false, "nextPollMs": 5000 }
   ```

2. Pass the cursor as `after`, and set `waitMs=50000` to hold the request open:

   **`cURL`**

   ```bash title="cURL"
   curl "https://api.runtype.com/v1/events?name=runtype.execution.failed&agentId=YOUR_AGENT_ID&after=1042&waitMs=50000" \
     -H "Authorization: Bearer YOUR_API_KEY"
   ```

   The request returns when a matching event exists. If none arrives within 50 seconds, it returns an empty `events` array.

3. Store the `cursor` from the response. The cursor moves past events that your filters exclude, so store it even when `events` is empty.

4. If `hasMore` is `true`, send the next request right away. Otherwise, wait `nextPollMs` milliseconds before the next request.

The response contains the following fields:

* `events`: the matching events after `after`, oldest first.
* `cursor`: the value to pass as `after` on the next request.
* `truncated`: `true` when `after` is older than the 7-day retention window or `maxAgeMs`, so the log skipped events.
* `hasMore`: `true` when more events matched than `maxEvents` allows.
* `nextPollMs`: the suggested wait before the next request, 5000 by default.

The following TypeScript loop runs the same steps with `fetch`:

**`TypeScript`**

```typescript title="TypeScript"
const headers = { Authorization: `Bearer ${process.env.RUNTYPE_API_KEY}` }
let cursor: string | undefined

while (true) {
  const params = new URLSearchParams({ name: 'runtype.execution.failed' })
  if (cursor) {
    params.set('after', cursor)
    params.set('waitMs', '50000')
  }
  const res = await fetch(`https://api.runtype.com/v1/events?${params}`, { headers })
  if (!res.ok) throw new Error(`Event poll failed with ${res.status}`)
  const page = await res.json()
  if (page.truncated) console.warn('Events older than the retention window were skipped')
  for (const event of page.events) console.log(event.name, event.data)
  cursor = page.cursor ?? cursor
  if (!page.hasMore) await new Promise((r) => setTimeout(r, page.nextPollMs))
}
```

To resume after a restart, persist `cursor` and load it before the loop starts.

## Query parameters

`GET /v1/events` accepts the following query parameters:

| Parameter   | Default    | Description                                                                                                               |
| ----------- | ---------- | ------------------------------------------------------------------------------------------------------------------------- |
| `name`      | Every type | One event type. Omit it to read every type.                                                                               |
| `after`     | Now        | The cursor to read after. Omit it to get the current cursor and no events.                                                |
| `waitMs`    | `0`        | Holds the request open until a matching event arrives. The maximum is 50000.                                              |
| `maxEvents` | `50`       | The page size, from 1 to 200.                                                                                             |
| `maxAgeMs`  | Not set    | Skips events older than this many milliseconds. The maximum is 7 days.                                                    |
| Filters     | Not set    | `agentId`, `flowId`, `productId`, `surfaceId`, `scheduleId`, `suiteId`, `taskId`, `onlyRegressions`, and `minIntervalMs`. |

## Filter events

Each event type accepts only the filters in the [event types](#event-types) table. A filter that the type doesn't accept returns `INVALID_EVENT_ARGUMENTS`. When you omit `name`, the request accepts every filter.

Two filters change which events of a type you get, rather than matching an ID:

* `onlyRegressions=true` keeps only eval runs where `regressed` is `true`. If you omit `name`, events of other types still pass through.
* `minIntervalMs` applies to execution failures only. For each Agent or flow and `errorClass`, it keeps the first failure and drops repeats within that many milliseconds. It compares events within one response only, so a repeat in a later poll still arrives. The maximum is 86400000 (24 hours).

## Handle errors

The endpoints return the following errors:

| Status | Code                      | Cause                                                                    |
| ------ | ------------------------- | ------------------------------------------------------------------------ |
| `400`  | `EVENT_NOT_FOUND`         | `name` isn't one of the event types.                                     |
| `400`  | `INVALID_EVENT_ARGUMENTS` | A filter isn't accepted by the event type, or a filter value is invalid. |
| `400`  | `INVALID_EVENT_CURSOR`    | `after` isn't a valid cursor.                                            |
| `403`  | None                      | The account has no organization workspace.                               |

A `truncated` response isn't an error. It means that your cursor was older than the oldest event in the log, and you missed the events in between. To find the runs that you missed, use [List runs](/api-reference/runtype-api/runs/list-runs).

## Watch events from MCP and the CLI

The same event log is available outside the REST API:

* The Runtype MCP server offers the `list_event_types`, `poll_events`, and `wait_for_event` tools. It also answers the draft MCP Events methods. For details, see [Watch product events from MCP](/developer-guides/integrations/runtype-mcp-server#watch-product-events).
* The `runtype events watch` CLI command long-polls the log and prints one line per event. With `--json`, it prints one JSON object per line. For details, see [Watch product events from the CLI](/developer-guides/getting-started/cli-reference#watch-product-events).

## Next steps

* [Poll product events](/api-reference/runtype-api/events/poll-product-events): review the endpoint's full request and response schema
* [Running an eval](/user-guide/evals/running-an-eval): start the eval runs that `runtype.eval.run_completed` reports
* [Improving your agents from production traces](/developer-guides/guides/improving-agents-from-production): turn the failures that you find into eval cases