Skip to content

Cumplimiento ​

Generado desde DialerDigital/core commit 0fc18008b3c8 — especificación openapi/dialer-v1.yaml v0.134.0. No se edita a mano; consulta Cómo se construye esta referencia. Descargar la especificación OpenAPI.

English version

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.

Datos de supresión que posee el tenant (su lista interna de no llamar, subida como CSV a la misma lista que lee la compuerta previa al marcado) y la cita de la cadena de auditoría: resolver un decision_id en su eslabón sellado más el veredicto de integridad sobre él.

Endpoints ​

MétodoRutaResumen
POST/v1/suppressions/importSubir la propia lista de no llamar del tenant (CSV)
GET/v1/compliance/decisions/{decision_id}Resolver y verificar un decision_id en la cadena de auditoría
GET/v1/compliance/sourcesEstado de las fuentes de cumplimiento (listas DNC, registros estatales, RND)
GET/v1/artificial-voice-disclosureObtener la divulgación de voz artificial NO IA del tenant
PUT/v1/artificial-voice-disclosureAlmacenar la divulgación de voz artificial NO IA del tenant

Subir la propia lista de no llamar del tenant (CSV) ​

http
POST /v1/suppressions/import

Carga la lista de supresión INTERNA del tenant desde un cuerpo text/csv.

Las filas se unen a la lista DNC internal_tenant que la compuerta de cumplimiento PREVIA AL MARCADO ya consulta en cada llamada y cada SMS, la misma lista en la que escribe el STOP de SMS. No hay una segunda vía de supresión ni nada que activar: un número aceptado queda suprimido para el siguiente intento.

Formato: una fila por número, number[,reason]. Una fila de cabecera (number, phone, phone_number, telephone, e164, msisdn) es opcional; se omiten las líneas en blanco y las que empiezan por #. Los números se normalizan igual que los volcados regulatorios: se conserva E.164, un número NANP de 10 dígitos o de 1+10 dígitos sin más se convierte en +1XXXXXXXXXX.

Las filas mal formadas se REPORTAN, no son fatales: rechazar un fichero entero por una errata dejaría marcables al resto de los consumidores, que es la dirección cara del error. Los valores rechazados vuelven ENMASCARADOS: el número de línea es lo que identifica la fila a corregir.

Volver a subir una lista es idempotente (recorded cuenta las filas realmente escritas, así que un solapamiento se lee como solapamiento y no como supresiones nuevas).

Límites: 1 000 000 bytes de cuerpo, 10 000 filas de datos, 200 caracteres de reason y como máximo 100 entradas en rejected (rejected_count lleva siempre el total real).

Respuestas

200 ​

Resultado de la subida. Un 200 NO significa que se aceptaron todas las filas: rejected_count es la referencia.

CampoTipoObligatorioDescripción
receivedintegersíFilas de datos que ofreció el documento (excluidas cabecera y líneas en blanco).
acceptedintegersíFilas que se normalizaron a un número E.164 distinto.
recordedintegersíFilas realmente escritas. Menor que accepted cuando la lista se solapa con supresiones que ya constan.
rejected_countintegersí
rejectedarreglo de objectsíHasta 100 entradas; consulta rejected_count para el total.
rejected[].lineintegersíNúmero de línea (base 1) en el documento subido.
rejected[].valuestringsíEl valor ofensivo, enmascarado a sus últimos 4 caracteres.
rejected[].reasonnot_e164 · duplicatesí
json
{
  "received": 3,
  "accepted": 2,
  "recorded": 2,
  "rejected_count": 1,
  "rejected": [
    {
      "line": 4,
      "value": "***5551",
      "reason": "not_e164"
    }
  ]
}

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"
  }
}

413 ​

El cuerpo CSV supera 1 000 000 bytes.

json
{
  "error": {
    "code": "payload_too_large",
    "message": "csv body exceeds 1000000 bytes"
  }
}

