What are Approvals?

Approvals are human-in-the-loop workflows that require manual review before an AI agent can execute sensitive or high-risk actions. They provide a safety mechanism to prevent agents from making irreversible or costly mistakes.

How Approvals Work

1

Agent Requests Action

Your AI agent calls guard.check() for an action that requires approval
2

Approval Request Created

AgentWarden creates an approval request and returns requires_approval: true
3

Human Reviews

An authorized user reviews the request in the dashboard
4

Decision Made

The user approves or denies the request
5

Agent Proceeds (or Not)

Your application polls for the decision and acts accordingly

When to Use Approvals

Consider requiring approval for actions that:

High Financial Impact

Large transactions, refunds over a threshold, subscription cancellations

Irreversible Changes

Data deletion, account termination, production deployments

External Communications

Mass emails, social media posts, customer notifications

Security Sensitive

Permission changes, access grants, credential updates

Configuring Approvals

Via Dashboard

When creating or editing a permission:
1

Open Permission

Navigate to your agent and click on a permission (or create a new one)
2

Toggle Approval

Enable the Requires Approval toggle
3

Save

Save the permission

Via API

curl -X POST https://api.agentwarden.io/api/agents/AGENT_ID/permissions \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "deploy.production",
    "requires_approval": true
  }'

SDK Integration

Basic Pattern

from agentwarden import AgentWarden

guard = AgentWarden(api_key="your-api-key")

# Check if action is allowed
result = guard.check(
    agent_id="devops-agent",
    action="deploy.production",
    context={
        "version": "v2.1.0",
        "environment": "production",
        "requested_by": "ai-agent"
    }
)

if result.allowed:
    # No approval needed - proceed immediately
    deploy_to_production()
    guard.log("devops-agent", "deploy.production", "success")
    
elif result.requires_approval:
    # Approval required - wait for human decision
    print("⏳ Deployment requires approval")
    approval_id = result.approval_id
    
    # Notify admins
    notify_admins(
        title="Production Deployment Approval Needed",
        details=f"Version {version} waiting for approval",
        approval_url=f"https://app.agentwarden.io/approvals/{approval_id}"
    )
    
    # Queue the deployment for later
    queue_deployment(version="v2.1.0", approval_id=approval_id)
    
else:
    # Action blocked for other reasons
    print(f"🚫 Deployment blocked: {result.reason}")

Polling for Approval

If your application needs to wait for approval:
import time

def wait_for_approval(approval_id, timeout=3600):
    """
    Poll for approval decision with timeout
    
    Args:
        approval_id: The approval request ID
        timeout: Maximum seconds to wait (default 1 hour)
    
    Returns:
        True if approved, False if denied or timeout
    """
    start_time = time.time()
    
    while time.time() - start_time < timeout:
        # Check approval status
        status = guard.get_approval_status(approval_id)
        
        if status == "approved":
            return True
        elif status == "denied":
            return False
        
        # Wait 30 seconds before checking again
        time.sleep(30)
    
    # Timeout reached
    return False

# Usage
if result.requires_approval:
    print("⏳ Waiting for approval...")
    
    if wait_for_approval(result.approval_id):
        print("✅ Approved! Proceeding...")
        deploy_to_production()
        guard.log("devops-agent", "deploy.production", "success")
    else:
        print("❌ Denied or timeout")
        guard.log("devops-agent", "deploy.production", "denied")
Instead of polling, use webhooks to be notified when a decision is made:
from flask import Flask, request

app = Flask(__name__)

@app.route('/webhooks/approval-decision', methods=['POST'])
def handle_approval_decision():
    data = request.json
    
    approval_id = data['approval_id']
    decision = data['decision']  # 'approved' or 'denied'
    action = data['action']
    context = data['context']
    
    if decision == 'approved':
        # Execute the pending action
        if action == 'deploy.production':
            deploy_to_production(context['version'])
        elif action == 'stripe.refund':
            process_refund(context['amount'])
    else:
        # Log that it was denied
        print(f"Action {action} was denied")
    
    return {'status': 'ok'}
Webhook support is available on Business and Enterprise plans. Configure webhooks

Approval Dashboard

Viewing Pending Approvals

1

Navigate to Dashboard

Click Dashboard or Approvals in the sidebar
2

View Pending

See all pending approval requests
3

Review Details

Click on a request to see full context

Approval Details

Each approval request shows:
  • Agent that requested the action
  • Action being requested
  • Context provided (amounts, parameters, etc.)
  • Timestamp of the request
  • Requester (if available)

Making a Decision

  1. Review the request details
  2. Click Approve
  3. Optionally add a comment
  4. Confirm approval

Approval Context

Always provide rich context when requesting approval:

Good Context Example

result = guard.check(
    agent_id="customer-support-bot",
    action="stripe.refund.large",
    context={
        "amount": 500.00,
        "customer_id": "cus_ABC123",
        "customer_email": "john@example.com",
        "order_id": "ord_XYZ789",
        "reason": "Defective product",
        "requested_by": "support-agent-42",
        "conversation_url": "https://zendesk.com/tickets/12345"
    }
)

Poor Context Example

result = guard.check(
    agent_id="customer-support-bot",
    action="stripe.refund.large",
    context={
        "amount": 500.00  # Not enough context to make a decision!
    }
)
The reviewer needs enough information to make an informed decision. Include:
  • Who is affected (customer ID, email)
  • What is being done (amount, resource)
  • Why it’s needed (reason, ticket link)
  • When it was requested (timestamp)
  • Where to find more info (links)

