---
title: "Answer a pending tool approval"
method: POST
path: "/v1/tool-approvals/{id}"
tags: ["Tools"]
---

# Answer a pending tool approval

`POST /v1/tool-approvals/{id}`

## Request

- Base URL: `http://localhost:9998`
- URL: `http://localhost:9998/v1/tool-approvals/{id}`
- Auth: HTTP bearer

Answers a consent requirement returned by a mirrored tool route. A caller
declares its answer channel with `X-ToolPiper-Approval-Channel: rest` on
the original request. The refusal carries an approval id; after approval,
repeat the original request with `X-ToolPiper-Approval-Id: <id>`.
The authenticated client key must match the client that requested approval.
An allow-once answer admits only the same tool and canonical JSON arguments
once. JSON object key order does not change the argument binding.
An allow-session answer, where the refusal's `offeredAnswers` lists it, admits
the one call and leaves a rule allowing that tool for the same client's session,
until ToolPiper exits or that client loses authority; `grant` is `session` when
the rule was written and `once` otherwise. It saves nothing lasting. Every
invocation still meets the current authorization gates.
An always-allow or always-deny answer is accepted only where the refusal's
`offeredAnswers` lists it. Always deny refuses the call and saves the rule at
once. Always allow admits the one call as allow-once does and asks ToolPiper to
save the rule, which the person confirms on the Mac; the reply waits for that
and its `remembered` says `saved`, or, when it was not confirmed, `session_only`
where a session answer is listed and `nothing` where it is not.
Pending approvals and unconsumed once approvals expire after 45 seconds;
an answer at exactly 45 seconds is refused. Only the first answer is accepted.
Device-owner authentication cannot be answered through this endpoint.
Callers that do not declare the REST channel use native approval on the Mac.
An operation that judges further work (arming a schedule judges the tool each
fire runs) can need a card for each: the repeated request answers
`consent_required` again with `continues` naming the approval it carried. Answer
that one and repeat the request with its id; each earlier call is admitted once
by it, and an earlier answer never admits a later call's arguments.

## Path parameters

- `id` string, required

## Request body

- ToolApprovalAnswer
  - `decision` 'allow-once' | 'allow-session' | 'always-allow' | 'deny' | 'always-deny' | 'withdraw', required

## Response `200`

The answer was recorded; repeat the original request to execute it

- ToolApprovalResponse
  - `approvalId` string, required
  - `status` 'approved' | 'denied' | 'withdrawn', required
  - `grant` 'once' | 'session'
  - `remembered` 'nothing' | 'session' | 'saved' | 'session_only': For an always-allow or always-deny answer, what ToolPiper remembered. Absent for every other decision, and from an older build.

## Other responses

- `400`: Missing or invalid decision
- `403`: The caller lacks authority or does not own this approval
- `404`: Unknown approval id
- `409`: The approval was already answered or consumed, or it does not offer this answer
- `410`: The approval expired

---

[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)
