> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pinework.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Approval

> An approval is a gate an agent run raises and one person resolves. It is open or resolved, and its result is approve, reject or expired.

An approval is a yes-or-no gate on one action, raised by an agent run or by the platform. Its id starts with `apr_`. For why agents ask and how the run waits, see [Approvals and questions](/concepts/approvals-and-questions).

## States

| State | Meaning | Ends here |
| - | - | - |
| `open` | The approval waits for a decision. | No |
| `resolved` | A person or the platform closed it. `result.outcome` says how. | Yes |

| From | To | Trigger |
| - | - | - |
| `open` | `resolved`, outcome `approve` | The assigned user approves it. |
| `open` | `resolved`, outcome `reject` | The assigned user rejects it. |
| `open` | `resolved`, outcome `reject` | The raising run ends for a reason other than asking. The platform rejects with reason `run_terminated`. This skips `tool_permission`, `learning_review`, `instruction_publish` and `platform_change`. |
| `open` | `resolved`, outcome `expired` | A `tool_permission` approval's run has been over for 24 hours. |
| `open` | `resolved`, outcome `expired` | A `platform_change` approval is 7 days old. |

`resolvedBy` is `{ "type": "system" }` when the platform closed the approval. An agent can never resolve an approval.

### Triggers

| `trigger` | What raised it |
| - | - |
| `tool_permission` | A tool call the agent's permissions set to Ask. |
| `budget_breach` | A spend budget was hit. A user cannot resolve it. |
| `connection_action` | An agent asked before an action. `pinework approval request` raises this trigger. |
| `learning_review` | A learning waits to be added to the wiki. It has no run. |
| `instruction_publish` | A proposed change to an agent's instructions. |
| `platform_change` | A stored API write. Approving it replays the write. |

## Fields

<ResponseField name="id" type="string" required>Public approval id, `apr_…`.</ResponseField>
<ResponseField name="object" type="string" required>Always `approval`.</ResponseField>
<ResponseField name="workspaceId" type="string" required>The workspace's public id. Left out of list rows.</ResponseField>
<ResponseField name="taskId" type="string">The linked task, `tsk_…`. Null when the approval has no task.</ResponseField>
<ResponseField name="sessionId" type="string">The raising session. Null when no run raised it. Left out of list rows.</ResponseField>
<ResponseField name="runId" type="string">The raising run, `run_…`. Null when no run raised it, as with a learning review.</ResponseField>
<ResponseField name="trigger" type="string" required>What raised it. One of the six triggers above.</ResponseField>
<ResponseField name="status" type="string" required>`open` or `resolved`.</ResponseField>
<ResponseField name="prompt" type="string" required>What is gated, in plain words, with secrets masked.</ResponseField>

<ResponseField name="gatedThing" type="object" required>
  The gated action.

  <Expandable title="properties">
    <ResponseField name="kind" type="string" required>`tool_call`, `budget`, `connection_action`, `learning_review`, `instruction_publish` or `platform_change`.</ResponseField>
    <ResponseField name="name" type="string" required>The tool name, budget scope or action name.</ResponseField>
    <ResponseField name="inputDigest" type="string" required>A fingerprint of the exact call.</ResponseField>
    <ResponseField name="safeSummary" type="string" required>A one-line summary with secrets masked.</ResponseField>
    <ResponseField name="scope" type="string" required>The scope the raiser proposed, `once` or `always`.</ResponseField>
    <ResponseField name="toolArgs" type="object">The masked tool arguments, for display. Present on tool calls only.</ResponseField>
    <ResponseField name="platformChange" type="object">The stored request: `method`, `path`, `body`, `source`, `commands`, `expiresAt` and `result`. Present on `platform_change` only.</ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="requestedBy" type="object" required>Who raised it. `type` is `agent` with an `agt_` id, or `system`. Never a user.</ResponseField>
<ResponseField name="assignedTo" type="object">The one user who may resolve it, or `{ "type": "system" }`. Null when no default approver was found. Any user may then resolve it.</ResponseField>
<ResponseField name="result" type="object">`outcome` (`approve`, `reject` or `expired`) and `reason`, the note. Null while open.</ResponseField>
<ResponseField name="appliedScope" type="string">`once` or `always`, for an approved `tool_permission`. Null otherwise.</ResponseField>
<ResponseField name="resolvedBy" type="object">`{ "type": "user", "id": "usr_…" }` or `{ "type": "system" }`. Null while open.</ResponseField>
<ResponseField name="resolvedAt" type="string">When it was resolved. Null while open.</ResponseField>
<ResponseField name="createdAt" type="string" required>When it was raised.</ResponseField>
<ResponseField name="updatedAt" type="string" required>When it last changed.</ResponseField>

## CLI

