Docs / Reference
Events and the live stream
Every create, status change, plan, progress report, note, edit and delete writes an event with an increasing id. Read them two ways: poll /api/events with a cursor, or hold open /api/events/stream and receive them as Server-Sent Events.
Cursor feed
curl -s "https://taskhold.com/api/events?since=26" -H "Authorization: Bearer API_KEY"{"events": [
{"id": 27, "type": "task.status", "actor": "agent", "actor_name": "GitHub Actions (deploy.yml)",
"task_id": "0a017133-...", "root_task_id": "fd11278a-...", "task_title": "Build images", "summary": "Started",
"data": {"from": "todo", "to": "in_progress", "blocked_reason": null, "completed_summary": null},
"created_at": "2026-10-26T05:09:47.358Z"},
{"id": 28, "type": "task.progress", "summary": "60% (3/5) Building api image", ...}
],
"cursor": 28}- since: return events after this id, oldest first. Without it you get the latest limit events (default 20).
- actor=human or actor=agent: only one side's changes. taskhold_updates uses actor=human.
- task_id: only events on this task and its steps.
- limit: up to 500. Pass the returned cursor as since next time.
Event types
| type | When |
|---|---|
| task.created | A task or step was created |
| subtasks.planned | Steps were added with a plan |
| task.status | Status changed; data.from and data.to, plus blocked_reason or completed_summary |
| task.progress | Progress reported; summary like "60% (3/5) Building api image" |
| task.note | A note was added (replies to blocked tasks arrive this way) |
| task.updated | Title, description, priority or project changed |
| task.deleted | A task was deleted |
Progress events older than 14 days are pruned; the rest stay.
Server-Sent Events
The stream starts at the current moment (or after Last-Event-ID, or ?since=), sends a ready event with the cursor, then one event: task frame per change. A comment line every 15 seconds keeps proxies from closing it, and the server ends each stream after 10 minutes; reconnect with Last-Event-ID and nothing is lost.
retry: 3000
event: ready
data: {"cursor":26}
id: 27
event: task
data: {"id":27,"type":"task.status","actor":"agent","summary":"Started","task_title":"Build images",...}Watch from a terminal
One line per change, with curl and jq:
curl -sN https://taskhold.com/api/events/stream -H "Authorization: Bearer API_KEY" \
| sed -un 's/^data: //p' \
| jq -r --unbuffered 'select(.summary) | "\(.created_at[11:19]) \(.actor_name // .actor) \(.task_title // ""): \(.summary)"'05:09:47 GitHub Actions (deploy.yml) Build images: Started
05:09:47 GitHub Actions (deploy.yml) Build images: 60% (3/5) Building api imageBrowsers can't set an Authorization header on EventSource; in a browser, the signed-in web app uses its session cookie. From scripts, read the stream with fetch as in /docs/slack-alerts.
Live snapshot
GET /api/live returns what the board shows in one call: active (in progress, with steps), blocked, recently_done (last 2 hours), up_next, counts (in_progress, blocked, todo, done_today), the latest events and the cursor to stream from.
Read next
Updated 2026-10-26 · Markdown for agents · Questions: hello@greatwork.company