415 ​

El cuerpo de la petición no es text/csv.

json
{
  "error": {
    "code": "unsupported_media_type",
    "message": "this endpoint takes a text/csv body"
  }
}

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"
  }
}

Resolver y verificar un decision_id en la cadena de auditoría ​

http
GET /v1/compliance/decisions/{decision_id}

Una operación rechazada te entrega un decision_id (422 compliance_blocked). Esto resuelve esa cita en su eslabón sellado de la cadena de auditoría de cumplimiento, de solo anexado, junto con el veredicto de integridad sobre él, de modo que la afirmación es algo que puedes comprobar en lugar de algo que tienes que aceptar.

decision.payload es el JSON canónico que se hasheó, enviado tal cual: vuelve a ejecutar sha256(prev_hash || canonical_json(payload)) tú mismo y compáralo con verification.hash. canonical_json ordena las claves de objeto lexicográficamente a cualquier profundidad. Los tres hashes son sha256 en hexadecimal minúscula; genesis_hash es la raíz fija de toda cadena de tenant.

Tres booleanos, no uno, porque pueden estar mal tres cosas distintas:

  • payload_hash_matches: el payload es coherente con el hash almacenado JUNTO A ÉL, en la misma fila. Léelo literalmente: prueba la coherencia interna, NO la autenticidad. Un adversario con acceso a la base de datos puede reescribir payload y hash en una sola sentencia y ponerlo en verde.
  • chains_to_previous: sigue colgando del eslabón anterior (o del génesis, cuando seq es 1). Borrar el eslabón ANTERIOR deja este internamente intacto mientras la cadena está rota.
  • successor_chains_to_this: el eslabón SIGUIENTE aún lleva el hash de este eslabón. Una reescritura coherente no puede falsificar esto sin volver a encadenar todo lo que vino después, así que es lo que convierte la primera comprobación de «coherente» en «inalterado».

status es intact solo cuando se cumplen los tres.

En la punta de la cadena no hay sucesor, y ahí una reescritura coherente es indetectable desde dentro de la cadena. Ese caso no se disfraza de intact: successor_chains_to_this es null, is_chain_tip es true y status es intact_unwitnessed. Pasa a intact por sí solo en cuanto se sella la siguiente decisión.

La detección de manipulación es un 200 con status: "tampered", no un error: que el endpoint responda con veracidad sobre una cadena dañada ES la funcionalidad.

No todo decision_id que entregamos tiene un eslabón, y esa es la arista afilada de esta superficie. La tabla de evidencia deduplica los rechazos por día: un segundo rechazo de la misma deuda (o, en los envíos ad hoc, los mismos últimos 4 enmascarados) por la misma compuerta el mismo día UTC no escribe ninguna fila de evidencia nueva, y por tanto no sella ningún eslabón nuevo, mientras que el 422 devuelve un decision_id nuevo. Esa cita se resuelve como 404 aquí, y el 404 es deliberadamente idéntico byte a byte al de un id que nunca existió, de modo que esta ruta no puede usarse para sondear qué ids son reales. En concreto: una cita que no se resuelve NO es una negación de que el rechazo ocurriera; el rechazo de ese día queda evidenciado por el PRIMER eslabón de esa deuda (o número enmascarado), compuerta y día.

Ámbito: esto resuelve solo las decisiones del propio tenant que llama, y nunca lleva el E.164: el payload sellado contiene los últimos 4 enmascarados por construcción. La verificación por terceros sin credenciales no forma parte de esta superficie.

Parámetros

NombreEnTipoObligatorioDescripción
decision_idpathstring (uuid)síEl decision_id devuelto por el rechazo.

Respuestas

200 ​

El eslabón y su veredicto de integridad.

