Subchapter 1.5
references/using-procedure-api.mdMarkdown15 KBView on GitHub
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 · .
For what belongs in trigger and content, see Writing Procedures.
The CLI exposes the complete procedure lifecycle, including draft operations and structured procedure compilation.
ELEVENLABS_API_KEY is set, with the CONVAI_READ and CONVAI_WRITE scopes.agent_id is known.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.
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.
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:
procedures.create, which takes its body as request=CreateProcedureRequestModel(...). Flat keywords on create raise TypeError.procedures.get(..., version_id=...) or procedures.get(agentId, branchId, procedureId, { versionId }).agent_version_id to procedures.list or procedures.get to resolve the procedures attached to a specific agent version.workflow returned by procedures.compile to agents.update.The flow below creates a free-form procedure, edits its draft, and publishes it.
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",
)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;
}PATCH /v1/convai/agents/{agent_id}?branch_id=... to version all changed procedure drafts on the branch.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.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 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_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.
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.
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:
400, errors is keyed by procedure ID, and each entry carries the path of the offending field and a message naming the step, such as steps[0].ask.instruction and Step 1: Ask step requires an instruction.workflow with 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 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.
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.
Common errors:
errors: structured validation failed. Fix every returned procedure error, recompile, and only then publish.no_draft_to_compile: nothing is staged on this branch, so there is nothing to publish either.ELEVENLABS_API_KEY is unset or invalid.CONVAI_READ/CONVAI_WRITE, the agent role is too low, or the branch is protected and only admins may publish to it.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.