Appearance
IA de voz
Generado desde
DialerDigital/corecommit0fc18008b3c8— especificaciónopenapi/dialer-v1.yamlv0.134.0. No se edita a mano; consulta Cómo se construye esta referencia. Descargar la especificación OpenAPI.
Traducción en curso
Esta referencia se genera desde la especificación OpenAPI. Las cadenas que todavía no tienen traducción aparecen en inglés.
Agentes de IA de voz: constructor + política versionada, conversaciones en vivo, transcripciones/resultados, toma de control por el supervisor, el interruptor de emergencia de la marcación por IA por tenant y las métricas de hoy.
Endpoints
Listar los agentes de IA de voz
http
GET /v1/ai/agentsRespuestas
200
Todos los agentes de IA del tenant.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
ai_agents | arreglo de AiAgent | sí | |
ai_agents[].id | string (uuid) | sí | |
ai_agents[].name | string | sí | |
ai_agents[].status | draft · live · paused · retired | sí | |
ai_agents[].voice | object (AiVoice) | no | |
ai_agents[].voice.language | string | sí | BCP-47 (p. ej. en-US). |
ai_agents[].voice.voice_id | string | sí | Identificador de voz del proveedor. Al escribir debe ser un voice_id de GET /v1/ai/voices (422 en caso contrario); un agente almacenado antes del catálogo puede seguir llevando un id fuera de él. |
ai_agents[].voice.pace | number | sí | 0 = lento … 1 = rápido (0.5 ≈ natural). |
ai_agents[].disclosure | object (AiDisclosure) | no | |
ai_agents[].disclosure.text | string | sí | La declaración inicial de voz artificial/pregrabada (con el contenido validado, EN/ES). |
ai_agents[].disclosure.mandated | true | sí | Siempre true: la declaración no se puede deshabilitar. |
ai_agents[].disclosure.locked | boolean | no | True una vez que el agente ha estado live; entonces text es inmutable. |
ai_agents[].policy | object (AiPolicy) | no | Salvaguardas de negociación que acotan a la IA. |
ai_agents[].policy.max_settlement_pct | integer | no | Acuerdo máximo como porcentaje del saldo. |
ai_agents[].policy.min_payment_cents | integer | no | |
ai_agents[].policy.max_plan_months | integer | no | |
ai_agents[].policy.take_payments | none · card_on_file | no | card_on_file se rechaza con 422 mientras el indicador ai_take_payments_enabled del tenant esté apagado (el valor por defecto): todavía nada cobra dinero. Un agente ya almacenado con card_on_file se sirve al runtime de voz como none. |
ai_agents[].policy.prohibited | arreglo de string | no | Conducta/temas no permitidos (etiquetas libres). |
ai_agents[].handoff_rules | arreglo de AiHandoffRule | no | |
ai_agents[].handoff_rules[].id | string (uuid) | null | no | |
ai_agents[].handoff_rules[].trigger | keywords · negative_sentiment · payment_over · human_requested | sí | |
ai_agents[].handoff_rules[].params | object | no | Configuración del disparador, p. ej. {keywords:[...]}, {seconds:20}, {cents:50000}. |
ai_agents[].handoff_rules[].action | transfer · offer_human · confirm_human | sí | |
ai_agents[].handoff_rules[].severity | hard · soft | sí | |
ai_agents[].handoff_rules[].locked | boolean | no | human_requested está SIEMPRE bloqueada (nunca se puede quitar ni editar). |
ai_agents[].policy_version | integer | sí | Monótona; se incrementa en cada cambio versionado. Las transcripciones la citan. |
ai_agents[].created_at | string (date-time) | no | |
ai_agents[].updated_at | string (date-time) | no |
401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}429 — ApiKeyRateLimited
Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}Crear un agente de IA (estado draft)
http
POST /v1/ai/agentsCrea un agente draft con policy_version: 1. El disclosure.text inicial es obligatorio, con el contenido validado (debe identificar la voz como automatizada, EN/ES) y pasa a ser INMUTABLE la primera vez que el agente pasa a live. Una regla de traspaso human_requested siempre está presente y locked. Un name duplicado → 409. voice.voice_id, cuando se indica, debe ser uno de GET /v1/ai/voices (de lo contrario 422). Alcance de gestión: un token de navegador ddw_ da 403, y también un login member (403 role_forbidden, core#2308): la ruta admite una clave dd_ o un login account_admin.
Cuerpo de la petición (JSON, obligatorio)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
name | string | no | |
status | draft · live · paused · retired | no | |
voice | object (AiVoice) | no | |
voice.language | string | sí | BCP-47 (p. ej. en-US). |
voice.voice_id | string | sí | Identificador de voz del proveedor. Al escribir debe ser un voice_id de GET /v1/ai/voices (422 en caso contrario); un agente almacenado antes del catálogo puede seguir llevando un id fuera de él. |
voice.pace | number | sí | 0 = lento … 1 = rápido (0.5 ≈ natural). |
disclosure | object | no | |
disclosure.text | string | no | |
policy | object (AiPolicy) | no | Salvaguardas de negociación que acotan a la IA. |
policy.max_settlement_pct | integer | no | Acuerdo máximo como porcentaje del saldo. |
policy.min_payment_cents | integer | no | |
policy.max_plan_months | integer | no | |
policy.take_payments | none · card_on_file | no | card_on_file se rechaza con 422 mientras el indicador ai_take_payments_enabled del tenant esté apagado (el valor por defecto): todavía nada cobra dinero. Un agente ya almacenado con card_on_file se sirve al runtime de voz como none. |
policy.prohibited | arreglo de string | no | Conducta/temas no permitidos (etiquetas libres). |
handoff_rules | arreglo de AiHandoffRule | no | |
handoff_rules[].id | string (uuid) | null | no | |
handoff_rules[].trigger | keywords · negative_sentiment · payment_over · human_requested | sí | |
handoff_rules[].params | object | no | Configuración del disparador, p. ej. {keywords:[...]}, {seconds:20}, {cents:50000}. |
handoff_rules[].action | transfer · offer_human · confirm_human | sí | |
handoff_rules[].severity | hard · soft | sí | |
handoff_rules[].locked | boolean | no | human_requested está SIEMPRE bloqueada (nunca se puede quitar ni editar). |
json
{
"name": "Ava — Collections Assistant",
"voice": {
"language": "en",
"voice_id": "b24f41fd-00a3-4cd8-992a-a0c9f13f3ef1",
"pace": 0.5
},
"disclosure": {
"text": "This call is from Ava, an automated assistant calling on behalf of Brightpath Recovery Group regarding an account. This call is recorded."
},
"policy": {
"max_settlement_pct": 70,
"min_payment_cents": 5000,
"max_plan_months": 12,
"take_payments": "none",
"prohibited": [
"legal threats",
"third-party disclosure",
"other debts"
]
},
"handoff_rules": [
{
"trigger": "keywords",
"params": {
"keywords": [
"attorney",
"lawyer",
"dispute",
"cease",
"bankruptcy"
]
},
"action": "transfer",
"severity": "hard"
}
]
}Respuestas
201
Creado (estado draft).
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
ai_agent | object (AiAgent) | sí | |
ai_agent.id | string (uuid) | sí | |
ai_agent.name | string | sí | |
ai_agent.status | draft · live · paused · retired | sí | |
ai_agent.voice | object (AiVoice) | no | |
ai_agent.voice.language | string | sí | BCP-47 (p. ej. en-US). |
ai_agent.voice.voice_id | string | sí | Identificador de voz del proveedor. Al escribir debe ser un voice_id de GET /v1/ai/voices (422 en caso contrario); un agente almacenado antes del catálogo puede seguir llevando un id fuera de él. |
ai_agent.voice.pace | number | sí | 0 = lento … 1 = rápido (0.5 ≈ natural). |
ai_agent.disclosure | object (AiDisclosure) | no | |
ai_agent.disclosure.text | string | sí | La declaración inicial de voz artificial/pregrabada (con el contenido validado, EN/ES). |
ai_agent.disclosure.mandated | true | sí | Siempre true: la declaración no se puede deshabilitar. |
ai_agent.disclosure.locked | boolean | no | True una vez que el agente ha estado live; entonces text es inmutable. |
ai_agent.policy | object (AiPolicy) | no | Salvaguardas de negociación que acotan a la IA. |
ai_agent.policy.max_settlement_pct | integer | no | Acuerdo máximo como porcentaje del saldo. |
ai_agent.policy.min_payment_cents | integer | no | |
ai_agent.policy.max_plan_months | integer | no | |
ai_agent.policy.take_payments | none · card_on_file | no | card_on_file se rechaza con 422 mientras el indicador ai_take_payments_enabled del tenant esté apagado (el valor por defecto): todavía nada cobra dinero. Un agente ya almacenado con card_on_file se sirve al runtime de voz como none. |
ai_agent.policy.prohibited | arreglo de string | no | Conducta/temas no permitidos (etiquetas libres). |
ai_agent.handoff_rules | arreglo de AiHandoffRule | no | |
ai_agent.handoff_rules[].id | string (uuid) | null | no | |
ai_agent.handoff_rules[].trigger | keywords · negative_sentiment · payment_over · human_requested | sí | |
ai_agent.handoff_rules[].params | object | no | Configuración del disparador, p. ej. {keywords:[...]}, {seconds:20}, {cents:50000}. |
ai_agent.handoff_rules[].action | transfer · offer_human · confirm_human | sí | |
ai_agent.handoff_rules[].severity | hard · soft | sí | |
ai_agent.handoff_rules[].locked | boolean | no | human_requested está SIEMPRE bloqueada (nunca se puede quitar ni editar). |
ai_agent.policy_version | integer | sí | Monótona; se incrementa en cada cambio versionado. Las transcripciones la citan. |
ai_agent.created_at | string (date-time) | no | |
ai_agent.updated_at | string (date-time) | no |
400 — BadRequest
Petición mal formada (errores de forma/tipo, filtros no válidos, marcas de tiempo erróneas).
json
{
"error": {
"code": "bad_request",
"message": "from must be RFC-3339"
}
}401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — AiAgentWriteForbidden
Se rechazó una escritura de un agente de IA (POST /v1/ai/agents, PATCH o DELETE /v1/ai/agents/{id}). code role_forbidden: quien llama es un login member (la ruta admite una clave dd_ o un login account_admin). code forbidden: el tenant está suspendido o el token es un token de widget.
json
{
"error": {
"code": "role_forbidden",
"message": "creating an AI agent takes a dd_ key or a dashboard login with the account_admin role"
}
}409 — Conflict
Transición de estado no válida, conflicto de unicidad, o un asiento que está ocupado/reservado.
json
{
"error": {
"code": "conflict",
"message": "invalid state transition"
}
}422 — Unprocessable
La forma es correcta, la semántica no (regla de dominio incumplida).
json
{
"error": {
"code": "unprocessable",
"message": "scheduled_at is closer than the minimum notice for a callback (10 minutes unless the tenant's callback_min_notice_seconds says otherwise)"
}
}429 — ApiKeyRateLimited
Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}El catálogo gobernado de IA de voz
http
GET /v1/ai/voicesLas voces con las que puede hablar un agente de IA (core#1539): los ÚNICOS valores aceptados de voice.voice_id en POST /v1/ai/agents y PATCH /v1/ai/agents/{id}. Datos versionados en el plano de control, iguales para todos los tenants; añadir una voz es un release. language es el subtag BCP-47 primario que habla la voz (en); ?language= filtra por él y también admite una etiqueta completa (en-US selecciona en). Un idioma sin voz es una lista vacía, no un error. Misma audiencia que GET /v1/ai/agents.
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
language | query | string | no | Subtag de idioma primario (o una etiqueta BCP-47 completa) por el que filtrar. |
Respuestas
200
Las voces, en el orden del catálogo.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
voices | arreglo de AiVoiceCatalogEntry | sí | |
voices[].voice_id | string | sí | El identificador de voz del proveedor: lo que un agente almacena en voice.voice_id. |
voices[].provider | string | sí | Quién la sintetiza (p. ej. cartesia). |
voices[].language | string | sí | Subtag BCP-47 primario que habla la voz (p. ej. en). |
voices[].label | string | sí | El nombre que ve un operador. |
401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}429 — ApiKeyRateLimited
Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}Alertas de deriva de la IA de voz, con su evidencia y lo que se vigila
http
GET /v1/ai/agents/driftLas alertas del evaluador de deriva, que se ejecuta para TODOS los tenants cada 15 minutos (siempre activo: no hay indicador de activación, y la deriva no cambia ningún agente, experimento ni compuerta). Lee los cubos por hora (UTC, por la hora en que se cerró la llamada) de ai_conversations + ai_call_metrics por alcance, (agent, variant | none) y (model), para cinco métricas: first_audio_p95_ms, handoff_rate, provider_error_rate, failed_rate y cost_per_call. Dos reglas las juzgan: zscore.v1 (la banda móvil del evaluador de insights: base de los 7 días anteriores, al menos 24 cubos y 30 llamadas, k = 3.0, solo la dirección dañina, que es al alza para las cinco) y budget.v1 (el p95 del primer audio del cubo por encima del presupuesto de latencia, 900 ms).
Cada alerta es su fila opened con la evidencia con la que se escribió; una fila cleared que la cita hace que state pase a cleared y rellena resolution. Las más recientes primero; pagina con cursor.
coverage dice qué se está juzgando: una serie cuyo último cubo tuvo menos de 30 llamadas es insufficient_sample y una sin 24 cubos de base es insufficient_baseline. Una lista drift vacía NO significa «todo bien» para esas series.
agent_id, variant_id y model son alcances distintos: model no se puede combinar con los otros dos (400). Requiere el alcance de tenant (una clave dd_ o un login de dashboard).
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
agent_id | query | string (uuid) | no | Solo el alcance de este agente de IA. |
variant_id | query | string | no | Solo esta variante del agente, o base para el agente sin variante. |
model | query | string | no | Solo el alcance de todas las llamadas que usaron este modelo. |
metric | query | first_audio_p95_ms · handoff_rate · provider_error_rate · failed_rate · cost_per_call | no | |
state | query | open · cleared | no | |
limit | query | integer | no | Tamaño de página, 50 por defecto y como máximo 200. |
cursor | query | string | no | El next_cursor de la página anterior. |
Respuestas
200
Una página de alertas y la cobertura de las series vigiladas.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
drift | arreglo de AiDriftEvent | sí | |
drift[].id | string (uuid) | sí | |
drift[].scope | agent · model | sí | |
drift[].ai_agent_id | string,null (uuid) | sí | null en el alcance model. |
drift[].variant_id | string,null (uuid) | sí | null para el agente base y en el alcance model. |
drift[].model | string,null | sí | Solo en el alcance model. |
drift[].metric | first_audio_p95_ms · handoff_rate · provider_error_rate · failed_rate · cost_per_call | sí | Las cinco series que vigila el evaluador de deriva, cada una por cubo horario (UTC, por la hora en que se cerró la llamada): el p95 de la latencia del primer audio de las llamadas en ms, la proporción de llamadas válidas traspasadas a una persona, la proporción de llamadas medidas con un error del proveedor, la proporción de llamadas que fallaron y el coste medio de las llamadas valoradas en micro-USD. Todas alertan al alza; una mejora nunca alerta. |
drift[].rule | zscore.v1 · budget.v1 | sí | |
drift[].direction | up · down | sí | La dirección dañina que se cruzó. |
drift[].state | open · cleared | sí | |
drift[].window_start | string (date-time) | sí | El intervalo horario que cruzó el umbral (inclusivo). |
drift[].window_end | string (date-time) | sí | El mismo intervalo (exclusivo). |
drift[].observed | number | sí | El valor del intervalo. |
drift[].baseline_mean | number,null | sí | null para budget.v1. |
drift[].baseline_stddev | number,null | sí | Desviación típica muestral de los cubos de base; null para budget.v1. |
drift[].stddev_floor | number,null | sí | El suelo aplicado a la desviación; null para budget.v1. |
drift[].threshold | number | sí | zscore.v1: baseline_mean + k·max(baseline_stddev, stddev_floor); budget.v1: el presupuesto de latencia en ms. |
drift[].k | number,null | sí | La sensibilidad de zscore.v1 (3.0); null para budget.v1. |
drift[].sample_n | integer | sí | Llamadas en el cubo. |
drift[].baseline_n | integer,null | sí | Cubos de base usados; null para budget.v1. |
drift[].series | arreglo de AiDriftSeriesPoint | sí | Los cubos que miró la regla seguidos del cubo que cruzó el umbral, del más antiguo al más reciente. |
drift[].series[].t | string (date-time) | sí | Inicio del intervalo horario. |
drift[].series[].sample | integer | sí | Llamadas en el cubo. |
drift[].series[].numerator | number | no | Solo para las métricas de proporción. |
drift[].series[].denominator | number | no | Solo para las métricas de proporción. |
drift[].series[].value | number,null | sí | |
drift[].opened_at | string (date-time) | sí | |
drift[].resolution | object,null | sí | La fila que la cerró; null mientras está abierta. |
drift[].resolution.id | string (uuid) | sí | |
drift[].resolution.window_start | string (date-time) | sí | |
drift[].resolution.window_end | string (date-time) | sí | |
drift[].resolution.observed | number | sí | |
drift[].resolution.cleared_at | string (date-time) | sí | |
coverage | arreglo de AiDriftCoverage | sí | |
coverage[].scope | agent · model | sí | |
coverage[].ai_agent_id | string,null (uuid) | sí | |
coverage[].variant_id | string,null (uuid) | sí | |
coverage[].model | string,null | sí | |
coverage[].metric | first_audio_p95_ms · handoff_rate · provider_error_rate · failed_rate · cost_per_call | sí | Las cinco series que vigila el evaluador de deriva, cada una por cubo horario (UTC, por la hora en que se cerró la llamada): el p95 de la latencia del primer audio de las llamadas en ms, la proporción de llamadas válidas traspasadas a una persona, la proporción de llamadas medidas con un error del proveedor, la proporción de llamadas que fallaron y el coste medio de las llamadas valoradas en micro-USD. Todas alertan al alza; una mejora nunca alerta. |
coverage[].status | ok · insufficient_sample · insufficient_baseline | sí | |
coverage[].window_start | string (date-time) | sí | El último cubo horario con llamadas. |
coverage[].sample_n | integer | sí | |
coverage[].baseline_n | integer | sí | |
coverage[].min_sample | integer | sí | |
coverage[].min_baseline | integer | sí | |
next_cursor | string,null | sí | null en la última página. |
400 — BadRequest
Petición mal formada (errores de forma/tipo, filtros no válidos, marcas de tiempo erróneas).
json
{
"error": {
"code": "bad_request",
"message": "from must be RFC-3339"
}
}401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — Forbidden
El tenant está suspendido. Una sesión del dashboard (ddu_) de un tenant cuya política de inicio de sesión (ADR 227) está en modo enforce responde 403 con el código login_policy_denied ante una solicitud desde una dirección o en una hora que la política no permite — el mismo cuerpo en ambos casos; la sesión no se revoca y una solicitud posterior dentro de la política se procesa. Un token de widget (ddw_) de un puesto queda sujeto a la misma política en cada solicitud (core#2406, indicador widget_login_honors_policy). Un tenant sintético (demo) responde 403 con el código synthetic_tenant en toda ruta que llegaría a un tercero — marcación manual, pierna de transferencia externa o de conferencia, envío de SMS (core#2251, adenda del ADR #280). Ningún pago lo cambia.
json
{
"error": {
"code": "forbidden",
"message": "tenant is suspended"
}
}Obtener un agente de IA
http
GET /v1/ai/agents/{id}Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
Respuestas
200
El agente de IA.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
ai_agent | object (AiAgent) | sí | |
ai_agent.id | string (uuid) | sí | |
ai_agent.name | string | sí | |
ai_agent.status | draft · live · paused · retired | sí | |
ai_agent.voice | object (AiVoice) | no | |
ai_agent.voice.language | string | sí | BCP-47 (p. ej. en-US). |
ai_agent.voice.voice_id | string | sí | Identificador de voz del proveedor. Al escribir debe ser un voice_id de GET /v1/ai/voices (422 en caso contrario); un agente almacenado antes del catálogo puede seguir llevando un id fuera de él. |
ai_agent.voice.pace | number | sí | 0 = lento … 1 = rápido (0.5 ≈ natural). |
ai_agent.disclosure | object (AiDisclosure) | no | |
ai_agent.disclosure.text | string | sí | La declaración inicial de voz artificial/pregrabada (con el contenido validado, EN/ES). |
ai_agent.disclosure.mandated | true | sí | Siempre true: la declaración no se puede deshabilitar. |
ai_agent.disclosure.locked | boolean | no | True una vez que el agente ha estado live; entonces text es inmutable. |
ai_agent.policy | object (AiPolicy) | no | Salvaguardas de negociación que acotan a la IA. |
ai_agent.policy.max_settlement_pct | integer | no | Acuerdo máximo como porcentaje del saldo. |
ai_agent.policy.min_payment_cents | integer | no | |
ai_agent.policy.max_plan_months | integer | no | |
ai_agent.policy.take_payments | none · card_on_file | no | card_on_file se rechaza con 422 mientras el indicador ai_take_payments_enabled del tenant esté apagado (el valor por defecto): todavía nada cobra dinero. Un agente ya almacenado con card_on_file se sirve al runtime de voz como none. |
ai_agent.policy.prohibited | arreglo de string | no | Conducta/temas no permitidos (etiquetas libres). |
ai_agent.handoff_rules | arreglo de AiHandoffRule | no | |
ai_agent.handoff_rules[].id | string (uuid) | null | no | |
ai_agent.handoff_rules[].trigger | keywords · negative_sentiment · payment_over · human_requested | sí | |
ai_agent.handoff_rules[].params | object | no | Configuración del disparador, p. ej. {keywords:[...]}, {seconds:20}, {cents:50000}. |
ai_agent.handoff_rules[].action | transfer · offer_human · confirm_human | sí | |
ai_agent.handoff_rules[].severity | hard · soft | sí | |
ai_agent.handoff_rules[].locked | boolean | no | human_requested está SIEMPRE bloqueada (nunca se puede quitar ni editar). |
ai_agent.policy_version | integer | sí | Monótona; se incrementa en cada cambio versionado. Las transcripciones la citan. |
ai_agent.created_at | string (date-time) | no | |
ai_agent.updated_at | string (date-time) | no |
401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}404 — NotFound
Id desconocido, mal formado (no UUID) O id de otro tenant: RLS devuelve cero filas, así que los tres son indistinguibles por diseño (sin fuga de existencia, nunca un 403 para ids ajenos).
json
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}429 — ApiKeyRateLimited
Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}Actualizar un agente de IA (incrementa policy_version)
http
PATCH /v1/ai/agents/{id}Cualquier cambio en voice / disclosure / policy / handoff_rules / status INCREMENTA policy_version y añade una instantánea inmutable (las transcripciones citan la versión vigente); una edición solo del nombre no la incrementa. Restricciones: una vez que el agente ha estado live, disclosure.text es inmutable (422); un voice.voice_id nuevo debe estar en GET /v1/ai/voices (422): el id que el agente YA almacena se acepta aunque esté fuera del catálogo, de modo que un agente anterior al catálogo sigue siendo editable; policy_version es monótona; la regla de traspaso human_requested sigue locked. status se mueve dentro de draft|live|paused|retired; pasarlo a retired mientras una ruta entrante ACTIVA de tipo ai_agent todavía nombra al agente da 409 que nombra la(s) ruta(s) (core#1547): deshabilítalas o redirígelas antes; pausar nunca se bloquea. Pasar a live necesita salvaguardas explícitas (core#2459): policy.max_settlement_pct, min_payment_cents, max_plan_months, al menos una frase prohibited, y una regla de traspaso payment_over cuando take_payments es card_on_file. Interruptor del servidor ai_agent_live_requires_guards: off, observe (por defecto: 200 más warnings) o enforce (422 policy_incomplete que nombra las claves que faltan: se rechazan la transición a live y la edición de un agente live que AÑADE una clave que falta; un agente ya live sin salvaguardas nunca se corta y responde warnings). Alcance de gestión: un token de navegador ddw_ da 403, y también un login member (403 role_forbidden, core#2308): la ruta admite una clave dd_ o un login account_admin.
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
Cuerpo de la petición (JSON, obligatorio)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
name | string | no | |
status | draft · live · paused · retired | no | |
voice | object (AiVoice) | no | |
voice.language | string | sí | BCP-47 (p. ej. en-US). |
voice.voice_id | string | sí | Identificador de voz del proveedor. Al escribir debe ser un voice_id de GET /v1/ai/voices (422 en caso contrario); un agente almacenado antes del catálogo puede seguir llevando un id fuera de él. |
voice.pace | number | sí | 0 = lento … 1 = rápido (0.5 ≈ natural). |
disclosure | object | no | |
disclosure.text | string | no | |
policy | object (AiPolicy) | no | Salvaguardas de negociación que acotan a la IA. |
policy.max_settlement_pct | integer | no | Acuerdo máximo como porcentaje del saldo. |
policy.min_payment_cents | integer | no | |
policy.max_plan_months | integer | no | |
policy.take_payments | none · card_on_file | no | card_on_file se rechaza con 422 mientras el indicador ai_take_payments_enabled del tenant esté apagado (el valor por defecto): todavía nada cobra dinero. Un agente ya almacenado con card_on_file se sirve al runtime de voz como none. |
policy.prohibited | arreglo de string | no | Conducta/temas no permitidos (etiquetas libres). |
handoff_rules | arreglo de AiHandoffRule | no | |
handoff_rules[].id | string (uuid) | null | no | |
handoff_rules[].trigger | keywords · negative_sentiment · payment_over · human_requested | sí | |
handoff_rules[].params | object | no | Configuración del disparador, p. ej. {keywords:[...]}, {seconds:20}, {cents:50000}. |
handoff_rules[].action | transfer · offer_human · confirm_human | sí | |
handoff_rules[].severity | hard · soft | sí | |
handoff_rules[].locked | boolean | no | human_requested está SIEMPRE bloqueada (nunca se puede quitar ni editar). |
json
{
"status": "live",
"policy": {
"max_settlement_pct": 65
}
}Respuestas
200
El agente actualizado (nueva policy_version).
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
ai_agent | object (AiAgent) | sí | |
ai_agent.id | string (uuid) | sí | |
ai_agent.name | string | sí | |
ai_agent.status | draft · live · paused · retired | sí | |
ai_agent.voice | object (AiVoice) | no | |
ai_agent.voice.language | string | sí | BCP-47 (p. ej. en-US). |
ai_agent.voice.voice_id | string | sí | Identificador de voz del proveedor. Al escribir debe ser un voice_id de GET /v1/ai/voices (422 en caso contrario); un agente almacenado antes del catálogo puede seguir llevando un id fuera de él. |
ai_agent.voice.pace | number | sí | 0 = lento … 1 = rápido (0.5 ≈ natural). |
ai_agent.disclosure | object (AiDisclosure) | no | |
ai_agent.disclosure.text | string | sí | La declaración inicial de voz artificial/pregrabada (con el contenido validado, EN/ES). |
ai_agent.disclosure.mandated | true | sí | Siempre true: la declaración no se puede deshabilitar. |
ai_agent.disclosure.locked | boolean | no | True una vez que el agente ha estado live; entonces text es inmutable. |
ai_agent.policy | object (AiPolicy) | no | Salvaguardas de negociación que acotan a la IA. |
ai_agent.policy.max_settlement_pct | integer | no | Acuerdo máximo como porcentaje del saldo. |
ai_agent.policy.min_payment_cents | integer | no | |
ai_agent.policy.max_plan_months | integer | no | |
ai_agent.policy.take_payments | none · card_on_file | no | card_on_file se rechaza con 422 mientras el indicador ai_take_payments_enabled del tenant esté apagado (el valor por defecto): todavía nada cobra dinero. Un agente ya almacenado con card_on_file se sirve al runtime de voz como none. |
ai_agent.policy.prohibited | arreglo de string | no | Conducta/temas no permitidos (etiquetas libres). |
ai_agent.handoff_rules | arreglo de AiHandoffRule | no | |
ai_agent.handoff_rules[].id | string (uuid) | null | no | |
ai_agent.handoff_rules[].trigger | keywords · negative_sentiment · payment_over · human_requested | sí | |
ai_agent.handoff_rules[].params | object | no | Configuración del disparador, p. ej. {keywords:[...]}, {seconds:20}, {cents:50000}. |
ai_agent.handoff_rules[].action | transfer · offer_human · confirm_human | sí | |
ai_agent.handoff_rules[].severity | hard · soft | sí | |
ai_agent.handoff_rules[].locked | boolean | no | human_requested está SIEMPRE bloqueada (nunca se puede quitar ni editar). |
ai_agent.policy_version | integer | sí | Monótona; se incrementa en cada cambio versionado. Las transcripciones la citan. |
ai_agent.created_at | string (date-time) | no | |
ai_agent.updated_at | string (date-time) | no | |
warnings | arreglo de object | no | Presente en modo observe y enforce cuando el agente está live y le faltan salvaguardas. |
warnings[].code | policy_incomplete | no | |
warnings[].message | string | no | |
warnings[].missing | arreglo de string | no |
400 — BadRequest
Petición mal formada (errores de forma/tipo, filtros no válidos, marcas de tiempo erróneas).
json
{
"error": {
"code": "bad_request",
"message": "from must be RFC-3339"
}
}401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — AiAgentWriteForbidden
Se rechazó una escritura de un agente de IA (POST /v1/ai/agents, PATCH o DELETE /v1/ai/agents/{id}). code role_forbidden: quien llama es un login member (la ruta admite una clave dd_ o un login account_admin). code forbidden: el tenant está suspendido o el token es un token de widget.
json
{
"error": {
"code": "role_forbidden",
"message": "creating an AI agent takes a dd_ key or a dashboard login with the account_admin role"
}
}404 — NotFound
Id desconocido, mal formado (no UUID) O id de otro tenant: RLS devuelve cero filas, así que los tres son indistinguibles por diseño (sin fuga de existencia, nunca un 403 para ids ajenos).
json
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}409 — Conflict
Transición de estado no válida, conflicto de unicidad, o un asiento que está ocupado/reservado.
json
{
"error": {
"code": "conflict",
"message": "invalid state transition"
}
}422 — Unprocessable
La forma es correcta, la semántica no (regla de dominio incumplida).
json
{
"error": {
"code": "unprocessable",
"message": "scheduled_at is closer than the minimum notice for a callback (10 minutes unless the tenant's callback_min_notice_seconds says otherwise)"
}
}429 — ApiKeyRateLimited
Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}Retirar un agente de IA (nunca borra)
http
DELETE /v1/ai/agents/{id}Retirada suave (estado retired): la fila y todas sus instantáneas de versión sobreviven porque las conversaciones y transcripciones vivas o históricas las referencian. Un agente retirado no puede marcar. 409 mientras una ruta entrante ACTIVA de tipo ai_agent todavía nombra al agente (core#1547): el cuerpo nombra la(s) ruta(s); deshabilítalas o redirígelas antes. Alcance de gestión: un token de navegador ddw_ da 403, y también un login member (403 role_forbidden, core#2308): la ruta admite una clave dd_ o un login account_admin.
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
Respuestas
200
El agente retirado.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
ai_agent | object (AiAgent) | sí | |
ai_agent.id | string (uuid) | sí | |
ai_agent.name | string | sí | |
ai_agent.status | draft · live · paused · retired | sí | |
ai_agent.voice | object (AiVoice) | no | |
ai_agent.voice.language | string | sí | BCP-47 (p. ej. en-US). |
ai_agent.voice.voice_id | string | sí | Identificador de voz del proveedor. Al escribir debe ser un voice_id de GET /v1/ai/voices (422 en caso contrario); un agente almacenado antes del catálogo puede seguir llevando un id fuera de él. |
ai_agent.voice.pace | number | sí | 0 = lento … 1 = rápido (0.5 ≈ natural). |
ai_agent.disclosure | object (AiDisclosure) | no | |
ai_agent.disclosure.text | string | sí | La declaración inicial de voz artificial/pregrabada (con el contenido validado, EN/ES). |
ai_agent.disclosure.mandated | true | sí | Siempre true: la declaración no se puede deshabilitar. |
ai_agent.disclosure.locked | boolean | no | True una vez que el agente ha estado live; entonces text es inmutable. |
ai_agent.policy | object (AiPolicy) | no | Salvaguardas de negociación que acotan a la IA. |
ai_agent.policy.max_settlement_pct | integer | no | Acuerdo máximo como porcentaje del saldo. |
ai_agent.policy.min_payment_cents | integer | no | |
ai_agent.policy.max_plan_months | integer | no | |
ai_agent.policy.take_payments | none · card_on_file | no | card_on_file se rechaza con 422 mientras el indicador ai_take_payments_enabled del tenant esté apagado (el valor por defecto): todavía nada cobra dinero. Un agente ya almacenado con card_on_file se sirve al runtime de voz como none. |
ai_agent.policy.prohibited | arreglo de string | no | Conducta/temas no permitidos (etiquetas libres). |
ai_agent.handoff_rules | arreglo de AiHandoffRule | no | |
ai_agent.handoff_rules[].id | string (uuid) | null | no | |
ai_agent.handoff_rules[].trigger | keywords · negative_sentiment · payment_over · human_requested | sí | |
ai_agent.handoff_rules[].params | object | no | Configuración del disparador, p. ej. {keywords:[...]}, {seconds:20}, {cents:50000}. |
ai_agent.handoff_rules[].action | transfer · offer_human · confirm_human | sí | |
ai_agent.handoff_rules[].severity | hard · soft | sí | |
ai_agent.handoff_rules[].locked | boolean | no | human_requested está SIEMPRE bloqueada (nunca se puede quitar ni editar). |
ai_agent.policy_version | integer | sí | Monótona; se incrementa en cada cambio versionado. Las transcripciones la citan. |
ai_agent.created_at | string (date-time) | no | |
ai_agent.updated_at | string (date-time) | no |
401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — AiAgentWriteForbidden
Se rechazó una escritura de un agente de IA (POST /v1/ai/agents, PATCH o DELETE /v1/ai/agents/{id}). code role_forbidden: quien llama es un login member (la ruta admite una clave dd_ o un login account_admin). code forbidden: el tenant está suspendido o el token es un token de widget.
json
{
"error": {
"code": "role_forbidden",
"message": "creating an AI agent takes a dd_ key or a dashboard login with the account_admin role"
}
}404 — NotFound
Id desconocido, mal formado (no UUID) O id de otro tenant: RLS devuelve cero filas, así que los tres son indistinguibles por diseño (sin fuga de existencia, nunca un 403 para ids ajenos).
json
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}409 — Conflict
Transición de estado no válida, conflicto de unicidad, o un asiento que está ocupado/reservado.
json
{
"error": {
"code": "conflict",
"message": "invalid state transition"
}
}429 — ApiKeyRateLimited
Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}Conversación en sandbox (sin llamada real)
http
POST /v1/ai/agents/{id}/testConduce al agente por un intercambio SANDBOX sin estado (reenviado al runtime de IA): NINGUNA llamada real, NINGUNA fila de ai_conversations, NINGUNA marcación. Omite session_id para empezar; devuélvelo para continuar el mismo hilo de sandbox. 503 cuando el sandbox del runtime no está configurado o no es alcanzable. Alcance de gestión: un token de navegador ddw_ da 403.
Cada turno gasta el LLM de la casa, así que pasa por el freno de gasto de inferencia (core#2421): un message de más de 4 000 bytes o un session_id de más de 128 bytes da 400; un tenant por encima de su cuota por ventana de turnos de sandbox (o por encima de la cuota por sesión) recibe 429 con Retry-After; un tenant de demostración sintético recibe 403 (synthetic_tenant); y cuando el freno rechaza el gasto (sin techo mensual, o techo alcanzado) la respuesta es 503 sandbox_budget_unavailable y no se llama al runtime.
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
Cuerpo de la petición (JSON, obligatorio)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
session_id | string (uuid) | no | Omítelo para empezar un hilo de sandbox nuevo; devuélvelo para continuar uno. Como máximo 128 bytes (400 por encima). |
message | string | sí | Como máximo 4 000 bytes (400 por encima). |
json
{
"message": "Yes — I can pay $150 on the 27th."
}Respuestas
200
La respuesta del agente en el sandbox.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
session_id | string (uuid) | sí | |
reply | string | sí |
json
{
"session_id": "7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d",
"reply": "I can set that up: $150 on June 27 using the card on file ending 4417. Shall I confirm?"
}400 — BadRequest
Petición mal formada (errores de forma/tipo, filtros no válidos, marcas de tiempo erróneas).
json
{
"error": {
"code": "bad_request",
"message": "from must be RFC-3339"
}
}401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — Forbidden
El tenant está suspendido. Una sesión del dashboard (ddu_) de un tenant cuya política de inicio de sesión (ADR 227) está en modo enforce responde 403 con el código login_policy_denied ante una solicitud desde una dirección o en una hora que la política no permite — el mismo cuerpo en ambos casos; la sesión no se revoca y una solicitud posterior dentro de la política se procesa. Un token de widget (ddw_) de un puesto queda sujeto a la misma política en cada solicitud (core#2406, indicador widget_login_honors_policy). Un tenant sintético (demo) responde 403 con el código synthetic_tenant en toda ruta que llegaría a un tercero — marcación manual, pierna de transferencia externa o de conferencia, envío de SMS (core#2251, adenda del ADR #280). Ningún pago lo cambia.
json
{
"error": {
"code": "forbidden",
"message": "tenant is suspended"
}
}404 — NotFound
Id desconocido, mal formado (no UUID) O id de otro tenant: RLS devuelve cero filas, así que los tres son indistinguibles por diseño (sin fuga de existencia, nunca un 403 para ids ajenos).
json
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}429 — AiSandboxThrottled
Este tenant (o esta sesión de sandbox) ya ha usado su cuota de turnos de sandbox en la ventana actual (code rate_limited). No se llamó al runtime. Espera Retry-After segundos y reintenta.
json
{
"error": {
"code": "rate_limited",
"message": "too many sandbox turns; retry later"
}
}502 — BadGateway
Una dependencia ascendente (el sandbox del runtime de IA) devolvió una respuesta inesperada o fallida.
json
{
"error": {
"code": "bad_gateway",
"message": "the AI runtime sandbox could not be reached"
}
}503 — SandboxUnavailable
El sandbox del runtime de IA no está configurado o no es alcanzable (fail-closed: puede que el runtime aún no exista), o el freno de gasto de inferencia rechazó el turno (code sandbox_budget_unavailable).
json
{
"error": {
"code": "sandbox_unavailable",
"message": "the AI runtime sandbox is not configured or unreachable"
}
}Escuchar la voz del agente (frase de muestra fija)
http
POST /v1/ai/agents/{id}/voice-previewReproduce UNA frase fija con la voz catalogada del agente y devuelve una URL firmada de corta duración con el audio, para que quien construye el agente oiga la voz antes que un consumidor. La frase NO es una entrada: es fija por idioma (el idioma primario de la voz, en o es), de modo que la factura de síntesis queda acotada por voices x languages sea lo que sea lo que envíe quien llama. El audio está direccionado por contenido (sha256(text|voice)): la misma voz leyendo la misma frase se sintetiza UNA vez, para todos los agentes y todos los tenants; toda petición posterior se sirve desde el almacén (cached: true) sin llamar al proveedor. POST porque un fallo de caché gasta tiempo de proveedor de pago, aunque el audio sea una función pura de la voz: una precarga o un rastreador nunca lo dispara.
Solo una petición que tiene que SINTETIZAR gasta el presupuesto por tenant (429, Retry-After); una petición en caché nunca lo hace. url se firma al responder y debe reproducirse pronto (caduca; nunca se almacena): pide una nueva en lugar de conservarla.
Rechazos: 404 para un agente que no existe o pertenece a otro tenant; 422 cuando el agente no tiene voice.voice_id o tiene uno fuera de GET /v1/ai/voices (una llamada real usaría la voz propia de la celda, así que una muestra de ese id sería una mentira) o la voz habla un idioma sin frase de muestra; 503 con la etapa que falla en error.reason cuando el servicio de TTS no puede producir el audio: nunca un audio vacío. Alcance de gestión: un token de navegador ddw_ da 403.
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
Respuestas
200
La muestra y dónde reproducirla.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
voice_preview | object (AiVoicePreview) | sí | |
voice_preview.url | string | sí | URL firmada de corta duración del audio; se firma al leer, nunca se almacena. |
voice_preview.voice_id | string | sí | La voz catalogada que leyó la frase. |
voice_preview.language | string | sí | Subtag BCP-47 primario de la voz (en o es): elige la frase. |
voice_preview.text | string | sí | La frase fija que se leyó. |
voice_preview.cached | boolean | sí | true cuando el audio ya existía (no se sintetizó nada para esta petición). |
json
{
"voice_preview": {
"url": "https://media.example.com/tts/3f9a...c1?X-Signature=...",
"voice_id": "b24f41fd-00a3-4cd8-992a-a0c9f13f3ef1",
"language": "en",
"text": "Hello, this is a preview of the voice your agent will use.",
"cached": true
}
}401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — Forbidden
El tenant está suspendido. Una sesión del dashboard (ddu_) de un tenant cuya política de inicio de sesión (ADR 227) está en modo enforce responde 403 con el código login_policy_denied ante una solicitud desde una dirección o en una hora que la política no permite — el mismo cuerpo en ambos casos; la sesión no se revoca y una solicitud posterior dentro de la política se procesa. Un token de widget (ddw_) de un puesto queda sujeto a la misma política en cada solicitud (core#2406, indicador widget_login_honors_policy). Un tenant sintético (demo) responde 403 con el código synthetic_tenant en toda ruta que llegaría a un tercero — marcación manual, pierna de transferencia externa o de conferencia, envío de SMS (core#2251, adenda del ADR #280). Ningún pago lo cambia.
json
{
"error": {
"code": "forbidden",
"message": "tenant is suspended"
}
}404 — NotFound
Id desconocido, mal formado (no UUID) O id de otro tenant: RLS devuelve cero filas, así que los tres son indistinguibles por diseño (sin fuga de existencia, nunca un 403 para ids ajenos).
json
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}422 — Unprocessable
La forma es correcta, la semántica no (regla de dominio incumplida).
json
{
"error": {
"code": "unprocessable",
"message": "scheduled_at is closer than the minimum notice for a callback (10 minutes unless the tenant's callback_min_notice_seconds says otherwise)"
}
}429 — VoicePreviewThrottled
Este tenant ya ha provocado su cuota de síntesis de voz en la ventana actual (code rate_limited). La petición no sintetizó nada. Espera Retry-After segundos y reintenta; una petición de una muestra que ya está sintetizada nunca se cuenta y este presupuesto nunca la rechaza.
json
{
"error": {
"code": "rate_limited",
"message": "too many voice samples rendered; retry later"
}
}503 — VoicePreviewUnavailable
La muestra no se pudo producir ahora mismo (code voice_preview_unavailable). error.reason nombra la etapa que falla y nunca lleva el cuerpo, el endpoint ni las credenciales del proveedor: renderer_unconfigured, renderer_unreachable, renderer_http_<status>, renderer_empty_audio, render_failed o store_unavailable. Una síntesis fallida no almacena nada y un reintento sintetiza de nuevo; tras store_unavailable el audio ya está almacenado y un reintento es un acierto de caché.
json
{
"error": {
"code": "voice_preview_unavailable",
"message": "the voice sample could not be produced right now",
"reason": "renderer_http_502"
}
}Experimentos A/B de un agente de IA
http
GET /v1/ai/agents/{id}/experimentsTodos los experimentos del agente (en cualquier estado), los más recientes primero, cada uno con sus variantes en orden de position.
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
Respuestas
200
Los experimentos del agente.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
experiments | arreglo de AiAgentExperiment | sí | |
experiments[].id | string (uuid) | no | |
experiments[].ai_agent_id | string (uuid) | no | |
experiments[].name | string | no | |
experiments[].state | draft · running · ended | no | |
experiments[].primary_metric | ptp_rate · contained_rate · handoff_rate · first_audio_p95_ms · cost_per_call | no | |
experiments[].started_at | string,null (date-time) | no | |
experiments[].ended_at | string,null (date-time) | no | |
experiments[].ended_reason | string,null | no | Por qué terminó, cuando lo terminó algo distinto de una parada manual; null para una parada manual. |
experiments[].variants | arreglo de AiAgentExperimentVariant | no | |
experiments[].variants[].id | string (uuid) | no | |
experiments[].variants[].position | integer | no | Orden en que se recorren las variantes para asignar una deuda (desde 0). |
experiments[].variants[].name | string | no | |
experiments[].variants[].is_control | boolean | no | |
experiments[].variants[].weight | integer | no | |
experiments[].variants[].overrides | object (AiAgentExperimentVariantOverrides) | no | Lo que una variante cambia de su agente de IA: una lista CERRADA. Cualquier otra clave se rechaza con 422 (nunca se ignora). Nada legal está en la lista: ni la declaración, ni la baja hablada, ni las reglas de traspaso, ni las herramientas, ni los límites de negociación, ni las frases prohibidas. |
experiments[].variants[].overrides.voice | object | no | Al menos una de las dos. voice_id debe estar en el catálogo de voces (GET /v1/ai/voices); la declaración se pregenera con la voz de CADA variante antes de marcar. |
experiments[].variants[].overrides.prompt_style | string | no | Una línea (sin salto de línea), añadida después de todo lo que compone el agente; no puede reemplazar nada de ello. |
experiments[].variants[].overrides.model | openai/gpt-oss-20b · openai/gpt-oss-120b · llama-3.3-70b-versatile · llama-3.1-8b-instant | no | Un modelo del catálogo de modelos de la plataforma. |
experiments[].variants[].overrides.llm | object | no | Al menos una de las dos. |
experiments[].created_at | string (date-time) | no | |
experiments[].updated_at | string (date-time) | no |
401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}404 — NotFound
Id desconocido, mal formado (no UUID) O id de otro tenant: RLS devuelve cero filas, así que los tres son indistinguibles por diseño (sin fuga de existencia, nunca un 403 para ids ajenos).
json
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}429 — ApiKeyRateLimited
Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}Crear un experimento A/B de un agente de IA (borrador)
http
POST /v1/ai/agents/{id}/experimentsAdmite una clave dd_ o un login de dashboard con el rol account_admin, como editar el propio agente (403 role_forbidden para un login member); las lecturas siguen abiertas a todo token con alcance de tenant.
Crea un experimento draft de 2 a 5 variantes con exactamente un control, sobre un agente live (cualquier otro agente da 409). Una variante solo puede sobrescribir la lista CERRADA voice (un voice_id del catálogo de voces y/o un pace en [0, 1]), prompt_style (una línea de hasta 600 caracteres, añadida DESPUÉS de todo lo que el agente dice por obligación), model (un modelo del catálogo de modelos) y llm (reasoning_effort, prompt_form). Cualquier otra clave (disclosure, policy, handoff_rules, tools, max_settlement_pct...) se rechaza: el aviso legal, los límites de negociación y el traspaso no son algo que se pueda probar. Cada campo rechazado vuelve en UN solo 422, cada uno con su ruta en error.details. Mientras el experimento corre, agent-config lleva la variante de cada llamada y la conversación la registra; un agente sin experimento se comporta como antes.
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
Cuerpo de la petición (JSON, obligatorio)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
name | string | no | |
primary_metric | ptp_rate · contained_rate · handoff_rate · first_audio_p95_ms · cost_per_call | no | |
variants | arreglo de AiAgentExperimentVariantWrite | no | |
variants[].name | string | sí | |
variants[].weight | integer | sí | Proporción del tráfico, relativa a la suma de los pesos. |
variants[].is_control | boolean | no | Exactamente una variante del experimento es el control (el agente base, así que no envía sobrescrituras). |
variants[].overrides | object (AiAgentExperimentVariantOverrides) | no | Lo que una variante cambia de su agente de IA: una lista CERRADA. Cualquier otra clave se rechaza con 422 (nunca se ignora). Nada legal está en la lista: ni la declaración, ni la baja hablada, ni las reglas de traspaso, ni las herramientas, ni los límites de negociación, ni las frases prohibidas. |
variants[].overrides.voice | object | no | Al menos una de las dos. voice_id debe estar en el catálogo de voces (GET /v1/ai/voices); la declaración se pregenera con la voz de CADA variante antes de marcar. |
variants[].overrides.voice.voice_id | string | no | |
variants[].overrides.voice.pace | number | no | |
variants[].overrides.prompt_style | string | no | Una línea (sin salto de línea), añadida después de todo lo que compone el agente; no puede reemplazar nada de ello. |
variants[].overrides.model | openai/gpt-oss-20b · openai/gpt-oss-120b · llama-3.3-70b-versatile · llama-3.1-8b-instant | no | Un modelo del catálogo de modelos de la plataforma. |
variants[].overrides.llm | object | no | Al menos una de las dos. |
variants[].overrides.llm.reasoning_effort | low · medium · high | no | |
variants[].overrides.llm.prompt_form | compact · full | no |
Respuestas
201
El experimento en borrador.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
experiment | object (AiAgentExperiment) | sí | |
experiment.id | string (uuid) | no | |
experiment.ai_agent_id | string (uuid) | no | |
experiment.name | string | no | |
experiment.state | draft · running · ended | no | |
experiment.primary_metric | ptp_rate · contained_rate · handoff_rate · first_audio_p95_ms · cost_per_call | no | |
experiment.started_at | string,null (date-time) | no | |
experiment.ended_at | string,null (date-time) | no | |
experiment.ended_reason | string,null | no | Por qué terminó, cuando lo terminó algo distinto de una parada manual; null para una parada manual. |
experiment.variants | arreglo de AiAgentExperimentVariant | no | |
experiment.variants[].id | string (uuid) | no | |
experiment.variants[].position | integer | no | Orden en que se recorren las variantes para asignar una deuda (desde 0). |
experiment.variants[].name | string | no | |
experiment.variants[].is_control | boolean | no | |
experiment.variants[].weight | integer | no | |
experiment.variants[].overrides | object (AiAgentExperimentVariantOverrides) | no | Lo que una variante cambia de su agente de IA: una lista CERRADA. Cualquier otra clave se rechaza con 422 (nunca se ignora). Nada legal está en la lista: ni la declaración, ni la baja hablada, ni las reglas de traspaso, ni las herramientas, ni los límites de negociación, ni las frases prohibidas. |
experiment.variants[].overrides.voice | object | no | Al menos una de las dos. voice_id debe estar en el catálogo de voces (GET /v1/ai/voices); la declaración se pregenera con la voz de CADA variante antes de marcar. |
experiment.variants[].overrides.prompt_style | string | no | Una línea (sin salto de línea), añadida después de todo lo que compone el agente; no puede reemplazar nada de ello. |
experiment.variants[].overrides.model | openai/gpt-oss-20b · openai/gpt-oss-120b · llama-3.3-70b-versatile · llama-3.1-8b-instant | no | Un modelo del catálogo de modelos de la plataforma. |
experiment.variants[].overrides.llm | object | no | Al menos una de las dos. |
experiment.created_at | string (date-time) | no | |
experiment.updated_at | string (date-time) | no |
400 — BadRequest
Petición mal formada (errores de forma/tipo, filtros no válidos, marcas de tiempo erróneas).
json
{
"error": {
"code": "bad_request",
"message": "from must be RFC-3339"
}
}401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — Forbidden
El tenant está suspendido. Una sesión del dashboard (ddu_) de un tenant cuya política de inicio de sesión (ADR 227) está en modo enforce responde 403 con el código login_policy_denied ante una solicitud desde una dirección o en una hora que la política no permite — el mismo cuerpo en ambos casos; la sesión no se revoca y una solicitud posterior dentro de la política se procesa. Un token de widget (ddw_) de un puesto queda sujeto a la misma política en cada solicitud (core#2406, indicador widget_login_honors_policy). Un tenant sintético (demo) responde 403 con el código synthetic_tenant en toda ruta que llegaría a un tercero — marcación manual, pierna de transferencia externa o de conferencia, envío de SMS (core#2251, adenda del ADR #280). Ningún pago lo cambia.
json
{
"error": {
"code": "forbidden",
"message": "tenant is suspended"
}
}404 — NotFound
Id desconocido, mal formado (no UUID) O id de otro tenant: RLS devuelve cero filas, así que los tres son indistinguibles por diseño (sin fuga de existencia, nunca un 403 para ids ajenos).
json
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}409 — Conflict
Transición de estado no válida, conflicto de unicidad, o un asiento que está ocupado/reservado.
json
{
"error": {
"code": "conflict",
"message": "invalid state transition"
}
}422 — AiAgentExperimentUnprocessable
El cuerpo se interpretó pero no es un experimento legal de un agente de IA (de 2 a 5 variantes, exactamente un control, pesos 1..1000, sobrescrituras dentro de la lista cerrada, un voice_id del catálogo de voces, un model del catálogo de modelos, un prompt_style de una línea de hasta 600 caracteres). Cada campo rechazado está en error.details, cada uno con su ruta (variants[1].overrides.model, o variants[1].overrides para una clave fuera de la lista, nombrada en message); no se almacenó nada.
json
{
"error": {
"code": "unprocessable",
"message": "variants[1].overrides has keys outside the closed list: disclosure; allowed: voice, prompt_style, model, llm",
"details": [
{
"path": "variants[1].overrides",
"message": "has keys outside the closed list: disclosure; allowed: voice, prompt_style, model, llm"
}
]
}
}429 — ApiKeyRateLimited
Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}Obtener un experimento A/B de un agente de IA
http
GET /v1/ai/agents/{id}/experiments/{experiment_id}Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
experiment_id | path | string (uuid) | sí | Id de un experimento A/B del agente de IA. |
Respuestas
200
El experimento con sus variantes.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
experiment | object (AiAgentExperiment) | sí | |
experiment.id | string (uuid) | no | |
experiment.ai_agent_id | string (uuid) | no | |
experiment.name | string | no | |
experiment.state | draft · running · ended | no | |
experiment.primary_metric | ptp_rate · contained_rate · handoff_rate · first_audio_p95_ms · cost_per_call | no | |
experiment.started_at | string,null (date-time) | no | |
experiment.ended_at | string,null (date-time) | no | |
experiment.ended_reason | string,null | no | Por qué terminó, cuando lo terminó algo distinto de una parada manual; null para una parada manual. |
experiment.variants | arreglo de AiAgentExperimentVariant | no | |
experiment.variants[].id | string (uuid) | no | |
experiment.variants[].position | integer | no | Orden en que se recorren las variantes para asignar una deuda (desde 0). |
experiment.variants[].name | string | no | |
experiment.variants[].is_control | boolean | no | |
experiment.variants[].weight | integer | no | |
experiment.variants[].overrides | object (AiAgentExperimentVariantOverrides) | no | Lo que una variante cambia de su agente de IA: una lista CERRADA. Cualquier otra clave se rechaza con 422 (nunca se ignora). Nada legal está en la lista: ni la declaración, ni la baja hablada, ni las reglas de traspaso, ni las herramientas, ni los límites de negociación, ni las frases prohibidas. |
experiment.variants[].overrides.voice | object | no | Al menos una de las dos. voice_id debe estar en el catálogo de voces (GET /v1/ai/voices); la declaración se pregenera con la voz de CADA variante antes de marcar. |
experiment.variants[].overrides.prompt_style | string | no | Una línea (sin salto de línea), añadida después de todo lo que compone el agente; no puede reemplazar nada de ello. |
experiment.variants[].overrides.model | openai/gpt-oss-20b · openai/gpt-oss-120b · llama-3.3-70b-versatile · llama-3.1-8b-instant | no | Un modelo del catálogo de modelos de la plataforma. |
experiment.variants[].overrides.llm | object | no | Al menos una de las dos. |
experiment.created_at | string (date-time) | no | |
experiment.updated_at | string (date-time) | no |
401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}404 — NotFound
Id desconocido, mal formado (no UUID) O id de otro tenant: RLS devuelve cero filas, así que los tres son indistinguibles por diseño (sin fuga de existencia, nunca un 403 para ids ajenos).
json
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}429 — ApiKeyRateLimited
Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}Editar un experimento A/B en borrador de un agente de IA
http
PATCH /v1/ai/agents/{id}/experiments/{experiment_id}Admite una clave dd_ o un login de dashboard con el rol account_admin, como editar el propio agente (403 role_forbidden para un login member); las lecturas siguen abiertas a todo token con alcance de tenant.
name y/o variants (que REEMPLAZA el conjunto entero); primary_metric solo puede repetirse, nunca cambiarse. Solo se puede editar un draft: una vez que corre (o terminó) sus variantes quedan congeladas y esto da 409 (para probar otro prompt, voz o modelo, crea otro experimento).
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
experiment_id | path | string (uuid) | sí | Id de un experimento A/B del agente de IA. |
Cuerpo de la petición (JSON, obligatorio)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
name | string | no | |
primary_metric | ptp_rate · contained_rate · handoff_rate · first_audio_p95_ms · cost_per_call | no | |
variants | arreglo de AiAgentExperimentVariantWrite | no | |
variants[].name | string | sí | |
variants[].weight | integer | sí | Proporción del tráfico, relativa a la suma de los pesos. |
variants[].is_control | boolean | no | Exactamente una variante del experimento es el control (el agente base, así que no envía sobrescrituras). |
variants[].overrides | object (AiAgentExperimentVariantOverrides) | no | Lo que una variante cambia de su agente de IA: una lista CERRADA. Cualquier otra clave se rechaza con 422 (nunca se ignora). Nada legal está en la lista: ni la declaración, ni la baja hablada, ni las reglas de traspaso, ni las herramientas, ni los límites de negociación, ni las frases prohibidas. |
variants[].overrides.voice | object | no | Al menos una de las dos. voice_id debe estar en el catálogo de voces (GET /v1/ai/voices); la declaración se pregenera con la voz de CADA variante antes de marcar. |
variants[].overrides.voice.voice_id | string | no | |
variants[].overrides.voice.pace | number | no | |
variants[].overrides.prompt_style | string | no | Una línea (sin salto de línea), añadida después de todo lo que compone el agente; no puede reemplazar nada de ello. |
variants[].overrides.model | openai/gpt-oss-20b · openai/gpt-oss-120b · llama-3.3-70b-versatile · llama-3.1-8b-instant | no | Un modelo del catálogo de modelos de la plataforma. |
variants[].overrides.llm | object | no | Al menos una de las dos. |
variants[].overrides.llm.reasoning_effort | low · medium · high | no | |
variants[].overrides.llm.prompt_form | compact · full | no |
Respuestas
200
El experimento editado.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
experiment | object (AiAgentExperiment) | sí | |
experiment.id | string (uuid) | no | |
experiment.ai_agent_id | string (uuid) | no | |
experiment.name | string | no | |
experiment.state | draft · running · ended | no | |
experiment.primary_metric | ptp_rate · contained_rate · handoff_rate · first_audio_p95_ms · cost_per_call | no | |
experiment.started_at | string,null (date-time) | no | |
experiment.ended_at | string,null (date-time) | no | |
experiment.ended_reason | string,null | no | Por qué terminó, cuando lo terminó algo distinto de una parada manual; null para una parada manual. |
experiment.variants | arreglo de AiAgentExperimentVariant | no | |
experiment.variants[].id | string (uuid) | no | |
experiment.variants[].position | integer | no | Orden en que se recorren las variantes para asignar una deuda (desde 0). |
experiment.variants[].name | string | no | |
experiment.variants[].is_control | boolean | no | |
experiment.variants[].weight | integer | no | |
experiment.variants[].overrides | object (AiAgentExperimentVariantOverrides) | no | Lo que una variante cambia de su agente de IA: una lista CERRADA. Cualquier otra clave se rechaza con 422 (nunca se ignora). Nada legal está en la lista: ni la declaración, ni la baja hablada, ni las reglas de traspaso, ni las herramientas, ni los límites de negociación, ni las frases prohibidas. |
experiment.variants[].overrides.voice | object | no | Al menos una de las dos. voice_id debe estar en el catálogo de voces (GET /v1/ai/voices); la declaración se pregenera con la voz de CADA variante antes de marcar. |
experiment.variants[].overrides.prompt_style | string | no | Una línea (sin salto de línea), añadida después de todo lo que compone el agente; no puede reemplazar nada de ello. |
experiment.variants[].overrides.model | openai/gpt-oss-20b · openai/gpt-oss-120b · llama-3.3-70b-versatile · llama-3.1-8b-instant | no | Un modelo del catálogo de modelos de la plataforma. |
experiment.variants[].overrides.llm | object | no | Al menos una de las dos. |
experiment.created_at | string (date-time) | no | |
experiment.updated_at | string (date-time) | no |
400 — BadRequest
Petición mal formada (errores de forma/tipo, filtros no válidos, marcas de tiempo erróneas).
json
{
"error": {
"code": "bad_request",
"message": "from must be RFC-3339"
}
}401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — Forbidden
El tenant está suspendido. Una sesión del dashboard (ddu_) de un tenant cuya política de inicio de sesión (ADR 227) está en modo enforce responde 403 con el código login_policy_denied ante una solicitud desde una dirección o en una hora que la política no permite — el mismo cuerpo en ambos casos; la sesión no se revoca y una solicitud posterior dentro de la política se procesa. Un token de widget (ddw_) de un puesto queda sujeto a la misma política en cada solicitud (core#2406, indicador widget_login_honors_policy). Un tenant sintético (demo) responde 403 con el código synthetic_tenant en toda ruta que llegaría a un tercero — marcación manual, pierna de transferencia externa o de conferencia, envío de SMS (core#2251, adenda del ADR #280). Ningún pago lo cambia.
json
{
"error": {
"code": "forbidden",
"message": "tenant is suspended"
}
}404 — NotFound
Id desconocido, mal formado (no UUID) O id de otro tenant: RLS devuelve cero filas, así que los tres son indistinguibles por diseño (sin fuga de existencia, nunca un 403 para ids ajenos).
json
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}409 — Conflict
Transición de estado no válida, conflicto de unicidad, o un asiento que está ocupado/reservado.
json
{
"error": {
"code": "conflict",
"message": "invalid state transition"
}
}422 — AiAgentExperimentUnprocessable
El cuerpo se interpretó pero no es un experimento legal de un agente de IA (de 2 a 5 variantes, exactamente un control, pesos 1..1000, sobrescrituras dentro de la lista cerrada, un voice_id del catálogo de voces, un model del catálogo de modelos, un prompt_style de una línea de hasta 600 caracteres). Cada campo rechazado está en error.details, cada uno con su ruta (variants[1].overrides.model, o variants[1].overrides para una clave fuera de la lista, nombrada en message); no se almacenó nada.
json
{
"error": {
"code": "unprocessable",
"message": "variants[1].overrides has keys outside the closed list: disclosure; allowed: voice, prompt_style, model, llm",
"details": [
{
"path": "variants[1].overrides",
"message": "has keys outside the closed list: disclosure; allowed: voice, prompt_style, model, llm"
}
]
}
}429 — ApiKeyRateLimited
Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}Borrar un experimento A/B en borrador de un agente de IA
http
DELETE /v1/ai/agents/{id}/experiments/{experiment_id}Admite una clave dd_ o un login de dashboard con el rol account_admin, como editar el propio agente (403 role_forbidden para un login member); las lecturas siguen abiertas a todo token con alcance de tenant.
Solo se puede borrar un draft; un experimento en curso o terminado da 409.
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
experiment_id | path | string (uuid) | sí | Id de un experimento A/B del agente de IA. |
Respuestas
204
Eliminado.
401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — Forbidden
El tenant está suspendido. Una sesión del dashboard (ddu_) de un tenant cuya política de inicio de sesión (ADR 227) está en modo enforce responde 403 con el código login_policy_denied ante una solicitud desde una dirección o en una hora que la política no permite — el mismo cuerpo en ambos casos; la sesión no se revoca y una solicitud posterior dentro de la política se procesa. Un token de widget (ddw_) de un puesto queda sujeto a la misma política en cada solicitud (core#2406, indicador widget_login_honors_policy). Un tenant sintético (demo) responde 403 con el código synthetic_tenant en toda ruta que llegaría a un tercero — marcación manual, pierna de transferencia externa o de conferencia, envío de SMS (core#2251, adenda del ADR #280). Ningún pago lo cambia.
json
{
"error": {
"code": "forbidden",
"message": "tenant is suspended"
}
}404 — NotFound
Id desconocido, mal formado (no UUID) O id de otro tenant: RLS devuelve cero filas, así que los tres son indistinguibles por diseño (sin fuga de existencia, nunca un 403 para ids ajenos).
json
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}409 — Conflict
Transición de estado no válida, conflicto de unicidad, o un asiento que está ocupado/reservado.
json
{
"error": {
"code": "conflict",
"message": "invalid state transition"
}
}429 — ApiKeyRateLimited
Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}Resultados y veredicto honesto de un experimento A/B de un agente de IA
http
GET /v1/ai/agents/{id}/experiments/{experiment_id}/resultsSe recalcula desde el libro en cada lectura (sin contadores): las conversaciones marcadas con el experimento (ai_conversations), sus métricas por llamada (ai_call_metrics) y, para la unidad de aleatorización, la deuda del intento de llamada. Una conversación válida es una cerrada cuyo resultado no es ni failed ni voicemail; las tasas de las métricas primarias son sobre las conversaciones válidas, failed_rate sobre todas las cerradas. El mismo experimento leído dos veces da la misma respuesta.
verdict.status es insufficient_data (algún brazo tiene menos de 100 conversaciones válidas o 10 éxitos para una tasa, menos de 100 llamadas con la medida para una media), no_significant_difference o winner. Las tasas usan el test z de dos proporciones, las medias (latencia, coste) el test de Welch en la aproximación normal; bilateral, alfa 0.05, Bonferroni sobre los retadores. Qué sentido es mejor depende de la métrica: ptp_rate y contained_rate más, handoff_rate, first_audio_p95_ms y cost_per_call menos. Cuando el reparto observado de unidades se desvía de los pesos (chi-cuadrado, alfa 0.001) allocation_warning es true y no se emite ningún winner.
Un coste sin tarifa cargada es unpriced (nunca 0): cost.status dice priced, partial o unpriced por variante, y cuando la métrica primaria es cost_per_call y algún brazo tiene una llamada sin valorar, el veredicto es insufficient_data con reason: unpriced. El importe del coste nunca se publica (es el coste de proveedor de la plataforma; al tenant se le factura por minuto): cada variante lleva su consumption en minutos, y un veredicto de cost_per_call conserva su significación y su ganador pero no las magnitudes (value, difference, ci_low, ci_high).
safety.breaches es la evidencia de la parada por cumplimiento: una variante cuyo compliance_violation_rate es significativamente mayor que el del control (z, alfa 0.05, al menos 100 conversaciones cerradas en ambos brazos). Core termina el experimento por sí mismo cuando eso ocurre (ended_reason dice por qué) y el agente vuelve a su configuración base. Leer nunca cambia el experimento.
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
experiment_id | path | string (uuid) | sí | Id de un experimento A/B del agente de IA. |
Respuestas
200
Los resultados del experimento.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
results | object (AiAgentExperimentResults) | sí | |
results.experiment | object | sí | |
results.experiment.id | string (uuid) | no | |
results.experiment.ai_agent_id | string (uuid) | no | |
results.experiment.name | string | no | |
results.experiment.state | draft · running · ended | no | |
results.experiment.primary_metric | ptp_rate · contained_rate · handoff_rate · first_audio_p95_ms · cost_per_call | no | |
results.experiment.started_at | string,null (date-time) | no | |
results.experiment.ended_at | string,null (date-time) | no | |
results.experiment.ended_reason | string,null | no | Por qué terminó cuando lo terminó algo distinto de una parada manual (la parada por cumplimiento escribe aquí su evidencia). |
results.variants | arreglo de AiAgentExperimentVariantResults | sí | |
results.variants[].variant_id | string (uuid) | no | |
results.variants[].name | string | no | |
results.variants[].is_control | boolean | no | |
results.variants[].weight | integer | no | |
results.variants[].assigned_units | integer | no | Unidades distintas con una conversación en esta variante: la deuda cuando la llamada tiene una, si no la llamada. |
results.variants[].conversations | integer | no | |
results.variants[].closed | integer | no | Conversaciones con su resultado terminal. |
results.variants[].valid | integer | no | Cerradas con un resultado distinto de failed y voicemail; el denominador de las tasas. |
results.variants[].failed | integer | no | |
results.variants[].ptp | integer | no | Conversaciones válidas que terminaron ptp o payment_taken. |
results.variants[].handoffs | integer | no | Conversaciones válidas de las que se hizo cargo una persona. |
results.variants[].contained | integer | no | Conversaciones válidas sin traspaso a una persona y con un resultado que no es ni handoff ni handoff_failed. |
results.variants[].violating | integer | no | Conversaciones cerradas con al menos una violación de cumplimiento. |
results.variants[].ptp_rate | number,null | no | |
results.variants[].contained_rate | number,null | no | |
results.variants[].handoff_rate | number,null | no | Mejor es MENOS. |
results.variants[].failed_rate | number,null | no | Sobre todas las conversaciones cerradas. |
results.variants[].compliance_violation_rate | number,null | no | Sobre todas las conversaciones cerradas. |
results.variants[].provider_error_rate | number,null | no | Conversaciones cerradas con algún error del proveedor sobre las cerradas que tienen métricas; null cuando ninguna las tiene. |
results.variants[].metrics_coverage | object | no | Cuántas conversaciones cerradas publicaron métricas. Un runtime que todavía no publica se muestra aquí, no como un 0 limpio. |
results.variants[].metrics_coverage.with_metrics | integer | no | |
results.variants[].metrics_coverage.closed | integer | no | |
results.variants[].consumption | object | no | Lo que consumieron las llamadas cerradas, en la unidad en que se factura al tenant (minutos). Ambos campos son null cuando ninguna llamada cerrada tiene duración. |
results.variants[].consumption.minutes_total | number,null | no | |
results.variants[].consumption.minutes_per_call | number,null | no | |
results.variants[].first_audio_ms_p95 | object | no | El p95 por llamada de la latencia del primer audio. |
results.variants[].first_audio_ms_p95.n | integer | no | |
results.variants[].first_audio_ms_p95.mean | number,null | no | |
results.variants[].first_audio_ms_p95.variance | number,null | no | |
results.variants[].first_audio_ms_p95.p50_mean | number,null | no | Media del p50 por llamada. |
results.variants[].first_audio_ms_p95.p95 | number,null | no | Percentil 95 del p95 por llamada. |
results.variants[].cost | object | no | Si se conoce el coste de proveedor de las llamadas. El importe nunca se publica: es el coste de la plataforma, no un precio (al tenant se le factura por minuto). |
results.variants[].cost.status | priced · partial · unpriced | no | unpriced cuando ninguna llamada con métricas tiene un coste congelado: nunca se muestra como 0. |
results.variants[].cost.n | integer | no | Llamadas con un coste congelado. |
results.variants[].cost.unpriced_calls | integer | no | |
results.unattributed_conversations | integer | sí | Conversaciones marcadas con el experimento pero con una variante que este no tiene. Un valor distinto de cero significa que la marca y el experimento no coinciden. |
results.verdict | object (AiAgentExperimentVerdict) | sí | |
results.verdict.status | insufficient_data · no_significant_difference · winner | sí | |
results.verdict.reason | unpriced · null | sí | Por qué un veredicto que se pudo leer es insufficient_data por una razón distinta del tamaño de la muestra. |
results.verdict.winner | null | object | sí | |
results.verdict.withheld_by | null | allocation_warning | sí | |
results.verdict.comparisons | arreglo de object | sí | Una entrada por retador; vacío mientras no se alcance el mínimo de muestra. |
results.verdict.comparisons[].variant_id | string (uuid) | no | |
results.verdict.comparisons[].value | number | no | La tasa o la media del retador. Ausente cuando la métrica primaria es cost_per_call. |
results.verdict.comparisons[].difference | number | no | Retador menos control. Ausente cuando la métrica primaria es cost_per_call. |
results.verdict.comparisons[].z | number,null | no | Null cuando ambos brazos no tienen varianza y tienen medias distintas. |
results.verdict.comparisons[].p_value | number | no | |
results.verdict.comparisons[].adjusted_p_value | number | no | |
results.verdict.comparisons[].ci_low | number | no | Ausente cuando la métrica primaria es cost_per_call. |
results.verdict.comparisons[].ci_high | number | no | Ausente cuando la métrica primaria es cost_per_call. |
results.verdict.comparisons[].significant | boolean | no | |
results.verdict.sample_floor | object | sí | |
results.verdict.sample_floor.min_n | integer | no | |
results.verdict.sample_floor.min_successes | integer,null | no | Null para una media. |
results.verdict.sample_floor.met | boolean | no | |
results.allocation_warning | boolean | sí | El reparto observado de unidades asignadas se desvía de los pesos (chi-cuadrado, alfa 0.001). Nunca se emite un winner mientras sea true. |
results.allocation | object | sí | |
results.allocation.chi_square | number | no | |
results.allocation.degrees_of_freedom | integer | no | |
results.allocation.p_value | number | no | |
results.allocation.warning | boolean | no | |
results.safety | object | sí | |
results.safety.breaches | arreglo de object | sí | Variantes cuya tasa de violaciones de cumplimiento es significativamente mayor que la del control. Vacío cuando no hay ninguna o cuando un brazo tiene menos de 100 conversaciones cerradas. |
results.safety.breaches[].variant_id | string (uuid) | no | |
results.safety.breaches[].name | string | no | |
results.safety.breaches[].control_rate | number,null | no | |
results.safety.breaches[].rate | number,null | no | |
results.safety.breaches[].control_closed | integer | no | |
results.safety.breaches[].closed | integer | no | |
results.safety.breaches[].z | number,null | no | |
results.safety.breaches[].p_value | number | no | |
results.safety.breaches[].diff | number | no |
401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}404 — NotFound
Id desconocido, mal formado (no UUID) O id de otro tenant: RLS devuelve cero filas, así que los tres son indistinguibles por diseño (sin fuga de existencia, nunca un 403 para ids ajenos).
json
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}429 — ApiKeyRateLimited
Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}Iniciar un experimento A/B de un agente de IA
http
POST /v1/ai/agents/{id}/experiments/{experiment_id}/startAdmite una clave dd_ o un login de dashboard con el rol account_admin, como editar el propio agente (403 role_forbidden para un login member); las lecturas siguen abiertas a todo token con alcance de tenant.
draft → running. El agente debe estar live y las variantes se validan de nuevo contra los catálogos de voces y de modelos tal como están ahora (422 con details si un modelo salió del catálogo desde que se escribió el borrador). Un agente con otro experimento en curso, un agente que no está live, o un experimento que no es un draft, da 409.
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
experiment_id | path | string (uuid) | sí | Id de un experimento A/B del agente de IA. |
Respuestas
200
El experimento, ahora en running.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
experiment | object (AiAgentExperiment) | sí | |
experiment.id | string (uuid) | no | |
experiment.ai_agent_id | string (uuid) | no | |
experiment.name | string | no | |
experiment.state | draft · running · ended | no | |
experiment.primary_metric | ptp_rate · contained_rate · handoff_rate · first_audio_p95_ms · cost_per_call | no | |
experiment.started_at | string,null (date-time) | no | |
experiment.ended_at | string,null (date-time) | no | |
experiment.ended_reason | string,null | no | Por qué terminó, cuando lo terminó algo distinto de una parada manual; null para una parada manual. |
experiment.variants | arreglo de AiAgentExperimentVariant | no | |
experiment.variants[].id | string (uuid) | no | |
experiment.variants[].position | integer | no | Orden en que se recorren las variantes para asignar una deuda (desde 0). |
experiment.variants[].name | string | no | |
experiment.variants[].is_control | boolean | no | |
experiment.variants[].weight | integer | no | |
experiment.variants[].overrides | object (AiAgentExperimentVariantOverrides) | no | Lo que una variante cambia de su agente de IA: una lista CERRADA. Cualquier otra clave se rechaza con 422 (nunca se ignora). Nada legal está en la lista: ni la declaración, ni la baja hablada, ni las reglas de traspaso, ni las herramientas, ni los límites de negociación, ni las frases prohibidas. |
experiment.variants[].overrides.voice | object | no | Al menos una de las dos. voice_id debe estar en el catálogo de voces (GET /v1/ai/voices); la declaración se pregenera con la voz de CADA variante antes de marcar. |
experiment.variants[].overrides.prompt_style | string | no | Una línea (sin salto de línea), añadida después de todo lo que compone el agente; no puede reemplazar nada de ello. |
experiment.variants[].overrides.model | openai/gpt-oss-20b · openai/gpt-oss-120b · llama-3.3-70b-versatile · llama-3.1-8b-instant | no | Un modelo del catálogo de modelos de la plataforma. |
experiment.variants[].overrides.llm | object | no | Al menos una de las dos. |
experiment.created_at | string (date-time) | no | |
experiment.updated_at | string (date-time) | no |
401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — Forbidden
El tenant está suspendido. Una sesión del dashboard (ddu_) de un tenant cuya política de inicio de sesión (ADR 227) está en modo enforce responde 403 con el código login_policy_denied ante una solicitud desde una dirección o en una hora que la política no permite — el mismo cuerpo en ambos casos; la sesión no se revoca y una solicitud posterior dentro de la política se procesa. Un token de widget (ddw_) de un puesto queda sujeto a la misma política en cada solicitud (core#2406, indicador widget_login_honors_policy). Un tenant sintético (demo) responde 403 con el código synthetic_tenant en toda ruta que llegaría a un tercero — marcación manual, pierna de transferencia externa o de conferencia, envío de SMS (core#2251, adenda del ADR #280). Ningún pago lo cambia.
json
{
"error": {
"code": "forbidden",
"message": "tenant is suspended"
}
}404 — NotFound
Id desconocido, mal formado (no UUID) O id de otro tenant: RLS devuelve cero filas, así que los tres son indistinguibles por diseño (sin fuga de existencia, nunca un 403 para ids ajenos).
json
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}409 — Conflict
Transición de estado no válida, conflicto de unicidad, o un asiento que está ocupado/reservado.
json
{
"error": {
"code": "conflict",
"message": "invalid state transition"
}
}422 — AiAgentExperimentUnprocessable
El cuerpo se interpretó pero no es un experimento legal de un agente de IA (de 2 a 5 variantes, exactamente un control, pesos 1..1000, sobrescrituras dentro de la lista cerrada, un voice_id del catálogo de voces, un model del catálogo de modelos, un prompt_style de una línea de hasta 600 caracteres). Cada campo rechazado está en error.details, cada uno con su ruta (variants[1].overrides.model, o variants[1].overrides para una clave fuera de la lista, nombrada en message); no se almacenó nada.
json
{
"error": {
"code": "unprocessable",
"message": "variants[1].overrides has keys outside the closed list: disclosure; allowed: voice, prompt_style, model, llm",
"details": [
{
"path": "variants[1].overrides",
"message": "has keys outside the closed list: disclosure; allowed: voice, prompt_style, model, llm"
}
]
}
}429 — ApiKeyRateLimited
Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}Terminar un experimento A/B de un agente de IA
http
POST /v1/ai/agents/{id}/experiments/{experiment_id}/stopAdmite una clave dd_ o un login de dashboard con el rol account_admin, como editar el propio agente (403 role_forbidden para un login member); las lecturas siguen abiertas a todo token con alcance de tenant.
running → ended (terminal); el agente vuelve a atenderse como agente base. Cualquier otra cosa → 409.
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
experiment_id | path | string (uuid) | sí | Id de un experimento A/B del agente de IA. |
Respuestas
200
El experimento, ahora en ended.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
experiment | object (AiAgentExperiment) | sí | |
experiment.id | string (uuid) | no | |
experiment.ai_agent_id | string (uuid) | no | |
experiment.name | string | no | |
experiment.state | draft · running · ended | no | |
experiment.primary_metric | ptp_rate · contained_rate · handoff_rate · first_audio_p95_ms · cost_per_call | no | |
experiment.started_at | string,null (date-time) | no | |
experiment.ended_at | string,null (date-time) | no | |
experiment.ended_reason | string,null | no | Por qué terminó, cuando lo terminó algo distinto de una parada manual; null para una parada manual. |
experiment.variants | arreglo de AiAgentExperimentVariant | no | |
experiment.variants[].id | string (uuid) | no | |
experiment.variants[].position | integer | no | Orden en que se recorren las variantes para asignar una deuda (desde 0). |
experiment.variants[].name | string | no | |
experiment.variants[].is_control | boolean | no | |
experiment.variants[].weight | integer | no | |
experiment.variants[].overrides | object (AiAgentExperimentVariantOverrides) | no | Lo que una variante cambia de su agente de IA: una lista CERRADA. Cualquier otra clave se rechaza con 422 (nunca se ignora). Nada legal está en la lista: ni la declaración, ni la baja hablada, ni las reglas de traspaso, ni las herramientas, ni los límites de negociación, ni las frases prohibidas. |
experiment.variants[].overrides.voice | object | no | Al menos una de las dos. voice_id debe estar en el catálogo de voces (GET /v1/ai/voices); la declaración se pregenera con la voz de CADA variante antes de marcar. |
experiment.variants[].overrides.prompt_style | string | no | Una línea (sin salto de línea), añadida después de todo lo que compone el agente; no puede reemplazar nada de ello. |
experiment.variants[].overrides.model | openai/gpt-oss-20b · openai/gpt-oss-120b · llama-3.3-70b-versatile · llama-3.1-8b-instant | no | Un modelo del catálogo de modelos de la plataforma. |
experiment.variants[].overrides.llm | object | no | Al menos una de las dos. |
experiment.created_at | string (date-time) | no | |
experiment.updated_at | string (date-time) | no |
401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — Forbidden
El tenant está suspendido. Una sesión del dashboard (ddu_) de un tenant cuya política de inicio de sesión (ADR 227) está en modo enforce responde 403 con el código login_policy_denied ante una solicitud desde una dirección o en una hora que la política no permite — el mismo cuerpo en ambos casos; la sesión no se revoca y una solicitud posterior dentro de la política se procesa. Un token de widget (ddw_) de un puesto queda sujeto a la misma política en cada solicitud (core#2406, indicador widget_login_honors_policy). Un tenant sintético (demo) responde 403 con el código synthetic_tenant en toda ruta que llegaría a un tercero — marcación manual, pierna de transferencia externa o de conferencia, envío de SMS (core#2251, adenda del ADR #280). Ningún pago lo cambia.
json
{
"error": {
"code": "forbidden",
"message": "tenant is suspended"
}
}404 — NotFound
Id desconocido, mal formado (no UUID) O id de otro tenant: RLS devuelve cero filas, así que los tres son indistinguibles por diseño (sin fuga de existencia, nunca un 403 para ids ajenos).
json
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}409 — Conflict
Transición de estado no válida, conflicto de unicidad, o un asiento que está ocupado/reservado.
json
{
"error": {
"code": "conflict",
"message": "invalid state transition"
}
}429 — ApiKeyRateLimited
Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}Listar las conversaciones de IA (flujo en vivo o historial)
http
GET /v1/ai/conversationsactive=true devuelve el piso EN VIVO (conversaciones aún en curso), las más recientes primero, SIN PAGINAR. Sin él, devuelve las conversaciones cerradas, paginadas por keyset (from/to acotan started_at).
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
active | query | boolean | no | true → solo las conversaciones en curso (el flujo en vivo). |
agent_id | query | string (uuid) | no | Limita a las conversaciones atendidas por este agente de IA. |
from | query | string (date-time) | no | Límite inferior RFC 3339 (campo específico del endpoint; por defecto los últimos 30 días UTC donde se indica). |
to | query | string (date-time) | no | Límite superior RFC 3339. |
limit | query | integer | no | Tamaño de página (por defecto 100, máximo 1000). |
cursor | query | string | no | Cursor keyset opaco del next_cursor de la página anterior. |
Respuestas
200
Una página/flujo de conversaciones.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
ai_conversations | arreglo de AiConversationListItem | sí | |
ai_conversations[].id | string (uuid) | sí | |
ai_conversations[].call_id | string | null | no | El uuid de FreeSWITCH de la llamada. |
ai_conversations[].ai_agent_id | string (uuid) | null | no | |
ai_conversations[].account_ref | string | null | no | |
ai_conversations[].us_state | string | null | no | |
ai_conversations[].phase | disclosure · verification · negotiation · payment_setup · wrapup | sí | |
ai_conversations[].duration_secs | integer | null | no | |
ai_conversations[].sentiment | calm · positive · frustrated · negative | null | no | |
ai_conversations[].handoff_suggested | boolean | no | |
ai_conversations[].started_at | string (date-time) | no | |
ai_conversations[].ended_at | string (date-time) | null | no | |
next_cursor | string | null | sí | Cursor keyset opaco; null en la última página y para el flujo active=true. |
400 — BadRequest
Petición mal formada (errores de forma/tipo, filtros no válidos, marcas de tiempo erróneas).
json
{
"error": {
"code": "bad_request",
"message": "from must be RFC-3339"
}
}401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}429 — ApiKeyRateLimited
Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}Obtener una conversación con transcripción y resultado
http
GET /v1/ai/conversations/{id}PII (core#2460, ADR #240). Cada text de la transcripción sale con el catálogo cerrado de PII redactado (un número de tarjeta de pago, un SSN, un número de cuenta bancaria etiquetado o un PIN/contraseña), conservando como máximo los últimos cuatro dígitos. include_pii=true pide el original: requiere una clave dd_ o un login de dashboard con el rol account_admin (403 role_forbidden en caso contrario, nunca una respuesta redactada en silencio) y queda auditado.
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
include_pii | query | boolean | no | true devuelve la transcripción SIN redacción de PII. Requiere una clave dd_ o un login account_admin: 403 role_forbidden para cualquier otro. Ausente o false ⇒ redactada. Cualquier cosa que no sea un booleano ⇒ 400. |
Respuestas
200
La conversación, su transcripción completa y su resultado.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
ai_conversation | any | sí |
400 — BadRequest
Petición mal formada (errores de forma/tipo, filtros no válidos, marcas de tiempo erróneas).
json
{
"error": {
"code": "bad_request",
"message": "from must be RFC-3339"
}
}401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — Forbidden
El tenant está suspendido. Una sesión del dashboard (ddu_) de un tenant cuya política de inicio de sesión (ADR 227) está en modo enforce responde 403 con el código login_policy_denied ante una solicitud desde una dirección o en una hora que la política no permite — el mismo cuerpo en ambos casos; la sesión no se revoca y una solicitud posterior dentro de la política se procesa. Un token de widget (ddw_) de un puesto queda sujeto a la misma política en cada solicitud (core#2406, indicador widget_login_honors_policy). Un tenant sintético (demo) responde 403 con el código synthetic_tenant en toda ruta que llegaría a un tercero — marcación manual, pierna de transferencia externa o de conferencia, envío de SMS (core#2251, adenda del ADR #280). Ningún pago lo cambia.
json
{
"error": {
"code": "forbidden",
"message": "tenant is suspended"
}
}404 — NotFound
Id desconocido, mal formado (no UUID) O id de otro tenant: RLS devuelve cero filas, así que los tres son indistinguibles por diseño (sin fuga de existencia, nunca un 403 para ids ajenos).
json
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}429 — ApiKeyRateLimited
Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}Toma de control por el supervisor de una conversación de IA en vivo
http
POST /v1/ai/conversations/{id}/takeoverTransfiere el tramo del deudor de la IA al endpoint REGISTRADO del supervisor y desmonta el tramo huérfano de la IA: la MISMA mecánica de evidencia primero que POST /v1/supervision/calls/{call_id}/takeover (una fila duradera de supervision_actions se escribe ANTES de cualquier comando al switch). El {id} es el id de la CONVERSACIÓN; el servidor resuelve su call_id y maneja Dialer.Supervision. Una conversación sin llamada viva (sandbox/nunca marcada) da 409. session_id en el resultado es null (no hay tramo de escucha). Las mismas compuertas de QUIÉN que las operaciones de supervisión: supervisor_ext debe llevar el rol supervisor|admin (ADR #116), y el principal debe poder actuar como ese agente (ADR #135: un ddu_ con puesto solo como su propio puesto, 403 forbidden; un ddu_ sin puesto que nombre a un tercero solo como account_admin una vez activado ddu_author_role_enforced, 403 role_forbidden); todo rechazo queda evidenciado como refused.
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
Cuerpo de la petición (JSON, obligatorio)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
supervisor_ext | string | sí | Debe ser una extensión ACTIVA de la lista de ESTE tenant con rol supervisor o admin (ADR #116; 403 role_forbidden en caso contrario), y una como la que el principal puede actuar (ADR #135; un ddu_ con puesto solo su propio puesto, 403 forbidden); su dispositivo con check-in recibe la pierna de supervisión (contestar en 20 s). |
json
{
"supervisor_ext": "2000"
}Respuestas
200
Tramo del deudor transferido al supervisor (fila de auditoría duradera).
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
supervision | object (SupervisionResult) | sí | |
supervision.action | listen · whisper · barge · takeover | no | |
supervision.call_id | string | no | |
supervision.supervisor_ext | string | no | |
supervision.session_id | string | null | no | uuid de la pierna del supervisor; null para takeover (sin pierna eavesdrop). |
supervision.audit_id | string (uuid) | no | La fila durable de supervision_actions. |
supervision.to_number | string | no | Últimos 4 enmascarados (***0184) — el E.164 completo nunca cruza esta superficie. |
400 — BadRequest
Petición mal formada (errores de forma/tipo, filtros no válidos, marcas de tiempo erróneas).
json
{
"error": {
"code": "bad_request",
"message": "from must be RFC-3339"
}
}401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — SupervisionRoleForbidden
Quien llama no puede supervisar con ese supervisor_ext (ADR #116, core#928; ADR #135, core#1037 fase 3). code role_forbidden: la extensión resuelve a un agente activo cuyo role no es supervisor ni admin — o un inicio de sesión ddu_ sin puesto vinculado nombró la extensión de otra persona sin el rol account_admin (una vez que ddu_author_role_enforced está activado). codeforbidden: un principal ligado a UN agente — un token ddw_, o un inicio de sesión ddu_ vinculado a un puesto — nombró una extensión que no es la suya. En cualquier caso el intento QUEDA con evidencia — una fila de supervision_actions con result: refused — y se ejecutaron CERO comandos al switch. Por tenant, supervision_role_enforced = false desactiva la comprobación de rol de la extensión (la vinculación de identidad y la regla de principal se mantienen).
role_forbidden
json
{
"error": {
"code": "role_forbidden",
"message": "supervisor_ext must belong to an agent with role supervisor or admin"
}
}forbidden
json
{
"error": {
"code": "forbidden",
"message": "supervisor_ext must be the agent bound to this token"
}
}404 — NotFound
Id desconocido, mal formado (no UUID) O id de otro tenant: RLS devuelve cero filas, así que los tres son indistinguibles por diseño (sin fuga de existencia, nunca un 403 para ids ajenos).
json
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}409 — Conflict
Transición de estado no válida, conflicto de unicidad, o un asiento que está ocupado/reservado.
json
{
"error": {
"code": "conflict",
"message": "invalid state transition"
}
}429 — ApiKeyRateLimited
Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}502 — SupervisionFailed
El switch rechazó el comando de supervisión (la llamada ya terminó / el dispositivo del supervisor no está registrado).
json
{
"error": {
"code": "supervision_failed",
"message": "the switch rejected the supervision command"
}
}503 — SupervisionUnavailable
code audit_unavailable: no se pudo escribir la fila de evidencia, por lo que NO se envió ningún comando al switch (evidencia primero, fail-closed). codeesl_unavailable: el enlace de control del switch está caído. Ambos son reintentables.
audit_unavailable
json
{
"error": {
"code": "audit_unavailable",
"message": "supervision audit unavailable, retry"
}
}esl_unavailable
json
{
"error": {
"code": "esl_unavailable",
"message": "telephony control unavailable, retry"
}
}Interruptor de emergencia: pausar toda la marcación por IA (en todo el tenant)
http
POST /v1/ai/pauseIdempotente. Impide de inmediato que los agentes de IA del tenant originen llamadas nuevas (el control «Pausar toda la marcación por IA»; entonces la compuerta AIDisclosure bloquea toda originación de IA en modo fail-closed). Las conversaciones en curso continúan; usa la toma de control para intervenir. El interruptor queda sellado (paused_at, paused_by). Alcance de gestión: un token de navegador ddw_ da 403.
Reglas de rol (core#2438): una clave dd_ o un login account_admin; un login member recibe 403 role_forbidden (palanca por tenant compliance_config_admin_only, activada por defecto).
Respuestas
200
La marcación por IA está en pausa.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
ai_dialing | active · paused | sí | |
paused_at | string (date-time) | null | no | |
paused_by | string | null | no |
json
{
"ai_dialing": "paused",
"paused_at": "2026-07-09T17:40:00Z"
}401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — Forbidden
El tenant está suspendido. Una sesión del dashboard (ddu_) de un tenant cuya política de inicio de sesión (ADR 227) está en modo enforce responde 403 con el código login_policy_denied ante una solicitud desde una dirección o en una hora que la política no permite — el mismo cuerpo en ambos casos; la sesión no se revoca y una solicitud posterior dentro de la política se procesa. Un token de widget (ddw_) de un puesto queda sujeto a la misma política en cada solicitud (core#2406, indicador widget_login_honors_policy). Un tenant sintético (demo) responde 403 con el código synthetic_tenant en toda ruta que llegaría a un tercero — marcación manual, pierna de transferencia externa o de conferencia, envío de SMS (core#2251, adenda del ADR #280). Ningún pago lo cambia.
json
{
"error": {
"code": "forbidden",
"message": "tenant is suspended"
}
}429 — ApiKeyRateLimited
Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}Interruptor de emergencia: reanudar la marcación por IA (en todo el tenant)
http
POST /v1/ai/resumeInverso idempotente de POST /v1/ai/pause. Alcance de gestión (403 para ddw_).
Reglas de rol (core#2438): una clave dd_ o un login account_admin; un login member recibe 403 role_forbidden (palanca por tenant compliance_config_admin_only, activada por defecto).
Respuestas
200
La marcación por IA está activa.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
ai_dialing | active · paused | sí | |
paused_at | string (date-time) | null | no | |
paused_by | string | null | no |
json
{
"ai_dialing": "active",
"paused_at": null
}401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — Forbidden
El tenant está suspendido. Una sesión del dashboard (ddu_) de un tenant cuya política de inicio de sesión (ADR 227) está en modo enforce responde 403 con el código login_policy_denied ante una solicitud desde una dirección o en una hora que la política no permite — el mismo cuerpo en ambos casos; la sesión no se revoca y una solicitud posterior dentro de la política se procesa. Un token de widget (ddw_) de un puesto queda sujeto a la misma política en cada solicitud (core#2406, indicador widget_login_honors_policy). Un tenant sintético (demo) responde 403 con el código synthetic_tenant en toda ruta que llegaría a un tercero — marcación manual, pierna de transferencia externa o de conferencia, envío de SMS (core#2251, adenda del ADR #280). Ningún pago lo cambia.
json
{
"error": {
"code": "forbidden",
"message": "tenant is suspended"
}
}429 — ApiKeyRateLimited
Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}Estado del interruptor de emergencia de la marcación por IA
http
GET /v1/ai/statusEl estado actual del interruptor de emergencia (misma forma que pause/resume).
Respuestas
200
El estado del interruptor de emergencia.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
ai_dialing | active · paused | sí | |
paused_at | string (date-time) | null | no | |
paused_by | string | null | no |
json
{
"ai_dialing": "active",
"paused_at": null
}401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}429 — ApiKeyRateLimited
Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}Métricas operativas de la IA de hoy (día UTC)
http
GET /v1/ai/metrics/todayAgregado del día UTC actual a partir de ai_conversations + resultados + el medidor duradero ai_usage (la misma fuente facturable que /v1/stats/ai-usage).
Respuestas
200
Las métricas de hoy.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
date | string (date) | no | |
handled | integer | no | Conversaciones atendidas hoy por la IA, resueltas o no. |
unsettled | integer | no | Conversaciones iniciadas hoy sin resultado terminal todavía (outcome_type null: aún en curso, o su cierre nunca llegó a core). Cuentan en handled, nunca en containment_rate. |
containment_rate | number | null | no | Proporción de las conversaciones de hoy resueltas CON UNA PERSONA que la IA resolvió por sí sola (0..1), redondeada a 4 decimales (core#1121, ADR #147). Numerador: outcome_type en resolved, ptp, payment_taken, callback, refused. Denominador: el numerador más failed, handoff, handoff_failed y dispute. En ninguno: voicemail (no hay persona en la línea; véase voicemail_drops) y las conversaciones unsettled. Una conversación traspasada a una persona (el runtime informó handoff.occurred) nunca está contenida, sea cual sea su outcome_type: un ptp o payment_taken seguido de un traspaso cuenta solo en el denominador (core#1131, ADR #149). null cuando containment_reason está fijado. |
containment_reason | no_settled_conversations | null | no | Por qué containment_rate es null: no_settled_conversations (ninguna conversación con una persona se resolvió hoy: el denominador es 0; ausencia, no un cero). null cuando containment_rate es un número. |
avg_handle_secs | integer | no | |
handled_delta_pct | number | null | no | Variación porcentual de handled frente a ayer hasta la MISMA hora del día; null cuando ayer no hubo ninguna. |
handoffs | object | no | |
handoffs.count | integer | no | |
handoffs.pct | number | no | |
handoffs_failed | object | no | Conversaciones resueltas handoff_failed hoy: se pidió una persona y no se ordenó el traspaso. No cuentan ni en handoffs ni en la contención. |
handoffs_failed.count | integer | no | |
handoffs_failed.pct | number | no | |
ai_minutes | number | no | Minutos de IA facturables hoy (del medidor ai_usage). |
billed_cents | integer | no | |
ptp | object | no | |
ptp.count | integer | no | |
ptp.amount_cents | integer | no | |
payments | object | no | |
payments.count | integer | no | |
payments.amount_cents | integer | no | |
voicemail_drops | integer | no | Conversaciones que el runtime clasificó como voicemail (su veredicto AMD) hoy. |
compliance_violations | integer | no |
401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}429 — ApiKeyRateLimited
Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}Elegibilidad de voz artificial por campaña
http
GET /v1/ai/eligibilityUna entrada por campaña que REPRODUCE voz artificial (IA o TTS/IVR/encuesta), cada una con si puede originar ahora dada la capacidad de declaración de IA del tenant y el interruptor de emergencia de la marcación por IA. El consentimiento por destinatario (la otra mitad de la compuerta de voz artificial) se hace cumplir en el momento de marcar, no se resume aquí.
Respuestas
200
La lista de elegibilidad.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
eligibility | arreglo de AiEligibilityItem | sí | |
eligibility[].campaign_id | string (uuid) | sí | |
eligibility[].name | string | sí | |
eligibility[].eligible | boolean | sí | |
eligibility[].reason | string | null | no | Por qué no puede originar (ai_dialing_paused / disclosure_missing); null cuando es elegible. |
401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}429 — ApiKeyRateLimited
Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}