Skip to main content

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

ParameterTypeRequiredDescription
full_namestringYesContact's full name
emailstringYesContact's email address
phonestringNoContact's phone number
companystringNoContact's company name
messagestringNoAdditional message or notes
chat_idstringNoAssociated chat session ID
message_idstringNoAssociated message ID
metadataobjectNoAdditional 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

ParameterTypeRequiredDescription
namestringYesAttendee's name
emailstringYesAttendee's email address
phonestringNoAttendee's phone number
datestringYesAppointment date (YYYY-MM-DD)
timestringYesAppointment time (HH:MM)
duration_minutesintegerNoMeeting duration in minutes (default: 30)
notesstringNoAdditional notes or agenda
chat_idstringNoAssociated chat session ID
message_idstringNoAssociated message ID
metadataobjectNoAdditional 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

ParameterTypeRequiredDescription
reasonstringNoReason for handoff to human support
urgencystringNoUrgency level: low, medium, high, critical
categorystringNoSupport category for routing
chat_idstringNoAssociated chat session ID
message_idstringNoAssociated message ID
customer_emailstringNoCustomer's email for follow-up
metadataobjectNoAdditional 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