> ## Documentation Index
> Fetch the complete documentation index at: https://docs.envole.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# API Integration

> Complete guide to implementing tool approval via the message API

## Overview

Tool approval responses are submitted through the standard [Add Session Thread Message](/api-reference/session-thread/add-session-thread-message) endpoint. The approval decisions are included in the `content` field of the message, alongside optional text feedback.

## Request Format

### Endpoint

```
POST /api/assistants/threads/{threadId}/messages
```

### Authentication

```typescript theme={null}
const headers = {
  'Authorization': `Bearer ${API_KEY}`,
  'Content-Type': 'application/json',
  'X-Envole-User-Id': USER_ID
};
```

### Basic Approval Response

```typescript theme={null}
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:

```typescript theme={null}
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';
}
```

**Important**: You must preserve all original fields from the `TOOL_EXECUTION_APPROVAL_REQUEST` event and only modify the `approvalResult` field.

## Batch Response Examples

### Mixed Approval/Denial

```typescript theme={null}
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

```typescript theme={null}
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

```typescript theme={null}
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

```typescript theme={null}
// ❌ 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

```typescript theme={null}
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:

1. `NOTIFICATION_TOOL_EXECUTION_APPROVAL_ACCEPTED` for approved tools
2. `NOTIFICATION_TOOL_EXECUTION_APPROVAL_DENIED` for denied tools
3. `NOTIFICATION_TOOL_EXECUTION_APPROVAL_ABORTED` for aborted tools
4. Standard `AGENT_RESPONSE_*` events as the agent continues processing

## Error Responses

### Validation Errors

```json theme={null}
{
  "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

```json theme={null}
{
  "error": "Invalid approval batch: cannot mix ABORTED with other approval states",
  "batchId": "batch_456",
  "conflictingStates": ["ABORTED_WITH_FEEDBACK", "ABORTED"]
}
```

## Next Steps

* Review [Best Practices](/human-in-the-loop/best-practices/index) for UX recommendations
* See [Implementation Examples](/human-in-the-loop/examples/index) for complete code samples
* Learn about [Error Handling](/human-in-the-loop/error-handling/index) strategies