CampoTipoObligatorioDescripción
decisionobjectnoEl eslabón sellado, tal como está almacenado.
decision.decision_idstring (uuid)no
decision.tenant_idstring (uuid)no
decision.seqintegernoPosición en la cadena de ESTE tenant; 1 es el primer eslabón.
decision.channelvoice · smsno
decision.allowedbooleannoSi la operación estaba permitida. false es el caso litigado.
decision.reason_codestringno
decision.rule_idstringno
decision.policy_versionstringno
decision.subject_tablestringnoQué fila de evidencia sella este eslabón (call_attempts, gate_blocks, sms_messages, tenant_posture_events, call_disclosure_events). Cadena vacía cuando la decisión no dejó ninguna fila de evidencia; no es un error.
decision.subject_idstring,null (uuid)noId de esa fila de evidencia; null cuando subject_table está vacío.
decision.debt_refstringnoTu referencia ESTABLE de cuenta, no nuestro id de fila. Vacío cuando no hay ninguna.
decision.campaign_idstring,null (uuid)nonull para una decisión fuera de cualquier campaña (p. ej. un POST /v1/sms/messages puntual), y para todo eslabón de cambio de postura.
decision.to_number_maskedstringnoSolo los últimos 4 (***1234). El E.164 nunca se sella ni se devuelve.
decision.decided_atstring (date-time)no
decision.sealed_atstring (date-time)noCuándo se escribió el eslabón: en la misma transacción que la fila de evidencia que sella.
decision.payloadobjectnoEl JSON canónico que se hasheó, tal cual. Esto es lo que hace que la cita sea comprobable sin confiar en nosotros: vuelve a ejecutar la receta sobre él.
verificationobjectnoEl veredicto de integridad sobre este ÚNICO eslabón.
verification.statusintact · intact_unwitnessed · tamperednointact solo cuando se cumplen LOS TRES booleanos de abajo. intact_unwitnessed cuando se cumplen los dos primeros y todavía no hay sucesor (la punta de la cadena). tampered cuando falla alguno de los tres.
verification.payload_hash_matchesbooleannoEl payload de este eslabón produce el hash almacenado JUNTO A ÉL. Coherencia interna, no autenticidad: un adversario a nivel de base de datos puede reescribir ambas columnas a la vez y ponerlo en verde. successor_chains_to_this es lo que lo descarta.
verification.chains_to_previousbooleannoSu prev_hash es el hash del eslabón seq - 1 (o el génesis cuando seq es 1). También es false cuando ese predecesor HA DESAPARECIDO, que es como se hace visible un borrado.
verification.successor_chains_to_thisboolean,nullnoEl eslabón en seq + 1 aún lleva el hash de este eslabón como su prev_hash. null cuando no hay sucesor (este es el eslabón más nuevo; véase is_chain_tip). Una reescritura coherente de este eslabón por sí solo lo pone en false.
verification.is_chain_tipbooleannotrue cuando todavía no existe ningún eslabón posterior. En la punta nada testifica este eslabón, así que una reescritura coherente de él no puede detectarse desde dentro de la cadena, y status dice intact_unwitnessed en lugar de intact.
verification.seqintegerno
verification.hashstringnosha256 en hexadecimal minúscula de este eslabón.
verification.prev_hashstringnosha256 en hexadecimal minúscula al que este eslabón se encadena hacia atrás.
verification.genesis_hashstringnoLa raíz fija de toda cadena de tenant, publicada para que la receta sea reproducible desde cero. Es una CONSTANTE, y el valor del ejemplo de arriba es el real, no un marcador de posición. seq: 1 se encadena a ella.
verification.algorithm"sha256"no
verification.recipestringnoLa receta literal a volver a ejecutar.
verification.canonical_jsonstringnoCómo debe serializarse payload antes de hashear.
verification.verified_atstring (date-time)no
json
{
  "decision": {
    "decision_id": "8c5a1f3e-2b7d-4e91-a0c6-3f5b9d2e7a41",
    "tenant_id": "0d4f4f9e-1f2a-4b53-9d3c-8a5e2f7b1c10",
    "seq": 42,
    "channel": "voice",
    "allowed": false,
    "reason_code": "quiet_hours",
    "rule_id": "usa-cell.quiet-hours",
    "policy_version": "usa-cell.v1",
    "subject_table": "gate_blocks",
    "subject_id": "5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
    "debt_ref": "ACCT-10293",
    "campaign_id": "3f2c1b0a-9d8e-4c7b-a6f5-4e3d2c1b0a99",
    "to_number_masked": "***1001",
    "decided_at": "2026-07-01T02:10:00.123456Z",
    "sealed_at": "2026-07-01T02:10:00.128901Z",
    "payload": {
      "decision_id": "8c5a1f3e-2b7d-4e91-a0c6-3f5b9d2e7a41",
      "gate": "pre_dial",
      "reason_code": "quiet_hours"
    }
  },
  "verification": {
    "status": "intact",
    "payload_hash_matches": true,
    "chains_to_previous": true,
    "successor_chains_to_this": true,
    "is_chain_tip": false,
    "seq": 42,
    "hash": "9f2c4d7a1b8e35c06a4f9d2e7b1c3a58d6f0e4b29c7a13d5e8f06b2c4a9d7e13",
    "prev_hash": "3a7e1c9d5b2f84061e7d3c5a9b0f2e4d8c6a1b3f5d7e9c0a2b4d6f8e1a3c5b70",
    "genesis_hash": "cc0941d5e3377c3422517794a524f59be703c6a7e1a2bae2d596c34d784dddd7",
    "algorithm": "sha256",
    "recipe": "sha256(prev_hash || canonical_json(payload))",
    "canonical_json": "JSON with object keys sorted lexicographically at every depth",
    "verified_at": "2026-07-05T10:00:00.000000Z"
  }
}

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 ​

