Appearance
Intentos de llamada (CDR)
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.
Consulta del CDR de solo anexado + disposiciones tipadas.
Endpoints
| Método | Ruta | Resumen |
|---|---|---|
GET | /v1/call_attempts | Consulta del CDR filtrada y paginada por keyset |
POST | /v1/call_attempts/{id}/disposition | Resultado tipado del agente/IA (+ promesa para PTP) |
GET | /v1/call_attempts/{id}/analysis | Resumen de IA y disposición sugerida de una llamada |
POST | /v1/call_attempts/{id}/disclosure | Atestación del agente sobre el aviso de cobranza |
Consulta del CDR filtrada y paginada por keyset
http
GET /v1/call_attemptsLas filas son la verdad del CDR de solo anexado: los originales tienen corrects_id: null; las correcciones apuntan al original, y la corrección MÁS RECIENTE es la disposición final. compliance_snapshot lleva la evidencia congelada de la decisión. disclosure.script (core#1690, ADR #219 Decisión 4) es el guion aprobado del aviso de cobranza que aplica a la llamada, o null; la estación lo lee y no calcula nada.
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 filas de CDR.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
call_attempts | arreglo de CallAttempt | sí | |
call_attempts[].id | string (uuid) | no | |
call_attempts[].tenant_id | string (uuid) | no | |
call_attempts[].debt_id | string (uuid) | no | |
call_attempts[].campaign_id | string (uuid) | null | no | |
call_attempts[].agent_id | string (uuid) | null | no | |
call_attempts[].consumer_ref | string | no | |
call_attempts[].debt_key | string | no | |
call_attempts[].call_uuid | string | null | no | |
call_attempts[].from_number | string | null | no | |
call_attempts[].to_number | string | no | |
call_attempts[].started_at | string (date-time) | null | no | |
call_attempts[].answered_at | string (date-time) | null | no | |
call_attempts[].ended_at | string (date-time) | null | no | |
call_attempts[].disposition | string | null | no | Disposiciones del motor: answered_human|abandoned|delivered|ai_conversation|answered_machine|ai_stream_lost|busy|no_answer|canceled|rejected|failed; los resultados tipificados del agente o de la IA son de forma libre (p. ej. promise_to_pay). Una llamada contestada que nunca se puentió se nombra por lo que fue la respuesta (core#1323): answered_machine ante un veredicto de máquina de AMD, ai_conversation para una campaña de IA de voz, delivered para una campaña de TTS/IVR/encuesta, y abandoned SOLO cuando se esperaba un agente (la llamada abandonada de TSR/FCC). Las filas escritas antes de ese release conservan abandoned para las cuatro. Una llamada de IA de voz cuya sesión la IA de voz rechazó por estar llena, y que ningún agente atendió, también es abandoned (core#1326): la persona contestó y nadie le habló. |
call_attempts[].hangup_cause | string | null | no | |
call_attempts[].sip_response_code | integer | null | no | |
call_attempts[].compliance_snapshot | object | null | no | Evidencia congelada de la decisión en el momento de originar. |
call_attempts[].amd_verdict | string | null | no | |
call_attempts[].recording_uri | string | null | no | Dónde quedó la grabación de la llamada (ruta u URI del objeto); null cuando la llamada no se grabó. |
call_attempts[].ai_used | boolean | no | Si el bot de IA de voz se ejecutó en esta llamada (la clave de medición del complemento de IA). |
call_attempts[].note | string | null | no | La nota de texto libre del agente sobre la llamada, introducida al asignar la disposición en CUALQUIER disposición. Viaja en el registro de corrección que lleva la disposición, por lo que la fila ORIGINAL normalmente es null. Cifrada en reposo (sobre por tenant) y nunca consultada por contenido. null = sin nota, o un registro escrito antes de que existiera la columna. |
call_attempts[].corrects_id | string (uuid) | null | no | |
call_attempts[].inserted_at | string (date-time) | no | |
call_attempts[].disclosure | object (CallAttemptDisclosure) | no | Lo que el agente DECLARÓ sobre el aviso obligatorio de cobranza en esta llamada (core#1587, ADR #211). En GET /v1/call_attempts y en las filas del paquete de defensa v2; todas las filas de un intento (la original y sus correcciones) llevan el estado del intento. Es una declaración, nunca una prueba de que el aviso se dijo o se oyó: no existe un estado "verificado" ni "conforme". attested — la atestación vigente dice delivered. declared_not_delivered — dice not_delivered (el motivo está en el evento). unattested — la llamada ESTÁ en alcance (tiene deuda y el pipeline la finalizó como answered_human o transferred) y nadie atestó. not_applicable — fuera de alcance (sin deuda, o no es un contacto humano). undecidable — la llamada aún no tiene fila final, así que el alcance no puede decidirse. El alcance nunca depende del código que tecleó el agente. |
call_attempts[].disclosure.status | attested · declared_not_delivered · unattested · not_applicable · undecidable | sí | |
call_attempts[].disclosure.event_id | string (uuid) | null | sí | La atestación vigente (null cuando no hay ninguna). |
call_attempts[].disclosure.at | string (date-time) | null | sí | Hora del servidor de la atestación vigente. |
call_attempts[].disclosure.revised | boolean | sí | true cuando el intento tiene más de una atestación (una rectifica a otra). |
call_attempts[].disclosure.script | CallAttemptDisclosureScript | null | no | Solo en GET /v1/call_attempts (core#1690, ADR #219 Decisiones 3-5); las filas del paquete de defensa v2 no la incluyen. El guion aprobado que se aplica a esta llamada, resuelto en el servidor a partir de los hechos congelados en el intento (estado del consumidor y tipo de deuda), de modo que la estación no calcula nada. Solo participan las versiones aprobadas antes de que terminara la llamada (o aprobadas ahora, mientras no ha terminado) y no retiradas hoy, así que la versión mostrada es una que la atestación acepta. null cuando el tenant no tiene tal versión, cuando el intento no tiene hechos congelados, o cuando ninguna versión aplica. No es la versión que el agente atestó: esa está sellada en el evento de atestación. |
call_attempts[].disclosure.script_match | matched · other_version · unscripted · not_applicable | no | Sólo en GET /v1/call_attempts (core#1691, ADR #219 Decision 7). Compara la versión del guion que NOMBRA la atestación vigente con la que script publica ahora. matched: la misma. other_version: nombra otra (por ejemplo una sustituida después de iniciada la llamada, que sigue siendo válida) o ya no hay ninguna publicada. unscripted: hay una atestación que no nombra ninguna versión y sí se aplicó un guion. not_applicable: no hay atestación, o no hay guion con el que comparar. Es una marca de lectura: no bloquea nada y no afirma que ningún texto cumpla ninguna norma. |
next_cursor | string | null | sí | Cursor keyset opaco; null en la última página. |
json
{
"call_attempts": [
{
"id": "8c7d6e5f-4a3b-2c1d-0e9f-8a7b6c5d4e3f",
"tenant_id": "0d4f4f9e-1f2a-4b53-9d3c-8a5e2f7b1c10",
"debt_id": "5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
"campaign_id": "3f2c1b0a-9d8e-4c7b-a6f5-4e3d2c1b0a99",
"agent_id": null,
"consumer_ref": "cms-consumer-9001",
"debt_key": "debt:cms-debt-0001",
"call_uuid": "6f0a1b2c-3d4e-5f6a-7b8c-9d0e1f2a3b4c",
"from_number": "+13125550199",
"to_number": "+13125551001",
"started_at": "2026-07-05T15:00:00Z",
"answered_at": null,
"ended_at": null,
"disposition": null,
"hangup_cause": null,
"sip_response_code": null,
"compliance_snapshot": {
"decision_id": "2c1d0e9f-8a7b-6c5d-4e3f-2a1b0c9d8e7f",
"policy_version": "usa-cell.v1"
},
"amd_verdict": null,
"corrects_id": null,
"inserted_at": "2026-07-05T15:00:00Z"
}
],
"next_cursor": null
}400 — BadRequest
Petición mal formada (errores de forma/tipo, filtros no válidos, marcas de tiempo erróneas).
json
{
"error": {
"code": "bad_request",
"message": "from must be RFC-3339"
}
}401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}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"
}
}Resultado tipado del agente/IA (+ promesa para PTP)
http
POST /v1/call_attempts/{id}/dispositionRegistra un resultado para un intento finalizado como un registro CDR compensatorio (la tabla sigue siendo de solo anexado). {id} debe ser una fila ORIGINAL (corrects_id: null); las correcciones y los ids desconocidos son 404. El contrato tipado es bidireccional: promise_to_pay EXIGE el payload promise; un payload promise en cualquier otra disposición → 422. La corrección y la promesa se confirman en UNA sola transacción; una PTP también avanza el estado de lead de la deuda a promise_to_pay.
Un campo no reconocido DENTRO de promise es un 400 que nombra la clave, y no se anexa nada al CDR. Alcance, aclarado porque la diferencia es fácil de malinterpretar: solo promise es estricto. Los campos desconocidos en el NIVEL SUPERIOR de este cuerpo se siguen ignorando, ya que el nivel superior se lee clave por clave en lugar de depurarse contra una lista blanca.
note es la nota de texto libre del agente sobre la LLAMADA y se acepta en CUALQUIER disposición, no solo en promise_to_pay. No la confundas con promise.note, que describe los TÉRMINOS del acuerdo y queda en el registro de la promesa; ambas pueden viajar en la misma solicitud y se almacenan por separado. La nota vuelve en call_attempt.note aquí y en cualquier otra superficie de intentos de llamada, de modo que el siguiente agente que trabaje la cuenta la lea en el historial de la deuda.
A QUIÉN se atribuye el resultado —y la promesa nacida con él— depende del principal (ADR #133, ADR #134). Una clave de máquina dd_ puede nombrar a cualquier agente de su tenant. Una sesión de inicio de sesión ddu_ cuya persona esté vinculada a un puesto de agente actúa como ESE puesto: agent_id puede omitirse (se rellena con el puesto) o repetirlo; nombrar a otro agente es 403 forbidden y no se anexa nada: ni corrección ni promesa. Un ddw_ solo puede nombrar a su propio agente. Un ddu_ sin puesto vinculado puede nombrar a otro agente solo con users.role = account_admin; un member recibe 403 role_forbidden, sin anexar nada, una vez que la clave de catálogo del tenant ddu_author_role_enforced está activada (nace desactivada: hasta entonces la sesión sin puesto conserva el contrato de dd_).
disclosure (opcional) es la atestación del agente sobre el aviso de cobranza obligatorio, escrita en la MISMA transacción que la corrección (consulta POST /v1/call_attempts/{id}/disclosure para ver qué declara y qué no). Es estricta como promise: una clave fuera de la lista, un supersedes_id mal formado, un not_delivered sin reason o la falta de outcome es un 400 y NO se anexa nada: ni corrección ni atestación. Toma al autor de la disposición, por lo que el objeto no repite agent_id; una solicitud que atesta sin nombrar a ningún agente es 400. Sin el objeto, la respuesta no tiene la clave disclosure y el cierre responde exactamente lo que siempre respondió.
APLICACIÓN, detrás de la clave de catálogo del tenant disclosure_attestation_enforced (nace DESACTIVADA: hasta que se active, el cierre responde exactamente lo que siempre respondió y una llamada contactada sin atestación se lee como unattested). Con ella ACTIVADA, una llamada EN ALCANCE —una con una deuda efectiva cuya fila de pipeline es answered_human o transferred, decidido a partir de los hechos de la llamada y nunca del código que escribe el agente— que no tenga atestación (anterior o en el disclosure de este cuerpo) recibe 422 con error.code: disclosure_attestation_required, y NO se escribe NADA: ni corrección, ni promesa, ni atestación. Una llamada cuya fila de pipeline aún no existe no puede decidirse y recibe 409 con error.code: attempt_not_final: reintenta cuando termine la llamada. Una llamada sin deuda no está en alcance y se cierra como antes. Desactivar la clave solo levanta el bloqueo; la plataforma nunca crea ninguna atestación.
suggestion_id (opcional) cita la sugerencia de IA que vio el agente (GET /v1/call_attempts/{id}/analysis). SOLO registra que la sugerencia se mostró y lo que escribió el agente; el resultado es siempre la disposition de este cuerpo, nunca la sugerida, y la sugerencia no cambia nada de la política de reintentos. Un id mal formado es 400; un id que no es el análisis de este intento es 404 y NO se anexa nada. La misma sugerencia citada dos veces registra una sola adopción.
Una vez registrada, la disposición también se entrega a la política de reintentos del tenant (/v1/disposition-rules), DESPUÉS de confirmado el resultado: un fallo de la política nunca deshace el resultado, y la respuesta no informa de los efectos de la política. El reintento se mide desde el INTENTO (el instante en que se cerró), no desde que llega esta solicitud, de modo que repetir la solicitud escribe el mismo instante. La política solo puede posponer o cerrar la deuda: nunca adelanta una deuda aparcada, nunca toca una deuda fuera de los estados marcables y no reprograma una deuda con una rellamada pendiente. Cada marcación sigue pasando por la compuerta de Reg F en el momento del originate. Un tenant sin regla para el código no ve ningún cambio.
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 |
|---|---|---|---|
disposition | string | sí | Resultado tipado no vacío, p. ej. promise_to_pay, wrong_number. |
agent_id | string (uuid) | no | El agente que registra la disposición. Opcional; para una sesión ddu_ vinculada a un puesto debe ser ese puesto (o omitirse). |
note | string | null | no | Nota de texto libre del agente sobre la llamada, aceptada en CUALQUIER disposición. Cifrada en reposo (sobre por tenant) y nunca consultada por contenido. Omítela (o envía null) para no dejar nota; "" se almacena como sin nota. |
promise | object | no | OBLIGATORIO si y solo si disposition es promise_to_pay. |
promise.amount_cents | integer | sí | |
promise.currency | MXN · USD | sí | |
promise.promised_date | string (date) | sí | Fecha futura, como máximo a promise_max_horizon_days del tenant (90 por defecto). |
promise.note | string | no | |
disclosure | DisclosureAttestation | null | no | La atestación del agente sobre el aviso de cobranza de esta llamada, escrita junto con la corrección en una sola transacción. Omítela (o envía null) para no escribir ninguna. |
suggestion_id | string (uuid) | null | no | El id del análisis de IA cuya sugerencia vio el agente. Solo registra la adopción; nunca elige ni cambia el resultado. |
simple
json
{
"disposition": "wrong_number"
}citing_the_ai_suggestion
json
{
"disposition": "promise_to_pay",
"suggestion_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"promise": {
"amount_cents": 12500,
"currency": "USD",
"promised_date": "2026-07-21"
}
}with_disclosure
json
{
"disposition": "answered_human",
"agent_id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
"disclosure": {
"outcome": "delivered"
}
}with_note
json
{
"disposition": "answered_third_party",
"note": "his wife answered, he gets home at 7pm"
}promise_to_pay
json
{
"disposition": "promise_to_pay",
"agent_id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
"promise": {
"amount_cents": 12500,
"currency": "USD",
"promised_date": "2026-07-21",
"note": "two installments"
}
}Respuestas
201
El registro CDR compensatorio (+ la promesa para PTP, + la atestación cuando se envió una).
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
call_attempt | object (CallAttempt) | sí | Fila del CDR de solo anexado. Los originales tienen corrects_id: null; las correcciones apuntan al original, y la corrección MÁS RECIENTE es la disposición final. |
call_attempt.id | string (uuid) | no | |
call_attempt.tenant_id | string (uuid) | no | |
call_attempt.debt_id | string (uuid) | no | |
call_attempt.campaign_id | string (uuid) | null | no | |
call_attempt.agent_id | string (uuid) | null | no | |
call_attempt.consumer_ref | string | no | |
call_attempt.debt_key | string | no | |
call_attempt.call_uuid | string | null | no | |
call_attempt.from_number | string | null | no | |
call_attempt.to_number | string | no | |
call_attempt.started_at | string (date-time) | null | no | |
call_attempt.answered_at | string (date-time) | null | no | |
call_attempt.ended_at | string (date-time) | null | no | |
call_attempt.disposition | string | null | no | Disposiciones del motor: answered_human|abandoned|delivered|ai_conversation|answered_machine|ai_stream_lost|busy|no_answer|canceled|rejected|failed; los resultados tipificados del agente o de la IA son de forma libre (p. ej. promise_to_pay). Una llamada contestada que nunca se puentió se nombra por lo que fue la respuesta (core#1323): answered_machine ante un veredicto de máquina de AMD, ai_conversation para una campaña de IA de voz, delivered para una campaña de TTS/IVR/encuesta, y abandoned SOLO cuando se esperaba un agente (la llamada abandonada de TSR/FCC). Las filas escritas antes de ese release conservan abandoned para las cuatro. Una llamada de IA de voz cuya sesión la IA de voz rechazó por estar llena, y que ningún agente atendió, también es abandoned (core#1326): la persona contestó y nadie le habló. |
call_attempt.hangup_cause | string | null | no | |
call_attempt.sip_response_code | integer | null | no | |
call_attempt.compliance_snapshot | object | null | no | Evidencia congelada de la decisión en el momento de originar. |
call_attempt.amd_verdict | string | null | no | |
call_attempt.recording_uri | string | null | no | Dónde quedó la grabación de la llamada (ruta u URI del objeto); null cuando la llamada no se grabó. |
call_attempt.ai_used | boolean | no | Si el bot de IA de voz se ejecutó en esta llamada (la clave de medición del complemento de IA). |
call_attempt.note | string | null | no | La nota de texto libre del agente sobre la llamada, introducida al asignar la disposición en CUALQUIER disposición. Viaja en el registro de corrección que lleva la disposición, por lo que la fila ORIGINAL normalmente es null. Cifrada en reposo (sobre por tenant) y nunca consultada por contenido. null = sin nota, o un registro escrito antes de que existiera la columna. |
call_attempt.corrects_id | string (uuid) | null | no | |
call_attempt.inserted_at | string (date-time) | no | |
call_attempt.disclosure | object (CallAttemptDisclosure) | no | Lo que el agente DECLARÓ sobre el aviso obligatorio de cobranza en esta llamada (core#1587, ADR #211). En GET /v1/call_attempts y en las filas del paquete de defensa v2; todas las filas de un intento (la original y sus correcciones) llevan el estado del intento. Es una declaración, nunca una prueba de que el aviso se dijo o se oyó: no existe un estado "verificado" ni "conforme". attested — la atestación vigente dice delivered. declared_not_delivered — dice not_delivered (el motivo está en el evento). unattested — la llamada ESTÁ en alcance (tiene deuda y el pipeline la finalizó como answered_human o transferred) y nadie atestó. not_applicable — fuera de alcance (sin deuda, o no es un contacto humano). undecidable — la llamada aún no tiene fila final, así que el alcance no puede decidirse. El alcance nunca depende del código que tecleó el agente. |
call_attempt.disclosure.status | attested · declared_not_delivered · unattested · not_applicable · undecidable | sí | |
call_attempt.disclosure.event_id | string (uuid) | null | sí | La atestación vigente (null cuando no hay ninguna). |
call_attempt.disclosure.at | string (date-time) | null | sí | Hora del servidor de la atestación vigente. |
call_attempt.disclosure.revised | boolean | sí | true cuando el intento tiene más de una atestación (una rectifica a otra). |
call_attempt.disclosure.script | CallAttemptDisclosureScript | null | no | Solo en GET /v1/call_attempts (core#1690, ADR #219 Decisiones 3-5); las filas del paquete de defensa v2 no la incluyen. El guion aprobado que se aplica a esta llamada, resuelto en el servidor a partir de los hechos congelados en el intento (estado del consumidor y tipo de deuda), de modo que la estación no calcula nada. Solo participan las versiones aprobadas antes de que terminara la llamada (o aprobadas ahora, mientras no ha terminado) y no retiradas hoy, así que la versión mostrada es una que la atestación acepta. null cuando el tenant no tiene tal versión, cuando el intento no tiene hechos congelados, o cuando ninguna versión aplica. No es la versión que el agente atestó: esa está sellada en el evento de atestación. |
call_attempt.disclosure.script_match | matched · other_version · unscripted · not_applicable | no | Sólo en GET /v1/call_attempts (core#1691, ADR #219 Decision 7). Compara la versión del guion que NOMBRA la atestación vigente con la que script publica ahora. matched: la misma. other_version: nombra otra (por ejemplo una sustituida después de iniciada la llamada, que sigue siendo válida) o ya no hay ninguna publicada. unscripted: hay una atestación que no nombra ninguna versión y sí se aplicó un guion. not_applicable: no hay atestación, o no hay guion con el que comparar. Es una marca de lectura: no bloquea nada y no afirma que ningún texto cumpla ninguna norma. |
promise | Promise | null | sí | |
disclosure | object (DisclosureEvent) | no | Una atestación sellada y de solo anexado. Declara lo que el agente DECLARÓ: evidence_kind es agent_attestation, nunca prueba de que el aviso se dijera o se escuchara. |
disclosure.id | string (uuid) | no | |
disclosure.tenant_id | string (uuid) | no | |
disclosure.call_attempt_id | string (uuid) | no | |
disclosure.debt_id | string (uuid) | no | |
disclosure.agent_id | string (uuid) | no | |
disclosure.evidence_kind | agent_attestation | no | |
disclosure.outcome | delivered · not_delivered | no | |
disclosure.reason | string | null | no | |
disclosure.note | string | null | no | |
disclosure.script_sha256 | string | null | no | |
disclosure.script_version_id | string (uuid) | no | Presente sólo cuando la atestación está vinculada a una versión de la biblioteca de guiones (core#1690). |
disclosure.script_resolution | exact · state · debt_type · tenant_default | no | Presente sólo con script_version_id; la rama de precedencia a la que pertenece el selector de la versión para esta llamada. |
disclosure.supersedes_id | string (uuid) | null | no | |
disclosure.occurred_at | string (date-time) | no | |
disclosure.created_at | string (date-time) | no |
400 — BadRequest
Petición mal formada (errores de forma/tipo, filtros no válidos, marcas de tiempo erróneas).
json
{
"error": {
"code": "bad_request",
"message": "from must be RFC-3339"
}
}401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — Forbidden
El tenant está suspendido. Una sesión del dashboard (ddu_) de un tenant cuya política de inicio de sesión (ADR 227) está en modo enforce responde 403 con el código login_policy_denied ante una solicitud desde una dirección o en una hora que la política no permite — el mismo cuerpo en ambos casos; la sesión no se revoca y una solicitud posterior dentro de la política se procesa. Un token de widget (ddw_) de un puesto queda sujeto a la misma política en cada solicitud (core#2406, indicador widget_login_honors_policy). Un tenant sintético (demo) responde 403 con el código synthetic_tenant en toda ruta que llegaría a un tercero — marcación manual, pierna de transferencia externa o de conferencia, envío de SMS (core#2251, adenda del ADR #280). Ningún pago lo cambia.
json
{
"error": {
"code": "forbidden",
"message": "tenant is suspended"
}
}404 — NotFound
Id desconocido, mal formado (no UUID) O id de otro tenant: RLS devuelve cero filas, así que los tres son indistinguibles por diseño (sin fuga de existencia, nunca un 403 para ids ajenos).
json
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}409 — Conflict
Transición de estado no válida, conflicto de unicidad, o un asiento que está ocupado/reservado.
json
{
"error": {
"code": "conflict",
"message": "invalid state transition"
}
}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 — DispositionThrottled
El freno de tasa de solicitudes NR-18 rechazó esta solicitud (reintentable en la siguiente ventana). code rate_limited. Acotado por el principal DURADERO más específico (agente > usuario > tenant, que sobrevive a la reautenticación), con un presupuesto PROPIO y más estrecho que el de los verbos de control: lo que acota es el crecimiento duradero de filas, ya que cada disposición aceptada hace un INSERT de una fila de corrección en el historial de intentos de solo anexado. Se rechaza ANTES de buscar el intento, por lo que esta respuesta es idéntica exista o no el id. Retry-After es el tiempo restante de la ventana fija.
json
{
"error": {
"code": "rate_limited",
"message": "disposition rate limit exceeded; retry later"
}
}Resumen de IA y disposición sugerida de una llamada
http
GET /v1/call_attempts/{id}/analysisEl resumen de UNA llamada finalizada y la disposición que la IA SUGIERE. Es una sugerencia, nunca un resultado: no se aplica nada, y el agente confirma escribiendo la disposición (POST /v1/call_attempts/{id}/disposition, citando opcionalmente suggestion_id = este id).
suggested_code es siempre un code ACTIVO en el catálogo propio del tenant, o null con el motivo en suggestion_note. suggested_class es la clase de ese código hoy; una sugerencia de clase do_not_call se devuelve como tal y un cliente no debe preseleccionarla.
Legible por el agente dueño del intento (un inicio de sesión ddu_ cuya persona esté vinculada al puesto, o el token ddw_ ligado a ese agente) y por un supervisor o administrador. Una clave dd_ es un servicio y recibe 403 role_forbidden, igual que un inicio de sesión sin puesto y sin rol de supervisor. 404 cuando el intento no tiene análisis en este tenant: pertenece a otro agente u otro tenant, nunca se analizó (no es elegible, no hay derecho de uso o sigue pendiente), su audio se purgó (la purga elimina el análisis junto con él) o el id está mal formado. Un cliente trata 404 como "aún no hay sugerencia" y mantiene la escritura manual.
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
Respuestas
200
El análisis.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
analysis | object (CallAnalysis) | sí | |
analysis.id | string (uuid) | sí | Cítalo como suggestion_id al escribir la disposición. |
analysis.call_attempt_id | string (uuid) | sí | |
analysis.source | transcript · ai_conversation | sí | De dónde provino el texto: una transcripción de grabación o una conversación de IA de voz. |
analysis.summary | string | sí | Resumen de la llamada. Datos personales; nunca se registran en logs. |
analysis.suggested_code | string,null | no | Un code ACTIVO en el catálogo del tenant, o null (consulta suggestion_note). |
analysis.suggested_class | contacted · no_contact · busy · machine · bad_number · do_not_call · other · null | no | Clase de suggested_code en el catálogo hoy. do_not_call nunca debe preseleccionarse. |
analysis.suggestion_note | suggested · model_none · not_in_catalog · inactive | sí | Por qué suggested_code está o no establecido. |
analysis.confidence_pct | integer,null | no | La confianza del modelo; solo presente con un código sugerido. |
analysis.provider | string | sí | |
analysis.model | string | sí | |
analysis.prompt_version | string | sí | |
analysis.created_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"
}
}Atestación del agente sobre el aviso de cobranza
http
POST /v1/call_attempts/{id}/disclosureRegistra, de solo anexado, lo que el agente DECLARÓ sobre el aviso de cobranza obligatorio en una llamada: que fue delivered, o not_delivered con un reason cerrado. NO prueba que el aviso se dijera ni se escuchara: evidence_kind es agent_attestation y así lo dice en el cable. La fila se sella en la cadena de cumplimiento del tenant en la misma transacción: si el sellado falla, no se escribe nada. {id} debe ser un intento ORIGINAL (corrects_id: null); una corrección, un id desconocido o un intento de otro tenant es 404. Una llamada sin deuda (ni siquiera por una asignación) no tiene contra qué atestar: 422.
NO hay valor por defecto: un cuerpo sin outcome es 400. Una clave desconocida en el cuerpo es 400. reason es obligatorio con not_delivered y se rechaza con delivered; note solo se acepta con reason: other y se almacena cifrada. script_sha256 (sha256 en hexadecimal minúscula del texto del aviso que el agente leyó) es opcional en v1.
script_version_id (opcional, core#1690, ADR #219) vincula la atestación a la versión de la biblioteca de guiones del tenant que leyó el agente. core comprueba que es una versión de ESTE tenant, que estaba aprobada y aún no retirada cuando la llamada comenzó (y no aprobada después de que terminara), y que su selector era un candidato para el estado y el tipo de deuda de la llamada (los hechos congelados en el compliance_snapshot del intento, nunca el registro de contacto de hoy); luego deriva el script_sha256 de la versión y la script_resolution (la rama de precedencia a la que pertenece el selector de la versión: exact, state, debt_type o tenant_default) y sella ambos con la atestación. Una versión reemplazada o retirada DESPUÉS de iniciada la llamada sigue siendo válida: lo que se atesta es lo que el agente vio, no lo que se resolvería hoy. Un script_version_id que no es un UUID, es de otro tenant, es un borrador, no califica para esta llamada o viene con un script_sha256 que no es su huella responde 422 con error.code: disclosure_script_mismatch, y NO se escribe NADA. script_resolution nunca se acepta del llamante (400). Sin script_version_id, la atestación es exactamente lo que siempre fue, y la respuesta no tiene la clave script_version_id ni script_resolution. Con la bandera disclosure_script_enforced del tenant activada y al menos una versión aprobada, una llamada en alcance debe nombrar su guion: sin script_version_id es 422 con error.code: disclosure_script_required; cuando ningún guion aprobado se resuelve para la llamada (none o undecidable) es 409 con error.code: disclosure_script_unresolvable; en ninguno de los dos casos se escribe nada. El mismo campo se acepta dentro del objeto disclosure de POST /v1/call_attempts/{id}/disposition, donde una discrepancia también deja sin escribir la corrección.
Una sola atestación «primera» por llamada: enviar una segunda sin supersedes_id es 409. Para rectificar, envía una nueva atestación cuyo supersedes_id sea la ACTUAL de la misma llamada; no se edita ni se elimina nada. Rectificar una que no es la actual es 409; un supersedes_id mal formado es 400.
QUIÉN atesta sigue la misma regla que POST /v1/call_attempts/{id}/disposition: una clave de máquina dd_ debe nombrar al agente en agent_id (ninguno → 400bad_request, no hay a quién atribuirla); una sesión ddu_ vinculada a un puesto de agente firma como ESE puesto, y nombrar a otro agente es 403; un ddw_ firma solo como su propio agente y solo una llamada propia (403 en caso contrario), y no se escribe nada. Comparte el freno de tasa de la disposición (429).
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)
delivered
json
{
"agent_id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
"outcome": "delivered"
}not_delivered
json
{
"agent_id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
"outcome": "not_delivered",
"reason": "consumer_hung_up"
}Respuestas
201
La atestación sellada.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
disclosure | object (DisclosureEvent) | sí | Una atestación sellada y de solo anexado. Declara lo que el agente DECLARÓ: evidence_kind es agent_attestation, nunca prueba de que el aviso se dijera o se escuchara. |
disclosure.id | string (uuid) | no | |
disclosure.tenant_id | string (uuid) | no | |
disclosure.call_attempt_id | string (uuid) | no | |
disclosure.debt_id | string (uuid) | no | |
disclosure.agent_id | string (uuid) | no | |
disclosure.evidence_kind | agent_attestation | no | |
disclosure.outcome | delivered · not_delivered | no | |
disclosure.reason | string | null | no | |
disclosure.note | string | null | no | |
disclosure.script_sha256 | string | null | no | |
disclosure.script_version_id | string (uuid) | no | Presente sólo cuando la atestación está vinculada a una versión de la biblioteca de guiones (core#1690). |
disclosure.script_resolution | exact · state · debt_type · tenant_default | no | Presente sólo con script_version_id; la rama de precedencia a la que pertenece el selector de la versión para esta llamada. |
disclosure.supersedes_id | string (uuid) | null | no | |
disclosure.occurred_at | string (date-time) | no | |
disclosure.created_at | string (date-time) | no |
400 — BadRequest
Petición mal formada (errores de forma/tipo, filtros no válidos, marcas de tiempo erróneas).
json
{
"error": {
"code": "bad_request",
"message": "from must be RFC-3339"
}
}401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — Forbidden
El tenant está suspendido. Una sesión del dashboard (ddu_) de un tenant cuya política de inicio de sesión (ADR 227) está en modo enforce responde 403 con el código login_policy_denied ante una solicitud desde una dirección o en una hora que la política no permite — el mismo cuerpo en ambos casos; la sesión no se revoca y una solicitud posterior dentro de la política se procesa. Un token de widget (ddw_) de un puesto queda sujeto a la misma política en cada solicitud (core#2406, indicador widget_login_honors_policy). Un tenant sintético (demo) responde 403 con el código synthetic_tenant en toda ruta que llegaría a un tercero — marcación manual, pierna de transferencia externa o de conferencia, envío de SMS (core#2251, adenda del ADR #280). Ningún pago lo cambia.
json
{
"error": {
"code": "forbidden",
"message": "tenant is suspended"
}
}404 — NotFound
Id desconocido, mal formado (no UUID) O id de otro tenant: RLS devuelve cero filas, así que los tres son indistinguibles por diseño (sin fuga de existencia, nunca un 403 para ids ajenos).
json
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}409 — Conflict
Transición de estado no válida, conflicto de unicidad, o un asiento que está ocupado/reservado.
json
{
"error": {
"code": "conflict",
"message": "invalid state transition"
}
}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 — DispositionThrottled
El freno de tasa de solicitudes NR-18 rechazó esta solicitud (reintentable en la siguiente ventana). code rate_limited. Acotado por el principal DURADERO más específico (agente > usuario > tenant, que sobrevive a la reautenticación), con un presupuesto PROPIO y más estrecho que el de los verbos de control: lo que acota es el crecimiento duradero de filas, ya que cada disposición aceptada hace un INSERT de una fila de corrección en el historial de intentos de solo anexado. Se rechaza ANTES de buscar el intento, por lo que esta respuesta es idéntica exista o no el id. Retry-After es el tiempo restante de la ventana fija.
json
{
"error": {
"code": "rate_limited",
"message": "disposition rate limit exceeded; retry later"
}
}