Generated banner for Agents
Generated logo for Agents

Agents

ElevenLabsOfficialPublished by ElevenLabs

  • Skill
  • Tool integration
  • Data and AI
  • MIT
  • Medium risk

From the author

Build voice AI agents with ElevenLabs. Use when creating voice assistants, customer service bots, interactive voice characters, or any real-time voice conversation experience, and when configuring an agent's tools, workflows, or procedures, including creating, editing, and publishing free-form and structured procedure drafts on an agent branch over the SDKs or REST API.
— the description by ElevenLabs

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

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/elevenlabs/agents/download
  2. Unzip 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_KEY isn't set for this project, run npx -y @elevenlabs/cli@latest onboard init from 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 push

Available 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

ProviderModels
OpenAIgpt-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
Anthropicclaude-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
Googlegemini-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
ElevenLabsglm-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
Customcustom-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.

ToolEnable for
end_callAll agents
language_detectionMultilingual agents
transfer_to_numberPhone-based human escalation
transfer_to_agentMulti-agent workflows
skip_turnTutoring / coaching (silent listening)
voicemail_detectionOutbound calling
play_keypad_touch_toneIVR 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:

IntegrationUse case
calcomScheduling appointments
salesforceCRM lookups, case creation
hubspotCRM, marketing, contacts
zendeskSupport 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):

ToolURLPurpose
get_weatherhttps://wttr.in/{location}?format=j1Current weather
search_wikipediahttps://en.wikipedia.org/api/rest_v1/page/summary/{topic}Topic summary
get_exchange_ratehttps://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:

OperationCall
List, create, read, update, discard, remove/v1/convai/agents/{agent_id}/branches/{branch_id}/procedures... (procedures.* and procedures.drafts.* in the SDKs)
PublishPATCH /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 returns 404 until that procedure's first publish. Read the /draft variant 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 current name, conversation_config, platform_settings, and workflow unchanged, then discard that agent draft with DELETE on the same path; see Using the Procedure API.

  • If a structured procedure is invalid, the publish or draft save returns 400 with status procedure_validation_failed and errors keyed by procedure ID, each entry carrying the path of 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, and trigger alongside the new content. type cannot change after creation.

  • content is markdown for a free_form procedure, and a JSON-encoded object with a steps array for a deterministic one. The trigger is the top-level trigger field in both cases, not part of content. Serialize it; do not hand-escape quotes.

  • Manually authored workflow edges cannot connect to or from a structured procedure. Use a sub_procedure step 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 no content or trigger; move a procedure into one with POST .../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 trigger text, 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 as procedure_ids in conversation initiation data. An empty list disables all procedures for that starting agent. The same setting allows start_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-js at 2.60.0 or 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.

TypePurpose
llmScenario test — does the agent respond appropriately to a message?
toolTool-call test — right tool, right parameters?
simulationMulti-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 true

See Outbound Calls Reference for provider-specific endpoints, configuration overrides, and dynamic variables.

Managing Agents

# 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 configs
SDK 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

Files

references/using-procedure-api.md

Using the Procedure API

Procedures are reusable instruction blocks that an agent runs when a trigger matches. Use the ElevenLabs CLI by default to create, edit, and publish them. Python and JavaScript SDKs are also available for application code. Reference: Procedures · API Reference.

For what belongs in trigger and content, see Writing Procedures.

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 operations validate every structured procedure on the branch and return any errors. Workflow edges cannot connect to or from a structured procedure or its compiled elements. Use a Sub-procedure step to chain structured procedures; entry from a matching trigger and completion are platform-managed.

The CLI exposes the complete procedure lifecycle, including draft operations.

Prerequisites

  • ELEVENLABS_API_KEY is set, with the CONVAI_READ and CONVAI_WRITE scopes.

  • Reading requires the viewer role on the target agent. Creating, updating, removing, and publishing require the editor role. Publishing to a protected branch requires admin.

  • The target agent_id is known.

  • The target branch_id is known. If not, read main_branch_id with elevenlabs agents get --agent-id "$AGENT_ID" --query main_branch_id, or list branches with elevenlabs agents branches list --agent-id "$AGENT_ID".

AGENT_ID="your-agent-id"
BRANCH_ID="your-branch-id"

The CLI reads ELEVENLABS_API_KEY from the environment automatically; never pass the key as a flag, and never print or persist it.

CLI

Use these command groups for procedure management:

OperationCommand
List, create, read, removeelevenlabs agents procedures ...
Read, update, discard draftelevenlabs agents procedures drafts ...
Publish pending changeselevenlabs agents update

