Appearance
Agentes y puestos
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.
Lista de puestos, presencia, check-in/check-out del softphone.
Endpoints
La aceptación más reciente de la grabación y supervisión de un agente
http
GET /v1/agents/{id}/monitoring-acknowledgementLa versión vigente del aviso, si ESTE agente la ha aceptado (current) y la última aceptación del agente de cualquier versión (null si no hay ninguna). Un puesto lee la suya; un puesto cuyo rol de plantilla es supervisor o admin y una clave dd_ del tenant leen a cualquier agente del tenant; otro puesto que lea el de un compañero recibe 403 forbidden. Un id de otro tenant es 404. Un token de widget ddw_ recibe 403 antes del handler.
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 estado del aviso de monitoreo del agente.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
monitoring | object (AgentMonitoringStatus) | sí | La situación de un agente frente al aviso vigente. |
monitoring.notice_version | integer | null | sí | La versión vigente; null mientras no haya ninguna en vigor. |
monitoring.current | boolean | sí | Verdadero solo cuando el agente ha aceptado la versión VIGENTE. |
monitoring.acknowledgement | AgentMonitoringAck | null | sí | La última aceptación del agente de cualquier versión; null si no hay ninguna. |
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"
}
}Lo que el dialer tiene frente al PROPIO puesto de quien llama
http
GET /v1/agent/queueUna LECTURA compuesta a partir de cuatro fuentes que ya existen; no hay tabla de cola por agente y no se inventa ninguna. Para el puesto que el BEARER identifica — ningún parámetro puede nombrar a un agente, por lo que no se puede leer la cola de otro agente:
presence: el estado de la FSM del puesto, igual queGET /v1/agents.live_call: la llamada en curso del puesto (CDR), onull.callbacks: rellamadas pendientes que el agente PROGRAMÓ, la más próxima primero (callbacks.agent_ides el creador, no una asignación: el dialer no asigna rellamadas por agente). Máximo 50.inbound_offers: ofertas de la cola entrante en vivo dirigidas a este puesto, derivadas del stream durable de la cola;[]cuando el flaginbound_queue_stream_enableddel tenant está desactivado.
NO está presente, porque el modelo no tiene ese estado: un lead de "vista previa pendiente" por agente (en modo preview la cuenta que se muestra se decide al marcarla). Las llamadas reservadas/sonando aparecen como presence.
Quién obtiene un puesto: un inicio de sesión de dashboard ddu_ con un puesto vinculado. Una clave de máquina dd_ no tiene a ninguna persona detrás y recibe 403; un ddu_ sin un puesto activo recibe 403 seat_not_linked; un token de widget ddw_ recibe 403 antes del handler (la ruta no está en la lista de permitidos del widget).
Ausentes deliberadamente (mismo contrato de proyección que /v1/debts): números telefónicos, la note de la rellamada y la instantánea de cumplimiento del intento — solo ids, marcas de tiempo y prioridad.
Respuestas
200
La cola propia de quien llama.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
queue | object (AgentQueue) | sí | Lo que el dialer tiene frente a un puesto (GET /v1/agent/queue). Solo ids, marcas de tiempo y prioridad — sin PII. |
queue.agent_id | string (uuid) | sí | |
queue.presence | offline · available · ringing · on_call · wrap_up · paused | sí | |
queue.live_call | object,null | sí | |
queue.live_call.attempt_id | string (uuid) | sí | |
queue.live_call.campaign_id | string,null (uuid) | sí | |
queue.live_call.debt_id | string,null (uuid) | sí | |
queue.live_call.started_at | string (date-time) | sí | |
queue.live_call.answered_at | string,null (date-time) | sí | |
queue.callbacks | arreglo de object | sí | |
queue.callbacks[].id | string (uuid) | sí | |
queue.callbacks[].debt_id | string,null (uuid) | sí | |
queue.callbacks[].campaign_id | string,null (uuid) | sí | |
queue.callbacks[].scheduled_at | string (date-time) | sí | |
queue.callbacks[].priority | integer | sí | |
queue.inbound_offers | arreglo de object | sí | |
queue.inbound_offers[].queue_id | string (uuid) | sí | |
queue.inbound_offers[].admission_id | string (uuid) | sí | |
queue.inbound_offers[].call_id | string (uuid) | sí | |
queue.inbound_offers[].enqueued_at | string (date-time) | sí |
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"
}
}Lista de puestos + presencia en vivo del puesto
http
GET /v1/agentsLista duradera combinada con la presencia en vivo (offline|available|ringing|on_call).
Respuestas
200
Todos los agentes con presencia.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
agents | arreglo de Agent | sí | |
agents[].id | string (uuid) | no | |
agents[].tenant_id | string (uuid) | no | |
agents[].name | string | no | |
agents[].email | string | no | |
agents[].role | agent · supervisor · admin | no | |
agents[].status | active · inactive | no | Indicador de plantilla — NO es presencia. |
agents[].sip_extension | string | null | no | |
agents[].device_mode | browser · external | no | |
agents[].login_enabled | boolean | no | Estado de autoinicio de sesión del widget — verdadero si y solo si hay una credencial de contraseña establecida. El hash de la contraseña nunca se expone. |
agents[].presence | offline · available · ringing · on_call · wrap_up · paused | no | Presencia en vivo del asiento desde el AgentFSM en memoria. wrap_up es el trabajo posterior a la llamada (se entra al colgar cuando el tope agent_wrap_up_seconds del tenant es superior a 0); paused es un asiento retirado de la rotación con un motivo (ADR #101 D1, MT-ROAD-R11). |
agents[].pause_reason | manual · break · lunch · training · meeting | null | no | Por qué el asiento está en pausa; null salvo que presence sea paused (core#2542). |
agents[].state_since | string (date-time) | null | no | Cuándo el asiento entró en su presence actual (RFC 3339); null cuando el agente no tiene asiento (core#2542). |
agents[].pause_armed | boolean | no | Hay una pausa armada para cuando termine la llamada actual (POST /v1/agents/{id}/pause con on_release: true). |
agents[].created_at | string (date-time) | no | |
agents[].updated_at | string (date-time) | no | |
truncated | boolean | sí | true cuando hay más filas que los 2 000 agentes que devuelve el listado: la respuesta es un prefijo en el orden documentado, no el conjunto completo. |
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 puesto
http
POST /v1/agentsCuerpo de la petición (JSON, obligatorio)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
name | string | sí | |
email | string (email) | sí | |
role | agent · supervisor · admin | no | |
sip_extension | string | no | La identidad de registro SIP a la que se puentean las llamadas. Solo caracteres inertes (dígitos, letras, punto, guion bajo, guion): el valor es a la vez la identidad SIP y la mitad de la dialstring de medios, por lo que cualquier otro se rechaza con 422 en lugar de divergir entre ambas. |
json
{
"name": "Postman Seat",
"email": "[email protected]",
"role": "agent",
"sip_extension": "1009"
}Respuestas
201
Puesto creado.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
agent | object (Agent) | sí | |
agent.id | string (uuid) | no | |
agent.tenant_id | string (uuid) | no | |
agent.name | string | no | |
agent.email | string | no | |
agent.role | agent · supervisor · admin | no | |
agent.status | active · inactive | no | Indicador de plantilla — NO es presencia. |
agent.sip_extension | string | null | no | |
agent.device_mode | browser · external | no | |
agent.login_enabled | boolean | no | Estado de autoinicio de sesión del widget — verdadero si y solo si hay una credencial de contraseña establecida. El hash de la contraseña nunca se expone. |
agent.presence | offline · available · ringing · on_call · wrap_up · paused | no | Presencia en vivo del asiento desde el AgentFSM en memoria. wrap_up es el trabajo posterior a la llamada (se entra al colgar cuando el tope agent_wrap_up_seconds del tenant es superior a 0); paused es un asiento retirado de la rotación con un motivo (ADR #101 D1, MT-ROAD-R11). |
agent.pause_reason | manual · break · lunch · training · meeting | null | no | Por qué el asiento está en pausa; null salvo que presence sea paused (core#2542). |
agent.state_since | string (date-time) | null | no | Cuándo el asiento entró en su presence actual (RFC 3339); null cuando el agente no tiene asiento (core#2542). |
agent.pause_armed | boolean | no | Hay una pausa armada para cuando termine la llamada actual (POST /v1/agents/{id}/pause con on_release: true). |
agent.created_at | string (date-time) | no | |
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"
}
}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
El puesto se rechaza y no se escribe ninguna fila. Ramifica según code:
seat_limit_reached— el tenant ya tiene losmax_seatspuestos activos del plan (tier) en vigor. Un puesto por encima del tope nunca se factura como extra: libera un puesto (unoinactiveno cuenta) o pasa a un plan mayor. El tope sigue al cambio de plan de inmediato. Un tenant que pasó a un plan menor conserva todos los puestos que tenía, por lo queactive_seatspuede estar por encima demax_seats; solo se rechazan los puestos nuevos hasta que vuelva a quedar por debajo.enterpriseno tiene tope.unpriceable_tier— el plan del tenant no tiene fila de catálogo con precio, por lo que no se puede leer su tope. El puesto se rechaza en lugar de crearse sin tope.
seat_limit_reached
json
{
"error": {
"code": "seat_limit_reached",
"message": "tier starter allows 5 active seats and the tenant already has them",
"max_seats": 5,
"tier": "starter",
"active_seats": 5
}
}unpriceable_tier
json
{
"error": {
"code": "unpriceable_tier",
"message": "the tenant's billing tier has no priced catalog row (tier_not_in_catalog)"
}
}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"
}
}Tiempo derivado por estado y puesto para una ventana explícita
http
GET /v1/agents/occupancySuperficie de gestión del tenant (dd_ o ddu_; un ddw_ de agente recibe 403). Devuelve Dialer.Agents.Occupancy.by_agent/4 sin unir con la lista de puestos ni recalcular del lado del cliente: un puesto sin evidencia de intervalos en la ventana está ausente en lugar de aparecer como un cero fabricado.
La ventana semiabierta es entered_at ∈ [from, to). Un intervalo contado aporta toda su duration_ms monotónica; los intervalos abiertos y sintéticos se excluyen y se informan en excluded. Cada puesto lleva un ratio occupancy bajo una definición con nombre y versión (v1): (on_call + wrap_up) / (available + ringing + on_call + wrap_up), es decir, el tiempo contado menos paused. Una fórmula posterior se publica como una nueva definición junto a v1; v1 nunca cambia de significado.
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
from | query | string (date-time) | sí | Inicio inclusivo de la ventana de entrada de intervalos (RFC 3339). |
to | query | string (date-time) | sí | Fin exclusivo de la ventana de entrada de intervalos (RFC 3339); debe ser posterior a from. |
agent_id | query | string (uuid) | no | Filtro opcional de puesto, siempre dentro del tenant resuelto a partir del bearer. |
Respuestas
200
La derivación de ocupación para los puestos con evidencia en la ventana.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
window | object | sí | |
window.from | string (date-time) | sí | |
window.to | string (date-time) | sí | |
agents | object | sí | Mapa indexado por UUID de agente; los puestos sin evidencia de intervalos están ausentes. |
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"
}
}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"
}
}Tiempo de atención por agente, acreditando a cada agente solo sus piernas de una llamada transferida
http
GET /v1/agents/handle-timeSuperficie de gestión del tenant (dd_ o ddu_; un ddw_ de agente recibe 403). Derivado del rastro de control de llamadas de solo anexado y del reloj del CDR (Dialer.Calls.TransferChain.handle_time/4, core#2403): una llamada transferida A -> B -> C se acredita a A hasta el traspaso, a B desde el traspaso hasta el siguiente, y a C desde ahí hasta el final — no completa al agente que nombra la fila del CDR. control_ms es el tiempo con la llamada en control, consult_ms el tiempo en una pierna de consulta, occupied_ms su suma y aht_ms es control_ms / calls (null cuando el agente no tuvo ninguna, nunca un cero fabricado). Las llamadas son las que INICIARON en la ventana semiabierta [from, to). Una pierna cuya duración aún no es medible (la llamada está en curso) se deja fuera y se cuenta en excluded.open_legs. El control que salió de la población de agentes (una transferencia externa) no acredita a ningún agente.
La ventana puede abarcar como máximo 31 días (400 en caso contrario) y como máximo 10.000 llamadas (422: se rechaza, nunca se trunca). definition: "v1" nunca cambia de significado; una fórmula posterior se publica como v2.
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
from | query | string (date-time) | sí | Inicio inclusivo de la ventana de inicio de llamada (RFC 3339). |
to | query | string (date-time) | sí | Fin exclusivo de la ventana de inicio de llamada (RFC 3339); debe ser posterior a from. |
agent_id | query | string (uuid) | no | Filtro opcional de puesto, siempre dentro del tenant resuelto a partir del bearer. |
Respuestas
200
Tiempo de atención de los agentes que tuvieron el control o consultaron en una llamada de la ventana.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
definition | v1 | sí | |
from | string (date-time) | sí | |
to | string (date-time) | sí | |
agents | arreglo de object | sí | |
agents[].agent_id | string (uuid) | sí | |
agents[].calls | integer | sí | Llamadas en las que el agente tuvo el control durante un tiempo medible. |
agents[].control_ms | integer | sí | |
agents[].consult_ms | integer | sí | |
agents[].occupied_ms | integer | sí | |
agents[].aht_ms | integer | null | sí | |
excluded | object | sí | |
excluded.open_legs | integer | 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"
}
}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"
}
}503
No se pudo leer la evidencia; reintenta. Falla de forma cerrada — nunca una respuesta vacía.
Obtener un agente + presencia
http
GET /v1/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.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
agent | object (Agent) | sí | |
agent.id | string (uuid) | no | |
agent.tenant_id | string (uuid) | no | |
agent.name | string | no | |
agent.email | string | no | |
agent.role | agent · supervisor · admin | no | |
agent.status | active · inactive | no | Indicador de plantilla — NO es presencia. |
agent.sip_extension | string | null | no | |
agent.device_mode | browser · external | no | |
agent.login_enabled | boolean | no | Estado de autoinicio de sesión del widget — verdadero si y solo si hay una credencial de contraseña establecida. El hash de la contraseña nunca se expone. |
agent.presence | offline · available · ringing · on_call · wrap_up · paused | no | Presencia en vivo del asiento desde el AgentFSM en memoria. wrap_up es el trabajo posterior a la llamada (se entra al colgar cuando el tope agent_wrap_up_seconds del tenant es superior a 0); paused es un asiento retirado de la rotación con un motivo (ADR #101 D1, MT-ROAD-R11). |
agent.pause_reason | manual · break · lunch · training · meeting | null | no | Por qué el asiento está en pausa; null salvo que presence sea paused (core#2542). |
agent.state_since | string (date-time) | null | no | Cuándo el asiento entró en su presence actual (RFC 3339); null cuando el agente no tiene asiento (core#2542). |
agent.pause_armed | boolean | no | Hay una pausa armada para cuando termine la llamada actual (POST /v1/agents/{id}/pause con on_release: true). |
agent.created_at | string (date-time) | no | |
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"
}
}Editar un puesto o liberarlo (estado inactive)
http
PATCH /v1/agents/{id}Corrige un puesto creado con POST /v1/agents y es el verbo que lo libera (core#2527). Solo administración: una clave dd_ o un login de dashboard con account_admin; cualquier otro login recibe 403 role_forbidden. El cuerpo está cerrado: una clave desconocida da 400 que la nombra. Envía solo lo que cambia.
status: "inactive"libera el puesto: deja de contar contra el tope del plan y la factura (inactiveno ocupa puesto), se revocan los tokensddw_vivos del puesto, se le hace check-out y se expulsa su credencial SIP (sip_teardown:ok | skipped | failed). La fila nunca se borra;deactivated_atlo estampa la base de datos.status: "active"sobre un puesto inactivo aplica el mismo tope que la creación:422 seat_limit_reached(atómico bajo el candado del tenant) o422 unpriceable_tier.409 last_admin_seat: el puesto es el único puestoadminactivo y la edición lo degradaría o lo desactivaría.409 seat_linked_to_user: el puesto está vinculado a un usuario de la consola y la edición cambia su email. El vínculo se conserva, nunca se reasigna, así que la dirección no puede cambiar aquí.409 conflict: el email o la extensión ya están en uso.
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 | |
email | string | no | |
role | agent · supervisor · admin | no | |
sip_extension | string | no | |
status | active · inactive | no |
json
{
"status": "inactive"
}Respuestas
200
El puesto tras la edición.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
agent | object (Agent) | sí | |
agent.id | string (uuid) | no | |
agent.tenant_id | string (uuid) | no | |
agent.name | string | no | |
agent.email | string | no | |
agent.role | agent · supervisor · admin | no | |
agent.status | active · inactive | no | Indicador de plantilla — NO es presencia. |
agent.sip_extension | string | null | no | |
agent.device_mode | browser · external | no | |
agent.login_enabled | boolean | no | Estado de autoinicio de sesión del widget — verdadero si y solo si hay una credencial de contraseña establecida. El hash de la contraseña nunca se expone. |
agent.presence | offline · available · ringing · on_call · wrap_up · paused | no | Presencia en vivo del asiento desde el AgentFSM en memoria. wrap_up es el trabajo posterior a la llamada (se entra al colgar cuando el tope agent_wrap_up_seconds del tenant es superior a 0); paused es un asiento retirado de la rotación con un motivo (ADR #101 D1, MT-ROAD-R11). |
agent.pause_reason | manual · break · lunch · training · meeting | null | no | Por qué el asiento está en pausa; null salvo que presence sea paused (core#2542). |
agent.state_since | string (date-time) | null | no | Cuándo el asiento entró en su presence actual (RFC 3339); null cuando el agente no tiene asiento (core#2542). |
agent.pause_armed | boolean | no | Hay una pausa armada para cuando termine la llamada actual (POST /v1/agents/{id}/pause con on_release: true). |
agent.created_at | string (date-time) | no | |
agent.updated_at | string (date-time) | no | |
sip_teardown | ok · skipped · failed | no | Presente cuando el puesto se liberó o su extensión cambió; nombra el resultado de expulsar la extensión que el puesto TENÍA. |
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
Reactivación rechazada: seat_limit_reached o unpriceable_tier (véase POST /v1/agents). No se escribe nada.
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"
}
}Las habilidades de enrutamiento de un agente
http
GET /v1/agents/{id}/skillsLas habilidades del agente, ordenadas; [] cuando no tiene ninguna. Un agente que no está en la lista de puestos de este tenant es 404. Solo admin, igual que la escritura.
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
Las habilidades del agente.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
agent_id | string (uuid) | sí | |
skills | arreglo de string | sí |
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
role_forbidden: un inicio de sesión de dashboard sin el rol account_admin; forbidden: un token de widget o un tenant suspendido.
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"
}
}Reemplazar las habilidades de enrutamiento de un agente
http
PUT /v1/agents/{id}/skillsDeclara qué habilidades tiene el agente (ADR #216 D3). El cuerpo REEMPLAZA el conjunto completo: una habilidad no listada se elimina, una faltante se agrega y una repetida cuenta una sola vez. Una habilidad es una etiqueta del tenant ^[a-z0-9][a-z0-9_-]{0,39}$ — sin catálogo global, sin niveles. 422 unprocessable cuando alguna habilidad está mal formada, skills no es un arreglo de cadenas, o hay más de 50; entonces no se escribe nada. Un agente que no está en la lista de puestos de este tenant es 404. La escritura funciona con inbound_queue_skill_routing_enabled DESACTIVADO: lo almacenado permanece inerte hasta que el tenant lo active. Una habilidad es una preferencia de enrutamiento, no una autorización: ninguna compuerta de cumplimiento la lee (ADR #216 D7). Solo admin: una clave dd_ o un inicio de sesión de dashboard con 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 |
|---|---|---|---|
skills | arreglo de string | sí |
json
{
"skills": [
"idioma_es",
"cobranza_tardia"
]
}Respuestas
200
Las habilidades del agente después de la escritura, ordenadas.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
agent_id | string (uuid) | sí | |
skills | arreglo de string | 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
role_forbidden: un inicio de sesión de dashboard sin el rol account_admin; forbidden: un token de widget o un tenant suspendido.
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 — 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"
}
}Habilitar o rotar la contraseña de acceso al widget de un agente
http
POST /v1/agents/{id}/credentialEstablece (o rota) la credencial de acceso al widget del agente para que pueda iniciar sesión en el widget con email + contraseña (POST /v1/auth/agent-login). Alcance de tenant (una clave dd_ o una sesión ddu_; un ddw_ recibe 403). Reglas de rol (core#2404): la contraseña abre un ddw_ vinculado al puesto, así que una clave dd_ y un inicio de sesión account_admin pasan, un ddu_ pasa solo en el puesto al que lo vincula users_agents.user_id, y cualquier otro inicio de sesión recibe 403 role_forbidden (parámetro por tenant agent_credential_admin_only, activado por defecto). La contraseña en texto plano va solo en el cuerpo — nunca se persiste (PBKDF2-SHA512 en reposo) ni se devuelve. Una ROTACIÓN revoca el ddw_ vivo del agente (la expulsión por cambio de clave) Y desmonta el endpoint SIP creado bajo esa sesión (NR-02), informado en sip_teardown.
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 |
|---|---|---|---|
password | string (password) | sí |
Respuestas
200
El agente (ahora con login_enabled true), más el resultado del desmontaje SIP.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
agent | object (Agent) | sí | |
agent.id | string (uuid) | no | |
agent.tenant_id | string (uuid) | no | |
agent.name | string | no | |
agent.email | string | no | |
agent.role | agent · supervisor · admin | no | |
agent.status | active · inactive | no | Indicador de plantilla — NO es presencia. |
agent.sip_extension | string | null | no | |
agent.device_mode | browser · external | no | |
agent.login_enabled | boolean | no | Estado de autoinicio de sesión del widget — verdadero si y solo si hay una credencial de contraseña establecida. El hash de la contraseña nunca se expone. |
agent.presence | offline · available · ringing · on_call · wrap_up · paused | no | Presencia en vivo del asiento desde el AgentFSM en memoria. wrap_up es el trabajo posterior a la llamada (se entra al colgar cuando el tope agent_wrap_up_seconds del tenant es superior a 0); paused es un asiento retirado de la rotación con un motivo (ADR #101 D1, MT-ROAD-R11). |
agent.pause_reason | manual · break · lunch · training · meeting | null | no | Por qué el asiento está en pausa; null salvo que presence sea paused (core#2542). |
agent.state_since | string (date-time) | null | no | Cuándo el asiento entró en su presence actual (RFC 3339); null cuando el agente no tiene asiento (core#2542). |
agent.pause_armed | boolean | no | Hay una pausa armada para cuando termine la llamada actual (POST /v1/agents/{id}/pause con on_release: true). |
agent.created_at | string (date-time) | no | |
agent.updated_at | string (date-time) | no | |
sip_teardown | ok · skipped · failed | sí | Resultado del desmontaje SIP que acompaña a una revocación (NR-02): el borrado de la credencial efímera agentcred del agente y de su registro usrloc vivo en Kamailio. * ok: credencial y registro eliminados; el softphone ya no puede registrarse. * skipped: no había nada que eliminar: el agente no tiene extensión SIP, el aprovisionamiento del SBC no está configurado en este entorno, o el agente aún conserva una sesión VIGENTE (véase abajo), por lo que esta revocación no dejó ningún acceso abierto. * failed: la revocación en la base de datos se mantiene, pero la credencial SIP SIGUE ACTIVA y lo seguirá hasta su caducidad automática a las 24 h. Trátalo como una vía de acceso abierta: reintenta o expulsa al agente por otros medios. Es un campo y no un código de estado a propósito: la revocación en sí es duradera y sí ocurrió, así que un 5xx mentiría en el otro sentido. Lo que nunca debe pasar es un 200 silencioso que implique una expulsión que no ocurrió. El desmontaje sigue a la TRANSICIÓN, no al agente. Revocar un id de token que ya estaba revocado (un id que un cliente puede conservar legítimamente, ya que GET /v1/widget_tokens lista las filas revocadas, y ya que emitir un token sustituye al anterior) NO debe expulsar, porque se cortaría la sesión SUCESORA del agente. Por tanto, el desmontaje solo se ejecuta cuando el agente se queda sin ningún token vigente; en caso contrario informa skipped. Esto es lo que mantiene útil el consejo de reintento de failed: un token ya revocado y sin sucesor vivo sí expulsa en un DELETE reintentado, de modo que reintentar es un remedio real y no una operación vacía. |
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 — AgentCredentialForbidden
Se rechazó establecer o borrar la contraseña de acceso al widget de un puesto (core#2404, adenda del ADR #126). coderole_forbidden: quien llama es un inicio de sesión de dashboard que no es ni account_admin ni el inicio de sesión vinculado a ese puesto (la ruta admite una clave dd_, un inicio de sesión account_admin o el inicio de sesión propio del puesto); el parámetro por tenant agent_credential_admin_only (activado por defecto) lo desactiva. Cualquier otro 403 es el rechazo simple de alcance (forbidden: un ddw_ o una clave de sitio público).
json
{
"error": {
"code": "role_forbidden",
"message": "setting or clearing a seat's widget password takes a dd_ key, a dashboard login with the account_admin role, or the seat's own login"
}
}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"
}
}Deshabilitar el acceso al widget de un agente
http
DELETE /v1/agents/{id}/credentialBorra la contraseña de acceso al widget del agente (vuelve a login_enabled: false) y revoca cualquier ddw_ vivo que tenga el agente, de modo que la deshabilitación surte efecto de inmediato — INCLUIDO el endpoint SIP (NR-02), informado en sip_teardown. Sin esa mitad, un agente deshabilitado seguía marcando por SIP hasta la autoexpiración de 24 h de la credencial. El puesto en sí no se toca. Idempotente. Alcance de tenant (un ddw_ recibe 403). Mismas reglas de rol que el POST (core#2404): un inicio de sesión member recibe 403 role_forbidden en cualquier puesto que no sea el suyo.
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 (ahora con login_enabled false), más el resultado del desmontaje SIP.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
agent | object (Agent) | sí | |
agent.id | string (uuid) | no | |
agent.tenant_id | string (uuid) | no | |
agent.name | string | no | |
agent.email | string | no | |
agent.role | agent · supervisor · admin | no | |
agent.status | active · inactive | no | Indicador de plantilla — NO es presencia. |
agent.sip_extension | string | null | no | |
agent.device_mode | browser · external | no | |
agent.login_enabled | boolean | no | Estado de autoinicio de sesión del widget — verdadero si y solo si hay una credencial de contraseña establecida. El hash de la contraseña nunca se expone. |
agent.presence | offline · available · ringing · on_call · wrap_up · paused | no | Presencia en vivo del asiento desde el AgentFSM en memoria. wrap_up es el trabajo posterior a la llamada (se entra al colgar cuando el tope agent_wrap_up_seconds del tenant es superior a 0); paused es un asiento retirado de la rotación con un motivo (ADR #101 D1, MT-ROAD-R11). |
agent.pause_reason | manual · break · lunch · training · meeting | null | no | Por qué el asiento está en pausa; null salvo que presence sea paused (core#2542). |
agent.state_since | string (date-time) | null | no | Cuándo el asiento entró en su presence actual (RFC 3339); null cuando el agente no tiene asiento (core#2542). |
agent.pause_armed | boolean | no | Hay una pausa armada para cuando termine la llamada actual (POST /v1/agents/{id}/pause con on_release: true). |
agent.created_at | string (date-time) | no | |
agent.updated_at | string (date-time) | no | |
sip_teardown | ok · skipped · failed | sí | Resultado del desmontaje SIP que acompaña a una revocación (NR-02): el borrado de la credencial efímera agentcred del agente y de su registro usrloc vivo en Kamailio. * ok: credencial y registro eliminados; el softphone ya no puede registrarse. * skipped: no había nada que eliminar: el agente no tiene extensión SIP, el aprovisionamiento del SBC no está configurado en este entorno, o el agente aún conserva una sesión VIGENTE (véase abajo), por lo que esta revocación no dejó ningún acceso abierto. * failed: la revocación en la base de datos se mantiene, pero la credencial SIP SIGUE ACTIVA y lo seguirá hasta su caducidad automática a las 24 h. Trátalo como una vía de acceso abierta: reintenta o expulsa al agente por otros medios. Es un campo y no un código de estado a propósito: la revocación en sí es duradera y sí ocurrió, así que un 5xx mentiría en el otro sentido. Lo que nunca debe pasar es un 200 silencioso que implique una expulsión que no ocurrió. El desmontaje sigue a la TRANSICIÓN, no al agente. Revocar un id de token que ya estaba revocado (un id que un cliente puede conservar legítimamente, ya que GET /v1/widget_tokens lista las filas revocadas, y ya que emitir un token sustituye al anterior) NO debe expulsar, porque se cortaría la sesión SUCESORA del agente. Por tanto, el desmontaje solo se ejecuta cuando el agente se queda sin ningún token vigente; en caso contrario informa skipped. Esto es lo que mantiene útil el consejo de reintento de failed: un token ya revocado y sin sucesor vivo sí expulsa en un DELETE reintentado, de modo que reintentar es un remedio real y no una operación vacía. |
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 — AgentCredentialForbidden
Se rechazó establecer o borrar la contraseña de acceso al widget de un puesto (core#2404, adenda del ADR #126). coderole_forbidden: quien llama es un inicio de sesión de dashboard que no es ni account_admin ni el inicio de sesión vinculado a ese puesto (la ruta admite una clave dd_, un inicio de sesión account_admin o el inicio de sesión propio del puesto); el parámetro por tenant agent_credential_admin_only (activado por defecto) lo desactiva. Cualquier otro 403 es el rechazo simple de alcance (forbidden: un ddw_ o una clave de sitio público).
json
{
"error": {
"code": "role_forbidden",
"message": "setting or clearing a seat's widget password takes a dd_ key, a dashboard login with the account_admin role, or the seat's own login"
}
}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"
}
}Puesto en línea + arranque del softphone
http
POST /v1/agents/checkinEl puesto pasa a available. El device_mode opcional (browser|external) persiste en el puesto y afina la búsqueda de puente de la campaña; si se omite se conserva el valor almacenado (el re-checkin sigue siendo idempotente). Cuando el aprovisionamiento del SBC está habilitado (KAMAILIO_RPC_URL; siempre activo en prod) la respuesta lleva el arranque COMPLETO del softphone — una contraseña SIP efímera generada en cada checkin, aprovisionada en el htable agentcred de Kamailio (el ÚNICO lugar donde existe; autoexpira a las 24 h). El re-checkin la reemite. Con el aprovisionamiento deshabilitado la clave sip simplemente está ausente.
Cuando la celda ejecuta su propio relay TURN, sip.turn lleva una credencial de relay efímera con el mismo horizonte (TurnIceServer, core#1076); ausente en caso contrario.
Dos fallas DESPUÉS de abrir el puesto responden 503 y lo revierten, de modo que el puesto nunca queda reclamando a un agente al que quien llama no puede llegar. Ramifica según code: sbc_unavailable cuando falla el RPC del SBC — nunca se devuelven credenciales que no puedan registrarse — y presence_unavailable cuando falla la escritura duradera de presencia del puesto (core#557).
QUÉ puesto se DERIVA DEL TOKEN, no del cuerpo (core#1033, ADR #131). Un inicio de sesión de dashboard (ddu_) hace check-in del puesto al que users_agents.user_id vincula a esa persona. Un token de navegador de agente (ddw_) recibe 403 en esta ruta por la lista de permitidos del widget (Dialer.API.WidgetAllowlist), igual que en el resto de /v1/agents. Por lo tanto sip_extension es OPCIONAL para una sesión ddu_, y enviar uno que no sea el puesto del propio token da 403 seat_mismatch — nunca se acepta en silencio. Una clave de tenant dd_ no representa a ninguna persona, así que sigue nombrando el puesto mediante sip_extension (el principal de toda la cuenta ya puede crear y eliminar cualquier puesto).
LA COMPUERTA DEL AVISO (core#2323, ADR #289). Con el flag del tenant gobernado por el founder agent_monitoring_ack_enforced activado y al menos una campaña que graba (record_calls) y no está completada ni archivada, un puesto que no ha aceptado el aviso de grabación y supervisión VIGENTE (POST /v1/agent-monitoring-acknowledgements) recibe 403 monitoring_ack_required ANTES de que cambie nada: el puesto permanece fuera de línea y no se genera ninguna credencial SIP. El cuerpo lleva error.notice_version, la versión que se debe aceptar (null cuando no hay ninguna publicada, lo que también cierra la compuerta: no hay nada que aceptar). Nace desactivado.
Cuerpo de la petición (JSON, obligatorio)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
sip_extension | string | no | OBLIGATORIO con una clave de tenant dd_; opcional con una sesión ddu_, donde el puesto sale del token y un valor que no coincida con él da 403. |
device_mode | browser · external | no | browser = softphone WSS que pasa primero por kamailio; external = directo al registrar de FS. |
json
{
"sip_extension": "1001",
"device_mode": "external"
}Respuestas
200
Puesto en línea (+ arranque del softphone cuando el aprovisionamiento del SBC está activado).
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
agent | object (Agent) | sí | |
agent.id | string (uuid) | no | |
agent.tenant_id | string (uuid) | no | |
agent.name | string | no | |
agent.email | string | no | |
agent.role | agent · supervisor · admin | no | |
agent.status | active · inactive | no | Indicador de plantilla — NO es presencia. |
agent.sip_extension | string | null | no | |
agent.device_mode | browser · external | no | |
agent.login_enabled | boolean | no | Estado de autoinicio de sesión del widget — verdadero si y solo si hay una credencial de contraseña establecida. El hash de la contraseña nunca se expone. |
agent.presence | offline · available · ringing · on_call · wrap_up · paused | no | Presencia en vivo del asiento desde el AgentFSM en memoria. wrap_up es el trabajo posterior a la llamada (se entra al colgar cuando el tope agent_wrap_up_seconds del tenant es superior a 0); paused es un asiento retirado de la rotación con un motivo (ADR #101 D1, MT-ROAD-R11). |
agent.pause_reason | manual · break · lunch · training · meeting | null | no | Por qué el asiento está en pausa; null salvo que presence sea paused (core#2542). |
agent.state_since | string (date-time) | null | no | Cuándo el asiento entró en su presence actual (RFC 3339); null cuando el agente no tiene asiento (core#2542). |
agent.pause_armed | boolean | no | Hay una pausa armada para cuando termine la llamada actual (POST /v1/agents/{id}/pause con on_release: true). |
agent.created_at | string (date-time) | no | |
agent.updated_at | string (date-time) | no | |
sip | object (SipBootstrap) | no | Arranque del softphone emitido en el checkin cuando el aprovisionamiento del SBC está habilitado. La contraseña efímera vive SOLO en el htable agentcred de Kamailio — nunca en Postgres, nunca en logs. Ausente por completo cuando el aprovisionamiento está deshabilitado. |
sip.extension | string | no | Usuario de digest (AOR simple). |
sip.password | string | no | Efímera, generada en cada checkin, NUNCA se persiste. |
sip.wss_url | string | no | SOFTPHONE_WSS_URL. |
sip.domain | string | no | SOFTPHONE_SIP_DOMAIN (URI From). |
sip.expires_at | string (date-time) | no | Cuándo deja de funcionar esta credencial. El horizonte de autoexpiración del htable (24 h) LIMITADO por la sesión de widget del agente, cuando ESA sesión aún la gobierna: el tope absoluto de esa sesión es de 12 h como máximo y el servidor expulsa la credencial en el momento en que caduca, de modo que el más temprano de los dos es el único horizonte contra el que un cliente puede planificar una renovación. Siempre en el futuro — una sesión cuyo tope ya caducó informa el horizonte plano, nunca un instante pasado para una credencial recién emitida. |
sip.turn | object (TurnIceServer) | no | El relay TURN propio de la celda (coturn) para agentes cuya red bloquea UDP (MT-CTI-16, core#1076). Una entrada RTCIceServer de WebRTC: el softphone la agrega a la lista STUN que lee de GET /v1/softphone/ice. Viaja en ESTA respuesta autenticada precisamente porque ese endpoint no está autenticado — una credencial de relay allí llegaría a todos los navegadores. Efímera, esquema use-auth-secret de coturn: username es <unix expiry>:<agent id> y credential es Base64(HMAC-SHA1(shared secret, username)); no se almacena nada por agente. Caduca en el MISMO instante que la contraseña SIP (expires_at), de modo que el re-checkin que renueva una renueva ambas. Ausente cuando la celda no tiene TURN configurado (SOFTPHONE_TURN_URLS / SOFTPHONE_TURN_SECRET sin definir): el softphone entonces negocia sin relay, como hacía antes de que existiera esta clave. |
sip.turn.urls | arreglo de string | sí | URLs turn: / turns: (:3478 UDP/TCP, :5349 TLS). |
sip.turn.username | string | sí | <unix expiry>:<agent id>. |
sip.turn.credential | string | sí | Efímera; NUNCA se persiste. |
json
{
"agent": {
"id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
"tenant_id": "0d4f4f9e-1f2a-4b53-9d3c-8a5e2f7b1c10",
"name": "Postman Seat",
"email": "[email protected]",
"role": "agent",
"status": "active",
"sip_extension": "1001",
"device_mode": "external",
"presence": "available",
"created_at": "2026-06-01T12:00:00Z",
"updated_at": "2026-07-05T10:00:00Z"
},
"sip": {
"extension": "1001",
"password": "ephemeral-example-not-real",
"wss_url": "wss://127.0.0.1:8443",
"domain": "sip.usa.dialerdigital.com",
"expires_at": "2026-07-06T10:00:00Z",
"turn": {
"urls": [
"turn:turn.example.test:3478?transport=udp",
"turns:turn.example.test:5349?transport=tcp"
],
"username": "1783332000:1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
"credential": "ephemeral-example-not-real"
}
}
}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 — SeatFromToken
No se pudo derivar el puesto del token, o el cuerpo lo contradijo (core#1033, ADR #131). code seat_not_linked: este inicio de sesión no tiene ningún puesto de agente activo vinculado (users_agents.user_id), o el puesto al que se vinculó el token ya no está activo — un administrador vincula el puesto, quien llama no puede elegir uno. code seat_mismatch: sip_extension nombró un puesto que no es el propio del token; el mensaje deliberadamente NO revela cuál lo es, para que el rechazo no sea un oráculo de las extensiones de los compañeros. Un puesto derivado que AÚN NO tiene sip_extension es 409 en su lugar (el token y el puesto son ambos correctos; lo que falta es un campo de la lista que solo un administrador puede completar).
json
{
"error": {
"code": "seat_mismatch",
"message": "sip_extension does not match the seat this token owns"
}
}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"
}
}503 — CheckinUnavailable
Checkin rechazado de forma cerrada DESPUÉS de abrir el puesto, por lo que el puesto se revirtió y no queda nada durable que afirme que el agente está disponible. Reintenta. DOS causas, que se distinguen por code (ErrorEnvelope indica ramificar según él, y un cliente que solo conoce una de ellas no hará nada, en silencio, ante la otra):
sbc_unavailable— falló el RPC del SBC. Nunca se devuelven credenciales que no puedan registrarse.presence_unavailable— falló la escritura duradera de presencia del puesto (core#557). No es cosmético: esa columna es lo queSessionCapSweeperlee para distinguir esta sesión de la que está desmontando, de modo que un checkin que respondiera 200 sin ella entrega una credencial SIP viva sobre un puesto que el sweeper puede recuperar.
Esta respuesta reemplazó a la antigua SbcUnavailable, cuyo nombre prometía la única causa que tenía cuando el checkin solo tenía una.
sbc_unavailable
json
{
"error": {
"code": "sbc_unavailable",
"message": "softphone provisioning unavailable, retry"
}
}presence_unavailable
json
{
"error": {
"code": "presence_unavailable",
"message": "seat presence could not be recorded, retry"
}
}Puesto fuera de línea (revoca la credencial SIP efímera)
http
POST /v1/agents/checkoutSolo un puesto available puede salir (409 en caso contrario, falla de forma cerrada — un puesto en una llamada no puede desaparecer). Revoca la credencial SIP efímera (borrado idempotente del htable; la autoexpiración de 24 h es el respaldo ante una revocación omitida).
Misma derivación del puesto que agentCheckin (core#1033, ADR #131): el puesto sale del token en una sesión ddu_, de modo que nadie puede hacer check-out de un compañero nombrando su extensión. Una clave de tenant dd_ sigue nombrándolo; un ddw_ recibe 403 aquí por la lista de permitidos del widget.
Cuerpo de la petición (JSON, obligatorio)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
sip_extension | string | no | OBLIGATORIO con una clave de tenant dd_; opcional con una sesión ddu_, donde el puesto sale del token y un valor que no coincida con él da 403. |
json
{
"sip_extension": "1001"
}Respuestas
200
Puesto fuera de línea.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
agent | object (Agent) | sí | |
agent.id | string (uuid) | no | |
agent.tenant_id | string (uuid) | no | |
agent.name | string | no | |
agent.email | string | no | |
agent.role | agent · supervisor · admin | no | |
agent.status | active · inactive | no | Indicador de plantilla — NO es presencia. |
agent.sip_extension | string | null | no | |
agent.device_mode | browser · external | no | |
agent.login_enabled | boolean | no | Estado de autoinicio de sesión del widget — verdadero si y solo si hay una credencial de contraseña establecida. El hash de la contraseña nunca se expone. |
agent.presence | offline · available · ringing · on_call · wrap_up · paused | no | Presencia en vivo del asiento desde el AgentFSM en memoria. wrap_up es el trabajo posterior a la llamada (se entra al colgar cuando el tope agent_wrap_up_seconds del tenant es superior a 0); paused es un asiento retirado de la rotación con un motivo (ADR #101 D1, MT-ROAD-R11). |
agent.pause_reason | manual · break · lunch · training · meeting | null | no | Por qué el asiento está en pausa; null salvo que presence sea paused (core#2542). |
agent.state_since | string (date-time) | null | no | Cuándo el asiento entró en su presence actual (RFC 3339); null cuando el agente no tiene asiento (core#2542). |
agent.pause_armed | boolean | no | Hay una pausa armada para cuando termine la llamada actual (POST /v1/agents/{id}/pause con on_release: true). |
agent.created_at | string (date-time) | no | |
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 — SeatFromToken
No se pudo derivar el puesto del token, o el cuerpo lo contradijo (core#1033, ADR #131). code seat_not_linked: este inicio de sesión no tiene ningún puesto de agente activo vinculado (users_agents.user_id), o el puesto al que se vinculó el token ya no está activo — un administrador vincula el puesto, quien llama no puede elegir uno. code seat_mismatch: sip_extension nombró un puesto que no es el propio del token; el mensaje deliberadamente NO revela cuál lo es, para que el rechazo no sea un oráculo de las extensiones de los compañeros. Un puesto derivado que AÚN NO tiene sip_extension es 409 en su lugar (el token y el puesto son ambos correctos; lo que falta es un campo de la lista que solo un administrador puede completar).
json
{
"error": {
"code": "seat_mismatch",
"message": "sip_extension does not match the seat this token owns"
}
}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"
}
}Sacar de la rotación un puesto disponible (en pausa)
http
POST /v1/agents/{id}/pauseMueve un puesto available (o wrap_up) a paused con un motivo de un conjunto CERRADO; a un puesto en pausa nunca se le entrega una llamada y aún puede hacer check-out. Solo alcance de tenant (un token de agente ddw_ recibe 403 por la lista de permitidos del widget). 409 cuando el agente no ha hecho check-in, está reservado / en una llamada, o ya está en pausa (falla de forma cerrada: no se fuerza nada) — salvo que se envíe on_release: true: en un puesto ringing / on_call eso ARMA la pausa (ADR #101 D5, core#2542), el puesto sigue en su llamada y se responde con pause_armed: true; cuando la llamada se libera (contestada o no) el puesto pasa directamente a paused con ese motivo, sin pasar nunca por available. Armarla de nuevo es idempotente (gana el último motivo); POST /v1/agents/{id}/resume la desarma; una baja armada por el barrido del tope de sesión gana y la descarta. on_release en un puesto sin llamada se comporta como la pausa simple. El puesto es estado en memoria; que el puesto siga fuera de la rotación tras un reinicio depende del flag agent_session_journal_enabled del tenant (ADR #101 D10).
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 |
|---|---|---|---|
reason | manual · break · lunch · training · meeting | sí | |
on_release | boolean | no | Arma la pausa para cuando termine la llamada actual en lugar de rechazar con 409 (ADR #101 D5). |
json
{
"reason": "lunch"
}Respuestas
200
Puesto en pausa; agent.presence se lee de vuelta del puesto.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
agent | object (Agent) | sí | |
agent.id | string (uuid) | no | |
agent.tenant_id | string (uuid) | no | |
agent.name | string | no | |
agent.email | string | no | |
agent.role | agent · supervisor · admin | no | |
agent.status | active · inactive | no | Indicador de plantilla — NO es presencia. |
agent.sip_extension | string | null | no | |
agent.device_mode | browser · external | no | |
agent.login_enabled | boolean | no | Estado de autoinicio de sesión del widget — verdadero si y solo si hay una credencial de contraseña establecida. El hash de la contraseña nunca se expone. |
agent.presence | offline · available · ringing · on_call · wrap_up · paused | no | Presencia en vivo del asiento desde el AgentFSM en memoria. wrap_up es el trabajo posterior a la llamada (se entra al colgar cuando el tope agent_wrap_up_seconds del tenant es superior a 0); paused es un asiento retirado de la rotación con un motivo (ADR #101 D1, MT-ROAD-R11). |
agent.pause_reason | manual · break · lunch · training · meeting | null | no | Por qué el asiento está en pausa; null salvo que presence sea paused (core#2542). |
agent.state_since | string (date-time) | null | no | Cuándo el asiento entró en su presence actual (RFC 3339); null cuando el agente no tiene asiento (core#2542). |
agent.pause_armed | boolean | no | Hay una pausa armada para cuando termine la llamada actual (POST /v1/agents/{id}/pause con on_release: true). |
agent.created_at | string (date-time) | no | |
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 — 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"
}
}Devolver a la rotación un puesto en pausa (disponible)
http
POST /v1/agents/{id}/resumeDevuelve un puesto paused a available, al FINAL de la cola de reservas. Solo alcance de tenant. 409 cuando el agente no ha hecho check-in o el puesto no está en pausa. Salir de la pausa es volver a Ready, así que pasa por la misma compuerta del aviso que el check-in: 403 monitoring_ack_required cuando la compuerta está activada y el agente no ha aceptado el aviso vigente (ver POST /v1/agents/checkin).
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
Puesto disponible de nuevo; agent.presence se lee de vuelta del puesto.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
agent | object (Agent) | sí | |
agent.id | string (uuid) | no | |
agent.tenant_id | string (uuid) | no | |
agent.name | string | no | |
agent.email | string | no | |
agent.role | agent · supervisor · admin | no | |
agent.status | active · inactive | no | Indicador de plantilla — NO es presencia. |
agent.sip_extension | string | null | no | |
agent.device_mode | browser · external | no | |
agent.login_enabled | boolean | no | Estado de autoinicio de sesión del widget — verdadero si y solo si hay una credencial de contraseña establecida. El hash de la contraseña nunca se expone. |
agent.presence | offline · available · ringing · on_call · wrap_up · paused | no | Presencia en vivo del asiento desde el AgentFSM en memoria. wrap_up es el trabajo posterior a la llamada (se entra al colgar cuando el tope agent_wrap_up_seconds del tenant es superior a 0); paused es un asiento retirado de la rotación con un motivo (ADR #101 D1, MT-ROAD-R11). |
agent.pause_reason | manual · break · lunch · training · meeting | null | no | Por qué el asiento está en pausa; null salvo que presence sea paused (core#2542). |
agent.state_since | string (date-time) | null | no | Cuándo el asiento entró en su presence actual (RFC 3339); null cuando el agente no tiene asiento (core#2542). |
agent.pause_armed | boolean | no | Hay una pausa armada para cuando termine la llamada actual (POST /v1/agents/{id}/pause con on_release: true). |
agent.created_at | string (date-time) | no | |
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 — 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"
}
}Terminar el trabajo posterior a la llamada (wrap_up -> available)
http
POST /v1/agents/{id}/wrap-upEl agente terminó el trabajo posterior a la llamada: mueve un puesto wrap_up de vuelta a available, al FINAL de la cola de reservas, y descarta el token de reserva que la llamada dejó en el puesto. Solo alcance de tenant. 409 cuando el agente no ha hecho check-in o el puesto no está en wrap_up.
NO hay ningún verbo que ENTRE en wrap_up: la FSM del puesto entra en él solo al colgar una llamada CONTESTADA, y solo cuando el tope agent_wrap_up_seconds del tenant es mayor que 0 (ADR #101 D5/D8). El tope es la otra salida — el puesto vuelve a available por sí solo cuando expira — y POST /v1/agents/{id}/pause es la tercera (un agente puede cerrar su ACW con una pausa).
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
Trabajo posterior a la llamada terminado; agent.presence se lee de vuelta del puesto.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
agent | object (Agent) | sí | |
agent.id | string (uuid) | no | |
agent.tenant_id | string (uuid) | no | |
agent.name | string | no | |
agent.email | string | no | |
agent.role | agent · supervisor · admin | no | |
agent.status | active · inactive | no | Indicador de plantilla — NO es presencia. |
agent.sip_extension | string | null | no | |
agent.device_mode | browser · external | no | |
agent.login_enabled | boolean | no | Estado de autoinicio de sesión del widget — verdadero si y solo si hay una credencial de contraseña establecida. El hash de la contraseña nunca se expone. |
agent.presence | offline · available · ringing · on_call · wrap_up · paused | no | Presencia en vivo del asiento desde el AgentFSM en memoria. wrap_up es el trabajo posterior a la llamada (se entra al colgar cuando el tope agent_wrap_up_seconds del tenant es superior a 0); paused es un asiento retirado de la rotación con un motivo (ADR #101 D1, MT-ROAD-R11). |
agent.pause_reason | manual · break · lunch · training · meeting | null | no | Por qué el asiento está en pausa; null salvo que presence sea paused (core#2542). |
agent.state_since | string (date-time) | null | no | Cuándo el asiento entró en su presence actual (RFC 3339); null cuando el agente no tiene asiento (core#2542). |
agent.pause_armed | boolean | no | Hay una pausa armada para cuando termine la llamada actual (POST /v1/agents/{id}/pause con on_release: true). |
agent.created_at | string (date-time) | no | |
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 — 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"
}
}Establecer la presencia de un puesto por id de la lista (una FACHADA sobre los verbos de la FSM del puesto)
http
PATCH /v1/agents/{id}/statusNO es un segundo modelo de presencia y NO es una escritura de estado: el status solicitado se traduce al verbo que la FSM del puesto ya tiene, y la FSM decide. Todo lo que rechace es 409 con el current_state del puesto; no se fuerza nada. Destino → verbo (el puesto se lee en el momento de la llamada):
status | desde paused | desde wrap_up | en otro caso |
|---|---|---|---|
available | POST /v1/agents/{id}/resume | POST /v1/agents/{id}/wrap-up | 409 |
paused (requiere reason) | 409 | POST /v1/agents/{id}/pause | POST /v1/agents/{id}/pause |
offline | POST /v1/agents/checkout | POST /v1/agents/checkout | POST /v1/agents/checkout |
available desde offline es 409 a propósito: el check-in deriva el puesto del token y genera una credencial SIP efímera (POST /v1/agents/checkin), que una llamada por id no debe entregar a quien nombre el puesto. ringing y on_call nunca son un destino: la FSM entra en ellos solo con una llamada. Los eventos de presencia en el piso en vivo (agent.presence) son los MISMOS frames que emiten los verbos anteriores, porque se ejecuta el mismo verbo.
Solo alcance de tenant (un token de agente ddw_ recibe 403 por la lista de permitidos del widget). available desde paused pasa por la misma compuerta del aviso que POST /v1/agents/{id}/resume (403 monitoring_ack_required). Un id de otro tenant es 404. offline además sigue la regla de autor del check-out: un inicio de sesión ddu_ CON un puesto vinculado solo puede establecer su PROPIO puesto (403 en caso contrario).
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 |
|---|---|---|---|
status | available · paused · offline | sí | |
reason | manual · break · lunch · training · meeting | no | Obligatorio cuando status es paused; se ignora en otro caso. |
json
{
"status": "paused",
"reason": "lunch"
}Respuestas
200
Estado aplicado; agent.presence se lee de vuelta del puesto.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
agent | object (Agent) | sí | |
agent.id | string (uuid) | no | |
agent.tenant_id | string (uuid) | no | |
agent.name | string | no | |
agent.email | string | no | |
agent.role | agent · supervisor · admin | no | |
agent.status | active · inactive | no | Indicador de plantilla — NO es presencia. |
agent.sip_extension | string | null | no | |
agent.device_mode | browser · external | no | |
agent.login_enabled | boolean | no | Estado de autoinicio de sesión del widget — verdadero si y solo si hay una credencial de contraseña establecida. El hash de la contraseña nunca se expone. |
agent.presence | offline · available · ringing · on_call · wrap_up · paused | no | Presencia en vivo del asiento desde el AgentFSM en memoria. wrap_up es el trabajo posterior a la llamada (se entra al colgar cuando el tope agent_wrap_up_seconds del tenant es superior a 0); paused es un asiento retirado de la rotación con un motivo (ADR #101 D1, MT-ROAD-R11). |
agent.pause_reason | manual · break · lunch · training · meeting | null | no | Por qué el asiento está en pausa; null salvo que presence sea paused (core#2542). |
agent.state_since | string (date-time) | null | no | Cuándo el asiento entró en su presence actual (RFC 3339); null cuando el agente no tiene asiento (core#2542). |
agent.pause_armed | boolean | no | Hay una pausa armada para cuando termine la llamada actual (POST /v1/agents/{id}/pause con on_release: true). |
agent.created_at | string (date-time) | no | |
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 — 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
La FSM del puesto no admite ese movimiento desde el estado actual del puesto.
json
{
"error": {
"code": "conflict",
"message": "seat is on_call; the FSM has no transition to that status from here",
"current_state": "on_call"
}
}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 los turnos planificados del tenant
http
GET /v1/planned-shiftsSin from/to: los 500 turnos con el start_time más temprano, y truncated: true cuando hay más. Los turnos son de solo anexar, así que pasado el 500.º un turno más nuevo NO está en esta lista: pide su ventana.
Con from y to (RFC 3339, ambos o ninguno, from anterior a to): todos los turnos que se solapan con [from, to), sin tope, ordenados por start_time; truncated es false.
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
from | query | string (date-time) | no | Inicio de la ventana (RFC 3339). Requiere to. |
to | query | string (date-time) | no | Fin de la ventana (RFC 3339), exclusivo. Requiere from. |
Respuestas
200
Lista de turnos planificados
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
shifts | arreglo de PlannedShift | sí | |
shifts[].id | string (uuid) | sí | |
shifts[].agent_id | string (uuid) | sí | |
shifts[].start_time | string (date-time) | sí | |
shifts[].end_time | string (date-time) | sí | |
shifts[].planned_state | available · ringing · on_call · wrap_up · paused | sí | |
truncated | boolean | sí | true cuando, sin ventana, hay más turnos que los 500 devueltos. Siempre false con from/to. |
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"
}
}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 turno planificado
http
POST /v1/planned-shiftsPlanifica un turno para un agente (agent_id, start_time, end_time, planned_state opcional). Un agent_id ausente es 400 invalid.
Quién puede nombrar a quién (ADR #133, ADR #134). El agent_id queda acotado por el PRINCIPAL, con la misma regla en cada verbo que lee uno: una clave de máquina dd_ nombra a cualquier agente de su tenant; un ddw_ solo al propio; una sesión de inicio de sesión ddu_ vinculada a un puesto de agente actúa como ESE puesto — agent_id puede omitirse (se completa con el puesto) o repetirlo, y nombrar a otro agente es 403 forbidden; una sesión ddu_ SIN puesto vinculado puede nombrar a otro agente solo con users.role = account_admin — un member recibe 403 role_forbidden — una vez que la clave de catálogo del tenant ddu_author_role_enforced está activada (nace desactivada: hasta entonces la sesión sin puesto conserva el contrato dd_ en este verbo).
Cuerpo de la petición (JSON, obligatorio)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
agent_id | string (uuid) | no | El agente para el que se planifica el turno. Obligatorio con una clave dd_; una sesión ddu_ vinculada a un puesto puede omitirlo (su puesto) y no puede nombrar a otro. |
start_time | string (date-time) | sí | |
end_time | string (date-time) | sí | |
planned_state | available · ringing · on_call · wrap_up · paused | no |
Respuestas
201
Turno planificado creado.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
id | string (uuid) | 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"
}
}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"
}
}