Skip to main content

Approvals

The human decisions a run is waiting on, and the calls that let it continue.

Overview​

An approval item is a frozen proposal: something an agent or an orchestration wants to do, held until a person says yes or no. Approving lets the paused work continue with the proposed action; rejecting stops it and records why.

Three things arrive with the proposal and matter more than the queue itself — the agent's reasoning, its evidence, and its predicted_impact. They are the whole point: a reviewer who has to reconstruct what the agent was trying to do will approve on reflex, which is the same as having no gate.

This module is a verbatim mirror of the runtime: every field, method, status code and error shape is the runtime's own, re-rooted under the project in the path.

Included from the Pro plan

Approvals are sold with guardrails and are part of the Pro plan and above. On a project whose billing owner is on a lower rung, listing them and listing recurrences answer 403 plan_feature_not_included with the plan and the feature in details, (no formation resource type declares an approval — the runtime writes them). The plan is the project owner's, not the caller's.

An approval that already exists stays settleable on every rung. A generation waiting on one is waiting on this project and on nothing else, so GET, POST …/approve and POST …/reject answer whatever the plan — otherwise a project that drops below the rung leaves the generation parked until the runtime expires it. Its id reaches such a project through the activity feed, which is on every rung — kind=approval_created carries it in ref_id from the moment the approval is raised. Settling still needs membership of the project; only the plan gate is lifted. Recurrences are not included, because a recurrence configures approvals still to come rather than settling one that exists.

See the OpenAPI spec for the full endpoint and schema reference, or browse it rendered under API Reference → Approvals.

Data Model​

ApprovalItem​

FieldTypeDescription
idstringPublic item ID (apr_ prefix).
project_idstringThe owning project.
originstringnode, tool_call or task_transition — what produced it. Filtering and analytics only.
statusstringpending, approved, rejected or expired.
proposed_actionobject, nullableThe frozen action. Null when the proposal is not a tool call — a task_transition item names its transition instead.
reasoningstring, nullableThe proposing agent's rationale.
evidenceobject, nullableSupporting structured data.
predicted_impactstring, nullableThe expected effect of executing it.
expires_atstring (date-time)Hard gate — see Expiry is enforced, not advisory.
dedup_keystring, nullableSet on tool-call items to suppress duplicate proposals; also what groups a recurrence.
orchestration_run_idstring, nullableOriginating orchestration run (node origin).
node_idstring, nullableOriginating node in that run's graph.
generation_idstring, nullableOriginating generation (tool_call origin).
session_idstring, nullableThe session that generation ran in.
agent_idstring, nullableThe proposing agent.
task_idstring, nullableThe gated task (task_transition origin).
task_transitionstring, nullableThe transition that fires on approval.
policy_versionstring, nullableThe policy version in force when it was filed.
previous_item_idstring, nullableThe prior item, when this proposal was re-filed after a matching one was rejected.
resolved_bystring, nullableThe deciding user's public ID. Null on expiry.
resolution_reasonstring, nullableRequired on rejection.
edited_argumentsobject, nullableSet when the decision was edit-then-approve.
created_atstring (date-time)
updated_atstring (date-time)

ApprovalRecurrenceGroup​

FieldTypeDescription
dedup_keystringThe shared key that defines the group.
agent_idstring, nullableThe proposing agent, shared across the group.
tool_idstring, nullableThe proposed tool, shared across the group.
countintegerItems in the group.
chainarray of ApprovalItemThe items oldest → newest, following previous_item_id.
reasonsarray of stringThe chain's resolution reasons, in order.

Key Concepts​

Where the items come from​

origin says which producer filed the item, and it also tells you which of the correlation fields are populated:

  • tool_call — an agent proposed a tool call that needs sign-off. Carries generation_id, session_id, agent_id and a proposed_action. Gate a tool with guardrails approves one.
  • node — an orchestration reached a node that requires approval. Carries orchestration_run_id and node_id. Pause a run for a human decision approves one.
  • task_transition — a task transition is gated. Carries task_id and task_transition, and no proposed_action.

Edit, then approve​

POST /v1/projects/{project_id}/approvals/{approval_id}/approve takes optional arguments that replace what the agent proposed. The original stays on the item and the replacement is recorded as edited_arguments, so the diff between what was asked for and what was allowed is part of the record rather than lost in a decision.

Rejecting requires a reason, for the same reason: the next reviewer of the same recurring proposal needs to know why the last one said no.

Expiry is enforced, not advisory​

expires_at is re-checked at decision time. An item past it can never be approved — the answer is a 409, not a late success. That is what makes a stalled queue safe to leave alone: nothing waits indefinitely for a person who has stopped looking.

What keeps coming back​

GET /v1/projects/{project_id}/approvals/recurrences groups items by dedup_key and returns those that recur at least min_count times, most-recurrent first, each with its chain and the reasons given along it.

It answers a different question from the queue: not "what needs deciding" but "what am I deciding over and over". A pattern that has been rejected the same way five times is a rule waiting to be written, not a decision worth making a sixth time. Grouping is by exact key — no clustering — so it reports repetition rather than guessing at similarity.

Who may do what​

Every route needs any project member. The deciding user is recorded on the item.

Examples​

Read the pending queue​

naturali list-approvals \
--project-id proj_V1StGXR8Z5jdHi6B \
--status pending \
--limit 20

Read one proposal in full​

naturali get-approval \
--project-id proj_V1StGXR8Z5jdHi6B \
--approval-id apr_V1StGXR8Z5jdHi6B

Approve it, with a smaller amount than proposed​

naturali approve-approval \
--project-id proj_V1StGXR8Z5jdHi6B \
--approval-id apr_V1StGXR8Z5jdHi6B \
--arguments '{ "amount": 250 }'

Reject it, on the record​

naturali reject-approval \
--project-id proj_V1StGXR8Z5jdHi6B \
--approval-id apr_V1StGXR8Z5jdHi6B \
--reason "Refunds over 200 go through finance."

Find what you keep deciding​

naturali list-approval-recurrences \
--project-id proj_V1StGXR8Z5jdHi6B \
--min-count 3