Use --dry-run to validate and inspect a generated request without sending it. Use --schema on any command to inspect its machine-readable input and output contract.

SDKs

Procedure APIs are available in both SDKs starting in 2.60.0. Earlier versions do not include a procedures client, so install at or above that version:

pip install "elevenlabs>=2.60.0"
npm install @elevenlabs/elevenlabs-js@^2.60.0

For JavaScript, use @elevenlabs/elevenlabs-js. The unscoped elevenlabs npm package is the deprecated v1.x and has no procedures client at any version.

Both clients read ELEVENLABS_API_KEY from the environment; never pass a literal key.

Use these SDK methods for the procedure endpoints. Python nests them under client.conversational_ai.agents; JavaScript uses client.conversationalAi.agents:

OperationEndpointMethod
ListGET .../proceduresprocedures.list
CreatePOST .../proceduresprocedures.create
Read branch HEADGET .../procedures/{procedure_id}procedures.get
Read draftGET .../procedures/{procedure_id}/draftprocedures.drafts.get
Update draftPATCH .../procedures/{procedure_id}/draftprocedures.drafts.update
Discard draftDELETE .../procedures/{procedure_id}/draftprocedures.drafts.delete
RemoveDELETE .../procedures/{procedure_id}procedures.remove
PublishPATCH /v1/convai/agents/{agent_id}?branch_id=...agents.update

SDK notes:

  • JavaScript takes the IDs positionally, then a body object. Python takes keyword arguments — except procedures.create, which takes its body as request=CreateProcedureRequestModel(...). Flat keywords on create raise TypeError.

  • Read one historical version with procedures.get(..., version_id=...) or procedures.get(agentId, branchId, procedureId, { versionId }).

  • Pass agent_version_id to procedures.list or procedures.get to resolve the procedures attached to a specific agent version.

  • To publish, call agents.update with branch_id and an optional version_description. That is the whole call.

The flow below creates a free-form procedure, edits its draft, and publishes it.

Python
from elevenlabs import ElevenLabs
from elevenlabs.types import CreateProcedureRequestModel

client = ElevenLabs()
procedures = client.conversational_ai.agents.procedures

created = procedures.create(
    agent_id=AGENT_ID,
    branch_id=BRANCH_ID,
    request=CreateProcedureRequestModel(
        name="Refund requests",
        type="free_form",
        trigger="When the user asks for a refund",
        content="Confirm the order number, check eligibility, and explain the next step.",
    ),
)

draft = procedures.drafts.get(
    agent_id=AGENT_ID, branch_id=BRANCH_ID, procedure_id=created.procedure_id
)
procedures.drafts.update(
    agent_id=AGENT_ID,
    branch_id=BRANCH_ID,
    procedure_id=created.procedure_id,
    name=draft.name,
    type="free_form",
    trigger=draft.trigger,
    content="Confirm the order number. Check refund eligibility. Explain the refund timeline.",
)

client.conversational_ai.agents.update(
    agent_id=AGENT_ID,
    branch_id=BRANCH_ID,
    version_description="Publish refund procedure",
)

If the branch has structured procedures, the publish validates them. Catch the validation error and repair the procedure draft:

from elevenlabs.errors import BadRequestError

try:
    client.conversational_ai.agents.update(
        agent_id=AGENT_ID,
        branch_id=BRANCH_ID,
        version_description="Publish refund procedure",
    )
except BadRequestError as error:
    detail = error.body.get("detail", {})
    if detail.get("status") == "procedure_validation_failed":
        for procedure_id, errors in detail["data"]["errors"].items():
            for item in errors:
                print(procedure_id, item["path"], item["message"])
    raise
JavaScript
import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";

const client = new ElevenLabsClient();
const procedures = client.conversationalAi.agents.procedures;

const created = await procedures.create(agentId, branchId, {
  name: "Refund requests",
  type: "free_form",
  trigger: "When the user asks for a refund",
  content: "Confirm the order number, check eligibility, and explain the next step.",
});

const draft = await procedures.drafts.get(agentId, branchId, created.procedureId);
await procedures.drafts.update(agentId, branchId, created.procedureId, {
  name: draft.name,
  type: "free_form",
  trigger: draft.trigger,
  content: "Confirm the order number. Check refund eligibility. Explain the refund timeline.",
});

await client.conversationalAi.agents.update(agentId, {
  branchId,
  versionDescription: "Publish refund procedure",
});

