Agents

Create, configure, pause and version the AI agents that talk to your customers on voice and text channels.
View as Markdown

An agent is the AI persona that places and answers calls, replies on WhatsApp and email, and chats on your website. It carries everything that decides how a conversation goes: its objective (category), voice and language, the prompt you write, the first thing it says, the channels it may answer on, the post-call analysis it runs, and the tools and conversation graph it can use. Campaigns, phone numbers, widgets and inboxes all point at an agent.

Every agent belongs to one workspace. The API reads and writes the same agents you see under Agents in the dashboard.

How it works

Creating an agent is a two-phase operation. Jelliu validates and screens your request, stores the agent and answers right away; the voice runtime for the agent is then provisioned in the background, which typically takes 30 to 45 seconds. Until that finishes, endpoints that need the runtime answer 409 AGENT_PROVISIONING.

What happens on POST /api/agents, in order:

  1. The body is validated. The system prompt, first message and objection handlers are screened for fraud; a blocked prompt fails with 403 COMPLIANCE_BLOCKED before anything is stored.
  2. Plan gates run: the agent cap for your plan, cloned voices (Enterprise only), and the dial policy for transferPhoneNumber.
  3. The agent is inserted, under a lock that enforces the plan’s agent cap atomically. Version 1 of its history is recorded.
  4. The response returns 201 with the stored agent. Provisioning of the voice runtime is queued with up to 3 attempts and exponential backoff.

The agent object does not expose a provisioning flag. To know whether an agent is ready, call an endpoint that needs the runtime, such as GET /api/agents/{agentId}/signed-url: it answers 409 AGENT_PROVISIONING with Retry-After: 5 until provisioning completes. Adding tools and saving a workflow behave the same way.

How the prompt is assembled

The agent’s effective instructions are layered. Jelliu supplies a universal base for the agent’s category, an optional per-objective playbook comes from the linked template (templateId), and your systemPrompt is the part you own. When you change the category, template or prompt, the agent is re-synced to its runtime.

  • category is the agent’s objective. It selects the base identity and the default post-call analysis. If you omit it on create, the linked template’s category applies.
  • Runtime tuning (turnTimeout, turnEagerness, ttsSpeed, ttsStability, maxCallDurationSeconds) is seeded from the category when you do not send a value, so each objective starts with pacing suited to it.
  • Analysis. If you do not send analysis, a default set of success criteria and data-collection fields is generated for the category. Changing the category later regenerates that default analysis, unless the same request also sends analysis.

Categories

ValueUse it for
salesSelling and closing.
supportCustomer support.
schedulingBooking and managing appointments.
surveysSurveys and feedback.
collectionsPayment collection.
retentionKeeping customers who want to leave.
notificationsInformational outreach.
interviewCandidate screening that ends in a hiring recommendation.
language_assessmentStructured spoken evaluations that end in a score (language level, skills checks).
personalA personal assistant that works for the person it talks to.
generalAnything else.

Channels

channels lists where the agent may answer: voice, whatsapp, email, webchat, instagram and messenger. An agent created without channels serves all of them.

channels is a full replacement, not a delta. Sending ["voice", "whatsapp"] to an agent that serves every channel takes it off email, webchat, instagram and messenger. Always send the complete set you want.

Object

GET /api/agents and GET /api/agents/{agentId} return agents with the fields below. Timestamps are ISO 8601 strings. Keys are snake_case, except for the three computed flags noted in the table.