Ningún eslabón resuelve este id para el tenant que llama. Idéntico byte a byte para toda causa, por diseño (sin oráculo de existencia): el id nunca existió, está mal formado, pertenece a otro tenant o, la causa específica de esta ruta, lo entregó un rechazo que se deduplicó contra una fila de evidencia existente de ese día y por tanto no selló ningún eslabón propio. Un 404 aquí NO es una negación de que el rechazo ocurriera.

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"
  }
}

Estado de las fuentes de cumplimiento (listas DNC, registros estatales, RND) ​

http
GET /v1/compliance/sources

Contra qué se depuran los marcados de este tenant, y cuán fresco está, como una lectura barata para un panel de ajustes. Lee solo las premisas: no se evalúa ninguna campaña, ningún lead ni ninguna compuerta (GET /v1/campaigns/{id}/compliance-preflight lleva las mismas premisas dentro de un ensayo por campaña sobre hasta 10000 leads, e informa la antigüedad solo en días). De solo lectura.

DNC (dnc). Las tres listas globales (federal, state, internal_global) y, bajo state_registries, cada registro estatal que la plataforma declara depurar, indexado por código de estado. Las listas son datos regulatorios globales, idénticos para todo tenant de la celda. Cada fuente lleva la postura sobre la que actúa la compuerta (status, age_days, max_age_days son los números propios de la compuerta, no una copia) más las fechas que la respaldan: updated_at es cuándo se cortaron los datos de la FUENTE (no cuándo se ejecutó la importación), y expires_at es el primer instante en que la compuerta trata la instantánea como stale y se niega a afirmar una depuración (updated_at + (max_age_days + 1) días: la compuerta compara días enteros). source es la cadena de procedencia de la carga (por ejemplo ftc-san:2026-07-28) y record_count las filas cargadas. Una lista que nadie importó es absent, con updated_at, expires_at, age_days y record_count en null: la compuerta rechaza sobre ella. max_age_days es 31 para toda lista hoy (el techo de puerto seguro de la FTC); un registro estatal reporta el suyo.

