Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.alterauth.com/llms.txt

Use this file to discover all available pages before exploring further.

By the end of this guide, an agent or backend service can issue a third-party call that pauses for explicit human approval before executing. The approver clicks a link, reviews the request, approves or denies, and the application gets the eventual result. The flow:
  1. An agent or backend issues proxy_request() for a sensitive call.
  2. Alter routes through the approval pipeline and returns a PendingApproval immediately.
  3. The application surfaces the approval link to the designated approver (in-app, by email, in chat).
  4. The approver approves or denies in the Wallet.
  5. The application polls or awaits the outcome and receives the eventual provider response (or an ApprovalDeniedError).

Prerequisites

  • An app with an approval-enabled grant or agent. Approvals are configured on the policy attached to the grant or agent.
  • A way to surface the approval URL to the approver — most apps render it as a notification, send a DM, or open a modal.

Walkthrough

1. Configure the approval policy

In the developer portal: pick the agent or managed-secret grant the approval should gate, open Policy → Require approval, and add the approver(s). Each call against the policy now pauses for approval.

2. Issue the request via proxy_request

proxy_request is the variant of request that runs through the approval pipeline. If the policy requires approval, the call returns a PendingApproval instead of a provider response:
from alter_sdk import Agent, HttpMethod
from alter_sdk.models import PendingApproval

agent = Agent(api_key=os.environ["AGENT_API_KEY"])

pending = await agent.proxy_request(
    HttpMethod.POST,
    "https://api.stripe.com/v1/refunds",
    grant_id=STRIPE_GRANT_ID,
    json={"charge": "ch_abc"},
    reason="Customer requested refund per ticket #1234",
)

if isinstance(pending, PendingApproval):
    # Surface pending.approval_url to the approver
    await notify_approver(pending.approval_url)
The reason field is recorded in the audit log and shown to the approver, alongside the request payload.

3. Wait for the decision

Two surfaces for collecting the outcome: Await inline — block until approved/denied/expired (with a timeout):
from alter_sdk import ApprovalDeniedError, ApprovalExpiredError, ApprovalTimeoutError

try:
    result = await agent.await_approval(pending.approval_id, timeout=300)
    # result.status_code is the provider response status
    # result.body_json() decodes the response body
    refund = result.body_json()
except ApprovalDeniedError as e:
    log.info("Approver denied", reason=e.details)
except ApprovalExpiredError:
    log.info("Approval window elapsed before decision")
except ApprovalTimeoutError:
    # The SDK gave up waiting; the row may still be pending on the backend
    pass
Poll — for long-running approvals (hours, days), persist pending.approval_id and poll status from a worker:
status = await agent.get_approval_status(approval_id)
if status.is_terminal:
    handle_terminal(status)

Patterns

The PendingApproval.approval_url is a deep link to the Wallet’s approval view. Most apps render it as:
  • An in-app banner with Approve / Deny buttons opening the URL.
  • A Slack DM to the configured approver.
  • A push notification with the URL as the action.
  • An email (when an email provider is configured at the app level; otherwise the URL is the only delivery).
The URL is signed and short-lived; treat it as semi-public — anyone with the URL can act on it during its lifetime.

Distinguishing pending from immediate

proxy_request may return either PendingApproval (policy required approval) or an ApprovalResult (no approval needed; the call ran synchronously). Branch on the return type:
if isinstance(result, PendingApproval):
    handle_async(result)
else:
    handle_immediate(result)  # ApprovalResult — body is the provider response
This means an approval policy can be enabled or disabled per-grant without changing application code; the call site handles both paths.

Recording why the approval was requested

reason is the single most useful field for the approver. Write a concrete sentence — “Refund $X to customer @ for ticket #Y” — not a generic “agent action.” The audit log preserves the reason permanently.

Troubleshooting

ErrorLikely causeFix
ApprovalDeniedErrorThe approver clicked Deny.e.details includes the approver’s reason if provided.
ApprovalExpiredErrorThe approval window elapsed (default 1 hour, configurable on the policy).Re-issue the call. Adjust the policy’s window if too short.
ApprovalTimeoutErrorThe SDK’s local wait timed out; the row may still be pending.Persist approval_id and re-poll.
ApprovalExecutionFailedErrorThe approver approved, but the provider call failed at execution time (grant revoked between approval and execution, provider error, etc.).Inspect e.details.
PendingApproval never resolvesNo approver was notified.Verify the policy’s approver list and the application’s notification path.

What’s next

Policies

Approval is one policy type among several.

Wallet

Where approvers see and act on requests.

Errors

The full approval error hierarchy.