FieldTypeNullableDescription
idstring (uuid)NoAgent ID.
tenant_idstring (uuid)NoWorkspace that owns the agent.
namestringNoDisplay name, 1 to 100 characters.
categorystringYesThe agent’s objective. See Categories. null on legacy agents, which fall back to the template’s category.
objectivestringYesSame value as category, kept for dashboard compatibility.
template_idstring (uuid)YesLinked template that contributes the objective playbook.
voice_idstringNoVoice used on calls. List voices with GET /api/voices.
languagestringNoOne of es, es-CO, es-MX, es-AR, es-neutral, en, en-US, pt, pt-BR.
channelsstring[]NoChannels the agent may answer on.
system_promptstringNoYour part of the instructions. Empty string means the generic role.
first_messagestringNoWhat the agent says when a call connects.
dynamic_variablesobjectNoDefault values for variables used in the prompt, as string pairs.
objection_handlersobjectNoMap of objection to suggested answer.
escalation_rulesarrayNoItems of trigger, threshold, action.
transfer_rulesarrayNoIn-call handoffs to other agents: targetAgentId, condition, transferMessage.
transfer_phone_numberstringYesE.164 number the agent can transfer a live call to.
max_call_duration_secondsintegerNoHard cap on call length, 30 to 3600. The category also acts as a ceiling.
response_delay_secondsintegerNoFixed pause before replying on text channels, 0 to 86400. 0 uses human pacing instead.
human_pacing_enabledbooleanNoText channels reply on a growing delay (10 s, 1 min, 1 min 30 s, capped at 2 min) instead of instantly.
enable_voicemail_detectionbooleanNoDetect voicemail on outbound calls.
disclose_aibooleanYesWhether the agent opens by saying it is an AI. null inherits the country policy.
departmentstringYesRouting label, up to 100 characters.
skillsstring[]YesRouting skills, up to 50.
expressive_modebooleanNoExpressive voice delivery.
tool_call_sound_enabledbooleanNoPlay a sound while a tool runs.
text_normalisation_typestringNosystem_prompt or elevenlabs.
turn_timeoutnumberNoSeconds of silence before the agent takes the turn on voice.
turn_eagernessstringNopatient, normal or eager (legacy rows may carry low, medium, high).
tts_speednumberNoSpeech speed, 0.5 to 2.0.
tts_stabilitynumberNoVoice stability, 0 to 1.
tts_similarity_boostnumberNoVoice similarity, 0 to 1.
analysisobjectYesPost-call analysis: successEvaluation (criteria with id, description) and dataCollection (fields with id, dataType, description, optional enum).
safetyobjectYesblockedInputTopics and blockedOutputTopics.
privacyobjectYesredactPiiAudio, deleteAudioAfterProcessing, retentionDays.
allowed_toolkitsstring[]YesConnected apps this agent may use. null means no restriction; [] means none.
disabled_toolsobjectYesPer-app list of tool slugs switched off for this agent.
toolkit_accessobjectYesPer-app access level, read or read_write. Apps not listed default to read_write.
toolkitAccessEnforcedbooleanNoComputed. true when toolkit_access is enforced.
mcp_enabledbooleanNoWhether MCP servers may be attached to this agent.
mcpEnabledbooleanNoComputed. Same value as mcp_enabled.
tool_surface_statusstringNoWhether tool changes have reached the runtime: syncing, synced, error or pending_provisioning.
tool_surface_errorstringYesReason for the last tool sync error.
tool_surface_updated_atstringYesWhen the tool sync status last changed.
paused_atstringYesWhen the agent was paused. null when running.
total_calls, calls_today, conversion_ratenumberNoPlaceholders, currently always 0. Use the analytics endpoints for real figures.
avg_sentimentnumberYesPlaceholder, currently always null.
created_atstringNoCreation time.
updated_atstringNoLast update time.

GET /api/agents/{agentId} returns every stored column, so it also includes, among others:

FieldTypeNullableDescription
high_riskbooleanNoChanges to this agent’s behavior need approval when the workspace has the approval gate on.
paused_bystringYesWho paused the agent. API keys appear as apikey: followed by the key ID.
paused_reasonstringYesThe reason given when pausing.
workflowobjectYesThe conversation graph, or null when the agent runs from its prompt alone.
action_limitsobjectYesTool-call budgets set under controls.

Treat every field as optional and ignore fields you do not recognize. The detail response can carry internal columns that are not part of the contract.

Common tasks

Create an agent

1

Pick a voice

List the voice catalog and copy the id of a voice with accessible: true.

curl -sS "https://api.jelliu.co/api/voices" \
-H "Authorization: Bearer $JELLIU_API_KEY"

The response is { "data": { "voices": [ ... ] } }. Each voice has id, name, previewUrl, category, gender, accent, age, language, useCase, description, accessible and inaccessibleReason. Voices with category: "cloned" require the Enterprise plan.

2

Create the agent

Only name, voiceId and language are required.