Litigante (litigator). Core no tiene lista propia de litigantes: los litigantes de la plataforma son filas de la lista DNC internal_global, mezcladas con honeypots, y nada los distingue. available es por tanto siempre false y covered_by nombra la lista que los contiene, para que un cliente pueda omitir la entrada en lugar de mostrar una frescura que no existe.

RND (rnd). La Reassigned Numbers Database. provider es lo que declara la celda (declared; stub, el proveedor de desarrollo que responde limpio sin consultar un registro; o no_verificable, ninguno declarado, y la compuerta rechaza todo marcado). Las respuestas son del tenant, por lo que last_collected_at es la última vez que se recogió un fichero de resultados para el tenant que llama (null si no hay ninguna registrada). valid_since es el piso del ciclo que aplica la compuerta (la última actualización de la base de datos; las respuestas recogidas en ese momento o antes ya no respaldan el puerto seguro) y database_updated_at la última actualización que observó la plataforma, null mientras solo se aplica la regla del calendario. status compara los dos: current (recogido después de valid_since), stale (recogido antes) o never.

Respuestas

200 ​

Estado de cada fuente de cumplimiento para el tenant que llama.

CampoTipoObligatorioDescripción
as_ofstring (date-time)síEl instante en que se calcularon todas las antigüedades y estados.
dncobjectsí
dnc.federalobject (ComplianceDncSource)sí
dnc.federal.statusprovisioned · stale · absentsístale y absent son rechazados por la compuerta: no se afirma ninguna depuración sobre ellos.
dnc.federal.sourcestringsíCadena de procedencia de la carga (none cuando está ausente).
dnc.federal.record_countinteger,nullsí
dnc.federal.updated_atstring,null (date-time)síCuándo se cortaron los datos de la FUENTE; null cuando está ausente.
dnc.federal.expires_atstring,null (date-time)síPrimer instante en que la compuerta trata la instantánea como stale; null cuando está ausente.
dnc.federal.age_daysinteger,nullsíDías enteros desde updated_at (0 para una fecha futura); null cuando está ausente.
dnc.federal.max_age_daysintegersíTecho de frescura para esta fuente.
dnc.stateobject (ComplianceDncSource)sí
dnc.state.statusprovisioned · stale · absentsístale y absent son rechazados por la compuerta: no se afirma ninguna depuración sobre ellos.
dnc.state.sourcestringsíCadena de procedencia de la carga (none cuando está ausente).
dnc.state.record_countinteger,nullsí
dnc.state.updated_atstring,null (date-time)síCuándo se cortaron los datos de la FUENTE; null cuando está ausente.
dnc.state.expires_atstring,null (date-time)síPrimer instante en que la compuerta trata la instantánea como stale; null cuando está ausente.
dnc.state.age_daysinteger,nullsíDías enteros desde updated_at (0 para una fecha futura); null cuando está ausente.
dnc.state.max_age_daysintegersíTecho de frescura para esta fuente.
dnc.state_registriesobjectsíCada registro DNC por estado declarado, indexado por código de estado, cada uno frente a su propio techo.
dnc.internal_globalobject (ComplianceDncSource)sí
dnc.internal_global.statusprovisioned · stale · absentsístale y absent son rechazados por la compuerta: no se afirma ninguna depuración sobre ellos.
dnc.internal_global.sourcestringsíCadena de procedencia de la carga (none cuando está ausente).
dnc.internal_global.record_countinteger,nullsí
dnc.internal_global.updated_atstring,null (date-time)síCuándo se cortaron los datos de la FUENTE; null cuando está ausente.
dnc.internal_global.expires_atstring,null (date-time)síPrimer instante en que la compuerta trata la instantánea como stale; null cuando está ausente.
dnc.internal_global.age_daysinteger,nullsíDías enteros desde updated_at (0 para una fecha futura); null cuando está ausente.
dnc.internal_global.max_age_daysintegersíTecho de frescura para esta fuente.
litigatorobjectsí
litigator.availablefalsesíCore no mantiene una lista propia de litigantes.
litigator.covered_bystringsíLa lista DNC cuyas filas incluyen a los litigantes de la plataforma.
rndobjectsí
rnd.providerobjectsí
rnd.provider.statusdeclared · stub · no_verificablesí
rnd.provider.providerstring,nullsíNombre del proveedor declarado; null cuando no hay ninguno.
rnd.statuscurrent · stale · neversíLa última recogida del tenant frente al piso del ciclo (valid_since).
rnd.last_collected_atstring,null (date-time)síÚltima vez que se recogió un fichero de resultados para el tenant que llama.
rnd.valid_sincestring (date-time)síLas respuestas recogidas en este instante o antes ya no respaldan el puerto seguro.
rnd.database_updated_atstring,null (date-time)síÚltima actualización de la base de datos del administrador que observó la plataforma; null mientras solo se aplica la regla del calendario.
json
{
  "as_of": "2026-10-15T09:30:00.000000Z",
  "dnc": {
    "federal": {
      "status": "provisioned",
      "source": "ftc-san:2026-10-01",
      "record_count": 240000000,
      "updated_at": "2026-10-01T00:00:00.000000Z",
      "expires_at": "2026-11-02T00:00:00.000000Z",
      "age_days": 14,
      "max_age_days": 31
    },
    "state": {
      "status": "provisioned",
      "source": "states:2026-10-01",
      "record_count": 1200000,
      "updated_at": "2026-10-01T00:00:00.000000Z",
      "expires_at": "2026-11-02T00:00:00.000000Z",
      "age_days": 14,
      "max_age_days": 31
    },
    "state_registries": {
      "FL": {
        "status": "provisioned",
        "source": "state-fl:2026-10-01",
        "record_count": 700000,
        "updated_at": "2026-10-01T00:00:00.000000Z",
        "expires_at": "2026-11-02T00:00:00.000000Z",
        "age_days": 14,
        "max_age_days": 31
      },
      "IN": {
        "status": "stale",
        "source": "state-in:2026-08-01",
        "record_count": 90000,
        "updated_at": "2026-08-01T00:00:00.000000Z",
        "expires_at": "2026-09-02T00:00:00.000000Z",
        "age_days": 75,
        "max_age_days": 31
      },
      "MO": {
        "status": "absent",
        "source": "none",
        "record_count": null,
        "updated_at": null,
        "expires_at": null,
        "age_days": null,
        "max_age_days": 31
      }
    },
    "internal_global": {
      "status": "provisioned",
      "source": "platform",
      "record_count": 1500,
      "updated_at": "2026-10-15T00:00:00.000000Z",
      "expires_at": "2026-11-16T00:00:00.000000Z",
      "age_days": 0,
      "max_age_days": 31
    }
  },
  "litigator": {
    "available": false,
    "covered_by": "internal_global"
  },
  "rnd": {
    "provider": {
      "status": "declared",
      "provider": "Dialer.Compliance.RND.Reader"
    },
    "status": "current",
    "last_collected_at": "2026-10-12T14:02:11.000000Z",
    "valid_since": "2026-09-17T12:00:00.000000Z",
    "database_updated_at": null
  }
}

