AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
The ElevenLabs Agents platform provides tools and SDKs to create, configure, and deploy voice-enabled AI agents. It supports multiple LLM providers, custom webhook and client-side tools, and structured workflows to manage conversational logic. Developers can integrate these agents into web applications via widgets or manage them programmatically through a CLI, Python SDK, or JavaScript SDK.
What it does
- Create and manage voice AI agents with custom system prompts and LLM configurations.
- Define custom tools for agents, including server-side webhooks and browser-based client functions.
- Implement branching conversational workflows with node-based logic.
- Configure safety guardrails to filter content and enforce domain-specific rules.
- Deploy agents via web widgets or integrate them into applications using SDKs.
- Manage agent procedures and testing scenarios through a dedicated API.
When to use it
- Building interactive voice assistants or customer service bots.
- Creating voice-enabled characters for real-time conversation experiences.
- Implementing multi-step conversational flows that require branching logic.
- Connecting AI agents to external APIs or local browser functions via tools.
Advantages
- Supports a wide range of LLM providers and custom endpoints.
- Provides granular control over agent behavior, including turn-taking and voice settings.
- Enables secure integration with external services through webhook and client tools.
- Offers built-in testing and simulation capabilities for agent evaluation.
- Supports both programmatic management via SDKs and rapid prototyping via CLI.
Usage examples
- “Create a new voice assistant agent using the ElevenLabs Python SDK.”
- The agent is initialized with a specified name, system prompt, LLM model, and voice ID.
- “Define a webhook tool for an agent to look up weather information.”
- The agent is configured with an API schema that allows it to call an external weather service during a conversation.
- “Embed a voice agent into a React application.”
- The agent is rendered using the provided widget component, enabling users to start and end voice sessions.
- “Configure a custom guardrail to prevent an agent from providing medical advice.”
- A blocking guardrail is added to the agent's platform settings to intercept and filter specific topics.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/elevenlabs/agents/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip agents.zip -d .agents/skills
Documentation by ElevenLabs
ElevenLabs Agents Platform
Build voice AI agents with natural conversations, multiple LLM providers, custom tools, and easy web embedding.
Setup, before writing any code: if
ELEVENLABS_API_KEYisn't set for this project, runnpx -y @elevenlabs/cli@latest onboard initfrom the app's folder first and follow what it prints: it signs the user in and stores a key for the project, so nobody copies one by hand. Otherwise see the Installation Guide for CLI and SDK setup.
Quick Start with CLI
The ElevenLabs CLI is the recommended way to create and manage agents:
# Install CLI and authenticate
npm install -g @elevenlabs/cli
elevenlabs auth login
# Initialize project and create an agent
elevenlabs agents init
elevenlabs agents add "My Assistant" --template complete
# Push to ElevenLabs platform
elevenlabs agents pushAvailable templates: complete, minimal, voice-only, text-only, customer-service, assistant
Python
from elevenlabs import ElevenLabs
client = ElevenLabs()
agent = client.conversational_ai.agents.create(
name="My Assistant",
conversation_config={
"agent": {
"first_message": "Hello! How can I help?",
"language": "en",
"prompt": {
"prompt": "You are a helpful assistant. Be concise and friendly.",
"llm": "gemini-2.0-flash",
"temperature": 0.7
}
},
"tts": {"voice_id": "JBFqnCBsd6RMkjVDRZzb"}
}
)JavaScript
import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
const client = new ElevenLabsClient();
const agent = await client.conversationalAi.agents.create({
name: "My Assistant",
conversationConfig: {
agent: {
firstMessage: "Hello! How can I help?",
language: "en",
prompt: {
prompt: "You are a helpful assistant.",
llm: "gemini-2.0-flash",
temperature: 0.7
}
},
tts: { voiceId: "JBFqnCBsd6RMkjVDRZzb" }
}
});CLI
The CLI reads ELEVENLABS_API_KEY from the environment automatically:
elevenlabs agents create \
--json '{"name": "My Assistant", "conversation_config": {"agent": {"first_message": "Hello!", "language": "en", "prompt": {"prompt": "You are helpful.", "llm": "gemini-2.0-flash"}}, "tts": {"voice_id": "JBFqnCBsd6RMkjVDRZzb"}}}'Starting Conversations
Authenticated WebRTC: Request a session token from your backend. The response includes both the token and the conversation ID:
session = client.conversational_ai.conversations.get_webrtc_token(
agent_id="your-agent-id",
)
print(session.token, session.conversation_id)Server-side (Python): Get signed URL for client connection:
signed_url = client.conversational_ai.conversations.get_signed_url(
agent_id="your-agent-id",
environment="staging",
)Client-side (JavaScript):
import { Conversation } from "@elevenlabs/client";
const conversation = await Conversation.startSession({
agentId: "your-agent-id",
environment: "staging",
overrides: { asr: { keywords: ["ElevenLabs", "TechCorp"] } },
onMessage: (msg) => console.log("Agent:", msg.message),
onUserTranscript: (t) => console.log("User:", t.message),
onPing: (event) => console.log("Estimated latency:", event.ping_ms),
onContextUsage: ({ model, context_tokens, context_limit_tokens }) =>
console.log(`${model}: ${context_tokens}/${context_limit_tokens} context tokens`),
onError: (e) => console.error(e)
});React Hook: Wrap hook consumers in ConversationProvider. Prefer granular hooks such as
useConversationControls and useConversationStatus for session controls and UI state;
useConversation remains available as the convenience all-in-one hook. Pass provider-level
callbacks such as onError when you want React to handle conversation errors in one place.
import {
ConversationProvider,
useConversationControls,
useConversationStatus,
} from "@elevenlabs/react";
function Agent({ signedUrl }: { signedUrl: string }) {
const { startSession, endSession } = useConversationControls();
const { status } = useConversationStatus();
if (status === "connected") {
return <button onClick={endSession}>End conversation</button>;
}
return (
<button onClick={() => startSession({ signedUrl })}>
Start conversation
</button>
);
}
function App({ signedUrl }: { signedUrl: string }) {
return (
<ConversationProvider
onError={(error) => console.error("Conversation error:", error)}
onPing={(event) => console.log("Estimated latency:", event.ping_ms)}
onContextUsage={({ model, context_tokens, context_limit_tokens }) =>
console.log(`${model}: ${context_tokens}/${context_limit_tokens} context tokens`)
}
>
<Agent signedUrl={signedUrl} />
</ConversationProvider>
);
}Configuration
| Provider | Models |
|---|---|
| OpenAI | gpt-6.1-sol, gpt-6.1-terra, gpt-6-sol, gpt-6-luna, gpt-6-astra, gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna, gpt-5.5, gpt-5.5-2026-04-23, gpt-5.4, gpt-5.4-mini, gpt-5.4-nano, gpt-5.4-2026-03-05, gpt-5.4-mini-2026-03-17, gpt-5.4-nano-2026-03-17, gpt-4.1, gpt-4.1-mini, gpt-4o, gpt-4o-mini, gpt-4-turbo |
| Anthropic | claude-opus-5-5, claude-opus-5, claude-sonnet-5-5, claude-opus-4-7, claude-opus-4-6, claude-sonnet-4-6, claude-sonnet-4, claude-haiku-4-5, claude-3-7-sonnet, claude-3-5-sonnet, claude-3-haiku |
gemini-4-argon, gemini-3.8-flash, gemini-3.7-flash, gemini-3.6-flash, gemini-3.5-flash, gemini-3.5-flash-lite, gemini-3.1-flash-lite-preview, gemini-3.1-pro-preview, gemini-3-pro-preview, gemini-3-flash-preview, gemini-2.5-flash, gemini-2.5-flash-lite, gemini-2.0-flash, gemini-2.0-flash-lite | |
| ElevenLabs | glm-52, deepseek-v41-flash, glm-45-air-fp8, qwen3-30b-a3b, qwen36-35b-a3b, qwen35-35b-a3b, qwen35-397b-a17b, nemotron-35-super-vl-nvfp4, gpt-oss-120b |
| Custom | custom-llm (bring your own endpoint) |
Use GET /v1/convai/llm/list to inspect the current model catalog, including deprecation state, token/context limits, capability flags such as image-input support, and model-specific reasoning effort support.
To run an agent on an ElevenLabs Realtime model instead of a separate LLM and TTS model (conversation_config.realtime_model), use the realtime skill.
Popular voices: JBFqnCBsd6RMkjVDRZzb (George), EXAVITQu4vr4xnSDxMaL (Sarah), onwK4e9ZLuTAKqWW03F9 (Daniel), XB0fDUnXU5powFXDhCwa (Charlotte)
Turn eagerness: patient (waits longer for user to finish), normal, or eager (responds quickly)
See Agent Configuration for all options.
System Prompt Structure
Section the prompt with markdown headings — the model prioritizes and interprets instructions more reliably (prompting guide):
# Personality – named character, 2-3 traits
# Environment – where they work, who they talk to
# Tone – vocal style as 4-5 bullets
# Goal – what success looks like (numbered for multi-step flows)Keep instructions short and action-based. Mark critical steps with "This step is important." For critical refusal/safety rules, include concise instructions in the prompt and also configure independent custom Guardrails via platform_settings.guardrails (see Guardrails).
Tools
Extend agents with webhook, client, or built-in system tools. Tools are defined inside conversation_config.agent.prompt:
Workspace environment variables can resolve per-environment server tool URLs, headers, and auth connections, and runtime system variables such as {{system__conversation_history}} can pass full conversation context into tool calls when needed.
"prompt": {
"prompt": "You are a helpful assistant that can check the weather.",
"llm": "gemini-2.0-flash",
"tools": [
# Webhook: server-side API call
{"type": "webhook", "name": "get_weather", "description": "Get weather",
"api_schema": {"url": "https://api.example.com/weather", "method": "POST",
"request_body_schema": {"type": "object", "properties": {"location": {"type": "string"}}, "required": ["location"]}}},
# Client: runs in the browser
{"type": "client", "name": "show_product", "description": "Display a product",
"parameters": {"type": "object", "properties": {"productId": {"type": "string"}}, "required": ["productId"]}}
],
"built_in_tools": {
"end_call": {},
"transfer_to_number": {"transfers": [{"transfer_destination": {"type": "phone", "phone_number": "+1234567890"}, "condition": "User asks for human support"}]}
}
}Client tools run in browser:
clientTools: {
show_product: async ({ productId }) => {
document.getElementById("product").src = `/products/${productId}`;
return { success: true };
}
}See Client Tools Reference for complete documentation.
Built-in System Tools
Set under conversation_config.agent.prompt.built_in_tools. {} enables defaults; provide description to customize; omit to disable.
| Tool | Enable for |
|---|---|
end_call | All agents |
language_detection | Multilingual agents |
transfer_to_number | Phone-based human escalation |
transfer_to_agent | Multi-agent workflows |
skip_turn | Tutoring / coaching (silent listening) |
voicemail_detection | Outbound calling |
play_keypad_touch_tone | IVR navigation |
start_procedure and end_procedure are not configured here. The platform adds them automatically whenever the agent has at least one procedure (see Procedures).
run_subagent is a system tool for delegating a task to another configured agent. Add it to
conversation_config.agent.prompt.tools with params.system_tool_type: "run_subagent" and an
agents array. Each entry requires agent_id and description; branch_id and a JSON-schema
parameters object are optional.
knowledge_base is a system tool for letting the model choose how to inspect attached knowledge.
Add it to conversation_config.agent.prompt.tools with type: "system", a name, and
params.system_tool_type: "knowledge_base". Use enabled_strategies to expose any combination of
cat, keyword, semantic, and ls:
{
"type": "system",
"name": "knowledge_base",
"description": "Search the attached knowledge base.",
"params": {
"system_tool_type": "knowledge_base",
"enabled_strategies": ["semantic", "keyword"]
}
}Integration Tools
Pre-built connectors managed by the platform. Create a connection with credentials, then attach via tool_ids:
| Integration | Use case |
|---|---|
calcom | Scheduling appointments |
salesforce | CRM lookups, case creation |
hubspot | CRM, marketing, contacts |
zendesk | Support ticketing |
Three-step flow: POST /v1/convai/api-integrations/{id}/connections → GET /v1/convai/api-integrations/{id}/tools → POST /v1/convai/tools with api_integration_id and api_integration_connection_id. Attach to the agent with "prompt": {"tool_ids": ["tool_xxxx"]}. Inline tools and tool_ids can coexist — prefer an integration over a duplicate custom webhook.
Public-API Webhook Examples
No-auth APIs useful for prototypes (URLs must be HTTPS):
| Tool | URL | Purpose |
|---|---|---|
get_weather | https://wttr.in/{location}?format=j1 | Current weather |
search_wikipedia | https://en.wikipedia.org/api/rest_v1/page/summary/{topic} | Topic summary |
get_exchange_rate | https://open.er-api.com/v6/latest/{base_currency} | FX rates |
Workflows
Route conversations through discrete steps with branching logic. Define under the agent's top-level workflow field. Reference: Agent Workflows.
Node types: start (ID must be "start_node"), end (outcome records the conversation as success, the default, or failure), override_agent (subagent step with label + additional_prompt), dispatch_tool (executes a tool with success/failure routing), agent_transfer, transfer_to_number.
Edge types: unconditional, llm (natural-language condition), expression (deterministic data check). Tool nodes have separate success/failure edges.
Scope tools per step with additional_tool_ids on a node — prevents the wrong tool firing at the wrong step. Set additional_tool_ids: [] on conversational routing nodes such as greeting and classify_intent so they only converse:
{
"type": "override_agent",
"label": "Book Appointment",
"additional_prompt": "Discuss preferred dates and doctors. Show the booking form once agreed.",
"entry_behavior": "wait_for_user",
"additional_tool_ids": ["show_booking_form", "display_appointment_card"],
"position": {"x": 0, "y": 400}
}Include position ({x, y}) on every node so the editor renders cleanly. Start at y=0, put end at the bottom, and space branches horizontally at x=-150 and x=150; suggested spacing is 200px vertical between levels and 300px horizontal between branches. Keep workflows to 4-7 nodes and always have a path to end.
Use entry_behavior on override_agent nodes to choose whether a sub-agent speaks immediately (generate_immediately), waits for user input (wait_for_user), or lets the platform decide (auto).
For nested agent transfers, set enable_nesting on a standalone_agent node and
return_when_nested on an end node that should return control to the parent workflow.
Procedures
Reusable instruction blocks an agent runs when a trigger matches. A procedure is free_form (markdown guidance the agent adapts, and the only type that can reference knowledge base documents) or deterministic (called "structured" in the dashboard: typed steps that run in a fixed order, for flows that must happen the same way every time). See Using the Procedure API for the full CLI and SDK flow, and Writing Procedures for the step reference, validation rules, and authoring guidance.
Procedures live on an agent branch, and every write stages a per-user draft:
| Operation | Call |
|---|---|
| List, create, read, update, discard, remove | /v1/convai/agents/{agent_id}/branches/{branch_id}/procedures... (procedures.* and procedures.drafts.* in the SDKs) |
| Publish | PATCH /v1/convai/agents/{agent_id}?branch_id=... (agents.update) |
Semantics worth knowing before writing any of these calls:
Nothing reaches the live agent until you publish. Publishing is not a procedure endpoint; one PATCH on the agent versions every changed procedure draft on the branch.
GET .../procedures/{procedure_id}reads branch HEAD and returns404until that procedure's first publish. Read the/draftvariant to see a procedure you just created; do not retry the create.Turning a structured procedure's steps into the form the agent executes is called compiling. The platform compiles every structured procedure when the agent draft is saved or the agent is published; authors never compile anything themselves. Both draft save and publish validate every structured procedure on the branch. A failed operation writes nothing. To validate without publishing, save an agent draft with
POST /v1/convai/agents/{agent_id}/drafts?branch_id=..., sending the agent's currentname,conversation_config,platform_settings, andworkflowunchanged, then discard that agent draft withDELETEon the same path; see Using the Procedure API.If a structured procedure is invalid, the publish or draft save returns
400withstatusprocedure_validation_failedanderrorskeyed by procedure ID, each entry carrying thepathof the offending field and a message. Nothing is written; repair the procedure draft and publish again.A draft update replaces the whole body. Read the draft first, then resend
name,type, andtriggeralongside the newcontent.typecannot change after creation.contentis markdown for afree_formprocedure, and a JSON-encoded object with astepsarray for adeterministicone. The trigger is the top-leveltriggerfield in both cases, not part ofcontent. Serialize it; do not hand-escape quotes.Manually authored workflow edges cannot connect to or from a structured procedure. Use a
sub_procedurestep to chain structured procedures; entry from a matching trigger and completion are platform-managed.A third
type,folder, groups procedures in the dashboard. Folders carry nocontentortrigger; move a procedure into one withPOST .../procedures/{procedure_id}/move.The agent keeps the five most recently started procedures in context. When more have been started, the content, inline tools, and knowledge base documents of the oldest free-form ones drop out of the prompt, although the procedures remain active. Keep procedures focused and use sub-procedures so that few are active at once.
Routing is driven by the
triggertext, not the procedure name. Write concrete, non-overlapping triggers that cover the phrasings a user would actually say. The model sees only a numbered menu of triggers; it never sees procedure names or IDs.To restrict the starting agent to selected procedures for one conversation, enable
platform_settings.overrides.enable_procedure_ids_from_client, then pass their IDs asprocedure_idsin conversation initiation data. An empty list disables all procedures for that starting agent. The same setting allowsstart_procedure_ids: up to five free-form procedures that start with the conversation, for when the user's task is already known (for example from an IVR choice or the page hosting the widget).Procedure APIs require
elevenlabs(Python) or@elevenlabs/elevenlabs-jsat2.60.0or newer.
Guardrails
Layered safety enforcement that runs independently of the LLM — configured under platform_settings.guardrails, not in the system prompt. Reference: Guardrails.
"platform_settings": {
"guardrails": {
"version": "1",
"focus": {"is_enabled": true},
"prompt_injection": {"is_enabled": true},
"content": {"config": {"harassment": {"is_enabled": true, "threshold": 0.5}}},
"custom": {
"config": {
"configs": [{
"is_enabled": true,
"name": "No medical diagnoses",
"prompt": "Block the agent from providing medical diagnoses or treatment advice.",
"execution_mode": "blocking",
"model": "gemini-3.1-flash-lite",
"history_message_count": 1,
"trigger_action": {"type": "retry", "feedback": "Reason: {{trigger_reason}}"}
}]
}
}
}
}Types: focus (on-topic), prompt_injection (manipulation defense), content (category filters), custom (LLM-evaluated domain rules). Content categories include harassment, profanity, sexual, violence, self_harm, and medical_and_legal_information — threshold range 0.0–1.0 (default 0.3). Custom rules use execution_mode: "blocking" with a model, history_message_count, and trigger_action (e.g., retry with feedback). Custom guardrails evaluate in parallel and fail-open.
Per vertical: healthcare/finance/legal → enable medical_and_legal_information; education/youth → sexual/violence/self_harm/profanity; support/sales → harassment/profanity. All agents benefit from focus + prompt_injection + 2-4 custom rules.
Testing Agents
Three test types via POST /v1/convai/agent-testing/create, then attached with PATCH on the agent. Reference: Agent Testing.
| Type | Purpose |
|---|---|
llm | Scenario test — does the agent respond appropriately to a message? |
tool | Tool-call test — right tool, right parameters? |
simulation | Multi-turn flow with a simulated user persona |
// Tool-call test (snake_case throughout; chat_history role is "user" or "agent")
{
"name": "Books with correct doctor and date",
"type": "tool",
"chat_history": [
{"role": "user", "message": "Dr. Smith on March 5 at 2pm", "time_in_call_secs": 10}
],
"tool_call_parameters": {
"referenced_tool": {"id": "show_booking_form", "type": "client"},
"parameters": [
{"path": "doctor_name", "eval": {"type": "llm", "description": "Should reference Dr. Smith"}},
{"path": "date", "eval": {"type": "regex", "pattern": "2025-03-05|March 5"}}
]
}
}Eval strategies: exact, regex, llm. Prompt evaluation criteria can use binary scoring or
numeric scoring with scoring_mode: "numeric_uniform", max_score, and score_instructions;
numeric scores are normalized into the aggregate conversation success percentage. Attach via an agent update:
elevenlabs agents update --agent-id "your-agent-id" \
--json '{"platform_settings": {"testing": {"attached_tests": [{"test_id": "test_xxxx"}]}}}'Run selected tests with POST /v1/convai/agents/{agent_id}/run-tests. The request
body requires tests and accepts repeat_count from 1 to 50 for repeated runs.
Simulation tests can define up to 30 success_conditions prompts; all criteria are
evaluated and merged into the final result.
Simulation tests can also define tool_mock_overrides, keyed by tool ID, to replace shared response
mocks for one test. Each override is an array of mocks with a required mock_result; set
is_error: true to exercise a tool-failure path. Overrides only apply to tools enabled for mocking
through tool_mock_config.
For completed conversations, rerun one evaluation criterion with POST /v1/convai/conversations/{conversation_id}/analysis/evaluations/run and a request body containing evaluation_id.
Widget Embedding
<elevenlabs-convai agent-id="your-agent-id"></elevenlabs-convai>
<script src="https://unpkg.com/@elevenlabs/convai-widget-embed" async type="text/javascript"></script>Customize with attributes: avatar-image-url, action-text, start-call-text, end-call-text.
See Widget Embedding Reference for all options.
Outbound Calls
Make outbound phone calls using your agent via Twilio or Exotel integration:
The examples below use Twilio. See the reference for Exotel usage.
Python
response = client.conversational_ai.twilio.outbound_call(
agent_id="your-agent-id",
agent_phone_number_id="your-phone-number-id",
to_number="+1234567890",
call_recording_enabled=True
)
print(f"Call initiated: {response.conversation_id}")JavaScript
const response = await client.conversationalAi.twilio.outboundCall({
agentId: "your-agent-id",
agentPhoneNumberId: "your-phone-number-id",
toNumber: "+1234567890",
callRecordingEnabled: true,
});CLI
elevenlabs agents twilio outbound_call \
--agent-id "your-agent-id" \
--agent-phone-number-id "your-phone-number-id" \
--to-number "+1234567890" \
--call-recording-enabled trueSee Outbound Calls Reference for provider-specific endpoints, configuration overrides, and dynamic variables.
Managing Agents
Using CLI (Recommended)
# List agents and check status
elevenlabs agents list
elevenlabs agents status
# Import agents from platform to local config
elevenlabs agents pull # Import all agents
elevenlabs agents pull --agent <agent-id> # Import specific agent
# Push local changes to platform
elevenlabs agents push # Upload configurations
elevenlabs agents push --dry-run # Preview changes first
# Add tools
elevenlabs tools add-webhook "Weather API"
elevenlabs tools add-client "UI Tool"Project Structure
The CLI creates a project structure for managing agents:
your_project/
├── agents.json # Agent definitions
├── tools.json # Tool configurations
├── tests.json # Test configurations
├── agent_configs/ # Individual agent configs
├── tool_configs/ # Individual tool configs
└── test_configs/ # Individual test configsSDK Examples
# List
agents = client.conversational_ai.agents.list()
# Get
agent = client.conversational_ai.agents.get(agent_id="your-agent-id")
# Update (partial - only include fields to change)
client.conversational_ai.agents.update(agent_id="your-agent-id", name="New Name")
client.conversational_ai.agents.update(agent_id="your-agent-id",
conversation_config={
"agent": {"prompt": {"prompt": "New instructions", "llm": "claude-sonnet-4"}}
})
# Delete
client.conversational_ai.agents.delete(agent_id="your-agent-id")See Agent Configuration for all configuration options and SDK examples.
Error Handling
try:
agent = client.conversational_ai.agents.create(...)
except Exception as e:
print(f"API error: {e}")Common errors: 401 (invalid key), 404 (not found), 422 (invalid config), 429 (rate limit)
References
Installation Guide - SDK setup and migration
Agent Configuration - All config options and CRUD examples
Client Tools - Webhook, client, and system tools
Using the Procedure API - Procedure CLI and SDK flow, drafts and publish
Writing Procedures - Trigger and content authoring, structured step reference and rules
Widget Embedding - Website integration
Outbound Calls - Phone call integrations
Files
- ATTRIBUTION.mdAdded by AutoGineer796 B
- LICENSELicence file added by AutoGineer1.0 KiB
- SKILL.md26.4 KiB
- references/agent-configuration.md34.7 KiB
- references/client-tools.md19.8 KiB
- references/installation.md4.2 KiB
- references/outbound-calls.md8.7 KiB
- references/using-procedure-api.md16.9 KiB
- references/widget-embedding.md8.5 KiB
- references/writing-procedures.md10.9 KiB
references/client-tools.md
Client Tools
Extend your agent with custom capabilities. Tools let the agent take actions beyond just talking.
Tool Types
| Type | Execution | Use Case |
|---|---|---|
| Webhook | Server-side via HTTP | Database queries, API calls, secure operations |
| Client | Browser-side JavaScript | UI updates, local storage, navigation |
| System | Built-in ElevenLabs | End call, transfer, standard actions |
Where Tools Live
Tools are defined inside conversation_config.agent.prompt. Webhook and client tools go in the tools array. System tools go in built_in_tools:
conversation_config={
"agent": {
"prompt": {
"prompt": "You are helpful.",
"llm": "gemini-2.0-flash",
"tools": [...], # Webhook and client tools
"built_in_tools": {...} # System tools (end_call, transfer, etc.)
}
}
}Webhook Tools
Execute server-side logic when the agent needs external data or actions.
Basic Webhook
agent = client.conversational_ai.agents.create(
name="Weather Assistant",
conversation_config={
"agent": {
"prompt": {
"prompt": "You are a helpful assistant that can check the weather.",
"llm": "gemini-2.0-flash",
"tools": [{
"type": "webhook",
"name": "get_weather",
"description": "Get current weather for a city. Use when user asks about weather.",
"api_schema": {
"url": "https://api.example.com/weather",
"method": "POST",
"request_headers": {
"Authorization": "Bearer {{API_KEY}}"
},
"request_body_schema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "City name, e.g., 'San Francisco'"
},
"units": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature units"
}
},
"required": ["city"]
}
}
}]
}
},
"tts": {"voice_id": "JBFqnCBsd6RMkjVDRZzb"}
}
)Webhook Request Format
When the agent calls a webhook tool, ElevenLabs sends:
{
"tool_call_id": "call_abc123",
"tool_name": "get_weather",
"parameters": {
"city": "San Francisco",
"units": "fahrenheit"
},
"conversation_id": "conv_xyz789"
}Webhook Response Format
Your server should respond with:
{
"result": "The weather in San Francisco is 68°F and sunny."
}Or for structured data:
{
"result": {
"temperature": 68,
"condition": "sunny",
"humidity": 45
}
}Webhook with Authentication
# Inside conversation_config.agent.prompt.tools:
{
"type": "webhook",
"name": "lookup_order",
"description": "Look up order status by order ID",
"response_timeout_secs": 10,
"api_schema": {
"url": "https://api.mystore.com/orders/lookup",
"method": "POST",
"request_headers": {
"Authorization": "Bearer {{ORDER_API_KEY}}",
"X-Store-ID": "store_123"
},
"request_body_schema": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "Order ID (e.g., ORD-12345)"
}
},
"required": ["order_id"]
}
}
}Use workspace environment variables to keep a single server tool configuration working across
staging and production. {{system_env__label}} works in server tool URLs, secret environment
variables can populate request_headers, and auth-connection environment variables can populate
api_schema.auth_connection. The same environment-variable resolution model also applies to MCP
server connections.
{
"api_schema": {
"url": "https://{{system_env__api_host}}.example.com/orders",
"method": "GET",
"request_headers": {
"X-Api-Key": { "env_var_label": "orders_api_key" }
},
"auth_connection": { "env_var_label": "orders_oauth" }
}
}Workspace auth connections support OAuth2 client credentials, OAuth2 JWT, private key JWT,
basic auth, bearer auth, custom header auth, and mutual TLS (mtls).
System dynamic variables are also available in tool parameters and headers. Use
{{system__conversation_history}} when a webhook or sub-agent needs the full conversation
context as a lazily evaluated JSON history object with user, agent, and tool entries.
Webhook Tool Options
| Field | Type | Default | Description |
|---|---|---|---|
response_timeout_secs | int | 20 | Timeout in seconds (5-120) |
interruption_mode | string | "allow" | Controls whether the user can interrupt around this tool call: allow, disable_during_tool, or disable_during_tool_and_turn |
execution_mode | string | "immediate" | immediate, post_tool_speech, or async |
tool_call_sound | string | - | Sound during execution: typing, elevator1-elevator4 |
pre_tool_speech | string | "auto" | Controls whether the agent speaks before execution: auto, force, or off |
tool_error_handling_mode | string | "auto" | auto, summarized, passthrough, or hide |
api_schema.response_filter | object | - | Filters JSON webhook responses before the LLM sees them. Use mode: "allow" with filters dot-paths to keep selected fields, or mode: "hide_all" to hide the response |
MCP server configuration supports the same pre_tool_speech, interruption_mode, execution_mode, and
response_timeout_secs controls at the server level, with per-tool overrides in
tool_config_overrides. Set a per-tool tool_call_sound override to "off" to silence that tool
while retaining the server default for other tools. MCP timeouts default to 30 seconds and must be
5-300 seconds.
Set request_meta on an MCP server configuration to send entries in the MCP _meta field of
tools/call requests. Values can be JSON scalars or references to workspace secrets, dynamic
variables, or environment variables that resolve for each call.
Note: The default api_schema.method is GET. Always set "method": "POST" explicitly for webhook tools that send request bodies.
Server Implementation (Node.js)
app.post("/webhook/get_weather", async (req, res) => {
const { parameters, conversation_id } = req.body;
const { city, units = "fahrenheit" } = parameters;
// Fetch weather from your data source
const weather = await weatherService.get(city, units);
res.json({
result: `It's ${weather.temp}°${units === "celsius" ? "C" : "F"} and ${weather.condition} in ${city}.`,
});
});Server Implementation (Python)
@app.post("/webhook/get_weather")
async def get_weather(request: Request):
data = await request.json()
city = data["parameters"]["city"]
units = data["parameters"].get("units", "fahrenheit")
# Fetch weather from your data source
weather = weather_service.get(city, units)
return {
"result": f"It's {weather['temp']}°{'C' if units == 'celsius' else 'F'} and {weather['condition']} in {city}."
}Client Tools
Execute JavaScript in the user's browser. Useful for UI updates, navigation, or accessing browser APIs.
Defining Client Tools
Client tools are registered when starting a conversation:
import { Conversation } from "@elevenlabs/client";
const conversation = await Conversation.startSession({
agentId: "your-agent-id",
clientTools: {
show_product: async ({ productId }) => {
// Update UI to show product
const modal = document.getElementById("product-modal");
modal.innerHTML = await fetchProductCard(productId);
modal.showModal();
return { success: true, message: "Showing product" };
},
navigate_to: async ({ page }) => {
// Navigate to a page
window.location.href = `/${page}`;
return { success: true };
},
save_preference: async ({ key, value }) => {
// Store in localStorage
localStorage.setItem(key, value);
return { saved: true };
},
},
});React Registration with useConversationClientTool
When you use the React SDK, wrap your component tree in ConversationProvider and register
client tools from components with useConversationClientTool. Handlers are cleaned up
automatically on unmount and always use the latest closure value. Prefer granular hooks such as
useConversationControls and useConversationStatus for the session UI; useConversation
remains available when you want the full conversation object in one hook.
import {
ConversationProvider,
useConversationClientTool,
useConversationControls,
useConversationStatus,
} from "@elevenlabs/react";
function Storefront() {
useConversationClientTool("show_product", async ({ productId }) => {
const modal = document.getElementById("product-modal");
modal.innerHTML = await fetchProductCard(productId);
modal.showModal();
return { success: true };
});
const { startSession, endSession } = useConversationControls();
const { status } = useConversationStatus();
if (status === "connected") {
return <button onClick={endSession}>End</button>;
}
return (
<button onClick={() => startSession({ agentId: "your-agent-id" })}>
Start
</button>
);
}
function App() {
return (
<ConversationProvider>
<Storefront />
</ConversationProvider>
);
}Registering Client Tools with Agent
Tell the agent about available client tools in conversation_config.agent.prompt.tools:
agent = client.conversational_ai.agents.create(
name="Shopping Assistant",
conversation_config={
"agent": {
"prompt": {
"prompt": """You are a shopping assistant.
When users want to see a product, use show_product.
When users want to go somewhere, use navigate_to.""",
"llm": "gemini-2.0-flash",
"tools": [
{
"type": "client",
"name": "show_product",
"description": "Display a product card to the user",
"parameters": {
"type": "object",
"properties": {
"productId": {
"type": "string",
"description": "Product ID to display"
}
},
"required": ["productId"]
}
},
{
"type": "client",
"name": "navigate_to",
"description": "Navigate user to a different page",
"parameters": {
"type": "object",
"properties": {
"page": {
"type": "string",
"enum": ["cart", "checkout", "account", "home"],
"description": "Page to navigate to"
}
},
"required": ["page"]
}
}
]
}
},
"tts": {"voice_id": "JBFqnCBsd6RMkjVDRZzb"}
}
)Client Tool Options
| Field | Type | Default | Description |
|---|---|---|---|
expects_response | bool | false | Whether the tool returns data to the agent |
response_timeout_secs | int | 20 | Seconds to wait for the client to respond (1-120), or -1 to wait indefinitely. -1 requires expects_response |
Client Tool Return Values
Return data that the agent can use in conversation:
clientTools: {
check_cart: async () => {
const cart = JSON.parse(localStorage.getItem("cart") || "[]");
return {
itemCount: cart.length,
total: cart.reduce((sum, item) => sum + item.price, 0),
items: cart.map((item) => item.name),
};
};
}The agent receives this data and can say: "You have 3 items in your cart totaling $45.99."
System Tools (built_in_tools)
Built-in tools provided by ElevenLabs. These are configured in conversation_config.agent.prompt.built_in_tools (not in the tools array):
"built_in_tools": {
"end_call": {},
"transfer_to_number": {...},
"transfer_to_agent": {...},
"language_detection": {},
"skip_turn": {},
"voicemail_detection": {...},
"play_keypad_touch_tone": {}
}Current API schemas also expose agent_prompt_change, memory_entry_create, memory_entry_delete, memory_entry_search, and memory_entry_update in built_in_tools.
Set only_at_conversation_start: true on the language_detection tool to constrain language
switching when no switch occurs in the first two user turns. Leave it false when the conversation
must remain able to switch languages later.
end_call
Ends the current conversation:
"built_in_tools": {
"end_call": {}
}The agent can say "Goodbye!" and then end the call programmatically.
transfer_to_number
Transfer to a phone number (requires telephony integration):
"built_in_tools": {
"transfer_to_number": {
"transfers": [{
"transfer_destination": {"type": "phone", "phone_number": "+1234567890"},
"condition": "User asks to speak with a human agent"
}]
}
}transfer_to_agent
Transfer to another ElevenLabs agent or workflow node:
"built_in_tools": {
"transfer_to_agent": {
"transfers": [{
"agent_id": "other-agent-id",
"node_id": "destination-workflow-node-id",
"preserve_client_tts_overrides": true,
"condition": "User asks about sales"
}]
}
}Use node_id when the transfer should start at a specific workflow node. Omit
agent_id when the transfer stays within the current agent's workflow.
Set preserve_client_tts_overrides when client-side TTS overrides should continue
after the transfer.
Best Practices
Tool Descriptions
Write clear descriptions so the LLM knows when to use tools:
# Good - specific and actionable
"description": "Look up order status. Use when customer asks about their order, delivery, or shipping."
# Bad - vague
"description": "Order tool"Parameter Descriptions
Help the LLM extract correct values:
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "Order ID in format ORD-XXXXX (e.g., ORD-12345)"
},
"email": {
"type": "string",
"description": "Customer email address for verification"
}
}
}For optional tool parameters that should never be sent in the request payload, set
is_omitted: true on the JSON schema property. Do not combine it with description,
dynamic_variable, is_system_provided, or constant_value.
For an LLM-supplied parameter that must match a runtime list, set
allowed_values: {"dynamic_variable": "allowed_ids"}. The dynamic variable must resolve to a
JSON array. Use allowed_values only with a description-sourced property; do not combine it with
dynamic_variable, is_system_provided, constant_value, or is_omitted.
Error Handling
Configure how tool errors are shared with the agent using tool_error_handling_mode:
| Mode | Behavior |
|---|---|
auto | ElevenLabs automatically decides how to handle errors |
summarized | Errors are summarized before being sent to the agent |
passthrough | Full error details are passed to the agent |
hide | Errors are hidden from the agent |
Return helpful error messages:
// Server webhook
app.post("/webhook/lookup_order", async (req, res) => {
const { order_id } = req.body.parameters;
const order = await db.orders.find(order_id);
if (!order) {
return res.json({
result: {
error: true,
message: `Order ${order_id} not found. Please verify the order ID.`,
},
});
}
res.json({ result: order });
});Timeouts
Set reasonable timeouts for webhooks using response_timeout_secs (5-120 seconds, default 20). MCP server tool calls use the same field with a 30-second default and a 5-300 second range:
{
"type": "webhook",
"name": "slow_operation",
"description": "Run a slow operation",
"response_timeout_secs": 30,
"api_schema": {
"url": "https://api.example.com/slow-operation",
"method": "POST"
}
}Complete Example
agent = client.conversational_ai.agents.create(
name="E-commerce Assistant",
conversation_config={
"agent": {
"first_message": "Hi! How can I help you today?",
"language": "en",
"prompt": {
"prompt": """You are an e-commerce support assistant.
Available actions:
- lookup_order: Check order status
- show_product: Display products to customer
- end_call: End conversation politely
- transfer_to_number: Transfer to human support
Always verify order ID before lookup. Offer transfer for complex issues.""",
"llm": "gemini-2.0-flash",
"tools": [
# Webhook: Server-side order lookup
{
"type": "webhook",
"name": "lookup_order",
"description": "Look up order status by order ID or email",
"api_schema": {
"url": "https://api.mystore.com/orders/lookup",
"method": "POST",
"request_headers": {"Authorization": "Bearer {{API_KEY}}"},
"request_body_schema": {
"type": "object",
"properties": {
"order_id": {"type": "string"},
"email": {"type": "string"}
}
}
}
},
# Client: Browser-side product display
{
"type": "client",
"name": "show_product",
"description": "Display product details to the customer",
"parameters": {
"type": "object",
"properties": {
"product_id": {"type": "string"}
},
"required": ["product_id"]
}
}
],
"built_in_tools": {
"end_call": {},
"transfer_to_number": {
"transfers": [{
"transfer_destination": {"type": "phone", "phone_number": "+1234567890"},
"condition": "User asks for human support"
}]
}
}
}
},
"tts": {"voice_id": "JBFqnCBsd6RMkjVDRZzb", "model_id": "eleven_v4_turbo"}
}
)Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs an API key, Needs Python, Needs Node.js
The author says: Requires internet access and an ElevenLabs API key (ELEVENLABS_API_KEY).
Security
What our scanner noticed· Medium risk
- Instructions that use the network
- Downloads piped into a shell
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Published by
- ElevenLabs
- Publisher
- ElevenLabs (Official)
- Source
- elevenlabs/skills · agents · commit 9763c39
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by ElevenLabs or ElevenLabs.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download agents.zip