curl -sS -X POST "https://api.jelliu.co/api/agents" \
-H "Authorization: Bearer $JELLIU_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Valentina",
"voiceId": "JBFqnCBsd6RMkjVDRZzb",
"language": "es-CO",
"category": "scheduling",
"channels": ["voice", "whatsapp"],
"systemPrompt": "Eres Valentina, asistente de la Clínica Dental Sonrisa en Medellín. Ayudas a pacientes a agendar, mover o cancelar citas de valoración.",
"firstMessage": "Hola, le habla Valentina de la Clínica Dental Sonrisa. ¿En qué le puedo ayudar?",
"maxCallDurationSeconds": 300,
"transferPhoneNumber": "+573001234567"
}'

The request takes a second or two because the prompt is screened synchronously. A successful request returns 201 Created:

{
"data": {
"id": "3f6c2a1e-8b4d-4c7a-9e21-5d0b7a6c9f13",
"tenant_id": "b2d4f6a8-1c3e-4a5b-8d7f-9e0a1b2c3d4e",
"name": "Valentina",
"category": "scheduling",
"objective": "scheduling",
"template_id": null,
"voice_id": "JBFqnCBsd6RMkjVDRZzb",
"language": "es-CO",
"channels": ["voice", "whatsapp"],
"system_prompt": "Eres Valentina, asistente de la Clínica Dental Sonrisa en Medellín. Ayudas a pacientes a agendar, mover o cancelar citas de valoración.",
"first_message": "Hola, le habla Valentina de la Clínica Dental Sonrisa. ¿En qué le puedo ayudar?",
"dynamic_variables": {},
"objection_handlers": {},
"escalation_rules": [],
"transfer_rules": [],
"transfer_phone_number": "+573001234567",
"max_call_duration_seconds": 300,
"response_delay_seconds": 0,
"human_pacing_enabled": true,
"enable_voicemail_detection": true,
"disclose_ai": null,
"department": null,
"skills": [],
"expressive_mode": false,
"tool_call_sound_enabled": false,
"text_normalisation_type": "elevenlabs",
"tts_similarity_boost": 0.75,
"analysis": {
"successEvaluation": [ { "id": "appointment_booked", "description": "..." } ],
"dataCollection": [ { "id": "appointment_date", "dataType": "string", "description": "..." } ]
},
"safety": null,
"privacy": { "redactPiiAudio": true, "deleteAudioAfterProcessing": true, "retentionDays": 90 },
"allowed_toolkits": null,
"mcpEnabled": true,
"toolkitAccessEnforced": true,
"paused_at": null,
"total_calls": 0,
"calls_today": 0,
"conversion_rate": 0,
"avg_sentiment": null,
"created_at": "2026-09-15T14:02:11.482Z",
"updated_at": "2026-09-15T14:02:11.482Z"
}
}

The default analysis above is abbreviated; its criteria depend on the category. Tuning fields such as turn_timeout, turn_eagerness, tts_speed and tts_stability are also present, with the values seeded from the category.

3

Wait for provisioning

Poll a runtime endpoint until it stops answering 409 AGENT_PROVISIONING, honoring Retry-After.

curl -sS -i "https://api.jelliu.co/api/agents/3f6c2a1e-8b4d-4c7a-9e21-5d0b7a6c9f13/signed-url" \
-H "Authorization: Bearer $JELLIU_API_KEY"

When ready, the endpoint returns { "data": { "signed_url": "wss://...", "conversation_token": "..." } }. These are short-lived credentials for a browser test session; conversation_token is omitted when it cannot be minted.

Create fields

name
stringRequired

1 to 100 characters.

voiceId
stringRequired

A voice id from GET /api/voices. Cloned voices require the Enterprise plan.

language
stringRequired

es, es-CO, es-MX, es-AR, es-neutral, en, en-US, pt or pt-BR. A few human labels are also accepted and normalized, for example Spanish (LATAM) becomes es-neutral.

category
string

The agent’s objective. See Categories. Omit it to inherit the template’s category.

templateId
string (uuid)

A template from GET /api/agent-templates: a system template or one your workspace owns. Any other ID fails with 400 VALIDATION_FAILED.

channels
string[]

At least one of voice, whatsapp, email, webchat, instagram, messenger. Defaults to all of them.

systemPrompt
stringDefaults to empty

Up to 8,000 characters. Must be empty (the agent introduces itself generically and never invents a product) or at least 10 characters. Put catalogs, price lists and long scripts in the knowledge base instead.

