Skip to navigation

Watch product events

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:

EventRecorded whenFilters
runtype.execution.failedAn 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.requestedAn Agent pauses for a person to approve a tool call.agentId, productId
runtype.schedule.run_failedA scheduled run of a flow or Agent fails.scheduleId, flowId, agentId, productId
runtype.eval.run_completedAn eval suite run ends with the status completed or failed.suiteId, onlyRegressions
runtype.conversation.human_takeoverA person takes over a messaging conversation from the Agent.surfaceId, productId
runtype.skill.proposal_createdA deployed Agent proposes a Skill that waits for review.agentId
runtype.task.input_requiredA delegated task stops to ask a question. question holds the question.taskId
runtype.task.completedA delegated task finishes. summary holds a short digest, not the full result.taskId
runtype.task.failedA delegated task fails.taskId
runtype.task.cancelledA 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.

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
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
    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:

    { "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
    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
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:

ParameterDefaultDescription
nameEvery typeOne event type. Omit it to read every type.
afterNowThe cursor to read after. Omit it to get the current cursor and no events.
waitMs0Holds the request open until a matching event arrives. The maximum is 50000.
maxEvents50The page size, from 1 to 200.
maxAgeMsNot setSkips events older than this many milliseconds. The maximum is 7 days.
FiltersNot setagentId, flowId, productId, surfaceId, scheduleId, suiteId, taskId, onlyRegressions, and minIntervalMs.

Filter events

Each event type accepts only the filters in the 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:

StatusCodeCause
400EVENT_NOT_FOUNDname isn’t one of the event types.
400INVALID_EVENT_ARGUMENTSA filter isn’t accepted by the event type, or a filter value is invalid.
400INVALID_EVENT_CURSORafter isn’t a valid cursor.
403NoneThe 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.

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.
  • 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.

Next steps