Appearance
Estadísticas y uso
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.
Contadores operativos, resúmenes de marcaciones bloqueadas, medición de uso.
Endpoints
| Método | Ruta | Resumen |
|---|---|---|
GET | /v1/stats/summary | Contadores operativos de hoy (día UTC) |
GET | /v1/stats/contact-windows | Tasa de contacto por hora local del consumidor, mejor ventana, teléfonos por persona |
GET | /v1/stats/promises | Resumen del embudo de PTP, totales del tenant y por campaña |
GET | /v1/stats/penetration | Analítica de penetración a nivel de persona |
GET | /v1/stats/abandonment | Informe duradero de abandono de todas las campañas, en una sola respuesta |
GET | /v1/stats/blocked | Resumen duradero de marcaciones bloqueadas |
GET | /v1/stats/capacity | Techos de capacidad del tenant + uso en vivo (ADR #12) |
GET | /v1/stats/ai-usage | Medición de minutos de IA de voz (lectura para facturación) |
GET | /v1/stats/sms-usage | Medición de segmentos de SMS (lectura para facturación) |
Contadores operativos de hoy (día UTC)
http
GET /v1/stats/summaryCalculado a partir del CDR solo anexo + el registro de puestos en vivo: verdad reconstruible, no estado en caché.
Respuestas
200
Contadores de hoy.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
date | string (date) | no | |
window | "utc_day" | no | |
calls_today | integer | no | |
connected_today | integer | no | |
connect_rate | number | no | |
agents_online | integer | no | |
agents_available | integer | no | |
campaigns_running | integer | no |
json
{
"date": "2026-07-05",
"window": "utc_day",
"calls_today": 412,
"connected_today": 67,
"connect_rate": 0.1626,
"agents_online": 5,
"agents_available": 3,
"campaigns_running": 2
}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"
}
}503
Los contadores no se pudieron leer dentro del tiempo límite de la sentencia (report_timeout).
Tasa de contacto por hora local del consumidor, mejor ventana, teléfonos por persona
http
GET /v1/stats/contact-windowsPara cada hora del día (0-23) en la hora local del CONSUMIDOR: cuántos intentos se realizaron, cuántos llegaron a una persona (answered_human) y la tasa. La hora es la del consumidor, no la del tenant ni la UTC: usa la zona que la compuerta de horario de silencio congeló en el intento cuando se realizó (las zonas del código de área del número más la zona declarada del contacto), incluido el horario de verano. Se deriva al leer a partir del CDR solo anexo; no se guarda ningún contador.
Un intento es una llamada saliente original realizada por la plataforma. Las llamadas entrantes, las piernas de transferencia externa y las filas que la plataforma nunca realizó no son intentos. Un contacto es un intento con al menos un resultado answered_human.
Un intento cuya hora local no puede conocerse se cuenta en unknown, nunca se reparte entre las horas: no_zone (nada congelado, o una zona que la base de datos de zonas horarias no conoce) y ambiguous_zone (un código de área que abarca varias zonas, o una zona declarada que no coincide con él). unknown.attempts es la suma de ambos motivos.
best_window son las dos horas locales consecutivas con la mayor tasa de contacto, y solo se nombra cuando CADA UNA de las dos horas reúne al menos min_attempts intentos (ajuste del tenant contact_window_min_attempts, por defecto 50). En caso contrario es null y best_window_reason explica por qué (insufficient_attempts, o no_resolved_attempts cuando ningún intento tenía una hora local conocida); nunca se inventa una hora a partir de un puñado de llamadas. Los empates van a la hora más temprana; la ventana no cruza la medianoche. Ambas horas deben además estar dentro de la ventana legal de llamadas [08:00, 21:00), de modo que nunca se recomiende una ventana en un horario en que la plataforma se negaría a marcar; las horas fuera de ella siguen apareciendo en hours pero no pueden anclar best_window.
weekdays es la misma tasa de contacto sobre el eje de día de la semana ISO local del consumidor (1 = lunes ... 7 = domingo). applied indica si el ajuste best_time_ranking_enabled del tenant está activado (el flujo del marcador ordena los leads según estos datos), source el nivel que lo respalda (campaign, portfolio o none), y weights la tabla de pesos que el marcador aplica a cada celda (día de la semana, hora local) dentro de la ventana legal mientras applied es verdadero, con el nivel que respaldó esa celda; cuando applied es falso el marcador mantiene el orden neutro y source/weights muestran lo que aplicaría con el ajuste activado. weights siempre proviene de los últimos 90 días y no se acota con from/to.
phones_per_person es el promedio de números de teléfono distintos por persona (consumer_ref) entre las cuentas activas del tenant (cualquier estado salvo paid, settled o closed). Una persona con una cuenta activa y sin teléfono cuenta como 0. Es de todo el tenant: from, to y campaign_id no lo acotan. average es null cuando no hay ninguna persona activa.
from/to por defecto abarcan los últimos 30 días; una ventana mayor de 90 días, o con from no anterior a to, es 422. Una campaña desconocida (o de otro tenant) es 404, nunca un informe todo en cero.
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
from | query | string (date-time) | no | Límite inferior RFC 3339 (campo específico del endpoint; por defecto los últimos 30 días UTC donde se indica). |
to | query | string (date-time) | no | Límite superior RFC 3339. |
campaign_id | query | string (uuid) | no |
Respuestas
200
Tasas de contacto por hora, la mejor ventana y teléfonos por persona.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
from | string (date-time) | sí | |
to | string (date-time) | sí | |
campaign_id | string (uuid) | null | sí | |
basis | "consumer_local_hour" | sí | |
min_attempts | integer | sí | El mínimo que debe cumplir cada hora de best_window (contact_window_min_attempts). |
hours | arreglo de ContactWindowHour | sí | Siempre 24 filas, de la hora 0 a la 23, en la hora local del consumidor. |
hours[].hour | integer | sí | |
hours[].attempts | integer | sí | |
hours[].contacts | integer | sí | |
hours[].contact_rate | number | null | sí | contacts / attempts, cuatro decimales; null cuando no hubo intentos. |
weekdays | arreglo de ContactWindowWeekday | no | Siempre 7 filas, día de la semana ISO 1 (lunes) a 7 (domingo), en la hora local del consumidor. Aditivo (core#1980); opcional para clientes escritos antes de él. |
weekdays[].weekday | integer | sí | Día de la semana ISO, 1 = lunes. |
weekdays[].attempts | integer | sí | |
weekdays[].contacts | integer | sí | |
weekdays[].contact_rate | number | null | sí | contacts / attempts, cuatro decimales; null cuando no hubo intentos. |
applied | boolean | no | Si el ajuste best_time_ranking_enabled del tenant está activado, es decir, si el flujo de marcación ordena los leads según estos datos. Aditivo (core#1980). |
source | campaign · portfolio · none | no | El nivel que respalda weights: el historial propio de la campaña, la cartera del tenant, o none (sin evidencia, orden neutro). Aditivo (core#1980). |
weights | arreglo de ContactWindowWeight | no | El peso que el marcador aplica a cada celda (día de la semana, hora local) dentro de la ventana legal [08:00, 21:00) mientras applied es verdadero (con applied falso mantiene el orden neutro y esto es lo que aplicaría): 7 x 13 filas, o vacío si no se pudo calcular. Últimos 90 días; no se acota con from/to. Aditivo (core#1980). |
weights[].weekday | integer | sí | |
weights[].hour | integer | sí | |
weights[].weight | number | sí | 1.0 es neutro. |
weights[].source | campaign · portfolio · none | sí | |
weights[].attempts | integer | sí | Intentos en esta celda en el nivel que respaldó; 0 cuando source es none. |
weights[].contacts | integer | sí | |
unknown | object | sí | Intentos cuya hora local no se puede conocer. Nunca se redistribuyen entre hours. |
unknown.attempts | integer | sí | |
unknown.contacts | integer | sí | |
unknown.no_zone | integer | sí | Intentos sin una zona congelada utilizable. |
unknown.ambiguous_zone | integer | sí | Intentos cuyo código de área abarca varias zonas o no coincide con el declarado. |
best_window | ContactWindow | null | sí | |
best_window_reason | insufficient_attempts · no_resolved_attempts | null | sí | Por qué best_window es null; null cuando se nombra una ventana. |
phones_per_person | object | sí | |
phones_per_person.persons | integer | sí | consumer_ref distintos con una cuenta activa. |
phones_per_person.phones | integer | sí | Suma de teléfonos distintos por persona. |
phones_per_person.average | number | null | sí | phones / persons, dos decimales; null sin ninguna persona activa. |
json
{
"from": "2026-09-04T00:00:00Z",
"to": "2026-10-04T00:00:00Z",
"campaign_id": null,
"basis": "consumer_local_hour",
"min_attempts": 50,
"hours": [
{
"hour": 9,
"attempts": 120,
"contacts": 18,
"contact_rate": 0.15
},
{
"hour": 10,
"attempts": 140,
"contacts": 28,
"contact_rate": 0.2
},
{
"hour": 11,
"attempts": 0,
"contacts": 0,
"contact_rate": null
}
],
"weekdays": [
{
"weekday": 1,
"attempts": 90,
"contacts": 15,
"contact_rate": 0.1667
}
],
"unknown": {
"attempts": 14,
"contacts": 1,
"no_zone": 9,
"ambiguous_zone": 5
},
"applied": true,
"source": "portfolio",
"weights": [
{
"weekday": 1,
"hour": 9,
"weight": 1.2,
"source": "portfolio",
"attempts": 120,
"contacts": 18
},
{
"weekday": 1,
"hour": 10,
"weight": 1,
"source": "none",
"attempts": 0,
"contacts": 0
}
],
"best_window": {
"start_hour": 9,
"end_hour": 11,
"attempts": 260,
"contacts": 46,
"contact_rate": 0.1769
},
"best_window_reason": null,
"phones_per_person": {
"persons": 4,
"phones": 7,
"average": 1.75
}
}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"
}
}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
La ventana es mayor de 90 días, o from no es anterior a to.
json
{
"error": {
"code": "unprocessable",
"message": "the window must not exceed 90 days"
}
}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"
}
}Resumen del embudo de PTP, totales del tenant y por campaña
http
GET /v1/stats/promisesLas monedas nunca se suman entre sí; kept_rate_30d es null cuando nada se resolvió en la ventana.
by_campaign tiene una fila por cada campaña con al menos una promesa, derivada al leer a través del intento en el que se capturó la promesa (promises.call_attempt_id -> call_attempts.campaign_id); no se guarda ningún contador. Una promesa sin intento, o cuyo intento no tiene campaña, se agrupa en la fila con campaign_id: null, nunca se descarta, de modo que las filas siempre suman los totales de nivel superior.
Con campaign_id toda cifra se acota a esa campaña. Una campaña desconocida (o de otro tenant) es 404, nunca un resumen todo en cero.
La tasa de cumplimiento la reporta el cliente. La plataforma no se entera por sí misma de si un consumidor pagó: una promesa solo pasa a kept o broken cuando el cliente lo indica mediante PATCH /v1/promises/{id}. Un cliente que nunca reporta deja sus promesas en pending y la tasa en null.
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
campaign_id | query | string (uuid) | no |
Respuestas
200
Resumen del embudo.
json
{
"campaign_id": null,
"open": {
"count": 4,
"amount_cents": {
"USD": 50000,
"MXN": 120000
}
},
"resolved_30d": {
"kept": 3,
"broken": 1
},
"kept_rate_30d": 0.75,
"window_days": 30,
"by_campaign": [
{
"campaign_id": "3f2c1b0a-9d8e-4c7b-a6f5-4e3d2c1b0a99",
"open": {
"count": 3,
"amount_cents": {
"USD": 50000,
"MXN": 0
}
},
"resolved_30d": {
"kept": 3,
"broken": 1
},
"kept_rate_30d": 0.75
},
{
"campaign_id": null,
"open": {
"count": 1,
"amount_cents": {
"USD": 0,
"MXN": 120000
}
},
"resolved_30d": {
"kept": 0,
"broken": 0
},
"kept_rate_30d": null
}
]
}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"
}
}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"
}
}Analítica de penetración a nivel de persona
http
GET /v1/stats/penetrationDeduplicación a nivel de persona (cada deuda cuenta una vez): attempted = al menos un intento original, reached = cualquier disposición o conversación no fallida, effective_contact = answered_human o conversación. campaign_ids es opcional (máx. 100; también se acepta campaign_id=): sin él el informe cubre TODAS las campañas del tenant que llama, con un único total a nivel de persona (una cuenta en dos campañas cuenta una vez), de modo que un tenant con más de 100 campañas lee toda su penetración en una sola llamada. Con la lista la respuesta es la de siempre.
granularity=day añade by_day: una fila por cada día LOCAL de la ventana y por campaña, con el día cortado en la zona IANA que nombra tz, incluido el horario de verano (CET no es un +01:00 fijo). La cuadrícula es densa (un día tranquilo es una fila de ceros) y se deriva al leer del CDR solo anexo. Con granularity=day, tz es OBLIGATORIO (no hay zona del tenant por defecto) y un tz ausente o que no sea IANA es 422, nunca un corte UTC silencioso; from y to también son obligatorios y la ventana está limitada a 31 días (400 en caso contrario, el criterio de /v1/reports/attempts). totals y by_campaign siguen siendo las cifras del rango: un conteo a nivel de persona no se suma entre días. Sin granularity (o con total) la respuesta no cambia y tz no se lee.
window=7d|30d|90d|all responde los mismos totals / by_campaign sobre una ventana RELATIVA a ahora, servida desde una instantánea en memoria de corta vida derivada del CDR solo anexo (no se escribe nada al marcar). La respuesta añade window y as_of, el instante en que se calcularon las cifras; una instantánea más antigua que el max_age del servidor (por defecto 60 s) no se sirve nunca, y el informe se calcula en vivo. window es excluyente con from / to y con granularity=day (422); sin él la respuesta no cambia.
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
campaign_ids | query | string | no | UUID de campañas separados por comas (máx. 100). Omítelo (o envíalo vacío) para todas las campañas del tenant. |
from | query | string (date-time) | no | Límite inferior RFC 3339 (campo específico del endpoint; por defecto los últimos 30 días UTC donde se indica). |
to | query | string (date-time) | no | Límite superior RFC 3339. |
window | query | 7d · 30d · 90d · all | no | Ventana relativa servida desde la instantánea derivada. Excluyente con from/to y con granularity=day. |
granularity | query | total · day | no | total (por defecto) o day (añade by_day; requiere tz, from y to). |
tz | query | string | no | Zona horaria IANA en la que se corta el día. Obligatoria con granularity=day; se ignora en otro caso. |
Respuestas
200
Totales de penetración + filas por campaña (+ by_day con granularity=day).
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
totals | object (PenetrationRow) | no | |
totals.campaign_id | string (uuid) | null | no | |
totals.unique_accounts | integer | no | |
totals.unique_consumers | integer | no | |
totals.attempted | integer | no | |
totals.reached | integer | no | |
totals.effective_contact | integer | no | |
totals.attempted_pct | number | no | |
totals.reached_pct | number | no | |
totals.effective_contact_pct | number | no | |
by_campaign | arreglo de PenetrationRow | no | |
by_campaign[].campaign_id | string (uuid) | null | no | |
by_campaign[].unique_accounts | integer | no | |
by_campaign[].unique_consumers | integer | no | |
by_campaign[].attempted | integer | no | |
by_campaign[].reached | integer | no | |
by_campaign[].effective_contact | integer | no | |
by_campaign[].attempted_pct | number | no | |
by_campaign[].reached_pct | number | no | |
by_campaign[].effective_contact_pct | number | no | |
window | 7d · 30d · 90d · all | no | Presente solo con window=. |
as_of | string (date-time) | no | Presente solo con window=: cuándo se calcularon las cifras. Nunca más antigua que el max_age de la instantánea del servidor. |
granularity | day | no | Presente solo con granularity=day. |
tz | string | no | La zona en la que se cortaron los días. Presente solo con granularity=day. |
by_day | arreglo de PenetrationDayRow | no | Presente solo con granularity=day. Cada día local que toca la ventana × cada campaña con un universo, ordenado por día y luego por campaña; con los ceros incluidos. El primer y el último día son parciales cuando from/to no son medianoches locales. Los conteos se suman entre campañas (una deuda pertenece a una campaña), no entre días. |
by_day[].day | string (date) | no | |
by_day[].campaign_id | string (uuid) | no | |
by_day[].unique_accounts | integer | no | El universo de la campaña: el mismo número que reporta su fila de by_campaign. |
by_day[].attempted | integer | no | |
by_day[].reached | integer | no | |
by_day[].effective_contact | integer | no | |
by_day[].attempted_pct | number | no | Sobre unique_accounts. |
by_day[].reached_pct | number | no | Sobre unique_accounts. |
by_day[].effective_contact_pct | number | no | Sobre unique_accounts. |
json
{
"totals": {
"campaign_id": null,
"unique_accounts": 4,
"unique_consumers": 4,
"attempted": 2,
"reached": 1,
"effective_contact": 1,
"attempted_pct": 50,
"reached_pct": 25,
"effective_contact_pct": 25
},
"by_campaign": [
{
"campaign_id": "3f2c1b0a-9d8e-4c7b-a6f5-4e3d2c1b0a99",
"unique_accounts": 4,
"unique_consumers": 4,
"attempted": 2,
"reached": 1,
"effective_contact": 1,
"attempted_pct": 50,
"reached_pct": 25,
"effective_contact_pct": 25
}
]
}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"
}
}422
granularity=day sin tz, o con un tz que no es una zona horaria IANA; o window desconocida o combinada con from/to o granularity=day.
json
{
"error": {
"code": "unprocessable",
"message": "tz is required with granularity=day"
}
}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"
}
}Informe duradero de abandono de todas las campañas, en una sola respuesta
http
GET /v1/stats/abandonmentEl mismo informe que da GET /v1/campaigns/{id}/abandonment, para TODAS las campañas del tenant que llama a la vez (la consola solía preguntar una vez por campaña). Servido desde una instantánea en memoria de corta vida derivada del CDR solo anexo (no se escribe nada al marcar): as_of es el instante en que se calcularon las cifras, y una instantánea más antigua que el max_age del servidor (por defecto 60 s) no se sirve nunca; el informe se calcula en vivo. La ruta por campaña sigue siendo en vivo y es el registro; esta es la vista general.
Respuestas
200
Un AbandonmentReport por campaña, ordenado por nombre de campaña.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
as_of | string (date-time) | sí | |
campaigns | arreglo de AbandonmentReport | sí | |
campaigns[].campaign_id | string (uuid) | sí | |
campaigns[].measured_at | string (date-time) | sí | |
campaigns[].period_days | 30 | sí | |
campaigns[].ceiling_percent | 3 | sí | |
campaigns[].anchored_at | string (date-time) | null | sí | La primera marcación realizada de la campaña; null = aún sin marcaciones (y sin período). |
campaigns[].predicate | object | sí | |
campaigns[].predicate.counts | arreglo de no_agent · ai_stream_lost · agent_bridge_failed · handoff_failed · late_bridge | sí | Los motivos por los que la tasa cuenta como abandonada. |
campaigns[].predicate.reported_apart | arreglo de no_agent · ai_stream_lost · agent_bridge_failed · handoff_failed · late_bridge | sí | Motivos informados por período y que NO se cuentan en la tasa. |
campaigns[].periods | arreglo de AbandonmentPeriod | sí | Una entrada por período de 30 días con al menos una marcación realizada, de la más antigua a la más reciente. |
campaigns[].periods[].index | integer | sí | [anchored_at + 30·index days, + 30 days). |
campaigns[].periods[].starts_at | string (date-time) | sí | |
campaigns[].periods[].ends_at | string (date-time) | sí | Exclusivo. |
campaigns[].periods[].complete | boolean | sí | ends_at no es posterior a measured_at. |
campaigns[].periods[].placed | integer | sí | |
campaigns[].periods[].answered | integer | sí | |
campaigns[].periods[].answered_machine | integer | sí | Contestada con un veredicto AMD machine. |
campaigns[].periods[].answered_live | integer | sí | El denominador: answered - answered_machine. |
campaigns[].periods[].abandoned | integer | sí | El numerador; la suma de abandoned_by_reason. |
campaigns[].periods[].abandoned_by_reason | object | sí | Una clave por cada motivo de predicate.counts. Los lectores no deben asumir un conjunto cerrado de claves: agent_bridge_failed se añadió en core#1383. |
campaigns[].periods[].handoff_failed | integer | sí | Traspasos de IA de voz sin puesto disponible; no entran en la tasa. |
campaigns[].periods[].late_bridge | integer | sí | Respuestas en vivo no contadas como abandonadas cuya pierna del consumidor se puenteó más de 2 s después de answered_at (core#1426); no entran en la tasa. |
campaigns[].periods[].rate | number | null | sí | abandoned / answered_live, 4 decimales; null si no hay respuesta en vivo. |
campaigns[].periods[].exceeds_ceiling | boolean | sí | abandoned * 100 > 3 * answered_live. |
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"
}
}Resumen duradero de marcaciones bloqueadas
http
GET /v1/stats/blockedLee la evidencia duradera y SOLO ANEXA de gate_blocks: una fila por (deuda, compuerta, campaña, día UTC) que el motor de cumplimiento SE NEGÓ a marcar. Cada cifra es un conteo de rechazos distintos, nunca una estimación. Por defecto: los últimos 30 días UTC.
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
from | query | string (date-time) | no | Límite inferior RFC 3339 (campo específico del endpoint; por defecto los últimos 30 días UTC donde se indica). |
to | query | string (date-time) | no | Límite superior RFC 3339. |
campaign_id | query | string (uuid) | no |
Respuestas
200
Resumen de marcaciones bloqueadas.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
from | string (date-time) | no | |
to | string (date-time) | no | |
campaign_id | string (uuid) | null | no | |
total | integer | no | |
by_gate | object | no | Las claves son el vocabulario de ComplianceGate. |
daily | arreglo de object | no | |
daily[].date | string (date) | no | |
daily[].total | integer | no | |
daily[].by_gate | object | no |
json
{
"from": "2026-06-05T00:00:00Z",
"to": "2026-07-05T00:00:00Z",
"campaign_id": null,
"total": 5,
"by_gate": {
"quiet_hours": 4,
"dnc_listed": 1
},
"daily": [
{
"date": "2026-06-01",
"total": 3,
"by_gate": {
"quiet_hours": 2,
"dnc_listed": 1
}
}
]
}400 — BadRequest
Petición mal formada (errores de forma/tipo, filtros no válidos, marcas de tiempo erróneas).
json
{
"error": {
"code": "bad_request",
"message": "from must be RFC-3339"
}
}401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}429 — ApiKeyRateLimited
Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}Techos de capacidad del tenant + uso en vivo (ADR #12)
http
GET /v1/stats/capacityLos techos de capacidad fijados por el administrador del tenant (max_cps, max_concurrent_channels; null = sin techo) más el uso en vivo leído de la compuerta de capacidad. limits: null = los techos no se pudieron leer Y nunca se almacenaron en caché: justo la condición ante la que la compuerta falla CERRADA; se muestra con honestidad, nunca fabricado como «ilimitado».
supervision es un SEGUNDO techo, independiente (ADR #37): las piernas de supervisión de llamadas en vivo se originan fuera del punto de estrangulamiento de marcación, así que se cuentan en su propia cuota. Ninguno de los dos carriles puede agotar al otro: el piso de marcación puede estar en su techo mientras la supervisión aún admite, y viceversa.
Respuestas
200
Techos de capacidad + uso en vivo.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
limits | object | null | no | Techos fijados por el administrador, o null cuando la compuerta falla cerrada (techos ilegibles y nunca almacenados en caché). Cada techo es null cuando no está definido. |
channels_in_use | integer | no | |
dials_last_second | integer | no | |
supervision | object | no | El SEGUNDO techo (ADR #37): sesiones concurrentes de supervisión de llamadas en vivo, contadas y acotadas por separado del carril de marcación para que ninguno pueda dejar sin recursos al otro. limit es el max_supervision_channels de despliegue; in_use cuenta sesiones (llamada, supervisor), de modo que un cambio de listen → barge en una llamada cuenta una vez. |
supervision.limit | integer | no | |
supervision.in_use | integer | no |
json
{
"limits": {
"max_cps": 10,
"max_concurrent_channels": 50
},
"channels_in_use": 3,
"dials_last_second": 2,
"supervision": {
"limit": 20,
"in_use": 1
}
}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"
}
}Medición de minutos de IA de voz (lectura para facturación)
http
GET /v1/stats/ai-usageSobre el flujo duradero ai_usage de exactamente una vez (una fila por llamada de IA contestada, facturada EXACTAMENTE UNA VEZ por call_uuid). Cada fila guarda una instantánea de la tarifa por minuto con la que se midió: cambiar la tarifa configurada nunca revalora el historial. La ventana es semiabierta (from inclusive, to EXCLUSIVO), de modo que una llamada contestada justo en un corte de facturación pertenece a un solo periodo (el que comienza en ese instante), nunca a ambos lados del corte. ?format=csv (o Accept: text/csv) devuelve el artefacto de facturación con su propio X-Artifact-SHA256.
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
from | query | string (date-time) | no | Límite inferior RFC 3339 (campo específico del endpoint; por defecto los últimos 30 días UTC donde se indica). |
to | query | string (date-time) | no | Límite superior RFC 3339. |
campaign_id | query | string (uuid) | no | |
format | query | csv | no | csv para el artefacto descargable (equivalente: Accept: text/csv). |
Respuestas
200
Estadísticas de medición (JSON) o el artefacto de facturación en CSV.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
from | string (date-time) | no | |
to | string (date-time) | no | |
campaign_id | string (uuid) | null | no | |
minute_rate_usd | number | no | |
calls | integer | no | |
billable_seconds | integer | no | |
billable_minutes | number | no | |
estimated_cost_usd | number | no | |
daily | arreglo de object | no | |
daily[].date | string (date) | no | |
daily[].calls | integer | no | |
daily[].billable_seconds | integer | no | |
daily[].billable_minutes | number | no | |
daily[].minute_rate_usd | number | no | |
daily[].estimated_cost_usd | number | no |
json
{
"from": "2026-06-05T00:00:00Z",
"to": "2026-07-05T00:00:00Z",
"campaign_id": null,
"minute_rate_usd": 0.25,
"calls": 3,
"billable_seconds": 270,
"billable_minutes": 4.5,
"estimated_cost_usd": 1.13,
"daily": [
{
"date": "2026-06-01",
"calls": 2,
"billable_seconds": 180,
"billable_minutes": 3,
"minute_rate_usd": 0.25,
"estimated_cost_usd": 0.75
}
]
}400 — BadRequest
Petición mal formada (errores de forma/tipo, filtros no válidos, marcas de tiempo erróneas).
json
{
"error": {
"code": "bad_request",
"message": "from must be RFC-3339"
}
}401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}429 — ApiKeyRateLimited
Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}Medición de segmentos de SMS (lectura para facturación)
http
GET /v1/stats/sms-usageDirectamente del libro unificado: eventos facturables sms_segment, exactamente una vez al ACCEPT del proveedor, cada uno valorado con su instantánea de tarifa por región. JSON, o ?format=csv / Accept: text/csv para el artefacto de facturación.
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
from | query | string (date-time) | no | Límite inferior RFC 3339 (campo específico del endpoint; por defecto los últimos 30 días UTC donde se indica). |
to | query | string (date-time) | no | Límite superior RFC 3339. |
campaign_id | query | string (uuid) | no | |
format | query | csv | no | csv para el artefacto descargable (equivalente: Accept: text/csv). |
Respuestas
200
Estadísticas de medición (JSON) o el artefacto de facturación en CSV.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
from | string (date-time) | no | |
to | string (date-time) | no | |
campaign_id | string (uuid) | null | no | |
segment_rates_usd | object | no | Tarifa por región vigente actualmente (la que fotografiarán los nuevos envíos). |
messages | integer | no | |
segments | string | no | Decimal como cadena. |
amount_usd | string | no | Decimal como cadena. |
daily | arreglo de object | no | |
daily[].date | string (date) | no | |
daily[].messages | integer | no | |
daily[].segments | string | no | |
daily[].segment_rate_usd | string | no | |
daily[].amount_usd | string | no |
json
{
"from": "2026-06-05T00:00:00Z",
"to": "2026-07-05T00:00:00Z",
"campaign_id": null,
"segment_rates_usd": {
"us": "0.0100",
"mx": "0.0300",
"eu": "0.0500"
},
"messages": 2,
"segments": "3",
"amount_usd": "0.03",
"daily": [
{
"date": "2026-07-01",
"messages": 2,
"segments": "3",
"segment_rate_usd": "0.0100",
"amount_usd": "0.03"
}
]
}400 — BadRequest
Petición mal formada (errores de forma/tipo, filtros no válidos, marcas de tiempo erróneas).
json
{
"error": {
"code": "bad_request",
"message": "from must be RFC-3339"
}
}401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}429 — ApiKeyRateLimited
Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}