401 — Unauthorized ​

Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

429 — ApiKeyRateLimited ​

Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.

json
{
  "error": {
    "code": "rate_limited",
    "message": "API key request quota exceeded; retry after the interval in Retry-After"
  }
}

Obtener la divulgación de voz artificial NO IA del tenant ​

http
GET /v1/artificial-voice-disclosure

La divulgación almacenada para el carril de voz artificial NO IA (TTS / IVR / encuesta). Un tenant sin nada almacenado recibe 404 not_found, NO un 200 con cuerpo null: «no hay divulgación almacenada» es un estado de cumplimiento real, y un cliente que ramifica según el código de estado no debe leerlo como configurado.

Respuestas

200 ​

La divulgación almacenada.

CampoTipoObligatorioDescripción
artificial_voice_disclosureobject (ArtificialVoiceDisclosure)sí
artificial_voice_disclosure.textstringsíEl texto de la divulgación almacenada.
artificial_voice_disclosure.sourceapi · import · migrationsíProcedencia: cómo llegó la fila hasta aquí. La fija el servidor, nunca se toma del cuerpo.
artificial_voice_disclosure.actorstringsíEl principal que la escribió, del más estrecho al más amplio: el id del agente que actúa; si no, el id de usuario del dashboard cuando quien llama es una sesión ddu_; si no, tenant-api-key para una clave dd_ que no lleva una identidad más fina. Lo fija el servidor.
artificial_voice_disclosure.validbooleansíSi el texto almacenado TODAVÍA pasa la comprobación de contenido. Se recalcula en cada lectura, no se almacena: el vocabulario de marcadores puede ganar un término, y una fila escrita bajo el anterior no debe seguir avalando un texto que la compuerta ya no aceptaría. Es la misma pregunta que hace la vía de marcado. NO indica si el texto lleva el marcador {optout_digit}, que una campaña TTS / IVR / encuesta TAMBIÉN necesita para marcar (core#1305).
artificial_voice_disclosure.updated_atstring (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"
  }
}

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"
  }
}

