Tools & Automation - /external/tools
Optimly's tools endpoints enable powerful automation features including lead capture, appointment scheduling, and email handoffs. These tools can be triggered automatically by the AI agent or called directly via API.
Available Tools
🎯 Lead Form Capture
Automatically capture and store lead information from conversations.
📅 Appointment Scheduling
Schedule appointments and meetings directly through chat.
📧 Email Handoff
Transfer conversations to human support via email.
Lead Form Capture
Endpoint
POST /external/tools/lead-form
Authentication
Authorization: Bearer YOUR_AGENT_ACCESS_TOKEN
Request Body
{
"full_name": "John Doe",
"email": "john@example.com",
"phone": "+1234567890",
"company": "Acme Corp",
"message": "Interested in your services",
"chat_id": "chat_123",
"message_id": "msg_456",
"metadata": {
"source": "website",
"campaign": "summer_2025"
}
}
Parameters
Parameter | Type | Required | Description |
---|---|---|---|
full_name | string | Yes | Contact's full name |
email | string | Yes | Contact's email address |
phone | string | No | Contact's phone number |
company | string | No | Contact's company name |
message | string | No | Additional message or notes |
chat_id | string | No | Associated chat session ID |
message_id | string | No | Associated message ID |
metadata | object | No | Additional tracking data |
Response
{
"status": "success",
"message": "Lead information saved successfully",
"lead_id": "lead_789",
"data": {
"full_name": "John Doe",
"email": "john@example.com",
"phone": "+1234567890",
"company": "Acme Corp",
"created_at": "2025-06-16T10:30:00Z"
}
}
Usage Examples
JavaScript
async function captureLead(leadData) {
try {
const response = await fetch('https://api.optimly.io/external/tools/lead-form', {
method: 'POST',
headers: {
'Authorization': 'Bearer your_access_token',
'Content-Type': 'application/json'
},
body: JSON.stringify({
full_name: leadData.name,
email: leadData.email,
phone: leadData.phone,
company: leadData.company,
message: leadData.message,
chat_id: leadData.chatId,
metadata: {
source: 'website',
page_url: window.location.href,
timestamp: new Date().toISOString()
}
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
console.log('Lead captured:', result.lead_id);
// Show success message to user
showSuccessMessage('Thank you! We\'ll be in touch soon.');
return result;
} catch (error) {
console.error('Failed to capture lead:', error);
showErrorMessage('Sorry, there was an error. Please try again.');
throw error;
}
}
// Usage with form submission
document.getElementById('lead-form').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const leadData = {
name: formData.get('name'),
email: formData.get('email'),
phone: formData.get('phone'),
company: formData.get('company'),
message: formData.get('message'),
chatId: getCurrentChatId()
};
await captureLead(leadData);
});
Python
import requests
from typing import Dict, Optional
def capture_lead(
access_token: str,
full_name: str,
email: str,
phone: Optional[str] = None,
company: Optional[str] = None,
message: Optional[str] = None,
chat_id: Optional[str] = None,
metadata: Optional[Dict] = None
) -> Dict:
"""Capture lead information via Optimly API."""
url = 'https://api.optimly.io/external/tools/lead-form'
headers = {
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json'
}
payload = {
'full_name': full_name,
'email': email
}
# Add optional fields
if phone:
payload['phone'] = phone
if company:
payload['company'] = company
if message:
payload['message'] = message
if chat_id:
payload['chat_id'] = chat_id
if metadata:
payload['metadata'] = metadata
response = requests.post(url, json=payload, headers=headers)
response.raise_for_status()
return response.json()
# Usage example
try:
result = capture_lead(
access_token='your_access_token',
full_name='Jane Smith',
email='jane@example.com',
phone='+1-555-0123',
company='Tech Startup Inc',
message='Interested in enterprise plan',
chat_id='chat_abc123',
metadata={
'source': 'contact_form',
'campaign': 'product_launch_2025'
}
)
print(f"Lead captured successfully: {result['lead_id']}")
except requests.exceptions.RequestException as e:
print(f"Failed to capture lead: {e}")
Appointment Scheduling
Endpoint
POST /external/tools/appointment
Authentication
Authorization: Bearer YOUR_AGENT_ACCESS_TOKEN
Request Body
{
"name": "Jane Smith",
"email": "jane@example.com",
"phone": "+1234567890",
"date": "2025-06-20",
"time": "14:00",
"duration_minutes": 30,
"notes": "Product demo meeting",
"chat_id": "chat_123",
"message_id": "msg_456",
"metadata": {
"timezone": "America/New_York",
"meeting_type": "demo"
}
}
Parameters
Parameter | Type | Required | Description |
---|---|---|---|
name | string | Yes | Attendee's name |
email | string | Yes | Attendee's email address |
phone | string | No | Attendee's phone number |
date | string | Yes | Appointment date (YYYY-MM-DD) |
time | string | Yes | Appointment time (HH:MM) |
duration_minutes | integer | No | Meeting duration in minutes (default: 30) |
notes | string | No | Additional notes or agenda |
chat_id | string | No | Associated chat session ID |
message_id | string | No | Associated message ID |
metadata | object | No | Additional appointment data |
Response
{
"status": "success",
"message": "Appointment scheduled successfully",
"appointment_id": "appt_789",
"data": {
"name": "Jane Smith",
"email": "jane@example.com",
"date": "2025-06-20",
"time": "14:00",
"duration_minutes": 30,
"timezone": "America/New_York",
"created_at": "2025-06-16T10:30:00Z"
}
}
Usage Examples
JavaScript
async function scheduleAppointment(appointmentData) {
try {
const response = await fetch('https://api.optimly.io/external/tools/appointment', {
method: 'POST',
headers: {
'Authorization': 'Bearer your_access_token',
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: appointmentData.name,
email: appointmentData.email,
phone: appointmentData.phone,
date: appointmentData.date,
time: appointmentData.time,
duration_minutes: appointmentData.duration || 30,
notes: appointmentData.notes,
chat_id: appointmentData.chatId,
metadata: {
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
meeting_type: appointmentData.type || 'general',
source: 'website'
}
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
console.log('Appointment scheduled:', result.appointment_id);
// Show confirmation to user
showAppointmentConfirmation(result.data);
return result;
} catch (error) {
console.error('Failed to schedule appointment:', error);
showErrorMessage('Sorry, couldn\'t schedule the appointment. Please try again.');
throw error;
}
}
// Usage with appointment form
async function handleAppointmentBooking(formData) {
const appointmentData = {
name: formData.get('name'),
email: formData.get('email'),
phone: formData.get('phone'),
date: formData.get('date'),
time: formData.get('time'),
duration: parseInt(formData.get('duration')) || 30,
notes: formData.get('notes'),
type: formData.get('meeting_type'),
chatId: getCurrentChatId()
};
await scheduleAppointment(appointmentData);
}
function showAppointmentConfirmation(appointment) {
const confirmation = document.createElement('div');
confirmation.innerHTML = `
<div class="appointment-confirmation">
<h3>✅ Appointment Scheduled!</h3>
<p><strong>Date:</strong> ${appointment.date}</p>
<p><strong>Time:</strong> ${appointment.time}</p>
<p><strong>Duration:</strong> ${appointment.duration_minutes} minutes</p>
<p>You'll receive a confirmation email shortly.</p>
</div>
`;
document.body.appendChild(confirmation);
}
React Component
import React, { useState } from 'react';
function AppointmentScheduler({ accessToken, chatId }) {
const [formData, setFormData] = useState({
name: '',
email: '',
phone: '',
date: '',
time: '',
duration: 30,
notes: ''
});
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
try {
const response = await fetch('/external/tools/appointment', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
...formData,
chat_id: chatId,
metadata: {
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
source: 'react_component'
}
})
});
if (!response.ok) {
throw new Error('Failed to schedule appointment');
}
const result = await response.json();
setSuccess(true);
console.log('Appointment scheduled:', result.appointment_id);
} catch (error) {
console.error('Error scheduling appointment:', error);
alert('Failed to schedule appointment. Please try again.');
} finally {
setLoading(false);
}
};
if (success) {
return (
<div className="appointment-success">
<h3>✅ Appointment Scheduled!</h3>
<p>Your appointment has been scheduled successfully.</p>
<p>You'll receive a confirmation email shortly.</p>
</div>
);
}
return (
<form onSubmit={handleSubmit} className="appointment-form">
<h3>📅 Schedule an Appointment</h3>
<div className="form-group">
<label htmlFor="name">Name *</label>
<input
type="text"
id="name"
value={formData.name}
onChange={(e) => setFormData({...formData, name: e.target.value})}
required
/>
</div>
<div className="form-group">
<label htmlFor="email">Email *</label>
<input
type="email"
id="email"
value={formData.email}
onChange={(e) => setFormData({...formData, email: e.target.value})}
required
/>
</div>
<div className="form-group">
<label htmlFor="phone">Phone</label>
<input
type="tel"
id="phone"
value={formData.phone}
onChange={(e) => setFormData({...formData, phone: e.target.value})}
/>
</div>
<div className="form-row">
<div className="form-group">
<label htmlFor="date">Date *</label>
<input
type="date"
id="date"
value={formData.date}
onChange={(e) => setFormData({...formData, date: e.target.value})}
min={new Date().toISOString().split('T')[0]}
required
/>
</div>
<div className="form-group">
<label htmlFor="time">Time *</label>
<input
type="time"
id="time"
value={formData.time}
onChange={(e) => setFormData({...formData, time: e.target.value})}
required
/>
</div>
</div>
<div className="form-group">
<label htmlFor="duration">Duration (minutes)</label>
<select
id="duration"
value={formData.duration}
onChange={(e) => setFormData({...formData, duration: parseInt(e.target.value)})}
>
<option value={15}>15 minutes</option>
<option value={30}>30 minutes</option>
<option value={45}>45 minutes</option>
<option value={60}>1 hour</option>
</select>
</div>
<div className="form-group">
<label htmlFor="notes">Notes</label>
<textarea
id="notes"
value={formData.notes}
onChange={(e) => setFormData({...formData, notes: e.target.value})}
placeholder="Meeting agenda, topics to discuss, etc."
/>
</div>
<button type="submit" disabled={loading}>
{loading ? 'Scheduling...' : 'Schedule Appointment'}
</button>
</form>
);
}
Email Handoff
Endpoint
POST /external/tools/email-handoff
Authentication
Authorization: Bearer YOUR_AGENT_ACCESS_TOKEN
Request Body
{
"reason": "Complex technical inquiry requiring human expert",
"urgency": "medium",
"category": "technical_support",
"chat_id": "chat_123",
"message_id": "msg_456",
"customer_email": "customer@example.com",
"metadata": {
"user_tier": "premium",
"issue_type": "billing"
}
}
Parameters
Parameter | Type | Required | Description |
---|---|---|---|
reason | string | No | Reason for handoff to human support |
urgency | string | No | Urgency level: low, medium, high, critical |
category | string | No | Support category for routing |
chat_id | string | No | Associated chat session ID |
message_id | string | No | Associated message ID |
customer_email | string | No | Customer's email for follow-up |
metadata | object | No | Additional context for support team |
Response
{
"status": "success",
"message": "Conversation transferred to email successfully",
"handoff_id": "handoff_789",
"data": {
"ticket_id": "ticket_456",
"assigned_team": "technical_support",
"estimated_response": "2 hours",
"created_at": "2025-06-16T10:30:00Z"
}
}
Usage Examples
JavaScript
async function requestEmailHandoff(handoffData) {
try {
const response = await fetch('https://api.optimly.io/external/tools/email-handoff', {
method: 'POST',
headers: {
'Authorization': 'Bearer your_access_token',
'Content-Type': 'application/json'
},
body: JSON.stringify({
reason: handoffData.reason,
urgency: handoffData.urgency || 'medium',
category: handoffData.category,
chat_id: handoffData.chatId,
customer_email: handoffData.customerEmail,
metadata: {
page_url: window.location.href,
user_agent: navigator.userAgent,
session_duration: handoffData.sessionDuration,
...handoffData.metadata
}
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
console.log('Handoff created:', result.handoff_id);
// Show handoff confirmation
showHandoffConfirmation(result.data);
return result;
} catch (error) {
console.error('Failed to create email handoff:', error);
showErrorMessage('Sorry, couldn\'t transfer to support. Please try again.');
throw error;
}
}
// Usage when user requests human support
async function handleSupportRequest(reason, urgency = 'medium') {
const handoffData = {
reason,
urgency,
category: detectSupportCategory(reason),
chatId: getCurrentChatId(),
customerEmail: getUserEmail(),
sessionDuration: getSessionDuration(),
metadata: {
user_tier: getUserTier(),
previous_interactions: getPreviousInteractionCount()
}
};
await requestEmailHandoff(handoffData);
}
function showHandoffConfirmation(handoffData) {
const confirmation = document.createElement('div');
confirmation.innerHTML = `
<div class="handoff-confirmation">
<h3>📧 Transferred to Human Support</h3>
<p>Your conversation has been transferred to our support team.</p>
<p><strong>Ticket ID:</strong> ${handoffData.ticket_id}</p>
<p><strong>Estimated Response:</strong> ${handoffData.estimated_response}</p>
<p>You'll receive an email with further details shortly.</p>
</div>
`;
document.body.appendChild(confirmation);
}
Automatic Tool Detection
The AI agent can automatically detect when tools should be executed based on conversation context:
Lead Capture Triggers
- User provides email address
- User expresses interest in services
- User asks for pricing or demo
Appointment Triggers
- User mentions scheduling, meeting, or appointment
- User asks about availability
- User provides specific dates/times
Email Handoff Triggers
- Complex technical questions
- Billing or account issues
- User explicitly requests human support
- Multiple failed attempts to resolve issue
Error Handling
Common Error Responses
{
"error": "Validation error",
"detail": "email field is required",
"status_code": 400
}
{
"error": "Tool execution failed",
"detail": "Unable to schedule appointment for past date",
"status_code": 422
}
Best Practices
async function safeToolExecution(toolFunction, data) {
try {
return await toolFunction(data);
} catch (error) {
if (error.status === 400) {
// Handle validation errors
console.error('Invalid data provided:', error.detail);
showValidationError(error.detail);
} else if (error.status === 422) {
// Handle business logic errors
console.error('Tool execution failed:', error.detail);
showBusinessError(error.detail);
} else if (error.status === 429) {
// Handle rate limiting
console.error('Rate limit exceeded');
setTimeout(() => safeToolExecution(toolFunction, data), 60000);
} else {
// Handle unexpected errors
console.error('Unexpected error:', error);
showGenericError();
}
throw error;
}
}
Rate Limits
- 50 tool executions per hour per access token
- 10 email handoffs per hour per access token
- 20 appointments per hour per access token
Integration Patterns
Progressive Lead Capture
// Capture basic info first, then progressively ask for more
async function progressiveLead(chatId) {
// First: just email
let leadData = await captureLead({
email: userEmail,
chat_id: chatId
});
// Later: add more details
if (userProvidedMoreInfo) {
leadData = await captureLead({
full_name: userName,
email: userEmail,
company: userCompany,
chat_id: chatId
});
}
}
Conditional Tool Execution
// Execute tools based on user context
async function conditionalToolExecution(userIntent, userData) {
switch (userIntent) {
case 'schedule_demo':
await scheduleAppointment({
...userData,
notes: 'Product demo requested',
duration_minutes: 45
});
break;
case 'request_support':
await requestEmailHandoff({
reason: 'User requested human support',
urgency: userData.tier === 'premium' ? 'high' : 'medium',
category: 'general_support'
});
break;
case 'show_interest':
await captureLead(userData);
break;
}
}
Next Steps
- Agent Integration - See how tools work with agent responses
- Chat Management - Understand the chat context for tools
- Message Handling - Learn about message lifecycle
- Authentication - Secure your tool integrations