Beta

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.

FieldTypeNotes
session_idstringRequired. The tool's session ID (Cursor: its conversation ID)
hook_event_namestringRequired. The event, e.g. PreToolUse
tsnumberWhen it happened, ms since 1970
repostringThe project's name (never a path)
osstringmac, linux, windows or cloud
agent_id, agent_typestringSet when the event belongs to a subagent
tool_namestringThe name of the tool being used, e.g. Bash — never its input
notification_type, messagestringFor Notification events
error_typestringFor StopFailure, e.g. rate_limit
reasonstringFor SessionEnd
mcp_serverstringFor Elicitation
model, to_modelstringThe model's name, e.g. claude-opus-5-5
asks_questionbooleanFor Stop: did the turn end by asking the person something?
questionstringFor Stop, only when Agent chats is on: that one sentence, up to 200 characters
replystringFor 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
turnnumberWork record: the turn number within the session (1, 2, …). Claude Code and Codex
workarrayWork record: the steps this event reports, at most 8 — see below
bgobjectHook 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)
actstringHook 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_linkobjectWork 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
A permission request
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"}'
Response · 200
{ "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_nameThe agent becomes
SessionStartStarting
UserPromptSubmit, PostToolUse, PostToolUseFailure, ElicitationResultWorking
PreToolUseWorking — or Needs you if tool_name is AskUserQuestion ("Asking you a question") or ExitPlanMode ("Plan ready for your review")
PermissionRequestNeeds you ("Needs your permission to use Bash")
ElicitationNeeds you ("*server* is asking you a question")
Notification with notification_type permission_prompt, elicitation_dialog, elicitation_url_dialog or agent_needs_inputNeeds 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
StopIdle — or Needs you ("Asked you a question") with asks_question: true
StopFailureFailed ("Stopped: rate limit")
SubagentStart, SubagentStopA subagent starts or finishes inside its agent (needs agent_id)
SessionEndDone
PostModelSwitchOnly 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:

actWhen
test, build, installA shell command that runs tests, a build or a package install (for example vitest, npm run build, pip install)
commit, push, prgit 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:

json
{ "kind": "tests", "counts": { "category": "test", "exitCode": 1, "passed": 47, "failed": 1 }, "detail": { "tool": "vitest", "failedTests": ["cursor pagination keeps order"] } }
FieldNotes
kindedit, 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
countsNumbers 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
detailNames, 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
mergetrue 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).

FieldNotes
labelThe launch label (AGENTOWER_LABEL), one line, at most 40 characters kept
branchThe git branch (letters, digits and . _ / + -, up to 100 characters; not HEAD)
worktreeThe folder name of a linked git worktree (never its path)
folderThe 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.

Opt-in settings
curl "$AGENTOWER_URL/v1/settings" -H "authorization: Bearer $AGENTOWER_KEY"
Response · 200
{ "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.

QueryNotes
session_idRequired. The tool's session ID
Guardrail check
curl "$AGENTOWER_URL/v1/guard/claude-code?session_id=3f1c9a52-docs-example" \
  -H "authorization: Bearer $AGENTOWER_KEY"
Response · 200
{ "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 path is relative to the repository, and only for projects with Work details on), and never a prompt except as a prompt step's text, 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, 429 or 5xx, 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.