API reference
Asks (answers from the app)
POST /v1/asks and GET /v1/asks/{id} — how any agent, or a tool's hook, waits for an answer given in the app.
An ask is how an agent waits for an answer given in Agentower — Allow or Deny, an answer to a multiple-choice question, or a message. It opens one, then checks for the answer — every 1.5 seconds at first, less often as time goes on — while the terminal carries on as usual. Whichever answer comes first — terminal or app — wins. Scope: ingest. People-facing detail: Answer agents from Agentower.
There are two sets of routes:
POST /v1/asksandGET /v1/asks/{id}— for any agent reported withPOST /v1/eventsor the SDKs.POST /v1/asks/{tool}andGET /v1/asks/{tool}/{id}— used by the Claude Code and Codex hooks;{tool}isclaude-codeorcodex(Cursor can't be answered).
Answers themselves can only be given in the app, signed in (or with an agent-actions key) — no ingest key can answer an ask.
#Any agent: POST /v1/asks
Opens an ask for an agent you report yourself. The SDKs do this for you with askApproval, askQuestion and waitForMessage.
| Field | Type | Notes |
|---|---|---|
session_id | string | Required. The agent's session, as reported in /v1/events |
tool | string | The same tool name the agent reports with in /v1/events. Default script. cursor always gets {"id": null} (Cursor can't be answered); codex asks stay open at most 9 minutes |
kind | string | Required. approval, question or reply (waiting for a message) |
tool_name | string | For approval: what it wants to do, e.g. deploy (up to 60 characters) |
detail | string | For approval: the details a person needs to decide (up to 300 characters) |
complete | boolean | For approval: true only when detail shows everything the step will do. Anything else means the app offers Deny only |
questions | array | For question, required: as below |
remote | string | Required. own (only the person whose key reports the agent may answer) or team (the project's approvers and owners too) |
wait | integer | How long you'll wait for an answer, in seconds: 10 to 3,600. The ask stays open that long, at most 60 minutes |
The same rules as Claude Code apply: the workspace's Agent chats must be on, the key must belong to a person (otherwise POST /v1/asks answers {"id": null} and GET /v1/asks/{id} answers {"status": "closed"}), the agent must be a known, open agent of that person, and the answer goes only to that person's key. Otherwise — or when rate-limited — the answer is {"id": null}, and your agent should decide by itself:
curl -X POST "$AGENTOWER_URL/v1/asks" \
-H "authorization: Bearer $AGENTOWER_KEY" \
-H "content-type: application/json" \
-d '{"session_id":"run-docs-example","tool":"release-bot","kind":"approval","tool_name":"deploy","detail":"Deploy billing-api 2.4.1 to production","complete":true,"remote":"own"}'{ "id": null }Check it with GET /v1/asks/{id}. It answers with the same statuses as GET /v1/asks/{tool}/{id} below:
curl "$AGENTOWER_URL/v1/asks/Vb3kR9tWq2LmZx8YpN4cHd7sJf1aGe6u" \
-H "authorization: Bearer $AGENTOWER_KEY"{ "status": "closed" }The rest of this page — fields, how long asks stay open, statuses and answers — applies to both sets of routes.
#Hooks: POST /v1/asks/{tool}
Opens an ask for an open session.
| Field | Type | Notes |
|---|---|---|
session_id | string | Required. The session that's asking |
kind | string | Required. approval (a permission request), question (a multiple-choice question asked with Claude Code's question tool) or reply (waiting for a message after a turn) |
wait | integer | Optional. How long the hook will wait for an answer, in seconds (hook version 5 sends it). The ask stays open that long, at most 60 minutes; without it, 9 minutes. Not used for reply |
tool_name | string | For approval: the tool it wants to use, e.g. Bash (up to 60 characters) |
detail | string | For approval: what it will do — the command, the file (within the project, or its full path outside it), or the web address (up to 300 characters) |
complete | boolean | For approval: true only when detail shows everything the tool will do. Anything else — or a detail that was cut, had invisible characters, or is empty — means the app offers Deny only. File changes (tool_name Write, Edit, MultiEdit or NotebookEdit) are always treated as false, whatever you send: the detail names only the file, not what would be written |
questions | array | For question, required: 1–4 items { "question": "…", "options": ["…"], "multiSelect": false } — each question one line, code removed, up to 200 characters; up to 6 option labels of up to 80 characters each (never their descriptions). If any question has no text or no options, or there are more than 4, no ask is opened |
plan | string | For an approval with tool_name ExitPlanMode: the plan's text, plain, up to 12,000 characters, invisible and control characters removed except line breaks. Send complete: true only if nothing was removed or cut; otherwise the app offers only Keep planning. Kept only where the workspace shows agents' messages |
remote | string | The computer's setting: own (only the person running the session may answer) or team (the project's approvers too). Leave it out when the computer hasn't opted in |
Agentower opens the ask only when the session is a known, open agent (not a subagent) of the key's person, in a workspace with Agent chats on, and remote is own or team. A question also needs questions the app can show. Otherwise — or when rate-limited — it answers {"id": null}, and the hook should leave everything to the terminal:
curl -X POST "$AGENTOWER_URL/v1/asks/claude-code" \
-H "authorization: Bearer $AGENTOWER_KEY" \
-H "content-type: application/json" \
-d '{"session_id":"3f1c9a52-docs-example","kind":"approval","tool_name":"Bash","detail":"npm run deploy:staging"}'{ "id": null }When it's opened, id is a 32-character token:
{ "id": "Vb3kR9tWq2LmZx8YpN4cHd7sJf1aGe6u" }An agent has at most one open ask of each kind; a new one replaces the old. Approvals and questions stay open for as long as the hook says it will wait (wait, in seconds) — at most 60 minutes for Claude Code; 9 minutes for other tools, or without wait (hooks before version 5). Replies stay open for 4 hours. The questions and the plan are held only while the ask is open, shown only to people who may approve the project, and deleted once it's answered, expires or closes. The ask also closes when the agent moves on — for example you answered in the terminal, or the session ended.
#GET /v1/asks/{tool}/{id}
Checks an ask. An answer is handed over once: the next check after it returns closed. Only the key of the agent's own person — whoever's key opened the ask — gets the answer; any other key, even a teammate's in the same workspace, gets closed and can't use the answer up.
curl "$AGENTOWER_URL/v1/asks/claude-code/Vb3kR9tWq2LmZx8YpN4cHd7sJf1aGe6u" \
-H "authorization: Bearer $AGENTOWER_KEY"{ "status": "closed" }status | Meaning | What the hook does |
|---|---|---|
pending | Nobody has answered yet | Check again |
answered | Answered in the app: "decision": "allow" or "deny" for an approval, "answers" for a question, or "text": "…" and "from" (who wrote it) for a reply. "own": true when the person running the session gave it | Pass it to the agent — on a computer set to own, only when own is true |
closed | Expired, replaced, the agent moved on, or already handed over | Stop waiting; the terminal decides |
{ "status": "answered", "decision": "allow", "own": true }{ "status": "answered", "text": "Commit your changes with a clear commit message.", "from": "Sam (approver)", "own": false }For a question, answers has one answer per question, in the order asked: {"labels": […]} — offered labels (several only for a multi-select question) — or {"text": "…"}, the person's own words (up to 2,000 characters). Check the labels against the question's own options again before passing them on; the Agentower hook builds Claude Code's answer from Claude Code's own input, and leaves anything that doesn't fit to the terminal.
{ "status": "answered", "answers": [{ "labels": ["Not now"] }], "own": false }A reply's text is up to 2,000 characters and may contain line breaks. It's deleted as soon as it's handed over.
The body of POST /v1/asks/{tool} can be up to 16,000 bytes, or 64,000 bytes for an ExitPlanMode approval carrying a plan.