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, compile, 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.
The CLI exposes the complete procedure lifecycle, including draft operations and structured procedure compilation.
Prerequisites
ELEVENLABS_API_KEYis set, with theCONVAI_READandCONVAI_WRITEscopes.- Reading requires the viewer role on the target agent. Creating, updating, removing, compiling, and publishing require the editor role. Publishing to a protected branch requires admin.
- The target
agent_idis known. - The target
branch_idis known. If not, readmain_branch_idwithelevenlabs agents get --agent-id "$AGENT_ID" --query main_branch_id, or list branches withelevenlabs 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:
| Operation | Command |
|---|---|
| List, create, read, remove | elevenlabs agents procedures ... |
| Read, update, discard draft | elevenlabs agents procedures drafts ... |
| Compile structured procedures | elevenlabs agents procedures compile |
| Publish pending changes | elevenlabs 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.0For 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:
| Operation | Endpoint | Method |
|---|---|---|
| List | GET .../procedures |
procedures.list |
| Create | POST .../procedures |
procedures.create |
| Read branch HEAD | GET .../procedures/{procedure_id} |
procedures.get |
| Read draft | GET .../procedures/{procedure_id}/draft |
procedures.drafts.get |
| Update draft | PATCH .../procedures/{procedure_id}/draft |
procedures.drafts.update |
| Discard draft | DELETE .../procedures/{procedure_id}/draft |
procedures.drafts.delete |
| Remove | DELETE .../procedures/{procedure_id} |
procedures.remove |
| Compile | POST .../procedures/compile |
procedures.compile |
| Publish | PATCH /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 asrequest=CreateProcedureRequestModel(...). Flat keywords oncreateraiseTypeError. - Read one historical version with
procedures.get(..., version_id=...)orprocedures.get(agentId, branchId, procedureId, { versionId }). - Pass
agent_version_idtoprocedures.listorprocedures.getto resolve the procedures attached to a specific agent version. - For structured changes, pass the
workflowreturned byprocedures.compiletoagents.update.
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 pending changes include structured procedures, compile before publishing:
from elevenlabs.errors import BadRequestError
try:
compiled = procedures.compile(agent_id=AGENT_ID, branch_id=BRANCH_ID)
except BadRequestError as error:
print(f"Compile failed, nothing published: {error.body}")
raise
client.conversational_ai.agents.update(
agent_id=AGENT_ID,
branch_id=BRANCH_ID,
workflow=compiled.workflow,
version_description="Publish refund procedure",
)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 pending changes include structured procedures, compile before publishing:
import { ElevenLabsError } from "@elevenlabs/elevenlabs-js";
try {
const compiled = await procedures.compile(agentId, branchId);
await client.conversationalAi.agents.update(agentId, {
branchId,
workflow: compiled.workflow,
versionDescription: "Publish refund procedure",
});
} catch (error) {
if (error instanceof ElevenLabsError && error.statusCode === 400) {
console.error("Compile or publish failed, nothing published:", error.body);
}
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_idto a publishedversion_id, or to no version while only a draft exists. A branch-HEAD read therefore returns404until the first publish. - Compile structured-procedure changes before publishing. Publish free-form-only changes without compiling. See Compile and Publish.
- Structured content has no dry-run. Save the draft, compile to validate it, and repair what compile reports. See Compile and Publish.
- Draft writes are last-write-wins. Read the draft immediately before editing and avoid concurrent writers.
Reads resolve against different sources:
| Request | Returns |
|---|---|
GET .../procedures/{procedure_id} |
Branch HEAD. 404 until the procedure's first publish. |
GET .../procedures/{procedure_id}/draft |
Your 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, type, and trigger unless the user requested changes to them, and send them with the new content. The API accepts an omitted trigger and then derives it from content; omit it only when that is intentional. Preserve type unless the user explicitly requests a conversion.
Publish with the flow under Compile and Publish.
Compile and 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"}'Compile only when structured procedures have changed. Compilation turns structured drafts into workflow nodes and merges them into the existing agent workflow. The agent loads free-form procedures from their published versions at the start of a conversation, so publish free-form-only changes without workflow.
Also compile after removing the last structured procedure; compilation removes the workflow nodes generated for it.
Compilation requires a pending draft on the branch. With nothing staged, it fails with no_draft_to_compile, which also means there is nothing to publish.
Compilation validates structured content using saved drafts rather than an inline request body:
- Save the content as a draft. A draft that does not validate still saves.
- Compile. On
400,errorsis keyed by procedure ID, and each entry carries thepathof the offending field and a message naming the step, such assteps[0].ask.instructionandStep 1: Ask step requires an instruction. - Repair every entry and compile again. Each compile returns the errors detected in that pass; fixing field-level errors may reveal structural errors on the next pass. Continue until compile returns a workflow.
- Publish, sending that
workflowwith the publish.
WORKFLOW=$(
elevenlabs agents procedures compile \
--agent-id "$AGENT_ID" --branch-id "$BRANCH_ID" \
| jq -c '.workflow'
)A successful compile returns 200 with workflow; validation failure returns 400 with errors
and no workflow, and the CLI exits non-zero and prints that error payload. Do not publish while
compile reports errors. Repair and recompile, and fail if WORKFLOW is empty or null.
SDK methods raise on compile failure. Catch the error around procedures.compile; see SDKs for the flow and Error Handling for the response fields.
Publish the drafts with the compiled workflow:
PUBLISH_BODY=$(
jq -n \
--argjson workflow "$WORKFLOW" \
--arg description "Publish refund procedure" \
'{workflow: $workflow, version_description: $description}'
)
elevenlabs agents update \
--agent-id "$AGENT_ID" --branch-id "$BRANCH_ID" \
--json "$PUBLISH_BODY"Include workflow whenever publishing structured changes. Without it, the publish versions the procedure drafts but leaves the previously published workflow unchanged.
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. If the procedure is structured, compile before publishing to remove its generated workflow nodes. Then confirm that the procedure is absent from the list and that a branch-HEAD lookup returns 404.
Error Handling
Common errors:
- 400 from compile, with
errors: structured validation failed. Fix every returned procedure error, recompile, and only then publish. - 400 from compile, with
no_draft_to_compile: nothing is staged on this branch, so there is nothing to publish either. - 401:
ELEVENLABS_API_KEYis 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.