Workspace Events
A unified, cross-project activity feed for your workspace: builds, changes, tasks, approvals, and deployments across every project in your account, in one newest-first stream.
Table of contents
Plan requirement
Multi-project observability is available on the Team plan and up (Team, Studio, Enterprise). Requests from accounts on other plans receive 403 Forbidden:
{
"error": "Multi-project observability requires a Team plan or higher. If you belong to a Team workspace, pass ?account_id=<account id> — this endpoint defaults to your personal account.",
"upgrade_url": "https://www.butterstack.com/pricing"
}
Note the account_id hint: API tokens belong to your user, and without account_id the endpoint resolves to your default (usually personal) account. If your Team-plan workspace is a separate account, pass its id explicitly.
Endpoint
GET /api/v1/workspace/events
Authenticate with an API token via the Authorization header - see Authentication.
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
"https://www.butterstack.com/api/v1/workspace/events?limit=20"
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | integer | 20 | Events per page. Clamped to 1-50. |
days_back | integer | 7 | How far back to look. Clamped to 1-90. Ignored when since is present. |
since | string | - | ISO 8601 timestamp (must include a time component - a date alone like 2026-08-16 returns 422). Bounds events from that moment forward and supersedes days_back. A timestamp with no UTC offset is interpreted in the account’s app timezone (Eastern), not UTC - so a bare +05:00 offset must be percent-encoded as %2B05:00, or HTTP decodes the + as a space and the request 422s. Silently clamped to the last 90 days; values further back (or absurd years, positive or negative) still return 200 rather than erroring. |
event_type / event_types | string, comma-separated string, or array of strings | - | Filter to specific event types (see Event types). Supports wildcards like approval.* and build_run.*; unknown types and wildcards that don’t match a known prefix both return 422 (never silently fall back to the unfiltered feed). Case-sensitive. Pass at most one of event_type / event_types - both present is 422. |
project_id | integer or numeric string | - | Filter to one project in the account. Nonexistent ids and ids belonging to another account both return 404 (no existence leak). Only numeric ids are accepted; proj_…-prefixed ids are not supported (Project has no prefix id) and also return 404. |
cursor | string | - | Opaque pagination cursor. Pass back pagination.next_cursor verbatim; malformed cursors return 400. |
account_id | string | your default account | Which of your accounts to read (acct_… prefix id or numeric id). Must be an account you belong to: anything else returns 404. |
Every parameter above must be a plain string value, except event_type/event_types which may also be an array of strings (event_types[]=a&event_types[]=b). Sending any other parameter as an array or a nested hash (limit[]=5, project_id[]=1) returns 422.
Response
{
"events": [
{
"event_type": "build_run.failed",
"project_id": 42,
"project_name": "Thrive",
"actor": "Ada Lovelace",
"timestamp": "2026-07-01T14:03:22.000Z",
"target_ref": "812",
"status": "err",
"message": "Build #812 failed",
"url": "/projects/42/build_runs/…",
"cursor_key": "2026-07-01T14:03:22.000000Z|build|9137"
}
],
"rollup": {
"changes_24h": 12,
"builds_passed_24h": 8,
"builds_failed_24h": 1,
"open_tasks": 23,
"total_projects": 3,
"active_projects": 2
},
"pagination": {
"limit": 20,
"has_more": true,
"next_cursor": "2026-07-01T09:41:07.512340Z|task|58821"
}
}
status is a pill kind for rendering: ok, err, info, warn, or neutral.
Event types
event_type | Source | Notes |
|---|---|---|
build_run.completed | Build runs | Includes builds that passed pending approval - a consumer filtering on “completed” will see builds that are not yet fully approved. |
build_run.failed | Build runs | |
change.submitted | Asset events | Asset created/updated by a connected tool (Perforce, Jira, …). |
task.state_changed | Tasks | Approximation: emitted from the task’s last update time, so any edit to a task (title, assignee) within the window re-surfaces it stamped with the edit time, not only genuine state transitions. |
approval.requested | Asset approvals | Pending approvals. |
approval.granted | Asset approvals | |
approval.denied | Asset approvals | |
approval.ignored | Asset approvals | Approval dismissed without a decision. |
deployment.created | Deployments | Steam, Epic Games Store, app stores, custom. |
Pagination
Cursor-based and stable under continuous insertion:
- Request page 1 (no
cursor). The response includesrollupand, when more events exist,pagination.next_cursor. - Request the next page with
?cursor=<next_cursor>. Cursor pages return"rollup": null, and so does any filtered request (since,event_type(s), orproject_id) even on its first page - the rule is “unfiltered only”, not “first page only”.rollupis always present as a key; it’snull, not omitted, on every filtered or cursor page. - Stop when
has_moreisfalse(next_cursorwill benull).
Treat cursors as opaque strings; their format may change.
Errors
| Status | Meaning |
|---|---|
400 Bad Request | Malformed cursor. |
401 Unauthorized | Missing or invalid API token. |
403 Forbidden | The account’s plan does not include multi-project observability (see Plan requirement). |
404 Not Found | account_id is not an account you belong to, or project_id does not resolve to a project in the target account. |
422 Unprocessable Entity | One of four codes: unknown_parameter (a query param outside the allowed set), invalid_parameter_type (a normally-scalar param arrived as an array or nested hash, e.g. limit[]=5), invalid_parameter (a present param’s value didn’t parse, e.g. a malformed since, or both event_type and event_types given together), or invalid_event_type (an unknown event type, or a wildcard that doesn’t expand to any known type). All four bodies include error and message; the event-type codes also include valid_event_types. |