firstMessage
string

Up to 500 characters. When omitted or blank, Jelliu generates a greeting in the agent’s language with the agent name and your company name, for example Hola, le habla Valentina de Clínica Dental Sonrisa. ¿Cómo está?.

dynamicVariables
objectDefaults to {}

Up to 50 entries. Keys up to 100 characters, values up to 500.

objectionHandlers
objectDefaults to {}

Up to 50 entries. Keys up to 100 characters, values up to 2,000. Each value is fraud-screened.

escalationRules
arrayDefaults to []

Items of trigger (negative_sentiment, explicit_request, repeated_objection, timeout), threshold (number, 0 or more) and action (transfer_to_human, end_call_politely, schedule_callback).

transferRules
arrayDefaults to []

Up to 10 items of targetAgentId (uuid of another agent in your workspace), condition (1 to 500 characters) and optional transferMessage (up to 500). Targets that are not provisioned yet are skipped.

transferPhoneNumber
string

E.164 (+ and 2 to 15 digits). Checked against your workspace’s dial policy when saved; a destination the policy refuses fails with 403 COMPLIANCE_BLOCKED.

maxCallDurationSeconds
number

30 to 3600. Defaults to the category’s value.

discloseAi
boolean | null

true or false overrides the country policy for this agent. Omit or send null to inherit it.

enableVoicemailDetection
booleanDefaults to true

Detect voicemail on outbound calls.

department
string

Up to 100 characters.

skills
string[]Defaults to []

Up to 50 items of 1 to 100 characters.

analysis
object

successEvaluation: up to 30 criteria of id (1 to 100 characters) and description (5 to 1,000). dataCollection: up to 40 fields of id, dataType (string, boolean, integer, number), description (5 to 1,000) and optional enum (up to 50 unique strings, only for string fields). Criterion IDs starting with qa_ are reserved. Omit to get the category default.

safety
object

blockedInputTopics and blockedOutputTopics: up to 20 topics each, up to 200 characters.

privacy
object

redactPiiAudio (default true), deleteAudioAfterProcessing (default true), retentionDays (0 to 365, default 90).

turnTimeout
number

Voice only. 1 to 10 seconds. Defaults to the category’s value.

turnEagerness
string

patient, normal, eager (or legacy low, medium, high). Defaults to the category’s value.

ttsSpeed
number

0.5 to 2.0. Defaults to the category’s value.

ttsStability
number

0 to 1. Defaults to the category’s value.

ttsSimilarityBoost
numberDefaults to 0.75

0 to 1.

expressiveMode
booleanDefaults to false

Expressive voice delivery.

toolCallSoundEnabled
booleanDefaults to false

Play a sound while a tool runs.

textNormalisationType
stringDefaults to elevenlabs

system_prompt or elevenlabs.

Asynchronous creation

Add ?async=true to POST /api/agents to run the whole creation, including fraud screening, in the background. The response is 202 Accepted:

