Overview
Tool approval responses are submitted through the standard Add Session Thread Message endpoint. The approval decisions are included in thecontent
field of the message, alongside optional text feedback.
Request Format
Endpoint
Copy
POST /api/assistants/threads/{threadId}/messages
Authentication
Copy
const headers = {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
'X-Envole-User-Id': USER_ID
};
Basic Approval Response
Copy
const approvalResponse = {
content: [
{
type: 'tool_approval_result',
tool_approval_results: [
{
toolId: 'tool_123',
toolName: 'google_calendar_create_event',
toolProvider: 'GOOGLE_CALENDAR',
toolCategory: 'CALENDAR',
toolExecutionId: 'exec_123',
toolExecutionBatchId: 'batch_456',
toolMemoryId: 'mem_789',
toolArguments: {
event_title: 'Project Review Meeting',
start_time: '2024-01-15T14:00:00Z',
end_time: '2024-01-15T15:00:00Z',
attendees: 'john@company.com,mary@company.com',
location: 'Conference Room A'
},
approvalResult: 'ABORTED_WITH_FEEDBACK'
}
]
}
]
};
await fetch(`${API_BASE_URL}/api/assistants/threads/${threadId}/messages`, {
method: 'POST',
headers,
body: JSON.stringify(approvalResponse)
});
Approval Result Structure
Each tool approval result must include all fields from the original approval request:Copy
interface ToolApprovalResult {
toolId: string; // From original request
toolName: string; // From original request
toolProvider: string; // From original request
toolCategory: string; // From original request
toolExecutionId: string; // From original request
toolExecutionBatchId: string; // From original request
toolMemoryId: string; // From original request
toolArguments: object; // From original request
approvalResult: 'ABORTED_WITH_FEEDBACK' | 'DENIED' | 'ABORTED';
}
TOOL_EXECUTION_APPROVAL_REQUEST
event and only modify the approvalResult
field.
Batch Response Examples
Mixed Approval/Denial
Copy
const mixedBatchResponse = {
content: [
{
type: 'tool_approval_result',
tool_approval_results: [
{
toolId: 'tool_123',
toolName: 'send_email',
toolProvider: 'GMAIL',
toolCategory: 'EMAIL',
toolExecutionId: 'exec_123',
toolExecutionBatchId: 'batch_456',
toolMemoryId: 'mem_789',
toolArguments: {
to: 'user@company.com',
subject: 'Project Update',
body: 'Progress report attached'
},
approvalResult: 'ABORTED_WITH_FEEDBACK'
},
{
toolId: 'tool_124',
toolName: 'schedule_meeting',
toolProvider: 'GOOGLE_CALENDAR',
toolCategory: 'CALENDAR',
toolExecutionId: 'exec_124',
toolExecutionBatchId: 'batch_456',
toolMemoryId: 'mem_790',
toolArguments: {
title: 'Follow-up Meeting',
date: '2024-01-16T10:00:00Z'
},
approvalResult: 'DENIED'
}
]
}
]
};
Abort with Feedback
Copy
const abortWithFeedback = {
content: [
{
type: 'text',
text: 'The email recipient list includes external contacts that should not receive this confidential information. Please revise the distribution list.'
},
{
type: 'tool_approval_result',
tool_approval_results: [
{
toolId: 'tool_123',
toolName: 'send_email',
toolProvider: 'GMAIL',
toolCategory: 'EMAIL',
toolExecutionId: 'exec_123',
toolExecutionBatchId: 'batch_456',
toolMemoryId: 'mem_789',
toolArguments: {
to: 'external@competitor.com,internal@company.com',
subject: 'Confidential Project Update',
body: 'Internal strategy document attached'
},
approvalResult: 'ABORTED'
},
{
toolId: 'tool_124',
toolName: 'save_draft',
toolProvider: 'GMAIL',
toolCategory: 'EMAIL',
toolExecutionId: 'exec_124',
toolExecutionBatchId: 'batch_456',
toolMemoryId: 'mem_790',
toolArguments: {
subject: 'Confidential Project Update',
body: 'Internal strategy document attached'
},
approvalResult: 'ABORTED'
}
]
}
]
};
File and Image Restrictions
Allowed: Files with Abort Responses
Copy
const abortWithScreenshot = {
content: [
{
type: 'text',
text: 'The calendar event has the wrong timezone. See screenshot of the correct settings:'
},
{
type: 'image',
image_url: {
url: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...'
}
},
{
type: 'tool_approval_result',
tool_approval_results: [
{
toolExecutionId: 'exec_123',
toolExecutionBatchId: 'batch_456',
// ... other required fields
approvalResult: 'ABORTED'
}
]
}
]
};
Not Allowed: Files with Approval/Denial
Copy
// ❌ Files will be ignored for non-abort responses
const approvalWithFile = {
content: [
{
type: 'image',
image_url: { url: 'reference.png' } // Will be ignored
},
{
type: 'tool_approval_result',
tool_approval_results: [
{
toolExecutionId: 'exec_123',
approvalResult: 'ABORTED_WITH_FEEDBACK' // Files ignored for approved/denied tools
}
]
}
]
};
Complete Implementation Example
Copy
class ToolApprovalHandler {
private pendingApprovals = new Map<string, ToolApprovalRequest[]>();
handleApprovalRequest(requests: ToolApprovalRequest[]) {
// Group by batch ID
const batchId = requests[0].toolExecutionBatchId;
this.pendingApprovals.set(batchId, requests);
// Show approval UI
this.showApprovalInterface(requests);
}
async submitApprovalBatch(
batchId: string,
decisions: Map<string, ApprovalDecision>,
feedback?: string
) {
const requests = this.pendingApprovals.get(batchId);
if (!requests) {
throw new Error(`No pending approvals for batch ${batchId}`);
}
// Build approval results
const toolApprovalResults = requests.map(request => ({
toolId: request.toolId,
toolName: request.toolName,
toolProvider: request.toolProvider,
toolCategory: request.toolCategory,
toolExecutionId: request.toolExecutionId,
toolExecutionBatchId: request.toolExecutionBatchId,
toolMemoryId: request.toolMemoryId,
toolArguments: request.toolArguments,
approvalResult: decisions.get(request.toolExecutionId)
}));
// Build content array
const content = [];
// Add feedback text if provided
if (feedback) {
content.push({
type: 'text',
text: feedback
});
}
// Add approval results
content.push({
type: 'tool_approval_result',
tool_approval_results: toolApprovalResults
});
// Submit to API
const response = await fetch(
`${API_BASE_URL}/api/assistants/threads/${this.threadId}/messages`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
'X-Envole-User-Id': this.userId
},
body: JSON.stringify({ content })
}
);
if (!response.ok) {
const error = await response.json();
throw new Error(`Approval submission failed: ${error.message}`);
}
// Clean up
this.pendingApprovals.delete(batchId);
}
// Helper method for individual tool decisions
setToolDecision(toolExecutionId: string, decision: ApprovalDecision) {
// Find batch containing this tool
let batchId: string | undefined;
let batchRequests: ToolApprovalRequest[] | undefined;
for (const [id, requests] of this.pendingApprovals.entries()) {
if (requests.some(req => req.toolExecutionId === toolExecutionId)) {
batchId = id;
batchRequests = requests;
break;
}
}
if (!batchId || !batchRequests) {
throw new Error(`Tool ${toolExecutionId} not found in pending approvals`);
}
// Update UI state
this.updateToolDecision(toolExecutionId, decision);
// Check if batch is complete
const allDecisions = this.getAllBatchDecisions(batchId);
if (allDecisions.size === batchRequests.length) {
// All tools decided - ready to submit
this.enableBatchSubmission(batchId);
}
}
}
Response Handling
The API responds with standard SSE events. After submitting approvals, you’ll receive:NOTIFICATION_TOOL_EXECUTION_APPROVAL_ACCEPTED
for approved toolsNOTIFICATION_TOOL_EXECUTION_APPROVAL_DENIED
for denied toolsNOTIFICATION_TOOL_EXECUTION_APPROVAL_ABORTED
for aborted tools- Standard
AGENT_RESPONSE_*
events as the agent continues processing
Error Responses
Validation Errors
Copy
{
"error": "Invalid tool approval batch",
"details": {
"batchId": "batch_456",
"issues": [
{
"toolExecutionId": "exec_123",
"error": "Missing required field: toolArguments"
},
{
"toolExecutionId": "exec_124",
"error": "Invalid approvalResult: must be ABORTED_WITH_FEEDBACK, DENIED, or ABORTED"
}
]
}
}
State Constraint Violations
Copy
{
"error": "Invalid approval batch: cannot mix ABORTED with other approval states",
"batchId": "batch_456",
"conflictingStates": ["ABORTED_WITH_FEEDBACK", "ABORTED"]
}
Next Steps
- Review Best Practices for UX recommendations
- See Implementation Examples for complete code samples
- Learn about Error Handling strategies