Events
An event is one row in the runtime's project_events table — the append-only audit trail of a pipeline project advancing: a project starting, a task going stale, a gate opening and being resolved, an artifact being promoted or reverted, a subtask retrying after a crash.
Two things about this resource are worth stating up front, because both are easy to assume wrong:
This event feed is pull-only, and it is a project timeline — rows are emitted by the pipeline runner, so every event hangs off a
project_id. It is not a generic workspace event bus, and there is no replay endpoint.If you want to be notified when a run or project finishes rather than polling, use Webhooks (outbound) — Catentio POSTs to a URL you register, HMAC-signed and retried. Those are a separate, smaller set of events (
run.succeeded,run.failed,project.completed) and are not this feed.
When to use this resource
- Reconstruct what a project did. The full ordered history of a run of the pipeline: which tasks ran, what got retried and why, which gate blocked and who resolved it.
- Watch for gates. Poll for
category=gateto find the projects waiting on a human or an external callback. - Catch failures.
level=erroracross the last N hours is the cheapest "what's broken" query. - Power the dashboard timeline. Dashboard → Events reads this endpoint directly.
The event object
{
"id": 84213,
"ts": "2026-05-12T10:42:34.812Z",
"project_id": "spring-lookbook-a1b2c3d4",
"task_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"subtask_id": "b1e2c3d4-5f6a-4b8c-9d0e-1f2a3b4c5d6e",
"attempt_number": 2,
"level": "warn",
"category": "retry",
"topic": "subtask_retry_queued",
"actor_kind": "system",
"actor_id": null,
"payload_jsonb": { "reason": "test_failure", "attempt": 2 },
"trace_id": "3f8a1b2c-4d5e-6f70-8192-a3b4c5d6e7f8"
}
| Field | Type | Notes |
|---|---|---|
id |
integer | Auto-incrementing row id. It is a number, not a prefixed string. |
ts |
timestamp | ISO 8601 UTC. When the event was emitted. |
project_id |
string | The owning project. Always set — these are project events. |
task_id |
UUID | null | Set when the event is scoped to a task. |
subtask_id |
UUID | null | Set when the event is scoped to a subtask. |
attempt_number |
integer | null | Which attempt of the subtask this refers to. |
level |
enum | debug, info, warn, error. |
category |
enum | Coarse bucket — see below. Filterable. |
topic |
string | The specific thing that happened, e.g. gate_awaiting. Underscored, not dotted. |
actor_kind |
string | Who emitted it: system, agent, action, human, external. |
actor_id |
string | null | Identifier for that actor (e.g. an agent slug). |
payload_jsonb |
object | Topic-specific detail. Shape varies by topic. |
trace_id |
UUID | null | Correlates events emitted within one unit of work. |
There is no workspace_id, no subject envelope, and no data wrapper on an individual event — the fields above are the whole row. (data does appear as the list envelope; see below.)
Categories
category is the coarse bucket, and it's what you filter on:
| Category | Covers |
|---|---|
project |
Project lifecycle. |
task |
Task lifecycle. |
subtask |
Subtask execution. |
review |
Reviewer feedback. |
gate |
Gates blocking and unblocking. |
artifact |
Artifact promotion / reversion. |
retry |
Retries and phase retriggers. |
cost |
Budget warnings and breaches. |
crash |
Crash detection and recovery. |
Levels
debug, info, warn, error.
Notable topics
topic is the specific thing that happened. It is a lowercase underscored string. The notable ones, by category:
| Category | Topics |
|---|---|
project |
project_started, project_completed, project_paused, edit_session_started |
task |
task_started, task_succeeded, task_skipped, task_stale |
subtask |
subtask_attempt_started, subtask_succeeded, subtask_failed, subtask_skipped, subtask_alive |
review |
review_feedback_recorded |
gate |
gate_awaiting, gate_resolved, gate_misconfigured |
artifact |
artifact_promoted, artifact_reverted, artifact_set_manually, cascade_skipped |
retry |
subtask_retry_queued, subtask_retry_triggered, phase_retriggered |
cost |
cost_threshold_warning, cost_budget_breached |
crash |
crash_recovered, repeat_crash_pause, nonidempotent_crash_paused, nonidempotent_crash_resolved |
topic is not a query parameter — filter by category server-side, then match topic client-side.
List events
GET /v1/events
Query params:
| Param | Type | Notes |
|---|---|---|
limit |
int (1–2000) | Default 500. |
since_hours |
int (0–720) | Restrict to the last N hours. Default 24. 0 means no time bound. |
category |
string | One of the categories above. |
level |
string | One of debug, info, warn, error. |
Returns newest-first (ts descending).
{
"data": [ /* event objects */ ],
"meta": {
"limit": 500,
"since_hours": 24,
"total": 1284
}
}
meta.total is the count of all rows matching the filter window, ignoring limit — so you can tell whether you truncated. There is no cursor; widen limit or narrow since_hours.
const { data, meta } = await catentio.events.list({
category: 'gate',
since_hours: 6,
limit: 200,
});
for (const e of data) console.log(e.ts, e.topic, e.project_id);
res = catentio.events.list({
"category": "gate",
"since_hours": 6,
"limit": 200,
})
for e in res["data"]:
print(e["ts"], e["topic"], e["project_id"])
curl 'https://catent.io/api/v1/cp/v1/events?category=gate&since_hours=6&limit=200' \
-H "Cookie: catentio_session=<session cookie set at login>"
Per-project timelines
The same rows, pre-filtered to one project:
GET /v1/projects/{project_id}/events
Takes limit (default 200, capped at 1000), returns the same ProjectEvent shape newest-first under { "data": [...], "meta": { "limit" } }. Reach for it rather than pulling the global feed and filtering by project_id yourself. See Projects.
A subtask-scoped variant exists too: GET /v1/projects/{project_id}/subtasks/{subtask_id}/events.
GET /v1/runs/{run_id}/eventsis a different table. It serves run events (one-off agent invocations from Discord / REST / CLI), whose rows haveevent_type,agent,transport,duration_ms,cost_usdandstatus— not theProjectEventfields documented here. Don't assume the two shapes interchange.
Errors
| Status | Code | When |
|---|---|---|
| 422 | validation_error |
limit / since_hours out of range. |
| 502 | runtime_unreachable |
CP couldn't reach the runtime. |
Replay
There's no POST /v1/events/replay — and nothing to replay to, since Catentio doesn't deliver events anywhere. If your own ingestion was down, pull the time window via GET /v1/events (raise since_hours) and re-process locally.