If the branch has structured procedures, the publish validates them. Catch the validation error and repair the procedure draft:

import { ElevenLabsError } from "@elevenlabs/elevenlabs-js";

try {
  await client.conversationalAi.agents.update(agentId, {
    branchId,
    versionDescription: "Publish refund procedure",
  });
} catch (error) {
  if (error instanceof ElevenLabsError && error.statusCode === 400) {
    const detail = error.body?.detail;
    if (detail?.status === "procedure_validation_failed") {
      for (const [procedureId, errors] of Object.entries(detail.data.errors)) {
        for (const item of errors) console.error(procedureId, item.path, item.message);
      }
    }
  }
  throw error;
}

Procedure Lifecycle

  • Procedures belong to an agent branch. Drafts are scoped to the current user.

  • Create, update, discard, and remove act on your draft working set. Nothing reaches the live agent until you publish.

  • Publishing is not a procedure endpoint. Use PATCH /v1/convai/agents/{agent_id}?branch_id=... to version all changed procedure drafts on the branch.

  • Each branch maps every procedure_id to a published version_id, or to no version while only a draft exists. A branch-HEAD read therefore returns 404 until the first publish.

  • Publishing validates structured procedures. If one is invalid, the publish fails with procedure_validation_failed and nothing is written. See Publish. To run the same check without publishing, save an agent draft; see Validate without publishing.

  • A procedure's type cannot change after creation. A draft update with a different type is rejected.

  • Draft writes are last-write-wins. Read the draft immediately before editing and avoid concurrent writers.

Reads resolve against different sources:

RequestReturns
GET .../procedures/{procedure_id}Branch HEAD. 404 until the procedure's first publish.
GET .../procedures/{procedure_id}/draftYour draft, falling back to branch HEAD when you have none.
GET .../procedures/{procedure_id}?version_id=...One pinned, immutable historical version.

List Procedures

List the effective working set:

elevenlabs agents procedures list \
  --agent-id "$AGENT_ID" --branch-id "$BRANCH_ID"

In the SDKs, pass agent_version_id when you need the procedure versions attached to one immutable agent version.

Each entry carries procedure_id, version_id, name, type, trigger, and has_draft. has_draft is true when the procedure has unpublished draft changes on this branch, in which case its name, type, and trigger reflect that draft. version_id is the version published on this branch, and is null exactly when has_draft is true — including for a procedure that was published earlier and has since been edited.

The list does not include procedure content. Read a body with GET .../procedures/{procedure_id} or its /draft variant.

Create

CREATE_RESPONSE=$(
  elevenlabs agents procedures create \
    --agent-id "$AGENT_ID" --branch-id "$BRANCH_ID" \
    --json '{
      "name": "Refund requests",
      "type": "free_form",
      "trigger": "When the user asks for a refund",
      "content": "Confirm the order number, check eligibility, and explain the next step."
    }'
)
PROCEDURE_ID=$(printf '%s' "$CREATE_RESPONSE" | jq -r '.procedure_id')

Fail if procedure_id is empty or null.

A structured procedure uses the same endpoint with type set to deterministic and its steps JSON-encoded into content. See Writing Procedures for what belongs in trigger and content, and for building that JSON string.

Read and Update the Draft

elevenlabs agents procedures drafts get \
  --agent-id "$AGENT_ID" --branch-id "$BRANCH_ID" --procedure-id "$PROCEDURE_ID"

elevenlabs agents procedures drafts update \
  --agent-id "$AGENT_ID" --branch-id "$BRANCH_ID" --procedure-id "$PROCEDURE_ID" \
  --json '{
    "name": "Refund requests",
    "type": "free_form",
    "trigger": "When the user asks for a refund",
    "content": "Confirm the order number. Check refund eligibility. Explain the refund timeline."
  }'

Treat the draft update body as a full replacement. Read the current draft, preserve name and trigger unless the user requested changes to them, and send them with the new content. Always send the existing type; it cannot change after creation, and a different value is rejected with procedure_type_cannot_change. Always send trigger explicitly rather than relying on a trigger embedded in content.

Publish with the flow under Publish.

Publish

One publish versions every changed procedure draft on the branch:

elevenlabs agents update \
  --agent-id "$AGENT_ID" --branch-id "$BRANCH_ID" \
  --json '{"version_description": "Publish refund procedure"}'

If the branch has structured procedures, the publish validates each one and, if all pass, publishes them in the new version. The publish above is the whole call. This also runs when the change was free-form only, and when the last structured procedure was removed, in which case its compiled result is removed.

