Appearance
Informes
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.
Artefactos de cumplimiento empaquetados.
Endpoints
| Método | Ruta | Resumen |
|---|---|---|
GET | /v1/reports/cdr | CDR como dialer.v1.CallDetailRecord en protojson canónico |
GET | /v1/report-catalog | Catálogo del generador de informes (dimensiones, métricas, límites) |
POST | /v1/reports/run | Ejecutar un agregado, o una página de intentos, del generador de informes |
GET | /v1/report-definitions | Las definiciones de informe guardadas del tenant |
POST | /v1/report-definitions | Guardar una definición de informe |
GET | /v1/report-definitions/{id} | Una definición de informe guardada |
PUT | /v1/report-definitions/{id} | Reemplazar una definición de informe |
DELETE | /v1/report-definitions/{id} | Eliminar una definición de informe |
GET | /v1/reports/attempts | Exportar el historial de intentos (Reg F) |
GET | /v1/reports/violations-prevented | Artefacto de cumplimiento de Violaciones Evitadas |
CDR como dialer.v1.CallDetailRecord en protojson canónico
http
GET /v1/reports/cdrLas filas de GET /v1/call_attempts — mismos filtros (campaign_id, debt_id, agent_id, disposition, from, to), mismo limit (por defecto 100, máx. 1000), mismo cursor opaco, mismo aislamiento de tenant — cada una proyectada sobre el mensaje dialer.v1.CallDetailRecord del repositorio contracts y serializada como protojson canónico: nombres de campo en lowerCamelCase, enums por nombre (DISPOSITION_ANSWERED_HUMAN), marcas de tiempo en RFC 3339 UTC, y todo campo en su valor por defecto de proto3 OMITIDO (una llamada nunca contestada no tiene answeredAt). La propia página sigue las mismas reglas: nextCursor está ausente en la última página. Los analizadores protojson generados de contracts decodifican cada elemento de callDetailRecords.
Una fila es un registro. El libro es solo anexo, así que un intento es su fila original más las correcciones anexadas a ella, y cada fila se proyecta por separado, exactamente como las pagina /v1/call_attempts; aquí no se colapsa un intento en un único registro final.
Solo se proyecta lo que /v1/call_attempts ya publica. Los campos del mensaje sin fuente allí (direction, mediaPath, carrier, carrierTrunkId, attestation, costWeight, fsNode, sipEdgeNode, originationSource, transferredToCallId) se omiten, y cell también. callId es el UUID del canal (call_uuid), o el id del intento cuando la fila nunca llegó a un canal. consumerId es el consumer_ref.
disposition filtra con la grafía REST (answered_human), como en /v1/call_attempts; el campo del registro lleva el nombre del enum. El vocabulario REST es abierto y el enum del contrato es cerrado: un código que el enum no lista (abandoned, ai_conversation, ...) deja disposition omitido (DISPOSITION_UNSPECIFIED) y el código sin procesar viaja en labels.disposition_code.
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
campaign_id | query | string (uuid) | no | |
debt_id | query | string (uuid) | no | |
agent_id | query | string (uuid) | no | |
disposition | query | string | no | Coincidencia exacta contra un vocabulario ABIERTO: call_attempts.disposition se escribe TEXTUALMENTE (20260725000003_create_disposition_engine.exs) y los valores del motor — answered_human, abandoned, delivered, ai_conversation, answered_machine, ai_stream_lost, busy, no_answer, canceled, rejected, failed — son un SUBCONJUNTO NOMBRADO de él, no su frontera: los resultados tipificados del agente o de la IA (promise_to_pay, wrong_number) y los marcadores de plataforma (not_placed) son igualmente válidos. Un código desconocido responde 200 con cero filas, nunca 400. Lo que SÍ es 400 es una FORMA mal formada: la cadena vacía, y el parámetro repetido o enviado en forma de arreglo. |
from | query | string (date-time) | no | Límite inferior RFC 3339 (campo específico del endpoint; por defecto los últimos 30 días UTC donde se indica). |
to | query | string (date-time) | no | Límite superior RFC 3339. |
limit | query | integer | no | Tamaño de página (por defecto 100, máximo 1000). |
cursor | query | string | no | Cursor keyset opaco del next_cursor de la página anterior. |
Respuestas
200
Una página de registros CDR.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
callDetailRecords | arreglo de CallDetailRecord | no | |
callDetailRecords[].callId | string | no | |
callDetailRecords[].tenantId | string | no | |
callDetailRecords[].debtId | string | no | |
callDetailRecords[].consumerId | string | no | |
callDetailRecords[].campaignId | string | no | |
callDetailRecords[].direction | DIRECTION_UNSPECIFIED · DIRECTION_OUTBOUND · DIRECTION_INBOUND · DIRECTION_INTERNAL | no | |
callDetailRecords[].fromNumber | string | no | |
callDetailRecords[].toNumber | string | no | |
callDetailRecords[].startedAt | string (date-time) | no | |
callDetailRecords[].answeredAt | string (date-time) | no | |
callDetailRecords[].endedAt | string (date-time) | no | |
callDetailRecords[].durationSeconds | integer (uint32) | no | |
callDetailRecords[].billsec | integer (uint32) | no | |
callDetailRecords[].disposition | DISPOSITION_UNSPECIFIED · DISPOSITION_ANSWERED_HUMAN · DISPOSITION_ANSWERED_MACHINE · DISPOSITION_BUSY · DISPOSITION_NO_ANSWER · DISPOSITION_FAILED · DISPOSITION_REJECTED · DISPOSITION_CANCELED · DISPOSITION_BLOCKED_COMPLIANCE · DISPOSITION_VOICEMAIL_DROP · DISPOSITION_TRANSFERRED | no | |
callDetailRecords[].hangupCause | string | no | |
callDetailRecords[].sipResponseCode | integer (uint32) | no | |
callDetailRecords[].recordingUri | string | no | |
callDetailRecords[].aiUsed | boolean | no | |
callDetailRecords[].mediaPath | MEDIA_PATH_UNSPECIFIED · MEDIA_PATH_RTPENGINE_KERNEL · MEDIA_PATH_FREESWITCH | no | |
callDetailRecords[].carrier | string | no | |
callDetailRecords[].carrierTrunkId | string | no | |
callDetailRecords[].attestation | ATTESTATION_UNSPECIFIED · ATTESTATION_A · ATTESTATION_B · ATTESTATION_C · ATTESTATION_NONE | no | |
callDetailRecords[].costWeight | number (double) | no | |
callDetailRecords[].cell | string | no | |
callDetailRecords[].fsNode | string | no | |
callDetailRecords[].sipEdgeNode | string | no | |
callDetailRecords[].agentId | string | no | |
callDetailRecords[].complianceDecisionId | string | no | |
callDetailRecords[].recordVersion | integer (uint32) | no | |
callDetailRecords[].labels | object | no | disposition_code lleva una disposición REST que el enum Disposition no lista. |
callDetailRecords[].originationSource | ORIGINATION_SOURCE_UNSPECIFIED · ORIGINATION_SOURCE_CAMPAIGN_AUTO · ORIGINATION_SOURCE_AGENT_PREVIEW · ORIGINATION_SOURCE_AGENT_MANUAL · ORIGINATION_SOURCE_API_CTI · ORIGINATION_SOURCE_AI · ORIGINATION_SOURCE_WEB_REQUEST | no | |
callDetailRecords[].transferredToCallId | string | no | |
nextCursor | string | no | Cursor opaco por conjunto de claves (keyset); ausente en la última página. |
json
{
"callDetailRecords": [
{
"callId": "6f0a1b2c-3d4e-5f6a-7b8c-9d0e1f2a3b4c",
"tenantId": "0d4f4f9e-1f2a-4b53-9d3c-8a5e2f7b1c10",
"debtId": "5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
"consumerId": "cms-consumer-9001",
"campaignId": "3f2c1b0a-9d8e-4c7b-a6f5-4e3d2c1b0a99",
"fromNumber": "+13125550199",
"toNumber": "+13125551001",
"startedAt": "2026-07-05T15:00:00Z",
"answeredAt": "2026-07-05T15:00:07.250Z",
"endedAt": "2026-07-05T15:02:31Z",
"durationSeconds": 151,
"billsec": 144,
"disposition": "DISPOSITION_ANSWERED_HUMAN",
"hangupCause": "NORMAL_CLEARING",
"recordVersion": 1,
"complianceDecisionId": "2c1d0e9f-8a7b-6c5d-4e3f-2a1b0c9d8e7f"
}
],
"nextCursor": "MjAyNi0wNy0wNVQxNTowMDowMFp8OGM3ZDZlNWY"
}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"
}
}Catálogo del generador de informes (dimensiones, métricas, límites)
http
GET /v1/report-catalogEl vocabulario CERRADO del generador de informes (core#1964, fase 1): las dimensiones y métricas que un tenant puede consultar sobre UN hecho, call_attempts, con los límites que aplica POST /v1/reports/run. Solo lectura; describe el vocabulario y no toca datos del tenant. Sigue respondiendo cuando el indicador report_builder_enabled del tenant está apagado.
La unidad es el INTENTO, no la fila: la marcación original que la plataforma realizó realmente más las correcciones anexadas a ella. disposition es la de la ÚLTIMA corrección que cierra el intento (in_flight mientras no hay ninguna). agent_id sigue la atribución por asignación (el agente del original, si no la última corrección que nombra a uno). Cada métrica tiene UNA definición, compartida con los contadores fijos de GET /v1/stats/summary.
pii es null en toda dimensión publicada: ninguna dimensión contiene datos personales (el número de teléfono y la nota de texto libre no son dimensiones).
detail_columns (core#1987) es el vocabulario de mode: detail: una fila por intento. Una columna cuyo pii es phone o identifier se ENMASCARA por defecto (*** + los últimos cuatro caracteres, leídos de la máscara almacenada sin descifrar nada); unmask: true en POST /v1/reports/run la devuelve completa, solo para una clave dd_ o un login con el rol account_admin, y deja una fila de evidencia. La note de texto libre y compliance_snapshot no son columnas.
Respuestas
200
El catálogo.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
fact | "call_attempts" | sí | |
version | integer | sí | Cambia cuando cambia una definición publicada. |
dimensions | arreglo de object | sí | |
dimensions[].key | string | sí | |
dimensions[].type | uuid · string · boolean · date | sí | |
dimensions[].pii | string | null | sí | null en toda dimensión publicada: ninguna contiene datos personales. |
dimensions[].description | string | sí | |
metrics | arreglo de object | sí | |
metrics[].key | string | sí | |
metrics[].unit | count · seconds · basis_points | sí | |
metrics[].description | string | sí | |
detail_columns | arreglo de object | sí | El vocabulario de mode: detail. pii phone o identifier significa enmascarado salvo que una solicitud autorizada indique unmask. |
detail_columns[].key | string | sí | |
detail_columns[].type | uuid · string · boolean · timestamp · integer | sí | |
detail_columns[].pii | phone · identifier | null | sí | |
detail_columns[].description | string | sí | |
limits | object | sí | |
limits.max_window_days | integer | sí | |
limits.max_groups | integer | sí | |
limits.max_dimensions | integer | sí | |
limits.max_filter_values | integer | sí | |
limits.max_detail_rows | integer | sí |
json
{
"fact": "call_attempts",
"version": 1,
"dimensions": [
{
"key": "campaign_id",
"type": "uuid",
"pii": null,
"description": "Campaign the attempt belongs to."
},
{
"key": "disposition",
"type": "string",
"pii": null,
"description": "Disposition of the last settling correction; in_flight while it has none."
},
{
"key": "day",
"type": "date",
"pii": null,
"description": "Local calendar day of started_at in the request zone."
}
],
"metrics": [
{
"key": "attempts",
"unit": "count",
"description": "Attempts the platform actually placed."
},
{
"key": "contact_rate_bp",
"unit": "basis_points",
"description": "Contacts over attempts in basis points; null when there are no attempts."
}
],
"detail_columns": [
{
"key": "attempt_id",
"type": "uuid",
"pii": null,
"description": "The attempt."
},
{
"key": "to_number",
"type": "string",
"pii": "phone",
"description": "Dialed number. Masked (***0184) unless an authorized request sets unmask."
}
],
"limits": {
"max_window_days": 31,
"max_groups": 1000,
"max_dimensions": 4,
"max_filter_values": 100,
"max_detail_rows": 1000
}
}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"
}
}Ejecutar un agregado, o una página de intentos, del generador de informes
http
POST /v1/reports/runEjecuta UN agregado sobre el catálogo cerrado (GET /v1/report-catalog) para el tenant de quien llama (alcance de tenant: una clave dd_ o un login del dashboard). El tenant es el del principal: un tenant_id en el cuerpo es 422 unknown_field, como cualquier otro campo fuera de from, to, dimensions, metrics, filters y zone.
from y to son marcas de tiempo RFC 3339 obligatorias; la ventana es [from, to) sobre started_at y de como máximo 31 días (400 window_too_large; también 400 cuando to no es posterior a from). Hasta 4 dimensions (ninguna da una sola fila de totales) y al menos una metric. filters acota por las dimensiones filtrables, cada una con una lista no vacía de como máximo 100 valores; agent_id coincide con un intento en el que CUALQUIERA de sus filas nombra al agente. zone (por defecto UTC) es un nombre IANA Area/Location y decide el calendario de las dimensiones day, week y month.
Una solicitud que respondería más de 1000 grupos se rechaza entera (422 too_many_groups), nunca se recorta en silencio. La lectura corre bajo un tiempo límite de sentencia (503 report_timeout). Por encima del freno por principal la respuesta es 429 rate_limited con Retry-After. Con el indicador report_builder_enabled del tenant apagado la respuesta es 503 report_builder_disabled y no se lee nada.
contact_rate_bp son los contactos sobre los intentos en puntos básicos, redondeado hacia arriba en el medio, y null (nunca 0) cuando no hay intentos. La respuesta es JSON salvo que la consulta pida ?format=csv (una cabecera Accept por sí sola no basta): el CSV tiene una fila de encabezado con las claves de columna, lleva X-Artifact-SHA256 y neutraliza las fórmulas de hoja de cálculo.
Modo detalle (core#1987). mode: detail responde una fila por intento realizado en lugar de un grupo, y recibe columns (de detail_columns del catálogo), filters, unmask, limit (por defecto y máximo 1000) y after (el next_cursor de la página anterior, un keyset sobre (started_at, id), del más antiguo al más reciente; uno mal formado es 400 invalid_cursor). dimensions, metrics y zone son solo del modo agregado y columns, unmask, limit y after son solo del modo detalle: la clave del modo equivocado es 422 field_not_for_mode. La ventana y el freno son los del agregado.
Las columnas de PII (pii: phone o identifier) se enmascaran por defecto a partir de las columnas almacenadas *_last4, SIN descifrar. unmask: true las devuelve completas y se permite para una clave dd_ o un login ddu_ con el rol account_admin; un member recibe 403 role_forbidden (nunca una respuesta enmascarada en silencio) y una sesión de soporte dds_ recibe 403 forbidden (su lista de permitidos es solo GET). unmask sin una columna de PII en columns es 422 unmask_without_pii_column. Cada página sin enmascarar escribe UNA fila en la tabla de evidencia solo anexa en la MISMA transacción que la lectura (actor, la definición, las columnas, las columnas sin enmascarar, el número de filas; nunca los valores); si esa fila no se puede escribir, no se devuelve nada. Con ?format=csv la página lleva una columna export_cursor en cada fila (la última es el after de la página siguiente) y X-Artifact-SHA256; un número sin enmascarar sale como '+1312... tras la protección de fórmulas.
En lugar de dimensions, metrics y filters el cuerpo puede llevar definition_id: una definición guardada del tenant (/v1/report-definitions). Ambos a la vez es 422 definition_conflict; un id mal formado es 422 invalid_definition_id; un id desconocido o de otro tenant es 404. Las claves guardadas se comprueban contra el catálogo ACTUAL en cada ejecución: una que el catálogo ya no publica es 422 catalog_changed que la nombra, y no se responde nada, ni siquiera un resultado parcial. from, to y zone son siempre los de la propia ejecución. Una ejecución por definition_id es siempre un agregado: mode: detail con ella es 422 field_not_for_mode.
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
format | query | csv | no | csv responde con un artefacto CSV; cualquier otro valor responde JSON. |
Cuerpo de la petición (JSON, obligatorio)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
definition_id | string (uuid) | no | Una definición guardada del tenant (/v1/report-definitions); sus claves se vuelven a validar contra el catálogo vigente (422 catalog_changed). |
mode | aggregate · detail | no | aggregate requiere metrics; detail requiere columns. |
columns | arreglo de string | no | Solo en modo detalle. Claves de detail_columns del catálogo. |
unmask | boolean | no | Solo en modo detalle. Devuelve completas las columnas con PII; solo dd_ o account_admin (en otro caso 403 role_forbidden) y queda auditado. |
limit | integer | no | Solo en modo detalle. Filas por página; si supera el máximo, se limita al máximo. |
after | string | no | Solo en modo detalle. El next_cursor de la página anterior. |
from | string (date-time) | sí | Límite inferior inclusivo sobre started_at. |
to | string (date-time) | sí | Límite superior exclusivo; como máximo 31 días después de from. |
dimensions | arreglo de string | no | Claves del catálogo; si no se indica ninguna, responde una sola fila de totales. |
metrics | arreglo de string | no | Claves del catálogo. |
filters | object | no | Clave de dimensión hacia una lista no vacía (máximo 100) de valores; solo dimensiones filtrables. |
zone | string | no | UTC o un nombre IANA Area/Location; el calendario de day, week y month. |
json
{
"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"
}Respuestas
200
El agregado, o una página del detalle.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
fact | "call_attempts" | sí | |
catalog_version | integer | sí | |
mode | detail | no | Presente (y detail) solo en una página de detalle. |
from | string (date-time) | sí | |
to | string (date-time) | sí | |
zone | string | no | Solo en agregado. |
unmasked | arreglo de string | no | Solo en detalle. Las columnas con PII devueltas completas (vacío cuando están enmascaradas). |
next_cursor | string | null | no | Solo en detalle. El after de la página siguiente; null en la última. |
columns | arreglo de object | sí | |
columns[].key | string | sí | |
columns[].kind | dimension · metric | no | Solo en agregado. |
columns[].type | string | sí | |
columns[].pii | string | null | no | Solo en detalle. |
row_count | integer | sí | |
rows | arreglo de object | sí | Un objeto por grupo, indexado por las claves de columna. Una métrica indefinida (contact_rate_bp sin intentos) es null. |
json
{
"fact": "call_attempts",
"catalog_version": 1,
"from": "2026-10-01T00:00:00Z",
"to": "2026-10-08T00:00:00Z",
"zone": "UTC",
"columns": [
{
"key": "disposition",
"kind": "dimension",
"type": "string"
},
{
"key": "attempts",
"kind": "metric",
"type": "count"
},
{
"key": "contact_rate_bp",
"kind": "metric",
"type": "basis_points"
}
],
"row_count": 2,
"rows": [
{
"disposition": "answered_human",
"attempts": 40,
"contact_rate_bp": 10000
},
{
"disposition": "no_answer",
"attempts": 60,
"contact_rate_bp": 0
}
]
}400
El cuerpo no es un objeto JSON, from/to faltan o no son RFC 3339, to no es posterior a from, la ventana supera los 31 días (window_too_large), o after no es un cursor válido (invalid_cursor).
json
{
"error": {
"code": "window_too_large",
"message": "the window may not exceed 31 days"
}
}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: unmask: true desde un member. Una sesión de soporte dds_ es rechazada (forbidden) por su lista permitida de solo GET. No se leyó nada y no existe fila de evidencia.
json
{
"error": {
"code": "role_forbidden",
"message": "unmask takes a dd_ key or a dashboard login with the account_admin role"
}
}404 — NotFound
Id desconocido, mal formado (no UUID) O id de otro tenant: RLS devuelve cero filas, así que los tres son indistinguibles por diseño (sin fuga de existencia, nunca un 403 para ids ajenos).
json
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}422
Algo fuera del vocabulario cerrado o de un límite de lista: unknown_field, unknown_dimension, unknown_metric, unknown_column, unknown_filter, invalid_dimensions, invalid_metrics, invalid_columns, invalid_filters, invalid_zone, invalid_mode, invalid_limit, invalid_unmask, unmask_without_pii_column, field_not_for_mode, o too_many_groups cuando la respuesta superaría los 1000 grupos. Con definition_id: definition_conflict, invalid_definition_id o catalog_changed (la definición guardada nombra una clave que el catálogo ya no publica; el mensaje la nombra).
json
{
"error": {
"code": "unknown_metric",
"message": "unknown metric(s): revenue; published: attempts, contacts"
}
}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
El generador de informes está desactivado para este tenant (report_builder_disabled), o la lectura superó su tiempo límite de sentencia (report_timeout).
json
{
"error": {
"code": "report_builder_disabled",
"message": "the report builder is not enabled for this tenant"
}
}Las definiciones de informe guardadas del tenant
http
GET /v1/report-definitionsTodas las definiciones guardadas del tenant del solicitante (alcance de tenant: una clave dd_ o un inicio de sesión del dashboard), por name. Una definición es una CONSULTA sobre el catálogo cerrado (dimensions, metrics, filters), nunca su resultado: la ventana y la zona son parámetros de cada POST /v1/reports/run. No depende del indicador report_builder_enabled del tenant: solo la ejecución.
Respuestas
200
Las definiciones.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
report_definitions | arreglo de ReportDefinition | sí | |
report_definitions[].id | string (uuid) | sí | |
report_definitions[].name | string | sí | |
report_definitions[].fact | "call_attempts" | sí | |
report_definitions[].catalog_version | integer | sí | La versión del catálogo contra la que se guardó por última vez la definición. |
report_definitions[].dimensions | arreglo de string | sí | |
report_definitions[].metrics | arreglo de string | sí | |
report_definitions[].filters | object | sí | |
report_definitions[].created_by | string (uuid) | null | sí | El usuario que la guardó; null cuando lo hizo una clave dd_. |
report_definitions[].created_at | string (date-time) | sí | |
report_definitions[].updated_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"
}
}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"
}
}Guardar una definición de informe
http
POST /v1/report-definitionsValida el cuerpo contra el catálogo cerrado exactamente igual que un POST /v1/reports/run en línea: una clave que el catálogo no publica es 422 (unknown_dimension, unknown_metric, unknown_filter, ...) y el mensaje la nombra. El tenant y el autor son los del principal: un tenant_id o created_by en el cuerpo es 422 unknown_field. created_by es el usuario de un inicio de sesión del dashboard y null para una clave dd_. name es único por tenant (409 name_taken). fact y catalog_version los fija el servidor.
Cuerpo de la petición (JSON, obligatorio)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
name | string | sí | Único dentro del tenant; se recortan los espacios en los extremos. |
dimensions | arreglo de string | no | Claves del catálogo. |
metrics | arreglo de string | sí | Claves del catálogo. |
filters | object | no | Clave de dimensión hacia una lista no vacía (máximo 100) de valores; solo dimensiones filtrables. |
json
{
"name": "Contactabilidad por campaña",
"dimensions": [
"campaign_id"
],
"metrics": [
"attempts",
"contacts",
"contact_rate_bp"
],
"filters": {
"source": [
"platform"
]
}
}Respuestas
201
La definición guardada.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
report_definition | object (ReportDefinition) | sí | |
report_definition.id | string (uuid) | sí | |
report_definition.name | string | sí | |
report_definition.fact | "call_attempts" | sí | |
report_definition.catalog_version | integer | sí | La versión del catálogo contra la que se guardó por última vez la definición. |
report_definition.dimensions | arreglo de string | sí | |
report_definition.metrics | arreglo de string | sí | |
report_definition.filters | object | sí | |
report_definition.created_by | string (uuid) | null | sí | El usuario que la guardó; null cuando lo hizo una clave dd_. |
report_definition.created_at | string (date-time) | sí | |
report_definition.updated_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"
}
}409
Otra definición de este tenant ya tiene ese nombre (name_taken).
json
{
"error": {
"code": "name_taken",
"message": "a report definition with this name already exists 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"
}
}Una definición de informe guardada
http
GET /v1/report-definitions/{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
La definición.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
report_definition | object (ReportDefinition) | sí | |
report_definition.id | string (uuid) | sí | |
report_definition.name | string | sí | |
report_definition.fact | "call_attempts" | sí | |
report_definition.catalog_version | integer | sí | La versión del catálogo contra la que se guardó por última vez la definición. |
report_definition.dimensions | arreglo de string | sí | |
report_definition.metrics | arreglo de string | sí | |
report_definition.filters | object | sí | |
report_definition.created_by | string (uuid) | null | sí | El usuario que la guardó; null cuando lo hizo una clave dd_. |
report_definition.created_at | string (date-time) | sí | |
report_definition.updated_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"
}
}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 una definición de informe
http
PUT /v1/report-definitions/{id}Reemplazo completo de name, dimensions, metrics y filters, validado igual que la creación; catalog_version se actualiza al catálogo vigente. id, created_by y created_at nunca cambian.
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 | sí | Único dentro del tenant; se recortan los espacios en los extremos. |
dimensions | arreglo de string | no | Claves del catálogo. |
metrics | arreglo de string | sí | Claves del catálogo. |
filters | object | no | Clave de dimensión hacia una lista no vacía (máximo 100) de valores; solo dimensiones filtrables. |
Respuestas
200
La definición reemplazada.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
report_definition | object (ReportDefinition) | sí | |
report_definition.id | string (uuid) | sí | |
report_definition.name | string | sí | |
report_definition.fact | "call_attempts" | sí | |
report_definition.catalog_version | integer | sí | La versión del catálogo contra la que se guardó por última vez la definición. |
report_definition.dimensions | arreglo de string | sí | |
report_definition.metrics | arreglo de string | sí | |
report_definition.filters | object | sí | |
report_definition.created_by | string (uuid) | null | sí | El usuario que la guardó; null cuando lo hizo una clave dd_. |
report_definition.created_at | string (date-time) | sí | |
report_definition.updated_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"
}
}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
Otra definición de este tenant ya tiene ese nombre (name_taken).
json
{
"error": {
"code": "name_taken",
"message": "a report definition with this name already exists 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"
}
}Eliminar una definición de informe
http
DELETE /v1/report-definitions/{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
204
Eliminado.
401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — Forbidden
El tenant está suspendido. Una sesión del dashboard (ddu_) de un tenant cuya política de inicio de sesión (ADR 227) está en modo enforce responde 403 con el código login_policy_denied ante una solicitud desde una dirección o en una hora que la política no permite — el mismo cuerpo en ambos casos; la sesión no se revoca y una solicitud posterior dentro de la política se procesa. Un token de widget (ddw_) de un puesto queda sujeto a la misma política en cada solicitud (core#2406, indicador widget_login_honors_policy). Un tenant sintético (demo) responde 403 con el código synthetic_tenant en toda ruta que llegaría a un tercero — marcación manual, pierna de transferencia externa o de conferencia, envío de SMS (core#2251, adenda del ADR #280). Ningún pago lo cambia.
json
{
"error": {
"code": "forbidden",
"message": "tenant is suspended"
}
}404 — NotFound
Id desconocido, mal formado (no UUID) O id de otro tenant: RLS devuelve cero filas, así que los tres son indistinguibles por diseño (sin fuga de existencia, nunca un 403 para ids ajenos).
json
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}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"
}
}Exportar el historial de intentos (Reg F)
http
GET /v1/reports/attemptsExportación CSV paginada por keyset del libro de intentos para el ciclo de ida y vuelta con el CMS (MT-REP-05 FASE 2). Una fila por intento con su disposición FINAL: el marcado original colapsado con cada corrección añadida a él, prevalece la fila escrita más recientemente. call_id es siempre el id del intento ORIGINAL, de modo que una corrección que llegue entre dos exportaciones no reidentifica el intento. source (platform | cms_import) va en cada fila para que el CMS pueda distinguir lo que declaró de lo que marcó la plataforma.
Columnas: debt_key, attempt_at (UTC RFC 3339), channel (voice), source, outcome, agent_id (empty when none), call_id, export_cursor, disclosure_status, disclosure_script_match. disclosure_status es lo que el agente DECLARÓ sobre el aviso de cobranza (attested | declared_not_delivered | unattested | not_applicable | undecidable, véase CallAttemptDisclosure), derivado mediante join como el resto de la fila: una declaración, nunca «verificada». disclosure_script_match (core#1691, ADR #219) es matched | other_version | unscripted | not_applicable, igual que CallAttemptDisclosure.script_match.
from y to son obligatorios y DEBEN distar como máximo 31 días (400 en caso contrario). Se deriva del libro en cada llamada: no se materializa nada. Las filas se ordenan por attempt_at descendente y luego por call_id; se continúa con ?after=<export_cursor of the last row>. Un cursor mal formado es 400, nunca un reinicio silencioso desde la primera página. format=csv (o Accept: text/csv) es obligatorio: sin él, 400.
campaign_id (uuid) y disposition acotan la exportación igual que acotan GET /v1/call_attempts; un campaign_id mal formado es 400, nunca se ignora. Las columnas no cambian. Un export_cursor solo es válido con los mismos filtros con los que se produjo: continúa una exportación filtrada con el mismo campaign_id/disposition, y nunca lleves su cursor a una exportación sin filtrar.
PII (core#2018, ADR #240). La exportación va redactada por defecto: una debt_key que contiene un número de tarjeta de pago, un SSN o un número de cuenta bancaria sale enmascarada (****-****-****-1234, ***-**-1234, ****1234), conservando como máximo los últimos cuatro dígitos. Un CMS que concilia por la clave original pide include_pii=true, que exige una clave dd_ o un inicio de sesión del dashboard con el rol account_admin (en otro caso 403 role_forbidden, nunca un archivo redactado en silencio) y queda auditado.
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
include_pii | query | boolean | no | true devuelve el archivo SIN redacción de PII. Requiere una clave dd_ o un inicio de sesión account_admin: 403 role_forbidden para cualquier otro. Ausente o false ⇒ redactado. Cualquier valor que no sea booleano ⇒ 400. |
from | query | string (date-time) | sí | Límite inferior RFC 3339 sobre attempt_at (inclusivo). |
to | query | string (date-time) | sí | Límite superior RFC 3339 sobre attempt_at (inclusivo); como máximo 31 días después de from. |
campaign_id | query | string (uuid) | no | |
disposition | query | string | no | Coincidencia exacta con la disposición FINAL del intento, el valor que el CSV imprime en outcome (una disposición anterior reemplazada no coincide). Mismo vocabulario y mismos 400 que DispositionFilter en GET /v1/call_attempts: forma vacía, repetida o de arreglo ⇒ 400; un código desconocido ⇒ 200 solo con la fila de encabezado. |
limit | query | integer | no | Tamaño de página (filas por respuesta); los valores por encima del máximo se limitan al máximo. |
after | query | string | no | Cursor keyset opaco: el export_cursor de la última fila de la página anterior. Mal formado ⇒ 400. |
format | query | csv | no | csv para el artefacto descargable (equivalente: Accept: text/csv). |
Respuestas
200
Artefacto CSV
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"
}
}Artefacto de cumplimiento de Violaciones Evitadas
http
GET /v1/reports/violations-preventedInforme de cumplimiento empaquetado sobre la evidencia append-only de gate_blocks. by_campaign ordenado por cantidad de bloqueos (desc); samples son las ≤3 filas de evidencia más recientes POR compuerta, siempre enmascaradas (solo los últimos 4: el CHECK de la base de datos rechaza teléfonos sin enmascarar al escribir). ?format=csv / Accept: text/csv genera el CSV descargable (Content-Disposition: attachment) con X-Artifact-SHA256 para la cadena de custodia.
Unidad de cada conteo de este informe — totals.unit, más las filas unit / unit_description del preámbulo del CSV — es deduplicated_gate_block_days: una fila de evidencia deduplicada por unidad, NO una llamada bloqueada. El informe cuenta DOS clases de fila y se deduplican con claves DISTINTAS: un rechazo de campaña cuenta una vez por (tenant, debt, gate, campaign, UTC day); un rechazo ad hoc (POST /v1/sms, sin deuda ni campaña) cuenta una vez por (tenant, gate, masked last-4, UTC day), lo que además colapsa dos números distintos que comparten sus últimos cuatro dígitos (ADR #8; migración 20260728000002). Por tanto, la cifra principal SUBESTIMA los intentos bloqueados.
Filtros de auditoría (campaign_id, debt_id, phone) acotan TODO el informe — totales, by_campaign, policy_versions y samples — y se reflejan en filters (y como filas filter_* del preámbulo del CSV) para que un artefacto filtrado no pueda pasar por el del tenant completo. Con debt_id o phone establecido, samples contiene hasta 25 filas por compuerta en lugar de 3. No hay filtro por agente: una compuerta rechaza un marcado ANTES de que exista cualquier agente, y gate_blocks no tiene columna de agente.
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 | |
debt_id | query | string (uuid) | no | |
phone | query | string | no | Número E.164 (+ codificado como %2B); cualquier otra cosa es un 400. La tabla solo conserva la forma enmascarada, así que el filtro coincide con los ÚLTIMOS CUATRO dígitos: dos números que los comparten son indistinguibles. El número en sí nunca se devuelve (sí filters.phone_last4). |
format | query | csv | no | csv para el artefacto descargable (equivalente: Accept: text/csv). |
Respuestas
200
El informe (JSON) o el artefacto CSV.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
report | "violations_prevented" | no | |
tenant_id | string (uuid) | no | |
generated_at | string (date-time) | no | |
period | object | no | |
period.from | string (date-time) | no | |
period.to | string (date-time) | no | |
filters | object | no | Los filtros de auditoría aplicados (null = no aplicado). |
filters.campaign_id | string (uuid) | null | no | |
filters.debt_id | string (uuid) | null | no | |
filters.phone_last4 | string | null | no | |
totals | object | no | |
totals.total | integer | no | |
totals.unit | "deduplicated_gate_block_days" | no | La unidad de total, by_gate y cada conteo de by_campaign. |
totals.unit_description | string | no | Glosa legible de unit, servida para que el artefacto dirigido al cliente declare qué significa su cifra principal: una fila de evidencia deduplicada por unidad, NO una llamada bloqueada. Un rechazo de campaña cuenta una vez por (tenant, debt, gate, campaign, UTC day); un rechazo ad hoc (POST /v1/sms, sin deuda ni campaña) cuenta una vez por (tenant, gate, masked last-4, UTC day), lo que además colapsa dos números distintos que comparten sus últimos cuatro dígitos (ADR #8; migración 20260728000002). |
totals.by_gate | object | no | |
by_campaign | arreglo de object | no | Ordenado por cantidad de bloqueos (desc); misma unidad que totals.unit. |
by_campaign[].campaign_id | string (uuid) | no | |
by_campaign[].campaign_name | string | no | |
by_campaign[].total | integer | no | |
by_campaign[].by_gate | object | no | |
policy_versions | arreglo de string | no | |
samples | arreglo de object | no | ≤3 filas de evidencia más recientes POR compuerta (≤25 cuando se filtra por debt_id o phone), siempre enmascaradas (solo los últimos 4). |
samples[].occurred_at | string (date-time) | no | |
samples[].gate | tcpa_no_consent · quiet_hours · regf_7in7 · regf_post_contact · state_limit · dnc_listed · consent_revoked · cease_and_desist · number_reassigned · tenant_policy · artificial_voice_no_consent | no | El vocabulario de códigos de motivo del motor de cumplimiento. |
samples[].rule_id | string | no | |
samples[].campaign_id | string (uuid) | null | no | |
samples[].debt_id | string (uuid) | no | |
samples[].contact_phone_masked | string | no | |
samples[].policy_version | string | no |
json
{
"report": "violations_prevented",
"tenant_id": "0d4f4f9e-1f2a-4b53-9d3c-8a5e2f7b1c10",
"generated_at": "2026-07-05T10:00:00Z",
"period": {
"from": "2026-06-05T00:00:00Z",
"to": "2026-07-05T00:00:00Z"
},
"totals": {
"total": 5,
"unit": "deduplicated_gate_block_days",
"unit_description": "one deduplicated evidence row per unit, NOT one blocked call: a campaign refusal counts once per (tenant, debt, gate, campaign, UTC day); an ad-hoc refusal (POST /v1/sms, no debt and no campaign) counts once per (tenant, gate, masked last-4, UTC day), which additionally collapses two different numbers sharing their last four digits (ADR #8; migration 20260728000002)",
"by_gate": {
"quiet_hours": 4,
"dnc_listed": 1
}
},
"by_campaign": [
{
"campaign_id": "3f2c1b0a-9d8e-4c7b-a6f5-4e3d2c1b0a99",
"campaign_name": "postman-golden",
"total": 5,
"by_gate": {
"quiet_hours": 4,
"dnc_listed": 1
}
}
],
"policy_versions": [
"usa-cell.v1"
],
"samples": [
{
"occurred_at": "2026-07-01T02:10:00Z",
"gate": "quiet_hours",
"rule_id": "usa-cell.quiet-hours",
"campaign_id": "3f2c1b0a-9d8e-4c7b-a6f5-4e3d2c1b0a99",
"debt_id": "5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
"contact_phone_masked": "***1001",
"policy_version": "usa-cell.v1"
}
]
}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"
}
}