Analytics
world.analytics is an optional, read-only namespace for observability surfaces: dashboards, command-line interface (CLI) tools, and admin tools that list large numbers of runs without touching payload data.
Prefer this namespace for observability: listing, filtering, and inspecting workflow state. Use the Storage API for payload-bearing reads, and for anything operational that has to see the canonical, up-to-the-moment record.
It differs from Storage in two ways:
- Metadata only: Results never include run input/output, step data, or hook tokens. There is no
resolveDataoption. - Served from the observability pipeline: On Vercel, queries are served from the Vercel observability data pipeline, so large listings do not compete with workflow execution. Data is ingested asynchronously and may trail the live state by a few seconds.
The namespace is optional: worlds that don't implement it (such as the local development world) leave it undefined, so feature-detect before use:
analytics.runs
runs.list()
List runs with metadata, current status, and attributes. Without an explicit time window, the listing defaults to the trailing 24 hours; pass startTime/endTime to reach older runs within your plan window.
| Parameter | Type | Description |
|---|---|---|
params.workflowName | string | Filter to one workflow |
params.status | string | pending, running, completed, failed, or cancelled |
params.startTime / params.endTime | string | ISO 8601 window; must be provided together |
params.attributes | Record<string, string> | Only return runs whose latest attributes match every pair (up to 8) |
params.pagination | PaginationOptions | Cursor pagination |
Returns: PaginatedResponse<AnalyticsRun>. Each run includes runId, status, workflowName, deploymentId, attributes, and lifecycle timestamps.
Attribute matching is latest-write-wins: a run whose attribute moved from "v1" to "v2" no longer matches { key: "v1" }. Reserved $-prefixed keys may be used in filters even though user code cannot write them.
runs.get()
Fetch one run by ID. Point lookups search the full plan window rather than only the trailing 24 hours.
analytics.attributes
Discover which attributes exist on your runs, for example to build filter dropdowns over arbitrary user-defined keys.
attributes.list()
List the distinct attribute keys observed on runs in the window, ordered alphabetically.
| Parameter | Type | Description |
|---|---|---|
params.workflowName | string | Only count runs of one workflow |
params.startTime / params.endTime | string | ISO 8601 window; must be provided together |
params.pagination | PaginationOptions | Cursor pagination |
Returns: PaginatedResponse<AnalyticsAttributeKey>: { key, runCount, firstSeenAt, lastSeenAt }
analytics.steps
Run-scoped step listings mirroring their Storage counterparts, minus payload data.
steps.list()
| Parameter | Type | Description |
|---|---|---|
params.runId | string | Required. The run to list steps for |
params.pagination | PaginationOptions | Cursor pagination, limit up to 1000 |
Returns: PaginatedResponse<AnalyticsStep>. Each step includes stepId, stepName, status, attempt, lifecycle timestamps, errorCode, and the computeInstanceId of the latest attempt.
steps.get()
Returns: AnalyticsStep. A step id is only unique within its run, so both arguments are required.
analytics.events
events.list()
| Parameter | Type | Description |
|---|---|---|
params.runId | string | Required. The run to list events for |
params.eventType | string | One event type, for example run_failed or step_retrying |
params.correlationId | string | Narrow to one entity: a step, hook, wait, or attribute id |
params.pagination | PaginationOptions | Cursor pagination, limit up to 1000 |
Returns: PaginatedResponse<AnalyticsEvent>. Each event includes eventId, eventType, correlationId, stepName, createdAt, and provenance fields (region, requestId, computeInstanceId).
Pass a step id as correlationId to build that step's timeline: step_created through step_completed, step_failed, or step_retrying.
events.get()
Returns: AnalyticsEvent.
events.getMany()
Look up a bounded set of event ids in one run with a single request.
Returns: AnalyticsEvent[] — not paginated, and no pageInfo. Duplicate ids are looked up once, and ids with no analytics row yet are omitted rather than erroring, since ingestion can trail canonical storage. Compare the returned length against your input to detect that.
analytics.hooks
hooks.list()
| Parameter | Type | Description |
|---|---|---|
params.runId | string | Required. The run to list hooks for |
params.pagination | PaginationOptions | Cursor pagination, limit up to 100 |
Returns: PaginatedResponse<AnalyticsHook>: hookId, status (created, received, disposed, or conflict), receivedAt, disposedAt, isWebhook, isSystem.
hooks.get()
Unlike steps and waits, a hook id identifies one hook on its own, so no runId is needed. Pass { runId } to scope the lookup when you already know it.
Hook listings never include the hook token. Resolve it separately through the runtime APIs if you need to deliver a payload.
analytics.waits
waits.list()
| Parameter | Type | Description |
|---|---|---|
params.runId | string | Required. The run to list waits for |
params.status | string | waiting or completed |
params.pagination | PaginationOptions | Cursor pagination, limit up to 1000 |
Returns: PaginatedResponse<AnalyticsWait>: waitId, status, resumeAt, completedAt.
waits.get()
Returns: AnalyticsWait. A wait id is only unique within its run, so both arguments are required.
Limits and validation
Arguments are validated in your process before a request goes out. An
out-of-range or malformed argument throws a RangeError naming the bound it
broke, rather than reaching the backend and coming back as a 400 — which
matters because analytics is optional and callers commonly wrap it in a
try/catch, where a rejected request is easy to mistake for "no data".
Page limits
pagination.limit defaults to 40 everywhere. The maximum depends on whether
the listing scans within one run or across runs:
| Method | Max limit |
|---|---|
steps.list(), events.list(), waits.list() | 1000 |
runs.list(), attributes.list(), hooks.list() | 100 |
events.getMany() is not paginated; it accepts 1 to 100 event ids per call.
The two page caps differ by a factor of ten, and hooks.list() takes the
lower one despite being run-scoped. Reusing one page size across listings is
the most common way to trip this.
Identifiers
Every id is a prefix plus a ULID, and each is checked before the request:
| Parameter | Shape |
|---|---|
runId | wrun_ |
stepId | step_ |
eventId | evnt_ |
hookId | hook_ |
waitId | wait_ |
correlationId | step_, hook_, wait_, or attr_ |
Step, event, and wait ids are only unique within their run, so the methods that take them require a runId too. A hook id stands alone.
Time windows
startTime and endTime must be supplied together and be parseable ISO 8601 timestamps with startTime no later than endTime. Passing one without the other throws: it used to be dropped silently, which turned a listing you meant to bound into a scan of the whole retention window that looked like a successful answer.
Attribute filters
runs.list({ attributes }) accepts 1 to 8 pairs. Keys are 1 to 256 characters; values are at most 256 UTF-8 bytes. Reserved $-prefixed keys are valid in a filter even though user code cannot write them.
Pagination
cursor is an opaque token from the previous response; do not construct or parse one. Branch on hasMore, not on cursor being non-null, and do not change sortOrder mid-walk — the cursor encodes the sort position, so reversing it can skip or repeat rows.
Lookback windows and pageInfo
Every paginated response carries pageInfo describing the window the query was allowed to scan:
Requests for a window older than currentWindowStart fail with an observability-upgrade-required error; windows older than maxWindowStart return not-found. Use pageInfo to size date pickers and to decide whether to surface an upgrade prompt.