{ "data": { "jobId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "status": "pending" } }

Poll GET /api/agents/jobs/{jobId}. data.status moves through pending, processing, then completed (with the agent in data.result) or failed (with the reason in data.error). Job records expire after one hour. The body is still validated synchronously, so an invalid request fails with 400 before a job is created.

List and search agents

GET /api/agents returns up to limit agents (1 to 200, default 50), newest first. Pass the created_at of the last agent as cursor for the next page, and search for a case-insensitive match on the name. See Pagination.

curl -sS "https://api.jelliu.co/api/agents?limit=50&search=valentina" \
-H "Authorization: Bearer $JELLIU_API_KEY"
{
"data": [
{
"id": "3f6c2a1e-8b4d-4c7a-9e21-5d0b7a6c9f13",
"name": "Valentina",
"category": "scheduling",
"objective": "scheduling",
"language": "es-CO",
"channels": ["voice", "whatsapp"],
"paused_at": null,
"tool_surface_status": "synced",
"created_at": "2026-09-15T14:02:11.482Z",
"updated_at": "2026-09-15T14:02:11.482Z"
}
]
}

The list response above is abbreviated; each item carries the fields in Object except the detail-only ones. Get one agent with GET /api/agents/{agentId}.

Agent lists and details are cached for up to 120 seconds. Creating, updating and deleting agents through the API clears the cache, but pausing does not. For the current pause state, read GET /api/agents/{agentId}/pause.

Update an agent

PATCH /api/agents/{agentId} accepts any subset of the create fields, and at least one field must be present. It also accepts fields that exist only on update:

FieldTypeDescription
responseDelaySecondsintegerFixed text-channel reply pause, 0 to 86400. Above 0 it wins over human pacing.
humanPacingEnabledbooleanTurn human pacing on text channels on or off. Voice is unaffected.
allowedToolkitsstring[] or nullConnected apps the agent may use (up to 300 slugs). null removes the restriction; [] allows none.
disabledToolsobject or nullPer-app lists of tool slugs to switch off, for example { "hubspot": ["HUBSPOT_DELETE_DEAL"] }.
toolkitAccessobject or nullPer-app read or read_write. Merged key by key with what is stored; null clears every restriction.
mcpEnabledbooleanfalse also detaches MCP servers already attached to this agent.
highRiskbooleanMark the agent as high risk for the approval gate. See Change approval.

Nullable fields on update: templateId, category, transferPhoneNumber, discloseAi and department accept null to clear them.

curl -sS -X PATCH "https://api.jelliu.co/api/agents/3f6c2a1e-8b4d-4c7a-9e21-5d0b7a6c9f13" \
-H "Authorization: Bearer $JELLIU_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"firstMessage": "Hola, gracias por comunicarse con la Clínica Dental Sonrisa. Le habla Valentina.",
"ttsSpeed": 0.95,
"analysis": {
"dataCollection": [
{ "id": "treatment_interest", "dataType": "string", "description": "Tratamiento por el que pregunta el paciente", "enum": ["valoracion", "ortodoncia", "blanqueamiento", "otro"] }
]
}
}'

The response is 200 with the updated agent in data.

How update merges values:

  • analysis, safety, privacy are merged half by half. An absent key keeps what is stored, null clears that half, and a value replaces it. null for the whole block clears it. In the example above, the stored successEvaluation is kept.
  • toolkitAccess is merged per app. channels, skills, escalationRules, transferRules, dynamicVariables and objectionHandlers are replaced whole.
  • systemPrompt on update must be 10 to 8,000 characters; an empty string is treated as not sent.

Unknown fields are silently dropped. A misspelled key such as system_prompt in a PATCH body returns 200 and changes nothing. Use the camelCase names documented here.

Estimate a prompt’s cost

POST /api/agents/prompt-cost measures a draft against the base instructions for its category before you save it. The body is systemPrompt (up to 200,000 characters, so an over-limit draft can still be measured) and optional category.

curl -sS -X POST "https://api.jelliu.co/api/agents/prompt-cost" \
-H "Authorization: Bearer $JELLIU_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "category": "scheduling", "systemPrompt": "Eres Valentina, asistente de la Clínica Dental Sonrisa..." }'
{
"data": {
"clientChars": 58,
"totalChars": 15342,
"totalTokens": 3836,
"guidelineTokens": 2000,
"overGuideline": true,
"model": "gemini-3.8-flash",
"usdPerMinute": 0.0071
}
}

Tokens are estimated at 4 characters per token over the full assembled prompt, and overGuideline flags prompts above 2,000 tokens, where latency and cost start to rise. usdPerMinute is null when the price cannot be fetched.

Pause and resume an agent

Pausing is the emergency stop. A paused agent accepts no new calls or conversation turns; attempts fail with 409 AGENT_PAUSED (or 503 AGENT_PAUSED when the pause state cannot be read, so the stop fails closed). Conversations already in progress continue to their natural end unless you also set terminateActive.

1

Pause

reason is required (10 to 500 characters) and is written to the audit log.

curl -sS -X POST "https://api.jelliu.co/api/agents/3f6c2a1e-8b4d-4c7a-9e21-5d0b7a6c9f13/pause" \
-H "Authorization: Bearer $JELLIU_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "reason": "Quoting outdated prices after the catalog change", "terminateActive": true }'
{
"data": {
"paused": true,
"pausedAt": "2026-09-15T16:20:03.114Z",
"pausedBy": "apikey:6d1f0c2b-4a9e-4f7b-8c3d-2e1a0b9c8d7f",
"reason": "Quoting outdated prices after the catalog change",
"termination": {
"attempted": 3,
"terminated": 2,
"stillLiveWithoutCarrierLeg": 1,
"failed": 0,
"truncated": false,
"elapsedMs": 812,
"slowestCallMs": 431,
"providerSupportsLiveTermination": false
},
"note": "No se aceptan llamadas ni turnos NUEVOS. Se colgaron 2 de 3 llamadas en curso, ..."
}
}

termination is null unless terminateActive is true. Pausing an agent that is already paused keeps the original pausedAt, returns alreadyPaused: true, and still runs the termination sweep if you ask for it.

2

Resume

curl -sS -X DELETE "https://api.jelliu.co/api/agents/3f6c2a1e-8b4d-4c7a-9e21-5d0b7a6c9f13/pause" \
-H "Authorization: Bearer $JELLIU_API_KEY"

Returns { "data": { "paused": false } }, or { "data": { "paused": false, "alreadyRunning": true } } if it was not paused. Both calls are idempotent.

terminateActive hangs up the telephony leg of each live call. Calls with no carrier leg of Jelliu’s own (a SIP trunk, or a number registered directly with the voice provider) cannot be cut: they are counted in stillLiveWithoutCarrierLeg and keep running. If truncated is true, there were more live calls than one sweep handles; repeat the request.

GET /api/agents/{agentId}/pause returns paused, pausedAt, pausedBy, reason and note without changing anything.

Delete an agent

DELETE /api/agents/{agentId} returns 204 No Content. Deleting:

  • pauses every active campaign that uses the agent, so they stop dialing on their next cycle;
  • unassigns the agent from its phone numbers, so inbound calls to those numbers need a new agent;
  • removes the agent from the voice runtime first, and aborts the delete if that fails, so no orphan is left behind;
  • is a soft delete on Jelliu’s side: the version history is kept.

Campaigns paused by the delete must be reassigned to another agent before they can resume.

Version history

Every change to an agent’s behavior is recorded as a numbered version: creation, updates, workflow edits, restores and deletion.

MethodPathReturns
GET/api/agents/{agentId}/versions?limit=50Versions, newest first. limit 1 to 200, default 50. Without the snapshot body.
GET/api/agents/{agentId}/versions/{version}One version, including snapshot.
POST/api/agents/{agentId}/versions/{version}/restoreThe restored agent. Requires a full key.

A version has id, agent_id, version, change_source (create, update, approved_update, workflow_put, workflow_delete, base_resync, tool_surface, restore, delete), changed_by, actor_type, summary, diff, snapshot_sha256, snapshot_pruned_at, restored_from_version and created_at.

The version record is kept forever, but its snapshot is removed once the version is older than 400 days and is not among the agent’s 50 most recent versions. Restoring such a version fails with 410 and code VALIDATION_FAILED. Restoring to a state identical to the current one is a no-op.

Change approval

A workspace can require a second person to approve behavior changes to agents marked highRisk. It takes both switches: the workspace setting (off by default) and the agent’s highRisk flag.

When both are on, a PATCH that touches a behavior field, a restore, or a workflow change is not applied. The API answers 409 AGENT_CHANGE_PENDING_APPROVAL and returns the request ID:

{
"error": {
"code": "AGENT_CHANGE_PENDING_APPROVAL",
"message": "Este agente está marcado de riesgo alto: el cambio quedó pendiente de que otro usuario lo apruebe.",
"metadata": {
"changeRequestId": "c7e1a2b3-4d5f-4a6b-9c8d-7e6f5a4b3c2d",
"fields": "firstMessage, systemPrompt"
}
}
}

Behavior fields are systemPrompt, firstMessage, category, templateId, objectionHandlers, escalationRules, transferRules, transferPhoneNumber, enableVoicemailDetection, safety, privacy, analysis, allowedToolkits, disabledTools, toolkitAccess, mcpEnabled, channels, maxCallDurationSeconds and highRisk itself. Renames, voice, language, pacing and TTS tuning pass straight through.

MethodPathRequired
GET/api/agents/change-requests?status=pending&agentId=...&limit=50read key
POST/api/agents/change-requests/{id}/approvefull key
POST/api/agents/change-requests/{id}/rejectfull key
POST/api/agents/change-requests/{id}/cancelwrite key, and only the proposer

status is one of pending, approved, rejected, applied, failed, cancelled. approve and reject accept an optional note (up to 2,000 characters). An approved change is re-validated against the current schema and applied; if applying fails, the request ends as failed and must be proposed again.

The proposer cannot approve or reject their own request (403 FORBIDDEN). An API key’s identity is the key itself, so a change proposed with one key must be reviewed by a person in the dashboard or with a different key.

Tools, workflows and connected apps

These are managed under the agent and are covered in the API reference:

  • Tools (/api/agents/{agentId}/tools): webhook tools the agent can call during a conversation, with method, URL, parameters and authentication.
  • Workflow (GET, PUT, DELETE /api/agents/{agentId}/workflow): an optional conversation graph of up to 12 nodes and 24 edges. Nodes are subagent steps or end; edges are unconditional, llm (a natural-language condition) or expression (a comparison over up to 6 state variables the agent scores as it goes). Without a workflow the agent runs from its prompt alone.
  • Knowledge (/api/agents/{agentId}/knowledge): documents the agent retrieves from. See Knowledge base.
  • MCP servers (/api/agents/{agentId}/mcp-servers): external tool servers attached to the agent, allowed only while mcpEnabled is true.
  • Controls (/api/agents/{agentId}/controls/limits): tool-call budgets perMinute, perHour, perDay and perConversation. When exhausted, actions fail with 429 AGENT_BUDGET_EXCEEDED. maxSpendPerDayCents is stored but not enforced yet, and the response says so with spendEnforced: false.

Tool and workflow changes need a provisioned agent and answer 409 AGENT_PROVISIONING until then.

Errors

CodeStatusWhen
VALIDATION_FAILED400The body, query or agentId is invalid; an update has no fields; templateId is not accessible; an analysis criterion ID starts with qa_. details carries formErrors and fieldErrors for create and update.
VALIDATION_FAILED409A change request is no longer pending.
VALIDATION_FAILED410The version’s snapshot expired and cannot be restored.
FORBIDDEN403The key’s scope does not allow the operation, or a proposer tried to approve or reject their own change request.
BILLING_ERROR403The plan’s agent cap was reached (metadata has limit, current, tier), there is no active plan, or a cloned voice was used without Enterprise.
COMPLIANCE_BLOCKED403Fraud screening blocked the prompt, first message or an objection handler, or the dial policy refused transferPhoneNumber.
AGENT_NOT_FOUND404The agent does not exist, was deleted, or belongs to another workspace.
NOT_FOUND404The version, change request or async job does not exist.
AGENT_PROVISIONING409The agent’s runtime is still being set up. Retry after Retry-After (5 seconds).
AGENT_CHANGE_PENDING_APPROVAL409The change was held for review and not applied.
AGENT_PAUSED409A call or turn was attempted on a paused agent. 503 when the pause state could not be read.
RATE_LIMIT_EXCEEDED429Too many agent mutations. See Limits.
TOOL_SYNC_FAILED502A restore could not be applied to the runtime. The agent was left unchanged.

See Errors for the envelope and retry guidance.

Limits

Rate limits. Agent routes are under the general API limit. Mutations (POST /api/agents, PATCH, DELETE, prompt-cost, restoring a version, approving a change request, and tool and workflow writes) also share the 10-per-minute configuration budget with campaigns and webhooks. See Rate limits.

Plan limits. Agents per workspace, including agent add-ons you buy on top:

PlanAgents
Starter2
Growth5
Business12
Enterprise9999

A workspace without an active plan cannot create agents. Cloned voices are available on Enterprise only.

Scopes.

OperationMinimum key scope
List, get, signed URL, pause state, versions, change requests, voices, templatesread
Create, update, prompt cost, pause, resume, cancel own change requestwrite
Delete, restore a version, approve or reject a change requestfull

See Authentication for how scopes map to roles.

Webhooks

EventSentRelevance
agent.action_recordedYesOne event each time an agent invokes a tool.
call.completed, call.failedYesCarry agentId; filter a webhook on agentIds to follow one agent.
audit.log_recordedYesPauses and resumes are audited as AGENT_PAUSED and AGENT_RESUMED.
agent.created, agent.updatedNot yetAccepted in subscriptions but not delivered.

See Webhooks for payloads and signature verification.