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

# Approvals

# Approvals Guide

This guide covers how to configure policies that require human approval, how approvals appear in the dashboard, and how to resolve them from different surfaces.

***

## Prerequisites

* A running Svantic deployment with Gateway, Mesh, and Dashboard
* A tenant with at least one registered agent
* Dashboard access for the tenant

***

<Steps>
  <Step title="Create a Policy That Requires Approval">
    Svantic ships with default policies for common guard types. To create a custom policy with `require_approval` enforcement:

    ```bash theme={null}
    curl -X POST https://your-gateway/internal/policies/new \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "bulk_ops_approval",
        "description": "Require human approval for bulk operations exceeding 10 items",
        "scope_type": "tool",
        "enforcement": "require_approval",
        "evaluator": {
          "plugin": "bulk_operation",
          "config": { "threshold": 10 }
        }
      }'
    ```

    When an agent invokes a tool matching the bulk operation pattern with more than 10 items, the policy engine returns `require_approval` and the message enters `input-required`.
  </Step>

  <Step title="Trigger an Approval">
    Send a message that triggers the policy:

    ```bash theme={null}
    curl -X POST https://your-mesh/send \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "jsonrpc": "2.0",
        "id": "1",
        "method": "message/send",
        "params": {
          "message": {
            "role": "user",
            "parts": [{ "kind": "text", "text": "Delete all expired records from the staging database" }],
            "contextId": "session-123"
          }
        }
      }'
    ```

    The response indicates the task is in `input-required`:

    ```json theme={null}
    {
      "jsonrpc": "2.0",
      "id": "1",
      "result": {
        "id": "task-uuid-456",
        "status": {
          "state": "input-required",
          "message": {
            "role": "agent",
            "parts": [
              {
                "kind": "data",
                "data": {
                  "type": "Form",
                  "props": {
                    "title": "Approval Required",
                    "description": "bulk_delete_expired requires approval.",
                    "fields": [{ "id": "decision", "type": "action", "options": [
                      { "value": "approve", "label": "Approve" },
                      { "value": "deny", "label": "Deny" }
                    ]}]
                  }
                },
                "metadata": { "mimeType": "application/json+a2ui" }
              }
            ]
          }
        }
      }
    }
    ```
  </Step>

  <Step title="View Pending Approvals">
    ### From the Dashboard

    Navigate to **Security → Approval Queue**. Pending approvals appear as actionable cards with the A2UI form rendered — Approve and Deny buttons for simple approvals, full forms for structured input.

    ### From the API

    ```bash theme={null}
    curl -X POST https://your-gateway/messages/pending \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -d '{ "tenant_id": "your-tenant" }'
    ```

    Response:

    ```json theme={null}
    {
      "messages": [
        {
          "message_id": "task-uuid-456",
          "session_id": "session-123",
          "state": "input-required",
          "user_input": "Delete all expired records from the staging database",
          "task_data": { "status": { "state": "input-required", "..." : "..." } },
          "created_at": "2026-04-07T14:30:00Z"
        }
      ]
    }
    ```
  </Step>

  <Step title="Resolve the Approval">
    ### From the Dashboard

    Click **Approve** or **Deny** in the Approval Queue. The resolution is sent immediately and the agent resumes (or receives a denial).

    ### From the API

    ```bash theme={null}
    curl -X POST https://your-gateway/messages/task-uuid-456/resolve \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "resolution": { "decision": "approve" }
      }'
    ```

    ### From Slack

    If a Slack notification channel is configured and linked to the policy, the Slack message includes Approve/Deny buttons. Clicking a button sends the resolution through Svantic's `/webhooks/receive/slack` endpoint.

    ### From a Webhook Callback

    If a webhook notification channel is configured and linked to the policy, the outbound notification includes a `callback_token`. Use it to resolve without a JWT:

    ```bash theme={null}
    curl -X POST https://your-gateway/messages/task-uuid-456/resolve \
      -H "Content-Type: application/json" \
      -d '{
        "callback_token": "token-from-notification",
        "resolution": { "decision": "approve" }
      }'
    ```
  </Step>

  <Step title="Permanently Allow a Tool">
    If you're tired of approving the same tool every session, update the policy:

    ```bash theme={null}
    curl -X POST https://your-gateway/internal/policies/update \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "policy_id": "bulk_ops_approval",
        "enforcement": "allow"
      }'
    ```

    This changes the policy from `require_approval` to `allow` — the tool will execute without asking. To re-enable approval, update the enforcement back to `require_approval`.
  </Step>
</Steps>

***

## Structured Input (Beyond Approve/Deny)

Not all approvals are yes/no decisions. An agent may need the user to fill in a form — credentials, categorization choices, configuration parameters.

When the A2UI spec contains multiple fields, the resolution includes values for each field:

```bash theme={null}
curl -X POST https://your-gateway/messages/task-uuid-789/resolve \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "resolution": {
      "username": "john@example.com",
      "password": "s3cret",
      "remember_me": true
    }
  }'
```

The field IDs in the resolution must match the field IDs in the A2UI spec.

***

## Race Conditions

If the same approval is resolved from multiple surfaces simultaneously (e.g., dashboard and Slack), the first resolution wins. Subsequent attempts receive `409 Conflict`.

***

## Further Reading

* [Long-Running Operations](./long-running-operations) — the underlying mechanism that powers approvals (pause, resolve, resume)
* [Policies Guide](./policies) — create and manage the rules that trigger approvals
* [Notification Channels](./channels) — configure where approval notifications are delivered (email, Slack, webhook, SMS, phone)
* [A2UI](../concepts/a2ui) — the UI specification format for forms and approvals
* [Guardrails & Safety](../concepts/guardrails) — the safety layers that trigger approvals
* [Notifications](../concepts/notifications) — how the notification system works
