API reference
REST events
POST /v1/events — report an agent's status from any tool or script.
POST /v1/events puts any agent on the board: another coding tool, a CI job, your own script. Send one event each time its state changes. Scope: ingest.
#The event
| Field | Type | Required | Notes |
|---|---|---|---|
session_id | string | Yes | Your ID for this run or session. One card per session_id (per tool). 1–128 of A–Z a–z 0–9 . _ : - |
event | string | Yes | One of the events below |
tool | string | No | Your tool's name, shown on the card. Default script. Up to 40 characters |
project | string | No | The project to file it under; created if new. Without it, the agent goes into Unsorted |
reason | string | No | For hold and error (and end with ok: false): a short reason shown on the card, up to 160 characters. Don't put secrets or content here |
ok | boolean | No | For end: false means the run failed |
ts | number | No | When it happened, in milliseconds since 1970. Default: when it arrives. A time more than 3 days in the past or more than 1 day ahead is replaced with the time it arrives |
event | The agent becomes | Notes |
|---|---|---|
start | Starting | A new session. Reopens a closed one |
activity | Working | Any sign of work. Send it at least every 30 minutes on long steps, or the session closes |
hold | Needs you | Waiting for a person. reason defaults to "Waiting for you" |
turn_end | Idle | Finished a piece of work; the session stays open |
error | Failed | Stopped by an error. reason defaults to "Error" |
end | Done (or Failed with "ok": false) | The session is over |
You don't have to send start first: any event creates the agent if it's new.
#Send one event
curl -X POST "$AGENTOWER_URL/v1/events" \
-H "authorization: Bearer $AGENTOWER_KEY" \
-H "content-type: application/json" \
-d '{"tool":"nightly-import","session_id":"run-2026-09-30","project":"data-pipeline","event":"start"}'{ "ok": true, "accepted": 1 }curl -X POST "$AGENTOWER_URL/v1/events" \
-H "authorization: Bearer $AGENTOWER_KEY" \
-H "content-type: application/json" \
-d '{"tool":"nightly-import","session_id":"run-2026-09-30","project":"data-pipeline","event":"hold","reason":"Needs a person to check the report"}'{ "ok": true, "accepted": 1 }The card now shows Needs you, with "Needs a person to check the report" on its Now row, and any notification channels for Needs you are told.
#Send several at once
Send an array of up to 1,000 events. If any one is invalid, none are recorded.
curl -X POST "$AGENTOWER_URL/v1/events" \
-H "authorization: Bearer $AGENTOWER_KEY" \
-H "content-type: application/json" \
-d '[{"tool":"nightly-import","session_id":"run-2026-09-30","event":"activity"},
{"tool":"nightly-import","session_id":"run-2026-09-30","event":"end","ok":true}]'{ "ok": true, "accepted": 2 }#Errors
curl -X POST "$AGENTOWER_URL/v1/events" \
-H "authorization: Bearer $AGENTOWER_KEY" \
-H "content-type: application/json" \
-d '{"tool":"nightly-import","session_id":"run-2026-09-30"}'{ "error": "invalid_event" }curl -X POST "$AGENTOWER_URL/v1/events" \
-H "authorization: Bearer at_test_00000000000000000000000000000000" \
-H "content-type: application/json" \
-d '{"session_id":"run-1","event":"start"}'{ "error": "unauthorized", "hint": "Send an ingest key: authorization: Bearer at_…" }Other errors are listed in Errors and limits.
#A small wrapper
From TypeScript or Python, use the SDKs: they send these reports for you, in order and without ever throwing into your agent, and can also wait for an answer from the app. From the shell, a function that reports the start, the end and any failure of a job:
report() { # report <event> [reason]
curl -sS -X POST "$AGENTOWER_URL/v1/events" \
-H "authorization: Bearer $AGENTOWER_KEY" -H "content-type: application/json" \
-d "{\"tool\":\"nightly-import\",\"session_id\":\"$RUN_ID\",\"project\":\"data-pipeline\",\"event\":\"$1\",\"reason\":\"${2:-}\"}" >/dev/null
}
RUN_ID="run-$(date +%Y%m%d-%H%M)"
report start
if ./import.sh; then report end; else report error "import.sh failed"; fi