---
title: "Approve an approval item"
method: POST
path: "/v1/projects/{project_id}/approvals/{approval_id}/approve"
tags: ["Approvals"]
---

# Approve an approval item

`POST /v1/projects/{project_id}/approvals/{approval_id}/approve`

## Request

- Base URL: `https://api.naturali.ai`
- URL: `https://api.naturali.ai/v1/projects/{project_id}/approvals/{approval_id}/approve`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://api.naturali.ai/authorize`, token URL `https://api.naturali.ai/token`, scopes: `mcp:access`)

Approves the item. Optionally supply edited `arguments` to replace the proposed arguments (edit-then-approve); the original is preserved on the item. Expiry is re-checked at decision time — an expired item can never be approved.

## Path parameters

- `approval_id` string, required

## Request body

- object
  - `arguments` object: Edited arguments to execute instead of the proposed ones. Editing composes a new call, so it additionally requires permission to make that call: `tools:CallTool` on the tool, and for a `builtin` proposal the action's own IAM action.
  - `tool_context` object: Key-value pairs forwarded as context headers on the approved action and on every tool call of the continuation turn, as on a generation's `tool_context`. Never stored on the item: supply it on the approve call itself, for a tool that authorizes with a credential minted at decision time. The server-pinned identity keys (`session_id`, `actor_id`, `actor_external_id`) are dropped. An invalid or colliding key is rejected with `400 INVALID_TOOL_CONTEXT_KEY` and nothing is resolved.

## Response `200`

Approval item approved

- ApprovalItem
  - `id` string
  - `project_id` string
  - `origin` 'node' | 'tool_call' | 'task_transition': How the item was produced (analytics/filtering only)
  - `status` 'pending' | 'approved' | 'rejected' | 'expired'
  - `proposed_action` object, nullable: The frozen proposed action. Null for producers whose proposal is not a tool call (a `task_transition` item gates a workflow transition, named by `task_transition`).
    - `tool_id` string
    - `action` string: Resolved action name (the action name) for `tool_call`-origin items — always present there, even for single-action tools. Omitted for `node`-origin items, whose downstream execution is wired by a separate `tool` node in the graph.
    - `arguments` object
  - `reasoning` string, nullable: The proposing agent's rationale
  - `evidence` object, nullable: Supporting structured data
  - `predicted_impact` string, nullable: Expected execution effect
  - `expires_at` string, date-time: Server-enforced hard gate; the item can never execute after this
  - `dedup_key` string, nullable: Set on tool-call items to suppress duplicate proposals
  - `orchestration_run_id` string, nullable: Originating orchestration run (node producer)
  - `node_id` string, nullable: Originating node id within the run's graph
  - `generation_id` string, nullable: Originating generation (tool-call producer)
  - `session_id` string, nullable: Session the originating generation ran in (tool-call producer)
  - `agent_id` string, nullable: Proposing agent
  - `task_id` string, nullable: Gated task (task_transition producer)
  - `task_transition` string, nullable: Transition fired on approval (task_transition producer)
  - `policy_version` string, nullable
  - `previous_item_id` string, nullable: Prior item's ID when this proposal was re-filed after an earlier matching item (same dedup_key) had been rejected
  - `resolved_by_principal_type` 'user' | 'api_key' | 'agent' | 'automation' | 'approval' | 'system': What kind of principal acted. `user` and `api_key` are authenticated callers, and the id is the user's (`user_…`) or the key's own (`key_…`), so a record names which key acted rather than its owner. `agent` is an agent writing under its own identity (`agent_…`). `automation` is the task engine acting on a dispatch outcome and `approval` an approval resolution; neither carries an id. `system` is the platform itself, with an id naming the mechanism (`retention_sweep`, `zero_retention`).
  - `resolved_by_principal_id` string, nullable: Public id of that principal: the user (`user_…`), or for API key auth the key itself (`key_…`), so the record names which key acted.
  - `resolution_reason` string, nullable: Required on rejection
  - `edited_arguments` object, nullable: Set on edit-then-approve
  - `created_at` string, date-time
  - `updated_at` string, date-time

## Other responses

- `400`: Edited arguments are not a JSON object, or do not satisfy the tool's parameters schema; or a `tool_context` key is not a valid header name
- `401`: Unauthorized
- `403`: Forbidden — including an edit by a caller who may resolve the item but not make the call it proposes
- `404`: Approval item not found
- `409`: Item already resolved or expired

---

[API](https://skmtc.dev/naturali/apis/naturali-ai-api.md) · [All operations](https://skmtc.dev/naturali/apis/naturali-ai-api/llms.txt) · [OpenAPI document](https://skmtc.dev/naturali/apis/naturali-ai-api/revisions/e4905b78f422?raw)
