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=gate to find the projects waiting on a human or an external callback.
  • Catch failures. level=error across 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}/events is a different table. It serves run events (one-off agent invocations from Discord / REST / CLI), whose rows have event_type, agent, transport, duration_ms, cost_usd and statusnot the ProjectEvent fields 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.

Next

  • Webhooks — webhook gates (inbound), and why there is no push delivery.
  • Gates — what gate_awaiting / gate_resolved actually mean.
  • Projects — the pipeline these events describe.