| Command | Does |
| - | - |
| `pinework approval list` | Lists approvals, newest first. Shows `open` ones unless you pass `--status resolved` or `--status all`. Filters: `--task`, `--run`, `--trigger`, `--assigned-to-user-id`. |
| `pinework approval get <apr_…>` | Shows one approval. For an instruction edit it also prints the proposed text and what it replaces. |
| `pinework approval approve <apr_…>` | Approves as you. `--note` adds a note. Human login only. |
| `pinework approval reject <apr_…>` | Rejects as you. The agent reads `--note`. Human login only. |
| `pinework approval request [task]` | Asks a person to approve `--type`, with `--description`. Works only inside an agent run. The agent then stops. |

```bash theme={null}
pinework approval list --status open --task PIN-42
pinework approval get apr_AbCdEfGh12345678
pinework approval approve apr_AbCdEfGh12345678 --note "Go ahead"
pinework approval reject apr_AbCdEfGh12345678 --note "Deploy after the freeze"
```

## API

Every `POST` below needs an `Idempotency-Key` header.

| Method | Path | Does |
| - | - | - |
| `GET` | `/api/v1/approvals` | Lists approvals, newest first. Filters: `status`, `trigger`, `taskId`, `runId`, `conversationId`, `assignedToUserId`. No status filter by default. |
| `GET` | `/api/v1/approvals/:approvalId` | Gets one approval. `include[]` takes `task`, `run` and `session`. |
| `POST` | `/api/v1/approvals` | Raises an approval from inside an agent run. Returns `approval` and a `sentinel`. A user login is refused. |
| `POST` | `/api/v1/approvals/:approvalId/approve` | Approves. Body: `reason`, and `appliedScope` (`once` or `always`) for a `tool_permission`. `appliedScope` defaults to `once`. |
| `POST` | `/api/v1/approvals/:approvalId/reject` | Rejects. Body: `reason`. |
| `POST` | `/api/v1/approvals/:approvalId/assign` | Hands an open approval to another user. Body: `assignedTo` (`{ "type": "user", "id": "usr_…" }`) and a required `reason`. Workspace admins only. |

Approving a `tool_permission` with `appliedScope: "always"` adds an allow rule to the requesting agent.

## Errors

| Code | HTTP | When | What to do |
| - | - | - | - |
| `approval_not_found` | 404 | No approval with that id in this workspace. | Check the `apr_` id. |
| `approval_resolved` | 409 | The approval is already resolved. | Read it again. Nothing is left to decide. |
| `agent_cannot_resolve_approval` | 403 | An agent tried to approve or reject. | Ask a person to decide. |
| `approval_assignee_mismatch` | 403 | You are not the assigned user. | Ask the user in `assignedTo`, or reassign it. |
| `user_cannot_resolve_system_approval` | 409 | You tried to resolve a `budget_breach` approval. | Raise the budget instead. |
| `approval_not_user_assignable` | 409 | You tried to reassign a `budget_breach` approval. | None. The platform resolves it. |
| `not_authorized` | 403 | A non-admin tried to reassign. | Ask a workspace admin. |
| `assignee_not_found` | 404 | The `assignedTo` user does not exist. | Check the `usr_` id. |
| `user_cannot_create_approval` | 403 | A user login called `POST /approvals`. | Raise approvals from an agent run. |
| `platform_change_not_raisable` | 403 | The body names `platform_change`. | Call the route you want to change. It raises its own approval. |
| `run_not_found` | 404 | The call has no run, or the run is gone. | Raise from inside a live agent run. |
| `hitl_stop_failed` | 409 | The run had already ended. | Start a new run, then ask again. |
| `author_must_be_self` | 403 | An agent named another agent in `requestedBy`. | Leave `requestedBy` out. |
| `invalid_cursor` | 400 | You passed both `startingAfter` and `endingBefore`. | Pass one cursor. |
| `invalid_include` | 400 | An unknown `include[]` value. | Use `task`, `run` or `session`. |

For shared codes, such as idempotency errors, see [API errors](/reference/api#errors).

## Limits

* A list page holds 25 approvals by default and 100 at most.
* A `tool_permission` approval expires 24 hours after its run ends. The sweep runs once an hour.
* A `platform_change` approval expires 7 days after it is raised.

<CardGroup cols={2}>
  <Card title="Approvals and questions" href="/concepts/approvals-and-questions">
    Why an agent asks, and how its run waits.
  </Card>

  <Card title="Answer approvals" href="/guides/answer-approvals">
    Decide on an approval from the inbox or the terminal.
  </Card>

  <Card title="Question" href="/reference/question">
    The other way an agent hands control back.
  </Card>

  <Card title="Run" href="/reference/run">
    The run that raises an approval.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.