On a validation failure the publish returns 400 and nothing is written:

{
  "detail": {
    "status": "procedure_validation_failed",
    "message": "Structured procedures failed validation.",
    "data": {
      "errors": {
        "agtprc_abc123": [
          { "path": "steps[0].ask.instruction", "message": "Step 1: Ask step requires an instruction" }
        ]
      }
    }
  }
}

errors is keyed by procedure ID. Each entry carries the path of the offending field and a message naming the step. Repair every entry in the procedure draft and publish again. Each attempt reports the errors detected in that pass; fixing field-level errors may reveal structural errors on the next pass.

Saving a procedure draft with PATCH .../procedures/{procedure_id}/draft does not validate structured content. The check happens at publish, or at agent draft save as described next.

Validate without publishing

A failed publish writes nothing, so when you are ready to publish, the publish itself is the validation step. Use the flow below only when you need to check structured content and are not ready to publish, for example while other edits on the branch are still pending, or on a protected branch you cannot publish to.

Saving an agent draft runs the same validation as publish and returns the same procedure_validation_failed payload. This is how the dashboard surfaces errors while editing. The endpoint is POST /v1/convai/agents/{agent_id}/drafts?branch_id=... (agents.drafts.create in the SDKs). There is no CLI command for it.

The body is the full agent draft, not a flag: name, conversation_config, platform_settings, and workflow are all required. Read them from the agent and resend them unchanged. Do not edit anything else in that body; a validation check is not the place to change agent configuration.

AGENT=$(
  curl -sS "https://api.elevenlabs.io/v1/convai/agents/$AGENT_ID?branch_id=$BRANCH_ID" \
    -H "xi-api-key: $ELEVENLABS_API_KEY"
)

curl -sS -X POST "https://api.elevenlabs.io/v1/convai/agents/$AGENT_ID/drafts?branch_id=$BRANCH_ID" \
  -H "xi-api-key: $ELEVENLABS_API_KEY" \
  -H "Content-Type: application/json" \
  -d "$(printf '%s' "$AGENT" | jq '{name, conversation_config, platform_settings, workflow}')"

A 400 with procedure_validation_failed carries the same errors map as a failed publish. Repair the procedure draft with PATCH .../procedures/{procedure_id}/draft and save the agent draft again.

A 200 means every structured procedure on the branch validated, and it also stored an agent draft for you on that branch. If you only wanted the check, discard it with DELETE /v1/convai/agents/{agent_id}/drafts?branch_id=... (agents.drafts.delete) so it does not linger as an unsaved change in the dashboard. Discarding the agent draft does not touch your procedure drafts.

Verify a published procedure and record its version_id:

elevenlabs agents procedures get \
  --agent-id "$AGENT_ID" --branch-id "$BRANCH_ID" --procedure-id "$PROCEDURE_ID"

Discard Edits

Discard only your own unpublished draft:

elevenlabs agents procedures drafts delete \
  --agent-id "$AGENT_ID" --branch-id "$BRANCH_ID" --procedure-id "$PROCEDURE_ID"

This restores the branch-HEAD version. For a procedure that was never published, it deletes the procedure. Read the draft afterwards to confirm what remains.

Remove a Procedure

Stage the removal:

elevenlabs agents procedures remove \
  --agent-id "$AGENT_ID" --branch-id "$BRANCH_ID" --procedure-id "$PROCEDURE_ID"

This removes the procedure from the branch working set. It does not erase versions still referenced by agent history.

The removal remains a draft until published. Publishing removes the compiled result of a structured procedure along with it. Then confirm that the procedure is absent from the list and that a branch-HEAD lookup returns 404.

Error Handling

Common errors:

  • 400 from publish or agent draft save, with status procedure_validation_failed: a structured procedure is invalid. Fix every entry under detail.data.errors and publish again.

  • 400 from a procedure draft update, with procedure_type_cannot_change: the body's type differs from the procedure's type. Resend the existing type.

  • 401: ELEVENLABS_API_KEY is unset or invalid.

  • 403: the key lacks CONVAI_READ/CONVAI_WRITE, the agent role is too low, or the branch is protected and only admins may publish to it.

  • 404: verify that the agent, branch, and procedure IDs belong together. Before a procedure's first publish, read the draft endpoint rather than branch HEAD.

The SDKs raise for these responses. The payload is on error.body, and the status is on error.status_code in Python or error.statusCode in JavaScript.

Do not blindly retry create, update, delete, or publish requests. Read current state before deciding whether a retry is safe.

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.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download agents.zip