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.

Approvals let an operator gate specific agent calls behind explicit human confirmation — a Slack message, a portal click, or a Wallet approval. The agent doesn’t make the call until a human approves. Approvals are an opt-in policy. Most agent calls flow straight through. Use approvals for calls that move money, send messages to customers, or delete data — anything where retroactive auditing isn’t enough and a stop point in the path is needed.

The flow

1

Agent submits a request that requires approval

proxy_request() returns a PendingApproval instead of an ApprovalResult. The pending approval carries an approval_id, a URL where the human can decide, and an expiry.
2

Approver decides

The human visits the approval URL (in Wallet, the developer portal, or wherever the operator team routes it) and approves or denies.
3

Agent picks up the result

await_approval(approval_id) blocks (with a timeout) until the human decides and the proxied call executes, or get_approval_status(approval_id) polls. The eventual return value is an ApprovalResult carrying the upstream response, or one of ApprovalDeniedError / ApprovalExpiredError / ApprovalExecutionFailedError / ApprovalTimeoutError.

Submitting a request that may require approval

from alter_sdk import PendingApproval

result = await vault.proxy_request(
    HttpMethod.POST,
    "https://api.stripe.com/v1/refunds",
    grant_id=STRIPE_GRANT_ID,
    json_body={"charge": "ch_...", "amount": "10000"},
    reason="Customer requested refund (ticket #4421)",
)

if isinstance(result, PendingApproval):
    print("Awaiting approval at:", result.approval_url)
    final = await vault.await_approval(result.approval_id, timeout=600.0)
else:
    final = result  # No approval required by policy

print(final.status_code, final.body_text())
reason is mandatory for approvals — it’s what the approver sees on the decision screen. The body comes back base64-encoded on body_b64 / bodyB64; use the body_text() / body_json() / body_bytes() helpers to decode it.

Polling vs awaiting

  • await_approval(id, timeout=...) blocks the calling task until the proxied call has executed and a result is available. Best for synchronous agent flows where waiting is acceptable.
  • get_approval_status(id) returns a snapshot immediately. The provider response is NOT on ApprovalStatus — once status.has_result is true (terminal status executed), call await_approval to fetch the ApprovalResult.
status = await vault.get_approval_status(approval_id)
if status.has_result:
    final = await vault.await_approval(approval_id)
    print(final.status_code, final.body_text())

Outcomes

StatusWhat it meansException (on await_approval)
executedHuman approved AND the upstream call executed successfully(returns ApprovalResult)
executingApproved; backend is currently running the upstream call(transient — keep waiting)
approvedApproved but not yet executed(transient — keep waiting)
deniedHuman denied; upstream call never ranApprovalDeniedError
expiredApproval window passed before a human decidedApprovalExpiredError
failedApproved, but the upstream call itself failedApprovalExecutionFailedError
pendingNo decision yet; keep polling or wait(n/a — await_approval keeps waiting)

Configuring approval policies

Approval policies are configured per-app in the developer portal under Policies → Approvals. Approvals can be required based on:
  • HTTP method (e.g., all DELETE and PUT).
  • URL pattern (e.g., /v1/refunds).
  • Caller (e.g., specific agents).
  • Cost-based heuristics (configurable per provider).
See Reference → Policies for the policy model.

See also