Approvals
Human-in-the-loop for sensitive agent actions. When an agent attempts a configured action, it pauses, snapshots its state, fires a webhook, and waits for an approve or deny decision.
How it works
- 1.Agent executes a task and reaches a configured action (e.g.
send_email). - 2.Theazo intercepts the action, pauses the agent, and creates a snapshot of its full state.
- 3.An
approval.requestedwebhook fires to your server with the action name and parameters. - 4.Your team reviews and calls
theazo.approvals.approve(id)ortheazo.approvals.deny(id). - 5.On approval, the agent resumes from the snapshot. On denial, it receives an error and can handle it gracefully.
Configuring approvals on an agent
Pass an approvals config when creating the agent. List the action names that require gating.
import { Theazo } from class="cb-str">'theazo'
const theazo = new Theazo({ apiKey: class="cb-str">'th_live_...' })
const session = await theazo.sessions.forUser(class="cb-str">'user_123')
const agent = await session.agents.create({
name: class="cb-str">'outreach-agent',
tools: [class="cb-str">'send_email', class="cb-str">'update_crm', class="cb-str">'web_search'],
approvals: {
require: [class="cb-str">'send_email', class="cb-str">'update_crm'],
timeout: class="cb-str">'24h', class="cb-cmt">// how long to wait before defaultAction fires
defaultAction: class="cb-str">'deny', class="cb-cmt">// 'deny' | 'approve' — what happens on timeout
},
})
// Agent runs normally; approval-gated actions pause automatically
const result = await agent.run(class="cb-str">'Draft and send a follow-up email to the Stripe lead')Approval webhooks
Configure a webhook endpoint in your dashboard or via API. Theazo will POST to it whenever an approval event occurs.
approval.requestedAn agent hit a gated action and is waiting for a decision. Payload includes approvalId, agentId, action name, and the full parameters the agent intends to use.
approval.approvedA human approved the action. The agent has resumed execution.
approval.deniedA human denied the action (or it timed out with defaultAction: deny). The agent received the denial and can continue or stop.
approval.timeoutThe approval window expired. defaultAction was applied automatically.
// Example: Next.js route handler
export async function POST(req: Request) {
const event = await req.json()
if (event.type === class="cb-str">'approval.requested') {
const { approvalId, agentId, action, params } = event.data
class="cb-cmt">// Send Slack message, email, etc.
await slack.send({
channel: class="cb-str">'#agent-approvals',
text: class="cb-str">`Agent ${agentId} wants to ${action}:`,
blocks: buildApprovalBlocks(approvalId, action, params),
})
}
return new Response(class="cb-str">'ok')
}Listing pending approvals
// All pending approvals for your platform
const pending = await theazo.approvals.list({ status: class="cb-str">'pending' })
// Filter to a specific user's session
const userPending = await theazo.approvals.list({
status: class="cb-str">'pending',
userId: class="cb-str">'user_123',
})
// Approval object shape:
// {
// id: 'apr_abc123',
// agentId: 'agt_xyz',
// sessionId: 'ses_...',
// userId: 'user_123',
// action: 'send_email',
// params: { to: 'ceo@acme.com', subject: '...', body: '...' },
// requestedAt: '2024-01-15T10:23:00Z',
// expiresAt: '2024-01-16T10:23:00Z',
// status: 'pending',
// }Approve and deny
Approve
Approve with optional modifications to override the parameters the agent intended to use. This lets reviewers correct values before the action executes.
// Approve as-is
await theazo.approvals.approve(class="cb-str">'apr_abc123')
// Approve with modifications — override the params the agent will use
await theazo.approvals.approve(class="cb-str">'apr_abc123', {
modifications: {
to: class="cb-str">'approvedrecipient@acme.com',
subject: class="cb-str">'[Reviewed] ' + originalSubject,
},
})Deny
await theazo.approvals.deny(class="cb-str">'apr_abc123', {
reason: class="cb-str">'Email tone is too aggressive. Revise before sending.',
})
// The agent receives the denial reason and can adapt:
// "I was denied because: Email tone is too aggressive..."
// The agent may then revise and try again, or stop.Bulk decisions
For high-volume workflows, approve or deny multiple approvals in a single call.
// Approve a batch
await theazo.approvals.bulkApprove([
class="cb-str">'apr_001',
class="cb-str">'apr_002',
class="cb-str">'apr_003',
])
// Deny a batch with shared reason
await theazo.approvals.bulkDeny(
[class="cb-str">'apr_004', class="cb-str">'apr_005'],
{ reason: class="cb-str">'Out of scope for this campaign.' }
)End-to-end example
import { Theazo } from class="cb-str">'theazo'
const theazo = new Theazo({ apiKey: class="cb-str">'th_live_...' })
const session = await theazo.sessions.forUser(class="cb-str">'user_456')
// Create agent with approval gates
const agent = await session.agents.create({
name: class="cb-str">'crm-updater',
tools: [class="cb-str">'web_search', class="cb-str">'read_file', class="cb-str">'update_crm', class="cb-str">'send_email'],
approvals: {
require: [class="cb-str">'update_crm', class="cb-str">'send_email'],
timeout: class="cb-str">'8h',
defaultAction: class="cb-str">'deny',
},
})
// Start the agent — it will pause when it tries to update_crm or send_email
const runPromise = agent.run(
class="cb-str">'Research Acme Corp and update their CRM record, then send a follow-up email to their CEO'
)
// Meanwhile, your webhook handler received 'approval.requested'
// Your UI shows the approval to the reviewer
// Reviewer clicks approve:
const pending = await theazo.approvals.list({
status: class="cb-str">'pending',
userId: class="cb-str">'user_456',
})
for (const approval of pending) {
console.log(class="cb-str">`Action: ${approval.action}`)
console.log(class="cb-str">`Params: ${JSON.stringify(approval.params, null, 2)}`)
class="cb-cmt">// Approve each one — agent resumes after last approval resolves
await theazo.approvals.approve(approval.id)
}
// Agent resumes and completes
const result = await runPromise
console.log(result.output)
console.log(result.cost) class="cb-cmt">// { amount: 340, currency: 'usd' }API reference
theazo.approvals.list({ status, userId, agentId })Promise<Approval[]>List approvals. Filter by status (pending | approved | denied | expired) and optionally by userId or agentId.theazo.approvals.get(approvalId)Promise<Approval>Fetch a single approval by ID.theazo.approvals.approve(id, { modifications? })Promise<void>Approve an action. Optionally override params before execution.theazo.approvals.deny(id, { reason? })Promise<void>Deny an action. The reason is passed to the agent.theazo.approvals.bulkApprove(ids)Promise<void>Approve multiple approvals in one call.theazo.approvals.bulkDeny(ids, { reason? })Promise<void>Deny multiple approvals with an optional shared reason.