Subagentes
Un subagente es una segunda persona especializada sobre el mismo agente: su propio prompt, voz, modelo y herramientas, que toma parte de una llamada y la devuelve cuando termina. El agente asociado al numero de telefono sigue siendo el agente principal durante toda la llamada - conserva el registro de llamada, la grabacion, el webhook y la facturacion - asi que un subagente solo cambia como suena y razona el asistente mientras esta activo.
Los subagentes y las transiciones entre ellos viven bajo /agents/{agent_id}. Todas las rutas aceptan la misma autenticacion que el resto de la API publica: envia tu clave en la cabecera X-API-Key (tambien funciona un JWT Bearer). La URL base es https://api.usevocals.com/api/v1.
Nodos y transiciones
El modelo tiene dos piezas:
- Un nodo es el agente principal o uno de sus subagentes. El agente principal siempre es un nodo y no necesita fila propia: donde se espera un id,
nullsignifica el agente principal. - Una transicion es un movimiento en un solo sentido de un nodo a otro nodo del mismo agente, con un disparador que decide cuando se activa.
Cada agente admite hasta 20 subagentes, y cada nodo hasta 20 transiciones salientes. Superar cualquiera de los dos limites devuelve 422 Unprocessable Entity. Ninguna de las dos listas se pagina.
Una misma llamada puede realizar como maximo 10 transiciones, por grande que sea el flujo. Cuando una llamada alcanza ese limite, se informa al nodo activo de que ya no hay mas traspasos disponibles y simplemente sigue atendiendo a quien llama: alcanzar el limite nunca termina una llamada. Lo mismo ocurre cuando un subagente no puede activarse - se elimino despues de iniciarse la llamada, o un proveedor del que depende no esta disponible - en cuyo caso la llamada vuelve al agente principal y continua.
Que puede y que no puede cambiar un subagente
Un subagente solo puede sobrescribir como se comporta el asistente. No puede tocar nada que pertenezca a la llamada completa: el numero de telefono, el proveedor de voz a texto y el idioma, la sensibilidad de respuesta, los limites de duracion y silencio, la deteccion de contestador, la grabacion, el analisis automatico, el webhook ni las variables personalizadas declaradas. Todo eso es del agente principal durante toda la llamada, y no existe campo para sobrescribirlo.
Dejar una sobrescritura sin valor (null) significa "comportate como el agente principal". Un subagente que solo define system_prompt conserva la voz, el modelo y las herramientas del agente principal.
Acceso
El acceso a los subagentes de un agente sigue el permiso de comparticion ya concedido sobre ese agente. El espacio de trabajo propietario tiene acceso completo; un agente compartido con permiso de lectura permite ver subagentes y transiciones pero no modificarlos (403 Forbidden al escribir); un agente compartido con permiso de escritura permite editarlo todo. Un agente que no es tuyo ni esta compartido contigo devuelve 404 Not Found.
La comparticion se concede a una persona, asi que aplica al JWT Bearer del dashboard. Una X-API-Key solo alcanza los agentes de su propio espacio de trabajo.
Esquema del Subagente
SubagentResponse lo devuelven las rutas de listado, obtencion, creacion y actualizacion.
| Campo | Tipo | Descripcion |
|---|---|---|
id | uuid | ID del subagente |
agent_id | uuid | ID del agente principal propietario |
name | string | Identifica al subagente y es el nombre con el que la IA le pasa la llamada. Unico dentro del agente y distinto del nombre del propio agente principal. |
system_prompt | string | null | Prompt usado mientras este subagente esta activo. null hereda el del agente principal. |
llm_provider_id | uuid | null | Proveedor de modelo de lenguaje a usar. null hereda. |
tts_provider_id | uuid | null | Proveedor de voz a usar. null hereda. |
voice_id | string | null | Voz con la que hablar. null hereda. |
tts_config | object | null | Ajustes de voz, con la misma forma que los del agente. null hereda. |
opening_line | string | null | Se dice la primera vez que la persona llega a este subagente en una llamada, y nunca se repite si vuelve. |
opening_line_enabled | boolean | Si la linea de apertura llega a decirse (por defecto true). |
interruptible | boolean | null | Si la persona puede interrumpir. null hereda. |
barge_in_sensitivity | string | null | very_low, low, medium, high o very_high. null hereda. |
thinking_cue_enabled | boolean | null | Si se reproduce una senal de espera. null hereda. |
transfer_enabled | boolean | null | Si este subagente puede transferir la llamada a una persona. null hereda. |
transfer_destinations | array | null | Destinos de transferencia a humano, con la misma forma que los del agente, hasta 50. null hereda. |
included_integration_ids | array | null | Que integraciones del agente puede usar este subagente. null significa todas, [] ninguna. |
included_custom_endpoint_ids | array | null | Lo mismo, para endpoints personalizados. |
included_knowledge_entry_ids | array | null | Lo mismo, para entradas de la base de conocimiento. |
entry_check | object | null | Una comprobacion de entrada - ver abajo. |
created_at | datetime | Marca de tiempo de creacion |
updated_at | datetime | Marca de tiempo de ultima actualizacion |
Comprobacion de entrada
Una comprobacion de entrada es un endpoint personalizado que el subagente llama automaticamente en cuanto se activa, antes de que la persona diga nada. Su resultado puede disparar una transicion, que es como un flujo enruta a partir de un hecho ("este cliente tiene una factura vencida") y no de lo que la persona dice.
{
"custom_endpoint_id": "3f1c9b7e-2a4d-4f88-9b0e-6d5c4a2b1e70",
"arguments": { "invoice_number": "4711" }
}
El endpoint debe pertenecer al mismo agente y ser del agente completo o estar acotado a este subagente. Se ejecuta con su propio tiempo de espera configurado, no se reintenta, y un fallo simplemente no dispara ninguna transicion: la llamada continua.
Herramientas y fuentes acotadas a un nodo
Un endpoint personalizado o una entrada de la base de conocimiento se pueden asociar a un unico subagente en lugar de a todo el agente enviando subagent_id al crearlos - consulta Endpoints Personalizados y Base de Conocimiento. Las filas acotadas cuentan para los mismos limites por agente. Una entrada de conocimiento acotada a un nodo solo se consulta mientras responde su subagente; la recuperacion del agente principal nunca la ve.
Esquema de la Transicion
| Campo | Tipo | Descripcion |
|---|---|---|
id | uuid | ID de la transicion |
agent_id | uuid | ID del agente principal propietario |
source_subagent_id | uuid | null | Nodo del que sale la transicion. null es el agente principal. |
target_subagent_id | uuid | null | Nodo al que entra la transicion. null es el agente principal. |
trigger_type | string | variable_match, entry_check_match o model_judgement |
trigger_config | object | Las claves dependen de trigger_type - ver abajo |
position | integer | Orden de evaluacion dentro del nodo origen, de menor a mayor |
fire_count | integer | Cuantas veces se ha disparado esta transicion en todas las llamadas |
last_fired_at | datetime | null | Cuando se disparo por ultima vez, null si nunca lo hizo |
created_at | datetime | Marca de tiempo de creacion |
updated_at | datetime | Marca de tiempo de ultima actualizacion |
Origen y destino deben ser distintos, y ambos deben ser nodos del mismo agente: un subagente de otro agente se rechaza con 422.
Tipos de disparador
trigger_type | trigger_config | Se dispara cuando |
|---|---|---|
variable_match | {"variable": "language", "value": "es"} | Una variable de la llamada es igual al valor |
entry_check_match | {"field": "status", "operator": "equals", "value": "200"} | El resultado de la comprobacion de entrada del nodo origen coincide. field es status o body, operator es equals o contains. |
model_judgement | {"condition": "la persona quiere pagar una factura"} | La IA juzga cierta la condicion en lenguaje natural, con el mismo estilo que una condicion de transferencia a humano |
Los disparadores deterministas se evaluan primero, por orden de position; el juicio del modelo es el recurso cuando ninguno se dispara. Una transicion entry_check_match necesita que su nodo origen declare un entry_check, de lo contrario crearla devuelve 422.
Que oye la persona que llama
Un traspaso esta pensado para ser inaudible. El subagente que entrega dice una frase breve con su propia voz - la termina antes de que nada cambie - y el subagente que recibe dice entonces su linea de apertura, si tiene una y es la primera vez que la persona llega a el en esa llamada. No se dice nada mas: nunca se le cuenta a la persona que ha habido un traspaso, nunca oye el nombre de un destino y nunca tiene que repetir algo ya tratado. Toda la conversacion hasta ese momento le acompana, junto con una nota breve del subagente que entrega.
Todo lo que la persona puede percibir cambia en ese punto: la voz, el modelo de lenguaje, las herramientas, la base de conocimiento, los destinos de escalado y el prompt. Nada de la llamada en si cambia. La linea sigue abierta, la transcripcion de voz sigue funcionando con los ajustes del agente principal y no se realiza ninguna operacion telefonica, que es lo que lo diferencia de transferir a una persona, donde la llamada sale realmente de VOCALS. Ambas funcionan en el mismo agente, y un subagente tambien puede escalar a una persona.
Despues de la llamada
Una llamada que uso subagentes produce un unico registro de llamada, una grabacion, una transcripcion y un webhook, todos pertenecientes al agente principal. Lo que hizo cada subagente se registra ademas:
node_journeyynode_usageen la respuesta de detalle de la llamada: quien atendio cada parte, y que uso y coste tuvo cada uno. Ver Llamadas.node_idynode_nameen cada turno de la transcripcion.nodesen la carga util del webhookcall.completed, con el mismo recorrido.fire_countylast_fired_aten cada transicion, para ver que rutas toman realmente las personas que llaman.
Endpoints
| Metodo | Ruta | Proposito |
|---|---|---|
GET | /agents/{agent_id}/subagents | Lista los subagentes del agente (mas antiguos primero) |
POST | /agents/{agent_id}/subagents | Crea un subagente |
GET | /agents/{agent_id}/subagents/{subagent_id} | Obtiene un subagente |
PUT | /agents/{agent_id}/subagents/{subagent_id} | Actualiza un subagente (parcial: envia solo lo que cambia) |
DELETE | /agents/{agent_id}/subagents/{subagent_id} | Elimina un subagente |
GET | /agents/{agent_id}/transitions | Obtiene el flujo completo: todos los nodos y todas las transiciones |
POST | /agents/{agent_id}/transitions | Crea una transicion |
PUT | /agents/{agent_id}/transitions/{transition_id} | Actualiza una transicion |
DELETE | /agents/{agent_id}/transitions/{transition_id} | Elimina una transicion |
Listar Subagentes
GET /agents/{agent_id}/subagents
curl -H "X-API-Key: voc_a1b2c3d4e5f6..." \
https://api.usevocals.com/api/v1/agents/{agent_id}/subagents
Respuesta
[
{
"id": "8c2e5a10-99f4-4c1e-9a6b-2d7f0e4b8a13",
"agent_id": "9f8b7c6d-1234-4a5b-9c8d-abcdef012345",
"name": "billing",
"system_prompt": "Gestionas facturas y pagos. Se preciso con los importes.",
"llm_provider_id": null,
"tts_provider_id": null,
"voice_id": null,
"tts_config": null,
"opening_line": "Facturacion, puedo ayudarte con eso.",
"opening_line_enabled": true,
"interruptible": null,
"barge_in_sensitivity": null,
"thinking_cue_enabled": null,
"transfer_enabled": null,
"transfer_destinations": null,
"included_integration_ids": null,
"included_custom_endpoint_ids": null,
"included_knowledge_entry_ids": null,
"entry_check": null,
"created_at": "2026-08-01T09:12:04Z",
"updated_at": "2026-08-01T09:12:04Z"
}
]
Crear Subagente
POST /agents/{agent_id}/subagents
Solo name es obligatorio. Todos los demas campos son sobrescrituras que puedes omitir.
curl -X POST \
-H "X-API-Key: voc_a1b2c3d4e5f6..." \
-H "Content-Type: application/json" \
-d '{
"name": "billing",
"system_prompt": "Gestionas facturas y pagos. Se preciso con los importes.",
"opening_line": "Facturacion, puedo ayudarte con eso.",
"barge_in_sensitivity": "high"
}' \
https://api.usevocals.com/api/v1/agents/{agent_id}/subagents
Devuelve 201 Created con el subagente almacenado.
Actualizar Subagente
PUT /agents/{agent_id}/subagents/{subagent_id}
Parcial: solo cambian los campos que envias. Enviar un campo como null elimina esa sobrescritura, de modo que el subagente vuelve a heredar el valor del agente principal.
curl -X PUT \
-H "X-API-Key: voc_a1b2c3d4e5f6..." \
-H "Content-Type: application/json" \
-d '{"voice_id": null}' \
https://api.usevocals.com/api/v1/agents/{agent_id}/subagents/{subagent_id}
Eliminar Subagente
DELETE /agents/{agent_id}/subagents/{subagent_id}
Devuelve 204 No Content. Las llamadas pasadas conservan su registro del subagente, de modo que las transcripciones y analiticas historicas siguen atribuidas a el.
Obtener el Flujo
GET /agents/{agent_id}/transitions
Devuelve el flujo completo en una sola llamada: todos los nodos, incluido el agente principal, y todas las transiciones entre ellos. tts_vendor es el proveedor de voz con el que cada nodo habla realmente una vez resuelta la herencia, de modo que puedes detectar un traspaso que cambia de proveedor de voz a mitad de llamada.
curl -H "X-API-Key: voc_a1b2c3d4e5f6..." \
https://api.usevocals.com/api/v1/agents/{agent_id}/transitions
Respuesta
{
"nodes": [
{ "node_id": null, "name": "Recepcion", "tts_vendor": "elevenlabs" },
{
"node_id": "8c2e5a10-99f4-4c1e-9a6b-2d7f0e4b8a13",
"name": "billing",
"tts_vendor": "elevenlabs"
}
],
"transitions": [
{
"id": "b7d1f0a2-3c44-4e91-8a0d-1f2e3d4c5b6a",
"agent_id": "9f8b7c6d-1234-4a5b-9c8d-abcdef012345",
"source_subagent_id": null,
"target_subagent_id": "8c2e5a10-99f4-4c1e-9a6b-2d7f0e4b8a13",
"trigger_type": "model_judgement",
"trigger_config": { "condition": "la persona quiere pagar una factura" },
"position": 0,
"fire_count": 42,
"last_fired_at": "2026-08-28T16:03:51Z",
"created_at": "2026-08-01T09:14:22Z",
"updated_at": "2026-08-01T09:14:22Z"
}
]
}
fire_count y last_fired_at son lo que indica que rutas toman realmente las personas que llaman, y que transicion no se dispara nunca y conviene reformular o eliminar.
Crear Transicion
POST /agents/{agent_id}/transitions
curl -X POST \
-H "X-API-Key: voc_a1b2c3d4e5f6..." \
-H "Content-Type: application/json" \
-d '{
"source_subagent_id": null,
"target_subagent_id": "8c2e5a10-99f4-4c1e-9a6b-2d7f0e4b8a13",
"trigger_type": "model_judgement",
"trigger_config": { "condition": "la persona quiere pagar una factura" },
"position": 0
}' \
https://api.usevocals.com/api/v1/agents/{agent_id}/transitions
Devuelve 201 Created. Para permitir el regreso, anade la transicion espejo con source_subagent_id apuntando al subagente y target_subagent_id a null.
Actualizar Transicion
PUT /agents/{agent_id}/transitions/{transition_id}
Parcial, y despues se vuelve a comprobar la transicion entera: cambiar solo trigger_type sigue validando el disparador contra el nodo origen almacenado.
Eliminar Transicion
DELETE /agents/{agent_id}/transitions/{transition_id}
Devuelve 204 No Content. El historial de disparos permanece en las llamadas que lo registraron.
Errores
| Estado | Significado |
|---|---|
403 Forbidden | El agente esta compartido contigo solo en lectura e intentaste escribir |
404 Not Found | El agente no es tuyo ni esta compartido contigo, o el subagente/transicion no existe |
422 Unprocessable Entity | Fallo la validacion: nombre duplicado, nombre igual al del agente principal, limite alcanzado, un nodo de otro agente o un disparador mal formado. El motivo esta en detail. |