Almacenar la divulgación de voz artificial NO IA del tenant ​

http
PUT /v1/artificial-voice-disclosure

Upsert sobre el tenant (una fila, gana la última escritura). El TEXTO lo juzga la MISMA comprobación de contenido que aplica el carril de IA: debe identificar claramente la voz como automatizada, en inglés o español. Un texto que no lo haga es 400 bad_request y NO se almacena NADA: no se le puede entregar a una compuerta un true que el texto no se ha ganado.

Instrucciones de exclusión (core#1305, 47 CFR §64.1200(b)(3)). Escribe la instrucción con tus propias palabras, con el marcador {optout_digit} donde va la tecla: Press {optout_digit} to stop these calls. Toda campaña TTS / IVR / encuesta reproduce primero esta divulgación y sustituye cada marcador por SU PROPIO optout_digit, de modo que la tecla que oye el consumidor es siempre la tecla que funciona. No escribas nunca la tecla en sí. Un texto sin el marcador se almacena igualmente (es una divulgación válida), pero ninguna campaña TTS / IVR / encuesta marca con él: el /stats de la campaña informa prompt_unavailable_reason: optout_instructions_missing.

source y actor son procedencia y los fija el SERVIDOR (api más el principal que actúa); enviar cualquiera de los dos es 400 bad_request que nombra la clave. Ámbito de gestión del tenant (clave de API dd_ o login del dashboard; ddw_ → 403).

Reglas de rol (core#2438): una clave dd_ o un login account_admin; un login member recibe 403 role_forbidden (opción por tenant compliance_config_admin_only, activada por defecto). El GET de este recurso no se ve afectado.

Cuerpo de la petición (JSON, obligatorio)

CampoTipoObligatorioDescripción
textstringsíDebe identificar claramente la voz como automatizada (EN o ES). Cualquier otra cosa es 400 bad_request y no se almacena nada. El techo existe porque la vía de marcado vuelve a leer esta fila en cada intento. Incluye la instrucción de exclusión con el marcador {optout_digit} donde va la tecla (ortografía exacta, en minúsculas); cada campaña lo sustituye por su propia tecla. Sin el marcador ninguna campaña TTS / IVR / encuesta marca (optout_instructions_missing, core#1305). El techo EFECTIVO es de 10000 BYTES (UTF-8), aplicado por el servidor y por un CHECK de base de datos. El maxLength de JSON Schema cuenta CARACTERES y no tiene palabra clave de longitud en bytes, así que el maxLength: 10000 de arriba es una cota SUPERIOR, no el límite real: un texto multibyte puede ser rechazado con 400 bad_request mucho antes de llegar a 10000 caracteres. La forma legible por máquina del límite real es x-maxLengthBytes. Véase el ADR #74.
json
{
  "text": "Hello, this is an automated call from Acme Collections. To stop these calls, press {optout_digit}."
}

Respuestas

200 ​

La divulgación almacenada.

CampoTipoObligatorioDescripción
artificial_voice_disclosureobject (ArtificialVoiceDisclosure)sí
artificial_voice_disclosure.textstringsíEl texto de la divulgación almacenada.
artificial_voice_disclosure.sourceapi · import · migrationsíProcedencia: cómo llegó la fila hasta aquí. La fija el servidor, nunca se toma del cuerpo.
artificial_voice_disclosure.actorstringsíEl principal que la escribió, del más estrecho al más amplio: el id del agente que actúa; si no, el id de usuario del dashboard cuando quien llama es una sesión ddu_; si no, tenant-api-key para una clave dd_ que no lleva una identidad más fina. Lo fija el servidor.
artificial_voice_disclosure.validbooleansíSi el texto almacenado TODAVÍA pasa la comprobación de contenido. Se recalcula en cada lectura, no se almacena: el vocabulario de marcadores puede ganar un término, y una fila escrita bajo el anterior no debe seguir avalando un texto que la compuerta ya no aceptaría. Es la misma pregunta que hace la vía de marcado. NO indica si el texto lleva el marcador {optout_digit}, que una campaña TTS / IVR / encuesta TAMBIÉN necesita para marcar (core#1305).
artificial_voice_disclosure.updated_atstring (date-time)sí

400 — BadRequest ​

Petición mal formada (errores de forma/tipo, filtros no válidos, marcas de tiempo erróneas).

json
{
  "error": {
    "code": "bad_request",
    "message": "from must be RFC-3339"
  }
}

401 — Unauthorized ​

Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

403 — Forbidden ​

El tenant está suspendido. Una sesión del dashboard (ddu_) de un tenant cuya política de inicio de sesión (ADR 227) está en modo enforce responde 403 con el código login_policy_denied ante una solicitud desde una dirección o en una hora que la política no permite — el mismo cuerpo en ambos casos; la sesión no se revoca y una solicitud posterior dentro de la política se procesa. Un token de widget (ddw_) de un puesto queda sujeto a la misma política en cada solicitud (core#2406, indicador widget_login_honors_policy). Un tenant sintético (demo) responde 403 con el código synthetic_tenant en toda ruta que llegaría a un tercero — marcación manual, pierna de transferencia externa o de conferencia, envío de SMS (core#2251, adenda del ADR #280). Ningún pago lo cambia.

json
{
  "error": {
    "code": "forbidden",
    "message": "tenant is suspended"
  }
}

429 — ApiKeyRateLimited ​

Esta clave de API ha superado su cuota de peticiones, o las claves del tenant en conjunto han superado el techo del tenant (core#2503) (code rate_limited). La petición no se procesó. Espera Retry-After segundos y reintenta; un rechazo no cuenta contra el presupuesto, así que esa espera siempre basta. Acotado por clave de API y, por encima de las claves, por tenant (api_tenant_requests_per_minute): una clave hermana no se ve afectada hasta que las claves del tenant en conjunto alcanzan ese techo. La cuota se aplica a toda operación alcanzable con una clave dd_, así que las operaciones que nombran un 429 más específico (control de llamadas, disposiciones, SMS, …) PUEDEN responder TAMBIÉN este, con sus cabeceras X-RateLimit-*.

json
{
  "error": {
    "code": "rate_limited",
    "message": "API key request quota exceeded; retry after the interval in Retry-After"
  }
}

Nothing in these docs is legal advice — always confirm compliance posture with your own counsel.