Approvals
Bots read freely, but anything that sends, creates, changes or deletes outside Lex waits for a person to approve it. Each tool has a risk class:
| Risk | Examples | Default |
|---|---|---|
read | Fetch emails, list channels | Runs on its own |
write | Send an email, create an issue | Asks first |
high_impact | Delete a repository | Always asks, even if a policy allows auto-run |
Workspace admins can change the defaults per connector with policies: block tools, always ask, or let specific write tools run on their own.
In a chat
When a run needs approval, the stream sends approval.required and then run.waiting:
{ "approvalId": "apr_1", "callId": "call_9", "toolSlug": "GMAIL_SEND_EMAIL",
"risk": "write", "summary": "Send email to bob@example.com", "expiresAt": "2026-10-22T10:15:00.000Z" }Show the summary to the user and send their decision:
POST /approvals/apr_1/decision
x-lex-client: my-app
Content-Type: application/json
{ "decision": "approve" }decision is approve or reject. The run resumes on its own: a rejected tool reports tool.completed with status: "rejected" and the bot carries on without it.
GET /approvals/pending lists everything waiting on the signed-in user, across all bots.
Guarantees
- Only the requester decides. Other users get
404 NOT_FOUND. - Bound to the exact action. An approval covers one tool call with one set of arguments. If the arguments change, it no longer applies.
- Single use. Once used, an approval can't run the action again.
- Expires. Undecided approvals expire at
expiresAt; the tool then completes withstatus: "expired".
Calling tools directly
POST /integrations/tools/{toolSlug}/execute runs a tool outside a chat. A write tool answers 409 APPROVAL_REQUIRED with details.approvalId; approve it as above, then repeat the same request with that approvalId:
{ "arguments": { "to": "bob@example.com", "body": "Hello" }, "approvalId": "apr_1" }