Appearance
Constructor de reportes
English: Report builder.
El constructor de reportes responde preguntas sobre las llamadas que un tenant colocó, sobre un catálogo cerrado de dimensiones y métricas. Lo sirven tres rutas: GET /v1/report-catalog (el vocabulario), POST /v1/reports/run (un agregado, o una página de intentos) y /v1/report-definitions (consultas guardadas). Los nombres de campo, enums, límites y códigos de error de abajo están copiados del documento OpenAPI vendorizado (core v0.105.0, openapi/dialer-v1.yaml); la Referencia de la API generada de él es la autoridad sobre las formas.
Disponibilidad
| Capacidad | Operación de la spec |
|---|---|
| Leer el catálogo | getReportCatalog |
| Ejecutar un agregado o una página de detalle, JSON o CSV | runReport |
| Definiciones guardadas: listar, crear, leer, reemplazar, borrar | listReportDefinitions, createReportDefinition, getReportDefinition, replaceReportDefinition, deleteReportDefinition |
Ejecutar depende de la bandera del tenant report_builder_enabled: con la bandera apagada, POST /v1/reports/run responde 503 report_builder_disabled y no se lee nada (runReport). El catálogo sigue respondiendo con la bandera apagada (getReportCatalog) y las definiciones guardadas no dependen de ella (listReportDefinitions). No des por hecho que la bandera está encendida en tu tenant: llama a la ruta de ejecución y maneja el 503.
Credenciales: las rutas son de ámbito tenant, así que sirve una llave dd_ o un login del dashboard (runReport). Una sesión de soporte dds_ es solo GET y no puede ejecutar reportes.
El catálogo
bash
curl -s "$API/v1/report-catalog" -H "Authorization: Bearer $TOKEN"El cuerpo 200 es el schema ReportCatalog: fact (siempre call_attempts), version (sube cuando cambia una definición publicada), dimensions, metrics, detail_columns y limits.
- La unidad es el intento: el marcado original que la plataforma colocó más las correcciones anexadas.
dispositiones la de la última corrección que cierra el intento, ein_flightmientras no hay ninguna (getReportCatalog). - Dimensiones publicadas en core
v0.105.0:campaign_id,agent_id,disposition,source,trunk_kind,amd_verdict,ai_used,hangup_cause,day,week,month. - Métricas:
attempts,contacts,contact_rate_bp(puntos base),accounts_attempted,talk_seconds,ai_calls. Cada métrica tiene una sola definición, compartida con los contadores fijos deGET /v1/stats/summary. - Ninguna dimensión lleva datos personales:
piiesnullen todas. - Límites (el ejemplo de
getReportCatalog):max_window_days: 31,max_groups: 1000,max_dimensions: 4,max_filter_values: 100,max_detail_rows: 1000. Lee los valores vivos del catálogo en lugar de fijarlos en tu código.
Las listas de claves salen de lib/dialer/reports/catalog.ex en el tag v0.105.0 de core; toma GET /v1/report-catalog como fuente de verdad, porque una versión posterior puede añadir claves.
Ejecutar un agregado
bash
curl -s -X POST "$API/v1/reports/run" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{
"from": "2026-10-01T00:00:00Z",
"to": "2026-10-08T00:00:00Z",
"dimensions": ["campaign_id", "disposition"],
"metrics": ["attempts", "contacts", "contact_rate_bp"],
"filters": { "source": ["platform"] },
"zone": "UTC"
}'Reglas de runReport y del schema ReportRunRequest:
fromytoson marcas RFC 3339 obligatorias. La ventana es[from, to)sobrestarted_aty de 31 días como máximo.- Hasta 4
dimensions(ninguna da una sola fila de totales) y al menos una métrica. filtersasocia una dimensión filtrable con una lista no vacía de hasta 100 valores.zoneesUTCpor defecto; es un nombre IANAArea/Locationy fija el calendario deday,weekymonth.- Cualquier campo fuera de
from,to,dimensions,metrics,filtersyzone(por ejemplotenant_id) es422 unknown_field: el tenant es el de quien llama. contact_rate_bpson contactos entre intentos en puntos base, redondeado a la mitad hacia arriba, ynull(nunca0) cuando no hay intentos.
El cuerpo 200 es el schema ReportRunResult: fact, catalog_version, from, to, zone, columns (cada una con kind dimension o metric), row_count y rows.
CSV
Añade ?format=csv. Una cabecera Accept sola no basta (runReport). El CSV lleva una fila de cabecera con las claves de columna, neutraliza las fórmulas de hoja de cálculo y trae la cabecera X-Artifact-SHA256, el SHA-256 del cuerpo del CSV. Compárala con tu propio hash del cuerpo para detectar una descarga truncada o alterada:
bash
curl -s -D headers.txt -o report.csv -X POST "$API/v1/reports/run?format=csv" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"from":"2026-10-01T00:00:00Z","to":"2026-10-08T00:00:00Z","metrics":["attempts"]}'
grep -i '^x-artifact-sha256' headers.txt
sha256sum report.csvModo detalle: una fila por intento
"mode": "detail" responde una fila por intento colocado en lugar de un grupo (runReport). Acepta columns (de detail_columns del catálogo), filters, unmask, limit (por defecto y máximo 1000) y after.
json
{ "mode": "detail", "from": "2026-10-01T00:00:00Z", "to": "2026-10-02T00:00:00Z",
"columns": ["attempt_id", "to_number"], "limit": 500 }- La paginación es un keyset sobre
(started_at, id), del más antiguo al más reciente. La página traenext_cursor; envíalo comoafterpara la siguiente.next_cursoresnullen la última página. Unaftermal formado es400 invalid_cursor. dimensions,metricsyzoneson solo del agregado;columns,unmask,limityafterson solo del detalle. La clave del modo equivocado es422 field_not_for_mode.- La nota de texto libre
noteycompliance_snapshotno son columnas. - Con
?format=csv, cada fila lleva una columnaexport_cursor(la última es elafterde la página siguiente) y la respuesta traeX-Artifact-SHA256.
Columnas de transferencia
Dos columnas del detalle describen una llamada transferida. Están en core main (core#2403, ADR #297 de core), posteriores al tag v0.129.0, y allí la version del catálogo es 3 (@version 3 en lib/dialer/reports/catalog.ex); las definiciones están en ese mismo fichero, claves transfer_count y transfer_chain. Lee detail_columns del catálogo vivo para ver si tu despliegue las tiene.
transfer_count(entero): los traspasos de control de la llamada, las filascontrol_handoffaplicadas decall_control_actions.0en una llamada que nadie transfirió.transfer_chain(texto): quién tuvo la llamada, en orden: el agente con quien empezó (unassignedsi empezó sin ninguno) y luego el de cada traspaso, unidos por>;externaldonde el control salió de la población de agentes.nullsi nadie la transfirió. Los tramos con rol y duración sontransfer_chaindeGET /v1/calls/{call_id}(ver la guía de la API REST).
Ninguna columna lleva datos personales (pii es null). En CSV son columnas normales como las demás.
Enmascarado de teléfonos y unmask
Una columna cuyo pii es phone o identifier sale enmascarada por defecto: *** más los últimos cuatro caracteres, leídos de la máscara almacenada sin descifrar nada (getReportCatalog).
"unmask": true devuelve esas columnas completas. Solo lo permite una llave dd_ o un login ddu_ con el rol account_admin. En los demás casos (runReport):
| Quien llama | Respuesta |
|---|---|
Rol member | 403 role_forbidden (nunca una respuesta enmascarada en silencio) |
Sesión de soporte dds_ | 403 forbidden |
unmask sin ninguna columna PII en columns | 422 unmask_without_pii_column |
Cada página con unmask escribe una fila en una tabla de evidencia de solo anexar, en la misma transacción que la lectura: actor, la definición, las columnas, las columnas sin máscara y el número de filas, nunca los valores. Si esa fila no se puede escribir, no se devuelve nada. En CSV un número sin máscara sale detrás de la guarda de fórmulas (con prefijo '). El campo unmasked de la respuesta lista las columnas PII devueltas completas, y va vacío si salieron enmascaradas (ReportRunResult).
Definiciones guardadas
Una definición es una consulta sobre el catálogo (dimensions, metrics, filters), nunca su resultado: la ventana y la zona son parámetros de cada ejecución (listReportDefinitions).
| Ruta | Para qué |
|---|---|
GET /v1/report-definitions | Lista las definiciones del tenant, por nombre |
POST /v1/report-definitions | Guarda una; 201 con { "report_definition": {...} } |
GET /v1/report-definitions/{id} | Lee una |
PUT /v1/report-definitions/{id} | Reemplazo total de name, dimensions, metrics, filters |
DELETE /v1/report-definitions/{id} | Borra; 204 |
- El cuerpo se valida contra el catálogo igual que una ejecución en línea. Una clave que el catálogo no publica es
422y el mensaje la nombra. nameva de 1 a 120 caracteres y es único por tenant: un duplicado es409 name_taken.- El tenant y el autor son los de quien llama.
tenant_idocreated_byen el cuerpo es422 unknown_field.created_byesnullsi lo guardó una llavedd_. factycatalog_versionlos sella el servidor; unPUTrefrescacatalog_version.- Aislamiento por tenant. La tabla de definiciones tiene seguridad por filas con una política
tenant_isolationsobre el id del tenant, así que la definición de otro tenant es un404, no un403(runReport; migracióncreate_report_definitionsde corev0.105.0).
Ejecuta una definición guardada con definition_id en lugar de dimensions, metrics y filters:
json
{ "definition_id": "<uuid>", "from": "2026-10-01T00:00:00Z", "to": "2026-10-08T00:00:00Z" }from, to y zone son siempre los de la ejecución. Una ejecución por definition_id es siempre un agregado.
Errores
Los errores usan el sobre estándar { "error": { "code", "message" } }. Tal como los devuelve el contrato (runReport):
| Estado | code | Cuándo |
|---|---|---|
| 400 | window_too_large | La ventana pasa de 31 días. Un to que no es posterior a from, o un límite ausente o que no es RFC 3339, también es 400 |
| 400 | invalid_cursor | after no es un cursor válido |
| 422 | too_many_groups | La respuesta pasaría de 1000 grupos. La petición se rechaza entera, nunca se recorta en silencio |
| 422 | catalog_changed | Una definición guardada nombra una clave que el catálogo ya no publica. El mensaje la nombra y no se responde nada, ni un resultado parcial |
| 422 | definition_conflict | definition_id junto con dimensions, metrics o filters |
| 422 | invalid_definition_id | definition_id mal formado |
| 422 | unknown_field, unknown_dimension, unknown_metric, unknown_column, unknown_filter | Una clave fuera del vocabulario cerrado |
| 422 | invalid_dimensions, invalid_metrics, invalid_columns, invalid_filters, invalid_zone, invalid_mode, invalid_limit, invalid_unmask | Un límite de lista o un valor fuera de rango |
| 422 | field_not_for_mode | Una clave solo de detalle en un agregado, o al revés |
| 403 | role_forbidden | unmask: true desde un member |
| 404 | (no encontrado) | definition_id desconocido, o de otro tenant |
| 429 | rate_limited | Pasaste el freno por principal; respeta Retry-After |
| 503 | report_builder_disabled | La bandera del tenant está apagada |
| 503 | report_timeout | La lectura superó su tiempo límite de sentencia |
Ante catalog_changed, abre el catálogo, actualiza la definición guardada con un PUT que use claves vigentes y ejecuta de nuevo.