API reference
Hook endpoints
POST /v1/hooks/{tool}, GET /v1/settings and GET /v1/guard/{tool} — what the Agentower hook script calls.
These are the endpoints the Agentower hook script calls for Claude Code, Codex and Cursor. You only need them to write your own hook; otherwise use the ready-made hook. Scope: ingest.
{tool} is claude-code, codex or cursor.
#POST /v1/hooks/{tool}
One hook event. The body is the tool's hook payload, cut down on your machine to the fields below — the Agentower script sends only these, and Agentower ignores anything else.
| Field | Type | Notes |
|---|---|---|
session_id | string | Required. The tool's session ID (Cursor: its conversation ID) |
hook_event_name | string | Required. The event, e.g. PreToolUse |
ts | number | When it happened, ms since 1970 |
repo | string | The project's name (never a path) |
os | string | mac, linux, windows or cloud |
agent_id, agent_type | string | Set when the event belongs to a subagent |
tool_name | string | The name of the tool being used, e.g. Bash — never its input |
notification_type, message | string | For Notification events |
error_type | string | For StopFailure, e.g. rate_limit |
reason | string | For SessionEnd |
mcp_server | string | For Elicitation |
model, to_model | string | The model's name, e.g. claude-opus-5-5 |
asks_question | boolean | For Stop: did the turn end by asking the person something? |
question | string | For Stop, only when Agent chats is on: that one sentence, up to 200 characters |
reply | string | For Stop, only when Agent chats is on: the agent's whole final message, up to 8,000 characters. Kept only as the latest per agent, never in the database |
turn | number | Work record: the turn number within the session (1, 2, …). Claude Code and Codex |
work | array | Work record: the steps this event reports, at most 8 — see below |
bg | object | Hook version 8: a background helper or command the agent started — { "kind", "id" } with kind agent or shell, the kind and an id only, never the command or prompt. Keeps the agent shown as Working in background until it finishes (see Board) |
act | string | Hook version 9 (plugin 0.4.3), PreToolUse only, top-level agents (Claude Code and Codex): what the step is, as a short key worked out on the computer — see Activity key |
repo_link | object | Work record: { "host", "repo" } — the repository's host (github.com, gitlab.com or bitbucket.org only) and owner/name, so commits and pull requests can link to it. Never the remote's address or credentials. Ignored for subagents |
curl -X POST "$AGENTOWER_URL/v1/hooks/claude-code" \
-H "authorization: Bearer $AGENTOWER_KEY" \
-H "content-type: application/json" \
-d '{"v":1,"session_id":"3f1c9a52-docs-example","hook_event_name":"PermissionRequest","tool_name":"Bash","repo":"billing-api","os":"mac"}'{ "ok": true, "accepted": 1 }Events Agentower doesn't use are accepted with "accepted": 0, so a hook never has to know which ones matter.
#How Claude Code and Codex events map to states
hook_event_name | The agent becomes |
|---|---|
SessionStart | Starting |
UserPromptSubmit, PostToolUse, PostToolUseFailure, ElicitationResult | Working |
PreToolUse | Working — or Needs you if tool_name is AskUserQuestion ("Asking you a question") or ExitPlanMode ("Plan ready for your review") |
PermissionRequest | Needs you ("Needs your permission to use Bash") |
Elicitation | Needs you ("*server* is asking you a question") |
Notification with notification_type permission_prompt, elicitation_dialog, elicitation_url_dialog or agent_needs_input | Needs you ("Needs your permission", or "Waiting for your input"). The message text is never the reason and is never stored: it shows only as the agent's question, where Agent chats is on |
Stop | Idle — or Needs you ("Asked you a question") with asks_question: true |
StopFailure | Failed ("Stopped: rate limit") |
SubagentStart, SubagentStop | A subagent starts or finishes inside its agent (needs agent_id) |
SessionEnd | Done |
PostModelSwitch | Only updates the model |
#Activity key
Before each tool runs, the hook sends act so the card can say what the agent is doing ("Running tests", "Reading files (src)" — see What it's doing). It's a kind, optionally followed by : and one name:
act | When |
|---|---|
test, build, install | A shell command that runs tests, a build or a package install (for example vitest, npm run build, pip install) |
commit, push, pr | git commit, git push, gh pr create |
deploy or deploy:<env> | A deploy command (for example wrangler deploy, vercel --prod, fly deploy), with the environment's name when the command names one from the list production, prod, staging, preview, dev, development, test (--env staging, -e staging; --prod gives production) |
run or run:<program> | Any other shell command: the program's plain name only, for example run:curl (sudo, env and time and their options are skipped, so sudo -u x psql gives run:psql); a script file (a name with a dot), or a command the hook can't read cleanly (an unbalanced quote, a backslash, $( or $'), gives a bare run |
read or read:<folder> | A read or search tool (Read, Grep, Glob, LS, NotebookRead), with the top-level folder inside the project |
edit or edit:<folder> | An edit tool (Edit, MultiEdit, Write, NotebookEdit), with the top-level folder inside the project |
A folder name is up to 40 letters, digits and . _ + -; a program's name up to 30 letters, digits and _ + - (a name with a dot becomes a bare run); a deploy environment must be on the list above. Anything else, or an unknown kind, is ignored — Agentower applies the same rules again when it arrives. From hook version 11 (plugin 0.4.5), the program's name is sent only when it's a plain name such as curl or psql (from version 12, sudo, env and time are skipped, and a command the hook can't read cleanly sends a bare run) — a script's file name (anything with a dot, such as deploy.sh) sends a bare run — and a deploy's environment only when it's one of production, prod, staging, preview, dev, development or test (otherwise a bare deploy). Never the command line, its arguments, a path below the first folder, a file name or any contents. Agentower keeps the folder only for projects with Work details on (it's dropped otherwise, whatever the hook sends), and holds the key in memory only — not in the database, exports or the log.
#Cursor events
sessionStart → Starting; beforeSubmitPrompt, preToolUse, beforeShellExecution, beforeMCPExecution, postToolUse, afterShellExecution, afterMCPExecution, afterFileEdit, postToolUseFailure, subagentStart, subagentStop → Working; stop → Idle (or Failed with "status": "error"); sessionEnd → Done. Cursor has no event for waiting on permission, so a Cursor agent never shows Needs you.
#Work record
From hook version 4, Claude Code and Codex reports can say what the agent did — see The work record. From version 6 they also carry the instruction and the agent's summary (with Work details on) and where the agent works (below), and Cursor reports carry where it works and, with Work details on, the instruction. The hook works it out on the computer: for test results it reads only the last 20 KB of a command's output and skips lines over 500 characters, and its own git calls run with signature display and filesystem monitors turned off. Each item in work is one step:
{ "kind": "tests", "counts": { "category": "test", "exitCode": 1, "passed": 47, "failed": 1 }, "detail": { "tool": "vitest", "failedTests": ["cursor pagination keeps order"] } }| Field | Notes |
|---|---|
kind | edit, command, tests, commit, push, pr, turn_end, more, prompt (the instruction that started the turn, sent with UserPromptSubmit) or summary (the agent's closing message, sent with Stop). Other kinds are dropped, and so is a prompt or summary without detail.text |
counts | Numbers only — leave out what you can't measure (never send 0 in its place). edit: added, removed, newFile, and again when the file was already changed earlier in the turn (so it isn't counted twice). command: category (test, build, lint, install, git, run or other), exitCode, and runs for commands of the same kind folded together. tests: passed, failed, skipped, exitCode. commit: commits, hash (the short hash). push: pushed. pr: prs. turn_end: the turn's totals — files, added, removed, commits, pushed, prs and the last test result. more: how many further steps the hook didn't send one by one |
detail | Names, sent only for projects listed in workDetails from GET /v1/settings: path (relative to the repository; never absolute, never leading out of it; up to 300 characters), tool (the git sub-command or test runner), failedTests (up to 10, 120 characters each), subject (the commit's first line, up to 120 characters, code removed), branch, prNumber and prUrl (a pull request on GitHub, GitLab or Bitbucket), and text on a prompt or summary — mask secrets before you send it; Agentower masks them again (replacing what looks like a key, token, password value or long random string with [hidden]), removes code blocks and keeps the first 600 characters |
merge | true on an edit or command folds it into this agent's previous step of the same kind (the same file, or the same kind of command) |
Agentower checks every field again and drops anything else. It keeps detail only while the project has Work details on — whatever a hook sends — and keeps at most 250 steps per agent and turn (a subagent's steps count towards its parent's turn). The Agentower hook sends at most 200 steps a turn, then one more with the rest. Send turn with every report that carries work, and with UserPromptSubmit (a new turn starts) and Stop; send a turn_end step with Stop.
#Where the agent works
These top-level fields name the agent's work stream. They're names only, sent at every setting; for Claude Code and Codex, branch, worktree and label count only on the main agent's events (not a subagent's).
| Field | Notes |
|---|---|
label | The launch label (AGENTOWER_LABEL), one line, at most 40 characters kept |
branch | The git branch (letters, digits and . _ / + -, up to 100 characters; not HEAD) |
worktree | The folder name of a linked git worktree (never its path) |
folder | The top-level folder of the repository that an edited file is in — one name, up to 40 characters |
The Agentower hook sends branch, worktree and label when a session starts, a turn starts and a turn ends, and folder with an edit.
#GET /v1/settings
Which projects have Agent chats on — listed for each of the three things it covers: questions (showQuestions), answering from Agentower (answerFromApp) and last messages (showReplies) — which have Work details on (workDetails), and whether any guardrail is on. The hook checks this (at most once a minute) before sending a question, a work record name or opening an ask, and before checking guardrails. Project names are lower-case.
curl "$AGENTOWER_URL/v1/settings" -H "authorization: Bearer $AGENTOWER_KEY"{ "showQuestions": [], "answerFromApp": [], "showReplies": [], "workDetails": [], "guarded": false }#GET /v1/guard/{tool}
Is this session paused by a guardrail? The hook asks before each tool use (only while guarded is true) and, if paused, refuses the tool use with the note as the reason.
| Query | Notes |
|---|---|
session_id | Required. The tool's session ID |
curl "$AGENTOWER_URL/v1/guard/claude-code?session_id=3f1c9a52-docs-example" \
-H "authorization: Bearer $AGENTOWER_KEY"{ "paused": false, "note": null }When paused: { "paused": true, "note": "45 min session limit" }.
#Rules for your own hook
- Send only the fields above; never commands, file contents, diffs or full paths (a work record
pathis relative to the repository, and only for projects with Work details on), and never a prompt except as apromptstep'stext, secrets masked, for projects with Work details on. - Never make the tool wait: send in the background, time out after a few seconds, and carry on if Agentower can't be reached. On a network error,
429or5xx, keep the event and resend it later, in order (the Agentower hook keeps up to 200). POST /v1/hooks/{tool}bodies are limited to 64 kB.