---
title: "Answer a parked interactive tool approval"
method: POST
path: "/v1/agent/approve"
tags: ["Agent"]
---

# Answer a parked interactive tool approval

`POST /v1/agent/approve`

## Request

- Base URL: `http://localhost:9998`
- URL: `http://localhost:9998/v1/agent/approve`
- Auth: HTTP bearer

Resolves a tool call the agent kernel parked for a human decision (B2).
A tool-call frame on `GET /v1/agent/stream` with `approvalRequired:true`
is the prompt; this endpoint answers it. The parked call resumes at once
(the tool's result rides the SSE stream). A parked approval also denies
on idle timeout, SSE disconnect, or turn Stop. Idempotent: a second
answer for the same call (already resolved / unknown / expired) loses
with `409`. Only the conversation's own client, or the person at the Mac,
may answer; another client is refused with `403`.

`always-allow` is saved as a standing rule only after the device owner
authenticates on the Mac, so a bearer's `always-allow` response waits
for that authentication for up to 45 seconds. Without it the tool is
allowed for this session and nothing is saved. An exec-class tool is
never saved as always allowed: it runs this once, and so does a call a
firewall Ask rule asks for, since the rule asks again on every call it
matches. `always-deny` is saved at once, shown in the Security pane
with the client that set it, where the person can revert it.
`standing_grant` reports which happened.

## Request body

- object
  - `session_id` string, required: The session the approval belongs to
  - `tool_call_id` string, required: The proposed call's id (from its tool-call frame)
  - `decision` 'allow-once' | 'allow-session' | 'always-allow' | 'deny' | 'always-deny', required
  - `run_id` string: Optional. The run the approval belongs to. Tool-call ids repeat across turns, so an answer naming a run that is not the running one is refused with `409` instead of answering a later call of the same id.
  - `tool_name` string: Optional. The tool the prompt showed. Tool-call ids repeat within a turn too, so an answer naming another tool than the pending call's is refused with `409`.
  - `arguments` string: Optional. The arguments the prompt showed, as the tool-call frame's `arguments` text. An answer whose arguments differ from the pending call's is refused with `409`.

## Response `200`

Approval resolved; the turn resumes over SSE

- object
  - `ok` true
  - `session_id` string
  - `tool_call_id` string
  - `decision` string
  - `standing_grant` 'recorded' | 'session_only' | 'once' | 'pending': Present for `always-allow` and `always-deny`. `recorded`: the standing rule was saved. `session_only`: the owner did not authenticate within the bound, so the tool is allowed for this session only. `once`: an exec-class tool ran this once and nothing was saved. `pending`: the call was still running when the reply stopped waiting; its Always allow is saved when it ends, after the owner confirms where the Security level asks.

## Other responses

- `403`: The conversation belongs to another client
- `404`: Unknown session
- `409`: No pending approval for this tool_call_id (already answered, expired, or unknown), or `run_id` names a run that is not the running one

---

[API](https://skmtc.dev/modelpiper/apis/toolpiper-api.md) · [All operations](https://skmtc.dev/modelpiper/apis/toolpiper-api/llms.txt) · [OpenAPI document](https://skmtc.dev/modelpiper/apis/toolpiper-api/revisions/6722bf56385c?raw)