Approval Timeouts

You can set timeouts for approval requests:
# In your application logic
APPROVAL_TIMEOUT = 3600  # 1 hour

if result.requires_approval:
    approval_id = result.approval_id
    created_at = time.time()
    
    # Store approval request with timeout
    store_approval_request({
        'id': approval_id,
        'expires_at': created_at + APPROVAL_TIMEOUT,
        'action': 'deploy.production',
        'context': context
    })
Automatic approval timeouts are coming soon as a built-in feature.

Approval Workflows by Use Case

E-commerce Refunds

# Tiered refund approval
def request_refund(customer_id, amount, reason):
    # Small refunds - auto-approve
    if amount <= 50:
        action = "stripe.refund.small"
        # No approval needed
    
    # Medium refunds - auto-approve
    elif amount <= 200:
        action = "stripe.refund.medium"
        # No approval needed
    
    # Large refunds - require approval
    else:
        action = "stripe.refund.large"
        # Approval required
    
    result = guard.check(
        agent_id="support-bot",
        action=action,
        context={
            "amount": amount,
            "customer_id": customer_id,
            "reason": reason
        }
    )
    
    if result.requires_approval:
        notify_finance_team(customer_id, amount, reason)
        return "pending_approval"
    elif result.allowed:
        process_refund(customer_id, amount)
        return "approved"
    else:
        return "denied"

DevOps Deployments

def deploy(environment, version):
    action = f"deploy.{environment}"
    
    result = guard.check(
        agent_id="devops-agent",
        action=action,
        context={
            "environment": environment,
            "version": version,
            "commit": get_git_commit(),
            "tests_passed": run_tests(),
            "deployed_by": "automation"
        }
    )
    
    if result.requires_approval:
        # Production deploys need approval
        slack_notify(
            channel="#deployments",
            message=f"🚀 Production deployment of {version} requires approval"
        )
        return "pending"
    
    elif result.allowed:
        # Staging deploys proceed automatically
        execute_deployment(environment, version)
        return "deployed"

Content Moderation

def delete_user_content(user_id, content_id, reason):
    result = guard.check(
        agent_id="moderation-bot",
        action="content.delete",
        context={
            "user_id": user_id,
            "content_id": content_id,
            "reason": reason,
            "flagged_by": "ai_moderation",
            "content_url": f"https://app.com/content/{content_id}"
        }
    )
    
    if result.requires_approval:
        # Borderline cases need human review
        create_moderation_ticket(user_id, content_id, reason)
        return "under_review"
    
    elif result.allowed:
        # Clear violations can be auto-deleted
        delete_content(content_id)
        notify_user(user_id, reason)
        return "deleted"

Approval Metrics

Track approval metrics in your dashboard:
  • Approval Rate: % of requests approved vs denied
  • Average Response Time: How long approvals take
  • Pending Count: Current backlog
  • Top Actions: Which actions require most approvals

View Metrics

See approval analytics in your dashboard

Best Practices

Always include detailed context in approval requests. The reviewer should be able to make a decision without additional research.
Don’t set approval timeouts too short. Consider time zones and business hours when setting SLAs.
Send approval notifications to people who can actually make the decision. Use role-based routing.
Create automatic approval for low-risk variants and require approval only for high-risk ones.
Regularly review approval patterns. If you’re always approving certain requests, consider making them automatic.
When an approval is denied, provide clear feedback to the user and log the decision.
Have a plan for what happens if an approval times out or is denied.

Security Considerations

Who Can Approve?

By default, all users with admin role can approve requests. On Enterprise plans, you can configure:
  • Role-based approvals
  • Multi-level approvals (2+ approvers required)
  • Specific approvers for specific actions

Approval Audit Trail

All approval decisions are logged with:
  • Who made the decision
  • When it was made
  • The decision (approve/deny)
  • Any comments provided
  • IP address of the approver
This creates a complete audit trail for compliance.

Approval Notifications

Configure where approval notifications are sent:
  • Email
  • Slack
  • SMS (Enterprise)
  • Webhook to your system

Configure Notifications

Set up approval notifications

Advanced: Conditional Approvals

Some use cases require approvals only under certain conditions:
def smart_approval_check(amount, customer_lifetime_value):
    """
    Require approval based on business logic
    """
    # High-value customers get higher auto-approval limits
    if customer_lifetime_value > 10000:
        threshold = 500
    else:
        threshold = 100
    
    # Determine if approval is needed
    if amount > threshold:
        action = "stripe.refund.requires_approval"
    else:
        action = "stripe.refund.auto_approve"
    
    return guard.check(
        agent_id="support-bot",
        action=action,
        context={
            "amount": amount,
            "customer_ltv": customer_lifetime_value
        }
    )

FAQ

Requests remain pending indefinitely unless you implement timeouts in your application logic. We recommend setting reasonable timeouts.
Not currently, but this is on our roadmap. For now, use different permissions with different approval requirements.
Yes, all approval decisions include the user who made the decision and when.
Multi-level approvals are available on Enterprise plans. Contact sales for details.
You can manually deny the request in the dashboard, or implement auto-cancellation after a timeout in your code.

Next Steps