Skip to content

Deudas y leads ​

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.

Importación masiva de leads, detalle de deuda, CDR por deuda, paquete de defensa.

Endpoints ​

MétodoRutaResumen
GET/v1/debtsLista de cuentas + búsqueda (filtros, paginación por keyset)
POST/v1/debts/searchBuscar cuentas con el filtro en el cuerpo de la solicitud (teléfono incluido)
POST/v1/debts/importImportación masiva de leads en JSON o CSV (idempotente)
GET/v1/debts/{id}Deuda + contactos
GET/v1/debts/{id}/call_attemptsHistorial duradero de CDR por deuda
GET/v1/debts/{id}/messagesQué se envió a una deuda y por qué canal
GET/v1/debts/{id}/defense-packetPaquete de defensa ante litigios (paquete de evidencia sellado)
GET/v1/debts/{id}/defense-packet/archivesListar los paquetes de defensa ante litigios archivados de una deuda
POST/v1/debts/{id}/defense-packet/archivesArchivar el paquete de defensa ante litigios (almacén WORM + sello de tiempo RFC 3161)
GET/v1/debts/{id}/defense-packet/archives/{aid}/attestationKit de atestación de un paquete de defensa ante litigios archivado

Lista de cuentas + búsqueda (filtros, paginación por keyset) ​

http
GET /v1/debts

Una página de las cuentas del tenant, leída de la vista ancha debts_wide. Con ámbito de tenant: la deuda de otro tenant nunca se devuelve con ningún filtro ni término de búsqueda — un id, un consumer_ref o un teléfono que pertenezca a otro devuelve una página VACÍA, nunca un 403 (no se confirma la existencia).

Los números de teléfono se devuelven ENMASCARADOS (primary_phone_masked, *** + últimos cuatro). Un listado es una superficie de barrido; el E.164 completo de UNA cuenta queda en GET /v1/debts/{id}. account_number no forma parte de esta proyección en absoluto.

No existe el parámetro phone: un número en la cadena de consulta acabaría en los registros de acceso del ingress, por lo que ?phone= se rechaza con un 400 cuyo código es phone_not_in_query. Busca por teléfono con POST /v1/debts/search, que lo recibe en el cuerpo. consumer_ref y external_ref coinciden de forma EXACTA.

dialable_by selecciona las cuentas marcables en ese instante: una deuda que el motor de disposiciones no ha aparcado (next_dialable_at nulo) cuenta como marcable ahora y se incluye.

campaign_id selecciona la POBLACIÓN de la campaña — las cuentas cuyo propio campaign_id es esa campaña, más los miembros de cualquier lista asociada a ella (/v1/debt-lists) — cada cuenta una sola vez. El campo campaign_id de cada fila sigue siendo la campaña propia de la cuenta (su ranura directa).

campaign_id, state, debt_type, us_state, min_amount_cents y max_amount_cents son los criterios de SEGMENTO (ADR #221): un vocabulario cerrado validado en un solo lugar (Dialer.Debts.Segment) y combinados con AND. Un valor fuera del vocabulario es un 400, nunca una página vacía. Los segmentos seleccionan cuentas solo por atributos: phone, consumer_ref, external_ref y debtor_name nunca son criterios de segmento (este listado conserva sus propias búsquedas exactas por los tres primeros; el nombre del deudor no es buscable). Un parámetro de consulta que este endpoint no conoce se ignora, como antes.

include=dialability (ADR #269) añade a cada fila dialability: por qué la cuenta no es marcable y el consentimiento de cada teléfono, evaluado ahora con las MISMAS compuertas que ejecuta el marcador (DebtDialability). Es opcional y cuesta un número acotado de lecturas por PÁGINA, no por fila, por lo que la página se limita a 50: un limit mayor es un 400, nunca un recorte silencioso. Sin include el cuerpo es idéntico byte a byte al de siempre (el campo está ausente, no es null). dialable: true nunca significa "se realizará una llamada": consulta DebtDialability.not_evaluated_gates. Un valor de include desconocido es un 400.

Parámetros

NombreEnTipoObligatorioDescripción
campaign_idquerystring (uuid)no
statequeryopen · in_collection · promise_to_pay · paid · settled · disputed · closednoEstado exacto de la deuda. Un valor desconocido es un 400, no una página vacía.
state[]queryarreglo de open · in_collection · promise_to_pay · paid · settled · disputed · closednoVarios estados de deuda a la vez (state[]=open&state[]=disputed): una cuenta coincide cuando su estado es CUALQUIERA de ellos. Las repeticiones se ignoran; una entrada vacía o desconocida es un 400.
debt_typequeryother · student_loannoTipo exacto de deuda. Un valor desconocido es un 400, no una página vacía. Forma parte del vocabulario cerrado de segmentos (Dialer.Debts.Segment, ADR #221).
debt_type[]queryarreglo de other · student_loannoVarios tipos de deuda a la vez (debt_type[]=other&debt_type[]=student_loan); coincide con CUALQUIERA.
us_statequerystringnoEstado de EE. UU. del contacto PRINCIPAL de la cuenta: un código postal USPS de dos letras en mayúsculas (código de estado, posesión o militar; Dialer.Compliance.UsStates). Cualquier otra cosa (tx, XX) es un 400, no una página vacía. Una cuenta sin contacto nunca coincide.
us_state[]queryarreglo de stringnoVarios estados de EE. UU. a la vez (us_state[]=TX&us_state[]=NY); coincide con CUALQUIERA.
min_amount_centsqueryintegernoCota inferior (inclusiva) del saldo, en centavos.
max_amount_centsqueryintegernoCota superior (inclusiva) del saldo, en centavos.
dialable_byquerystring (date-time)noInstante RFC-3339; devuelve las cuentas marcables en ese instante o antes.
consumer_refquerystringnoId exacto de consumidor del CMS.
external_refquerystringnoId exacto de deuda del CMS.
limitqueryintegernoTamaño de página (por defecto 50, máximo 200). Con include=dialability el máximo es 50 y un valor mayor es un 400.
includequeryarreglo de dialabilitynoProyecciones adicionales que se añaden a cada fila, separadas por comas (include=dialability); include[]=dialability también se acepta. dialability (ADR #269): por qué la cuenta no es marcable y el consentimiento de cada teléfono, evaluado ahora con las compuertas propias del marcador, opcional y limitado a una página de 50. Un valor desconocido es un 400, nunca se ignora.
cursorquerystringnoCursor keyset opaco del next_cursor de la página anterior.

Respuestas

200 ​

Una página de cuentas.

CampoTipoObligatorioDescripción
debtsarreglo de DebtSummarysí
debts[].idstring (uuid)no
debts[].tenant_idstring (uuid)no
debts[].campaign_idstring (uuid) | nullnoLa ranura DIRECTA de la deuda, como en Debt. Una deuda que también está en una campaña por una lista se lista bajo ?campaign_id= de esa campaña con este campo sin cambios.
debts[].campaign_namestring | nullno
debts[].external_refstringno
debts[].consumer_refstringno
debts[].debt_typeother · student_loanno
debts[].amount_centsintegerno
debts[].currencystringno
debts[].stateopen · in_collection · promise_to_pay · paid · settled · disputed · closedno
debts[].last_conversation_atstring (date-time) | nullno
debts[].next_dialable_atstring (date-time) | nullnoNulo significa que la deuda es marcable ahora (no está aparcada por el motor de disposiciones).
debts[].primary_phone_maskedstring | nullno*** + los últimos cuatro del contacto principal; null cuando la deuda no tiene contacto.
debts[].primary_line_typemobile · landline · voip · unknown | nullno
debts[].primary_timezonestring | nullno
debts[].primary_us_statestring | nullno
debts[].primary_citystring | nullno
debts[].contact_countintegerno
debts[].created_atstring (date-time)no
debts[].updated_atstring (date-time)no
debts[].dialabilityobject (DebtDialability)noPresente solo con include=dialability (ADR #269). El veredicto es "ahora" y envejece en segundos: informa, nunca concede permiso; el motor decide en el momento de marcar. Se evalúa con las compuertas PROPIAS del marcador sobre una única lectura de instantánea por página.
debts[].dialability.evaluated_atstring (date-time)sí
debts[].dialability.dialablebooleansíEl teléfono que el marcador llamaría pasa todas las compuertas evaluadas Y no se aplica ningún motivo a nivel de cuenta. NO es una promesa de que se realizará una llamada: consulta not_evaluated_gates.
debts[].dialability.blocked_reasonsarreglo de DebtBlockedReasonsíLos motivos de la cuenta más los del teléfono que el marcador llamaría; vacío si y solo si dialable. ["no_phones"] cuando la cuenta no tiene contacto.
debts[].dialability.eligible_atstring (date-time) | nullsíevaluated_at cuando nada bloquea. De lo contrario, el primer instante, dentro de 9 días, en que las compuertas dependientes del tiempo (horario de silencio, Reg F, la matriz de estados) dejan de bloquear el teléfono que el marcador llamaría, SOLO cuando nada más lo bloquea; null cuando se aplica otro motivo o no existe tal instante.
debts[].dialability.phonesarreglo de DebtDialabilityPhonesíHasta 5 contactos, en el orden de rango del marcador. Vacío cuando la cuenta no pudo evaluarse.
debts[].dialability.phones[].phone_maskedstring | nullsí*** + los últimos cuatro dígitos, nunca más.
debts[].dialability.phones[].line_typemobile · landline · voip · unknown | nullsí
debts[].dialability.phones[].feed_candidatebooleansíVerdadero para el teléfono que el marcador llamaría (el contacto de rango 1 de la cuenta: primero el principal, luego el más antiguo). Los demás son informativos.
debts[].dialability.phones[].consentobject (DebtDialabilityConsent)síEl consentimiento registrado para el número. Solo el estado, el canal y el instante: nunca la fuente en texto libre.
debts[].dialability.phones[].dialablebooleansíTodas las compuertas evaluadas dejan pasar ESTE teléfono; blocked_reasons está vacío si y solo si es verdadero.
debts[].dialability.phones[].blocked_reasonsarreglo de DebtBlockedReasonsíTODOS los motivos, no solo el primero (las compuertas no se cortocircuitan), en el orden de la cadena de compuertas del marcador.
debts[].dialability.phones_truncatedbooleansíLa cuenta tiene más contactos de los que lista phones.
debts[].dialability.not_evaluated_gatesarreglo de stringsíLo que dialable NO cubre porque depende de la campaña, la llamada o el tenant (divulgación de IA, voz artificial, atestación del identificador de llamada, estado de la campaña, plan, topes de gasto, ritmo de marcación).
next_cursorstring | nullsíCursor keyset opaco; null en la última página.

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

Buscar cuentas con el filtro en el cuerpo de la solicitud (teléfono incluido) ​

http
POST /v1/debts/search

Mismos filtros, paginación, ámbito de tenant (RLS) y proyección enmascarada que GET /v1/debts, con el filtro en un cuerpo JSON. Existe para que el número de teléfono de un deudor nunca viaje en la URL (registros de acceso del ingress y del proxy). Una búsqueda es una lectura: no cambia ningún estado.

phone busca mediante índice ciego determinista y exige un E.164 normalizado (+13125550100); cualquier otra cosa es un 400, no una página vacía. Una clave del cuerpo fuera del esquema es un 400. Un cuerpo vacío {} devuelve la primera página de toda la cartera.

"include": ["dialability"] añade el veredicto de marcabilidad por cuenta, con el mismo significado, límite (página de 50) y costo que en GET /v1/debts.

Cuerpo de la petición (JSON, obligatorio)

CampoTipoObligatorioDescripción
campaign_idstring (uuid)no
stateopen · in_collection · promise_to_pay · paid · settled · disputed · closed | arreglo de open · in_collection · promise_to_pay · paid · settled · disputed · closednoUn estado de deuda o varios (coincide con CUALQUIERA).
debt_typeother · student_loan | arreglo de other · student_loannoUn tipo de deuda o varios (coincide con CUALQUIERA).
us_statestring | arreglo de stringnoUn código USPS de dos letras del contacto PRINCIPAL o varios (coincide con CUALQUIERA).
min_amount_centsintegerno
max_amount_centsintegerno
dialable_bystring (date-time)no
phonestringnoE.164 normalizado de cualquier contacto de la deuda.
consumer_refstringno
external_refstringno
limitintegernoTamaño de página. Con include el máximo es 50 y un valor mayor es un 400.
cursorstringno
includearreglo de dialabilitynoProyecciones adicionales para cada fila (["dialability"], ADR #269): mismo significado, límite y costo que include en GET /v1/debts.

Respuestas

200 ​

Una página de cuentas.

CampoTipoObligatorioDescripción
debtsarreglo de DebtSummarysí
debts[].idstring (uuid)no
debts[].tenant_idstring (uuid)no
debts[].campaign_idstring (uuid) | nullnoLa ranura DIRECTA de la deuda, como en Debt. Una deuda que también está en una campaña por una lista se lista bajo ?campaign_id= de esa campaña con este campo sin cambios.
debts[].campaign_namestring | nullno
debts[].external_refstringno
debts[].consumer_refstringno
debts[].debt_typeother · student_loanno
debts[].amount_centsintegerno
debts[].currencystringno
debts[].stateopen · in_collection · promise_to_pay · paid · settled · disputed · closedno
debts[].last_conversation_atstring (date-time) | nullno
debts[].next_dialable_atstring (date-time) | nullnoNulo significa que la deuda es marcable ahora (no está aparcada por el motor de disposiciones).
debts[].primary_phone_maskedstring | nullno*** + los últimos cuatro del contacto principal; null cuando la deuda no tiene contacto.
debts[].primary_line_typemobile · landline · voip · unknown | nullno
debts[].primary_timezonestring | nullno
debts[].primary_us_statestring | nullno
debts[].primary_citystring | nullno
debts[].contact_countintegerno
debts[].created_atstring (date-time)no
debts[].updated_atstring (date-time)no
debts[].dialabilityobject (DebtDialability)noPresente solo con include=dialability (ADR #269). El veredicto es "ahora" y envejece en segundos: informa, nunca concede permiso; el motor decide en el momento de marcar. Se evalúa con las compuertas PROPIAS del marcador sobre una única lectura de instantánea por página.
debts[].dialability.evaluated_atstring (date-time)sí
debts[].dialability.dialablebooleansíEl teléfono que el marcador llamaría pasa todas las compuertas evaluadas Y no se aplica ningún motivo a nivel de cuenta. NO es una promesa de que se realizará una llamada: consulta not_evaluated_gates.
debts[].dialability.blocked_reasonsarreglo de DebtBlockedReasonsíLos motivos de la cuenta más los del teléfono que el marcador llamaría; vacío si y solo si dialable. ["no_phones"] cuando la cuenta no tiene contacto.
debts[].dialability.eligible_atstring (date-time) | nullsíevaluated_at cuando nada bloquea. De lo contrario, el primer instante, dentro de 9 días, en que las compuertas dependientes del tiempo (horario de silencio, Reg F, la matriz de estados) dejan de bloquear el teléfono que el marcador llamaría, SOLO cuando nada más lo bloquea; null cuando se aplica otro motivo o no existe tal instante.
debts[].dialability.phonesarreglo de DebtDialabilityPhonesíHasta 5 contactos, en el orden de rango del marcador. Vacío cuando la cuenta no pudo evaluarse.
debts[].dialability.phones[].phone_maskedstring | nullsí*** + los últimos cuatro dígitos, nunca más.
debts[].dialability.phones[].line_typemobile · landline · voip · unknown | nullsí
debts[].dialability.phones[].feed_candidatebooleansíVerdadero para el teléfono que el marcador llamaría (el contacto de rango 1 de la cuenta: primero el principal, luego el más antiguo). Los demás son informativos.
debts[].dialability.phones[].consentobject (DebtDialabilityConsent)síEl consentimiento registrado para el número. Solo el estado, el canal y el instante: nunca la fuente en texto libre.
debts[].dialability.phones[].dialablebooleansíTodas las compuertas evaluadas dejan pasar ESTE teléfono; blocked_reasons está vacío si y solo si es verdadero.
debts[].dialability.phones[].blocked_reasonsarreglo de DebtBlockedReasonsíTODOS los motivos, no solo el primero (las compuertas no se cortocircuitan), en el orden de la cadena de compuertas del marcador.
debts[].dialability.phones_truncatedbooleansíLa cuenta tiene más contactos de los que lista phones.
debts[].dialability.not_evaluated_gatesarreglo de stringsíLo que dialable NO cubre porque depende de la campaña, la llamada o el tenant (divulgación de IA, voz artificial, atestación del identificador de llamada, estado de la campaña, plan, topes de gasto, ritmo de marcación).
next_cursorstring | nullsíCursor keyset opaco; null en la última página.

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

Importación masiva de leads en JSON o CSV (idempotente) ​

http
POST /v1/debts/import

Acepta el sobre JSON existente o una carga multipart/form-data. El formulario multipart lleva file (text/csv), mapping (un objeto JSON cuyas claves son destinos de importación y cuyos valores son nombres de cabecera del CSV), y separator opcional (comma, semicolon o tab; por defecto comma). Destinos de deuda: los nombres de campo JSON menos state. Una columna last_conversation_at mapeada pasa por la misma validación que el campo JSON (RFC 3339 con desfase UTC, nunca en el futuro) y siembra la misma conversación cms_import; una celda incorrecta falla solo su fila, y una celda en blanco equivale a omitir el campo. Se puede mapear un único contacto con contact.phone_e164, contact.line_type, contact.timezone, contact.us_state, contact.city, contact.postal_code, contact.is_primary o contact.consumer_ref. El nombre del deudor se mapea a debtor_name (opcional, máximo 200 caracteres, cifrado en reposo; consulta DebtImportItem). Una columna delinquent_since mapeada (una fecha YYYY-MM-DD) pasa por la misma validación que el campo JSON; una celda incorrecta falla solo su fila.

Con ?mapping_id= el formulario multipart lleva solo file: la plantilla almacenada aporta el mapeo y el separador, y enviar también mapping o separator se rechaza con 422. Una plantilla que mapea un mismo campo desde dos columnas, un campo que el importador CSV no conoce, o options.default_currency (la carga no tiene moneda por defecto), responde 422 y no importa nada.

El CSV es UTF-8; se acepta un BOM inicial. El límite de archivo por defecto es 4 000 000 de bytes y el límite de filas por defecto es 10 000 filas de datos. Se gobiernan por tenant mediante las claves del catálogo R19 debt_csv_max_bytes y debt_import_max_rows; esta última también se aplica a JSON. El análisis es fail-closed: un CSV mal formado, una codificación no válida, campos multipart ausentes o desconocidos, cabeceras mapeadas ausentes o un separador no admitido no importan nada y responden 422; un campo demasiado grande responde 413. El archivo cargado permanece en memoria y nunca se escribe en disco ni se registra.

Tras el análisis y el mapeo, ambas representaciones entran en el MISMO pipeline de depuración, normalización e importación por elemento. Tolerancia a fallos por elemento, el límite de filas indicado arriba por solicitud y un máximo de 100 contactos por deuda. Las deudas se actualizan o insertan por external_ref (LA clave del contador de Reg F) y los contactos por phone_e164. Reenviar la misma carga es seguro. Las filas fallidas devuelven solo su índice base cero y el motivo; no se repite ningún valor de celda del CSV.

Un campo no reconocido en un elemento — o en uno de sus contactos — hace fallar ESE elemento y nada más: vuelve en failed con el índice propio del elemento y un mensaje que nombra la clave, mientras todos los demás elementos se importan. Un contacto incorrecto se localiza por su posición dentro del elemento (contact 0: ...). La tolerancia a fallos es la razón por la que esta ruta responde por elemento en lugar de rechazar todo el cuerpo con un 400 como hacen las rutas de objeto único.

Un contacto enviado con is_primary: true afirma cuál es EL principal de esa deuda: el principal anterior se degrada en la misma transacción y se conserva (los números nunca se borran), y el nuevo se inserta o se promueve. Omitir is_primary, o enviar false, nunca cambia el principal existente. Un elemento con más de un is_primary: true falla ESE elemento y no escribe nada — la corrección es ambigua y que gane el último la perdería en silencio.

La importación es ADITIVA sobre los campos de datos de un contacto, y son TRIESTADO. Omitir un campo y enviarlo como "" (o como espacios en blanco) significan lo mismo — "el feed no lo dijo" — y el valor almacenado queda exactamente como estaba; una reimportación puede por tanto CORREGIR un valor pero nunca BORRARLO. Enviar el campo como null tampoco es un borrado: hace FALLAR ese elemento, con un mensaje que nombra el campo, y no se escribe nada para él. No hay ningún verbo en esta ruta que vacíe un campo almacenado, y es deliberado: "" es lo que un exportador pone en cada celda en blanco de un feed masivo, así que leerlo como "borrar" haría que una exportación rutinaria eliminara en silencio el estado y el código postal que deciden el tope por deuda de NYC. line_type: "unknown" NO es un valor vacío — es un miembro publicado del enum, de modo que el feed SÍ está reclasificando el número y se aplica.

Parámetros

NombreEnTipoObligatorioDescripción
mapping_idquerystring (uuid)noLa plantilla de mapeo de importación con la que se mapeó el archivo. Se resuelve ANTES de que se importe nada (404 cuando no es del tenant, 409 cuando está archivada: no se escribe nada) y la versión usada se registra después de la importación (GET /v1/import-mappings/{id}/uses). En el formulario multipart de POST /v1/debts/import la plantilla reemplaza las partes mapping y separator. Si se omite, la importación no cambia.
mapping_versionqueryintegernoLa versión de mapping_id que se registra. Por defecto, la actual. Requiere mapping_id.
import_idquerystring (uuid)noNombra UNA importación que el cliente envía en varias solicitudes (fragmentos). Los usos que esas solicitudes dejan se leen en GET /v1/import-mappings/{id}/uses como un único uso — los conteos sumados, id = este import_id — siempre que compartan la versión y la lista. Requiere mapping_id (400 en caso contrario, y para un valor que no sea un UUID). Si se omite, cada solicitud es su propio uso.

Cuerpo de la petición (JSON, obligatorio)

CampoTipoObligatorioDescripción
debtsarreglo de DebtImportItemsí
debts[].external_refstringsíLA clave del contador de Reg F (clave de upsert).
debts[].consumer_refstringsí
debts[].debt_typeother · student_loanno
debts[].account_numberstringno
debts[].debtor_namestring | nullnoNombre completo opcional del deudor, texto libre. Los espacios en blanco se colapsan y se recortan; un valor en blanco se almacena como sin nombre. Más de 200 caracteres hace fallar el ELEMENTO (error de fila debtor_name should be at most 200 character(s)), no el lote. Cifrado en reposo, no buscable, y nunca se envía a analítica, webhooks ni registros. Como los demás campos de la deuda es una instantánea: una reimportación que lo omite borra el nombre almacenado.
debts[].amount_centsintegerno
debts[].currencystringno
debts[].stateopen · in_collection · promise_to_pay · paid · settled · disputed · closedno
debts[].campaign_idstring (uuid)no
debts[].prior_attempts_7dintegernoCuántas llamadas sobre ESTA deuda ya realizó el cliente desde su sistema anterior en los últimos 7 días. Sin él, el contador 7-en-7 de Reg F arranca A CIEGAS en una cartera migrada — solo vio los intentos que realizó esta plataforma — y una cartera trasladada a mitad de ciclo puede agotar el tope legal en su primera semana. Se materializa como filas de intento que la compuerta ya lee, de modo que la compuerta en sí no cambia. Es un TOTAL para la ventana, no un delta: reenviar la misma carga no suma nada, y reenviar un número MENOR nunca retracta el historial (el conteo solo sube, como last_conversation_at). Los valores superiores a 7 se almacenan como 7 — 7 ya satura todos los topes de frecuencia que tiene el motor, así que el veredicto es idéntico y el tope solo acota la escritura. Un valor que no sea un entero >= 0 hace fallar ESE elemento, como cualquier otro campo incorrecto en esta ruta; omitirlo no cambia nada.
debts[].last_conversation_atstring (date-time)noCuándo habló el cliente por última vez con el deudor, según su sistema anterior (RFC 3339 con desfase UTC). Sin él, el periodo de enfriamiento de 7 días posterior al contacto de Reg F (12 CFR 1006.14(b)(2)(i)(B)) arranca A CIEGAS en una cartera migrada: la plataforma solo conoce las conversaciones que registró y podría volver a llamar dentro de la ventana. Se materializa como una fila de conversations con source: cms_import, que la compuerta ya lee, de modo que la compuerta en sí no cambia; el ancla last_conversation_at de la deuda avanza hasta ella. Reenviar la misma carga no suma nada, y una fecha MÁS ANTIGUA nunca retrocede el ancla. Los reportes que miden la actividad de la plataforma (penetración, tasa de abandono, la exportación de intentos) no la cuentan. Un valor mal formado, uno sin desfase UTC o uno en el futuro hace fallar ESE elemento (una fecha futura mantendría abierto el enfriamiento más allá de su ventana); omitirlo no cambia nada.
debts[].delinquent_sincestring (date)noLa fecha de calendario en que la deuda entró en mora (YYYY-MM-DD), según el sistema anterior del cliente. Es la antigüedad de la deuda: los filtros de segmentación como "en mora de 31 a 90 días" se calculan contra ella. Es una instantánea, como debtor_name: reimportar la deuda sin ella la borra. Una deuda sin fecha nunca coincide con un filtro de antigüedad. Cualquier cosa que no sea una fecha de calendario real en ese formato exacto (null, una cadena vacía, un número, una marca de tiempo, 2026-02-30) o una fecha futura hace fallar ESE elemento; omitirla está bien. En una importación CSV una celda en blanco equivale a omitir el campo.
debts[].contactsarreglo de objectno
debts[].contacts[].phone_e164stringsíE.164 (clave de upsert dentro de la deuda).
debts[].contacts[].line_typemobile · landline · voip · unknownno
debts[].contacts[].timezonestringnoIANA.
debts[].contacts[].us_statestringno
debts[].contacts[].citystringno
debts[].contacts[].postal_codestringnoCódigo postal del consumidor. Es lo que resuelve el distrito de NYC para el tope por deuda: sin él, un contacto de NY no puede descartarse de la ciudad y recibe el tope estricto. Se actualiza en la reimportación solo cuando la carga lo incluye — omitirlo, o enviarlo como "", nunca borra un valor almacenado, y enviarlo como null hace fallar el elemento. Misma regla que consumer_ref, timezone, us_state y city: el contrato es uno solo, aunque esta columna sea la única anulable de las seis.
debts[].contacts[].is_primarybooleannoAfirma EL contacto principal de la deuda. En la importación, true degrada al principal anterior (se conserva, nunca se borra) y promueve a este; omitido o false deja intacto al principal existente. Más de uno por elemento hace fallar el elemento.
debts[].contacts[].consumer_refstringno
json
{
  "debts": [
    {
      "external_ref": "cms-debt-0001",
      "consumer_ref": "cms-consumer-9001",
      "debt_type": "other",
      "account_number": "ACC-1001",
      "debtor_name": "Ana María Pérez",
      "amount_cents": 125000,
      "currency": "USD",
      "campaign_id": "3f2c1b0a-9d8e-4c7b-a6f5-4e3d2c1b0a99",
      "contacts": [
        {
          "phone_e164": "+13125551001",
          "line_type": "mobile",
          "timezone": "America/Chicago",
          "us_state": "IL",
          "city": "Chicago",
          "is_primary": true
        }
      ]
    }
  ]
}

Respuestas

200 ​

Resultado de la importación (tolerancia a fallos por elemento).

CampoTipoObligatorioDescripción
importedintegersí
failedarreglo de objectsí
failed[].indexintegerno
failed[].errorstringno
mappingobjectnoPresente solo cuando la solicitud nombró mapping_id: la versión de la plantilla registrada como usada.
mapping.idstring (uuid)sí
mapping.versionintegersí
json
{
  "imported": 1,
  "failed": []
}

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

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

413 ​

El archivo CSV u otro campo multipart supera su límite de bytes.

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 — ImportThrottled ​

La importación se rechazó ANTES de leer ninguna fila (core#2505). Dos códigos, ambos con Retry-After. import_in_progress: el tenant ya tiene en ejecución su máximo de importaciones (2 por defecto, por nodo); reintenta cuando termine una. rate_limited: más de 10 importaciones en la ventana fija (por defecto) para el tenant, que es un límite por tenant y no por clave porque el pool de conexiones que drena una importación es el de la celda. Se aplica por igual a POST /v1/debts/import y POST /v1/debt-lists/{id}/import. La cuota de solicitudes por clave aún puede responder rate_limited primero.

in_flight

json
{
  "error": {
    "code": "import_in_progress",
    "message": "an import of this tenant is still running; retry when it finishes"
  }
}

window

json
{
  "error": {
    "code": "rate_limited",
    "message": "import rate limit exceeded; retry later"
  }
}

Deuda + contactos ​

http
GET /v1/debts/{id}

Parámetros

NombreEnTipoObligatorioDescripción
idpathstring (uuid)síUUID del recurso. Los ids mal formados o de otro tenant se leen como 404.

Respuestas

200 ​

La deuda con sus contactos.

CampoTipoObligatorioDescripción
debtobject (Debt)sí
debt.idstring (uuid)no
debt.tenant_idstring (uuid)no
debt.campaign_idstring (uuid) | nullnoLa ranura DIRECTA de la deuda: la campaña a la que se importó o asignó. No es la respuesta completa a "qué campañas trabajan esta deuda": una deuda también está en la población de toda campaña a la que esté asociada una lista que la contenga (/v1/debt-lists).
debt.external_refstringno
debt.consumer_refstringno
debt.debt_typeother · student_loanno
debt.account_numberstringno
debt.debtor_namestring | nullnoEl nombre del deudor tal como lo envió el CMS en la importación (core#1529); null cuando no envió ninguno. Cifrado en reposo. Lo sirven GET /v1/debts/{id}, la tarjeta del deudor y la tarjeta de llamada del agente; NO lo sirven el listado GET /v1/debts, los webhooks ni la exportación de analítica.
debt.amount_centsintegerno
debt.currencystringno
debt.stateopen · in_collection · promise_to_pay · paid · settled · disputed · closedno
debt.last_conversation_atstring (date-time) | nullno
debt.contactsarreglo de ContactnoPresente solo en GET /v1/debts/{id}.
debt.contacts[].idstring (uuid)no
debt.contacts[].debt_idstring (uuid)no
debt.contacts[].consumer_refstringno
debt.contacts[].phone_e164stringno
debt.contacts[].line_typemobile · landline · voip · unknownno
debt.contacts[].timezonestring | nullno
debt.contacts[].us_statestring | nullno
debt.contacts[].citystring | nullno
debt.contacts[].postal_codestring | nullno
debt.contacts[].is_primarybooleanno
debt.created_atstring (date-time)no
debt.updated_atstring (date-time)no

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

Historial duradero de CDR por deuda ​

http
GET /v1/debts/{id}/call_attempts

Historial duradero completo de la deuda (originales + correcciones compensatorias), paginado por keyset. Acepta los mismos filtros que GET /v1/call_attempts; el debt_id de la ruta siempre prevalece sobre cualquier ?debt_id= de la consulta.

Parámetros

NombreEnTipoObligatorioDescripción
idpathstring (uuid)síUUID del recurso. Los ids mal formados o de otro tenant se leen como 404.
campaign_idquerystring (uuid)no
agent_idquerystring (uuid)no
dispositionquerystringnoCoincidencia 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.
fromquerystring (date-time)noLímite inferior RFC 3339 (campo específico del endpoint; por defecto los últimos 30 días UTC donde se indica).
toquerystring (date-time)noLímite superior RFC 3339.
limitqueryintegernoTamaño de página (por defecto 100, máximo 1000).
cursorquerystringnoCursor keyset opaco del next_cursor de la página anterior.

Respuestas

200 ​

Una página de filas de CDR.

CampoTipoObligatorioDescripción
call_attemptsarreglo de CallAttemptsí
call_attempts[].idstring (uuid)no
call_attempts[].tenant_idstring (uuid)no
call_attempts[].debt_idstring (uuid)no
call_attempts[].campaign_idstring (uuid) | nullno
call_attempts[].agent_idstring (uuid) | nullno
call_attempts[].consumer_refstringno
call_attempts[].debt_keystringno
call_attempts[].call_uuidstring | nullno
call_attempts[].from_numberstring | nullno
call_attempts[].to_numberstringno
call_attempts[].started_atstring (date-time) | nullno
call_attempts[].answered_atstring (date-time) | nullno
call_attempts[].ended_atstring (date-time) | nullno
call_attempts[].dispositionstring | nullnoDisposiciones 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_causestring | nullno
call_attempts[].sip_response_codeinteger | nullno
call_attempts[].compliance_snapshotobject | nullnoEvidencia congelada de la decisión en el momento de originar.
call_attempts[].amd_verdictstring | nullno
call_attempts[].recording_uristring | nullnoDónde quedó la grabación de la llamada (ruta u URI del objeto); null cuando la llamada no se grabó.
call_attempts[].ai_usedbooleannoSi el bot de IA de voz se ejecutó en esta llamada (la clave de medición del complemento de IA).
call_attempts[].notestring | nullnoLa 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_idstring (uuid) | nullno
call_attempts[].inserted_atstring (date-time)no
call_attempts[].disclosureobject (CallAttemptDisclosure)noLo 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.statusattested · declared_not_delivered · unattested · not_applicable · undecidablesí
call_attempts[].disclosure.event_idstring (uuid) | nullsíLa atestación vigente (null cuando no hay ninguna).
call_attempts[].disclosure.atstring (date-time) | nullsíHora del servidor de la atestación vigente.
call_attempts[].disclosure.revisedbooleansítrue cuando el intento tiene más de una atestación (una rectifica a otra).
call_attempts[].disclosure.scriptCallAttemptDisclosureScript | nullnoSolo 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_matchmatched · other_version · unscripted · not_applicablenoSó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_cursorstring | nullsíCursor keyset opaco; null en la última página.

400 — BadRequest ​

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

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

401 — Unauthorized ​

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

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

404 — NotFound ​

Id desconocido, mal formado (no UUID) O id de otro tenant: RLS devuelve cero filas, así que los tres son indistinguibles por diseño (sin fuga de existencia, nunca un 403 para ids ajenos).

json
{
  "error": {
    "code": "not_found",
    "message": "resource not found for this tenant"
  }
}

429 — ApiKeyRateLimited ​

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

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

Qué se envió a una deuda y por qué canal ​

http
GET /v1/debts/{id}/messages

Lectura unificada por deuda sobre los libros de SMS y de correo electrónico (core#1772). Es un modelo de lectura DERIVADO en el momento de leer (ADR #218): sin vista materializada. Cada entrada es un ORIGINAL saliente; status es el vigente (la última transición). Con ventana (from/to, máximo 31 días, por defecto los últimos 31) y paginación por keyset (por defecto 50, máximo 200); channel limita a un solo libro.

Parámetros

NombreEnTipoObligatorioDescripción
idpathstring (uuid)síUUID del recurso. Los ids mal formados o de otro tenant se leen como 404.
fromquerystring (date-time)noLímite inferior RFC 3339 (campo específico del endpoint; por defecto los últimos 30 días UTC donde se indica).
toquerystring (date-time)noLímite superior RFC 3339.
channelquerysms · emailno
limitqueryintegernoTamaño de página (por defecto 100, máximo 1000).
cursorquerystringnoCursor keyset opaco del next_cursor de la página anterior.

Respuestas

200 ​

Una página de mensajes enviados, del más reciente al más antiguo.

CampoTipoObligatorioDescripción
messagesarreglo de DebtMessageno
messages[].channelsms · emailno
messages[].idstring (uuid)no
messages[].statusstringnoEstado vigente (la última transición del original).
messages[].recipientstring | nullno
messages[].subjectstring | nullno
messages[].body_previewstring | nullno
messages[].provider_idstring (uuid) | nullno
messages[].campaign_idstring (uuid) | nullno
messages[].debt_idstring (uuid)no
messages[].occurred_atstring (date-time)no
messages[].last_event_atstring (date-time)no
next_cursorstring | nullno

400 — BadRequest ​

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

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

401 — Unauthorized ​

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

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

404 — NotFound ​

Id desconocido, mal formado (no UUID) O id de otro tenant: RLS devuelve cero filas, así que los tres son indistinguibles por diseño (sin fuga de existencia, nunca un 403 para ids ajenos).

json
{
  "error": {
    "code": "not_found",
    "message": "resource not found for this tenant"
  }
}

429 — ApiKeyRateLimited ​

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

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

Paquete de defensa ante litigios (paquete de evidencia sellado) ​

http
GET /v1/debts/{id}/defense-packet

UN solo paquete de evidencia JSON sellado por deuda, ensamblado en una única transacción del tenant: historial completo e inmutable de CDR con instantáneas de cumplimiento congeladas, rechazos de compuertas, estado de consentimiento/DNC/cese y desistimiento (C&D), promesas, rellamadas, auditoría de supervisión y las versiones de política vigentes. integrity.digest es un SHA-256 sobre el JSON canónico SIN la clave integrity (claves de objeto ordenadas bytewise, sin espacios no significativos: cualquier tercero puede reverificarlo; receta en API.md). Con ?format=zip (o Accept: application/zip) entrega un ZIP con packet.json + summary.txt con Content-Disposition: attachment y X-Artifact-SHA256 calculado sobre los bytes del ZIP.

Parámetros

NombreEnTipoObligatorioDescripción
idpathstring (uuid)síUUID del recurso. Los ids mal formados o de otro tenant se leen como 404.
formatqueryzipnozip para el artefacto descargable (equivalente: Accept: application/zip).

Respuestas

200 ​

El paquete de evidencia sellado (sobre JSON o artefacto ZIP).

CampoTipoObligatorioDescripción
packet"litigation_defense"no
packet_versionintegerno
tenant_idstring (uuid)no
packet_generated_atstring (date-time)no
debtobjectno
contactsarreglo de objectno
call_attemptsarreglo de objectnoHistorial COMPLETO, originales + correcciones, cada uno con su compliance_snapshot congelado.
conversationsarreglo de objectnoAnclas de contacto de Reg F G5. Cada una lleva source: platform (registrada por este sistema) o cms_import (declarada por el cliente al importar).
gate_blocksarreglo de objectnoTodo rechazo duradero (reason_detail congelado).
consentsarreglo de objectno
dncobjectno
dnc.tenant_listingsarreglo de objectno
dnc.global_listingsarreglo de objectno
dnc.reassigned_numbersarreglo de objectno
cease_and_desistarreglo de objectnoA nivel de consumidor (alcance "") + por deuda.
promisesarreglo de objectno
callbacksarreglo de objectno
suppression_eventsarreglo de objectnoVersión 5 (core#2554). El historial de las exclusiones (opt-out) de los números de la deuda y de su consumer_ref, de la más antigua a la más reciente: kind (dnc_added, dnc_removed, consent_granted, consent_revoked, cease_filed), actor (user:<id>, key:<id>, system:sms_inbound, system:ivr…), source, channel, effective_at y occurred_at. El número se publica sólo con sus últimos cuatro dígitos. Cada evento queda sellado en la cadena del tenant (subject_table: suppression_events, en audit_chain.links). Los eventos existen desde el lanzamiento que introdujo el historial: una supresión anterior no tiene ninguno.
supervision_actionsarreglo de objectno
conference_legsarreglo de objectnoVersión 3 (core#1701, ADR #223). Los TERCEROS que se unieron a una llamada de la deuda como pierna de conferencia: las filas call_attempts propias de cada pierna, originales y correcciones (corrects_id). No llevan deuda, así que call_attempts nunca las lista; compliance_snapshot.conference_of nombra la llamada a la que se unieron y la corrección de cierre indica cuándo y por qué terminó la pierna (conference_call_ended cuando la llamada terminó primero).
conference_actionsarreglo de objectnoVersión 3. Los comandos conference_add / conference_remove sobre las llamadas de la deuda: quién fue añadido (destination), la pierna (conf=<uuid> en reason), la atestación del consumidor (consumer_agreed=true) y el resultado.
policy_versionsarreglo de stringno
disclosure_eventsarreglo de DisclosureEventnoVersión 2 (core#1587). TODAS las atestaciones del aviso de cobranza sobre las llamadas de la deuda, también las que una atestación posterior rectificó (supersedes_id). Indican lo que el agente DECLARÓ. Los eslabones de cadena que las sellan viajan en audit_chain.links (subject_table: call_disclosure_events).
disclosure_events[].idstring (uuid)no
disclosure_events[].tenant_idstring (uuid)no
disclosure_events[].call_attempt_idstring (uuid)no
disclosure_events[].debt_idstring (uuid)no
disclosure_events[].agent_idstring (uuid)no
disclosure_events[].evidence_kindagent_attestationno
disclosure_events[].outcomedelivered · not_deliveredno
disclosure_events[].reasonstring | nullno
disclosure_events[].notestring | nullno
disclosure_events[].script_sha256string | nullno
disclosure_events[].script_version_idstring (uuid)noPresente sólo cuando la atestación está vinculada a una versión de la biblioteca de guiones (core#1690).
disclosure_events[].script_resolutionexact · state · debt_type · tenant_defaultnoPresente sólo con script_version_id; la rama de precedencia a la que pertenece el selector de la versión para esta llamada.
disclosure_events[].supersedes_idstring (uuid) | nullno
disclosure_events[].occurred_atstring (date-time)no
disclosure_events[].created_atstring (date-time)no
disclosure_summaryobjectnoVersión 2. Conteos por intento de la deuda: contacted (en alcance o ya atestado) = attested + unattested, donde attested incluye los declared_not_delivered (listados aparte). Las llamadas undecidable aún no tienen fila final y no están en contacted.
disclosure_summary.contactedintegerno
disclosure_summary.attestedintegerno
disclosure_summary.declared_not_deliveredintegerno
disclosure_summary.unattestedintegerno
disclosure_summary.undecidableintegerno
disclosure_scriptsarreglo de DisclosureScriptnoVersión 4 (core#1691, ADR #219 Decision 7). Toda versión de guion que alguna atestación de la deuda NOMBRA (script_version_id en una fila de disclosure_events), con el text completo, text_sha256, legal_ref, authored_by, status y los eventos de approval / retirement (actor, reviewed_by, occurred_at). Una versión retirada después de la llamada sigue listada; una atestación que no nombra ninguna versión no añade nada. Vacío cuando ninguna atestación nombra una.
disclosure_scripts[].idstring (uuid)sí
disclosure_scripts[].tenant_idstring (uuid)sí
disclosure_scripts[].kindcollection_noticesí
disclosure_scripts[].jurisdictionstringsí
disclosure_scripts[].debt_typestringsí
disclosure_scripts[].versionintegersíPor tenant y selector.
disclosure_scripts[].textstringsí
disclosure_scripts[].text_sha256stringsíSHA-256 (hex en minúsculas) de text, derivado por core.
disclosure_scripts[].legal_refstringsí
disclosure_scripts[].authored_bystringsí
disclosure_scripts[].statusdraft · approved · retiredsíSe lee de los eventos sellados; si no hay evento approved, es draft.
disclosure_scripts[].approvalDisclosureScriptEvent | nullno
disclosure_scripts[].retirementDisclosureScriptEvent | nullno
disclosure_scripts[].created_atstring (date-time)no
claimsobjectnoVersión 2, ampliada en la versión 4. Texto fijo, idéntico en todo paquete: lo que el producto afirma sobre el aviso de cobranza (la declaración del agente) y lo que NO afirma (que el aviso se pronunciara, se oyera o se entendiera, ni que su texto satisfaga una norma). El bloque script (versión 4) dice lo mismo de las versiones de guion: fueron aprobadas por el actor registrado, con la referencia y el revisor registrados, y el producto no afirma que ningún texto satisfaga ninguna norma ni jurisdicción.
integrityobjectno
integrity.algorithm"sha256"no
integrity.digeststringnoSHA-256 hex sobre el sobre canónico sin integrity.
integrity.canonicalizationstringno
integrity.worm_attestationobject | nullno
json
{
  "packet": "litigation_defense",
  "packet_version": 5,
  "tenant_id": "0d4f4f9e-1f2a-4b53-9d3c-8a5e2f7b1c10",
  "packet_generated_at": "2026-07-05T10:00:00Z",
  "debt": {
    "id": "5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
    "external_ref": "cms-debt-0001"
  },
  "contacts": [],
  "call_attempts": [],
  "conversations": [],
  "gate_blocks": [],
  "consents": [],
  "dnc": {
    "tenant_listings": [],
    "global_listings": [],
    "reassigned_numbers": []
  },
  "cease_and_desist": [],
  "suppression_events": [],
  "promises": [],
  "callbacks": [],
  "supervision_actions": [],
  "policy_versions": [
    "usa-cell.v1"
  ],
  "integrity": {
    "algorithm": "sha256",
    "digest": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
    "canonicalization": "sorted-keys utf-8, no insignificant whitespace",
    "worm_attestation": 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"
  }
}

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

Listar los paquetes de defensa ante litigios archivados de una deuda ​

http
GET /v1/debts/{id}/defense-packet/archives

Las atestaciones de esta deuda, de la más reciente a la más antigua. El token RFC 3161 no va incrustado: viaja en el kit de atestación. Mismo permiso que el GET del paquete.

Parámetros

NombreEnTipoObligatorioDescripción
idpathstring (uuid)síUUID del recurso. Los ids mal formados o de otro tenant se leen como 404.

Respuestas

200 ​

Las atestaciones (lista vacía cuando no se archivó nada).

CampoTipoObligatorioDescripción
archivesarreglo de DefensePacketArchivesí
archives[].idstring (uuid)sí
archives[].debt_idstring (uuid)sí
archives[].artifact_sha256stringsíSHA-256 (hex) de los bytes del ZIP archivado; el digest que la autoridad selló con marca de tiempo.
archives[].integrity_digeststringsíEl integrity.digest propio del paquete (hex) en el momento del archivado.
archives[].object_keystringsí
archives[].version_idstring,nullnoLa versión del objeto que escribió el almacén. Nula sólo para bucket_lock.
archives[].storestringsí
archives[].retention_modeCOMPLIANCE · GOVERNANCE · bucket_locksí
archives[].retain_untilstring,null (date-time)noFin de retención que confirmó el almacén. Nulo sólo para bucket_lock, donde rige la regla del bucket.
archives[].tsa_urlstring (uri)sí
archives[].tsa_gen_timestring (date-time)síLa hora que la autoridad puso en el token (genTime).
archives[].created_bystringsí
archives[].created_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"
  }
}

Archivar el paquete de defensa ante litigios (almacén WORM + sello de tiempo RFC 3161) ​

http
POST /v1/debts/{id}/defense-packet/archives

Construye el paquete exactamente igual que GET /v1/debts/{id}/defense-packet?format=zip, sella con marca de tiempo el SHA-256 del ZIP en la autoridad RFC 3161 configurada, escribe el ZIP en el almacén de archivo con retención bloqueada y lee la retención de vuelta antes de registrar UNA fila de atestación de sólo anexado. La fila contiene lo que el almacén RESPONDIÓ en la lectura de vuelta (retention_mode, retain_until, version_id), nunca lo que se pidió. Mismo permiso que el GET del paquete. El cuerpo es opcional y su única clave es retention_days; cualquier otra clave es un 400.

Retención: el almacén sella el objeto por el valor por defecto de la plataforma (DEFENSE_RETENTION_DAYS, un valor de despliegue que debe estar definido para activar la función). Un tenant sólo puede ALARGARLA: un retention_days inferior al valor por defecto es un 422, y también lo es uno superior a DEFENSE_RETENTION_MAX_DAYS (cuando éste no está definido, el tenant no puede alargar en absoluto). Nadie puede acortar una retención, y cambiar el valor por defecto sólo afecta a los archivos creados después. La ampliación sólo cuenta para un archivo NUEVO: los bytes ya archivados responden 200 con la fila existente y su retain_until original.

Fail-closed: si el almacén, la autoridad o la lectura de vuelta fallan, NO se escribe ninguna fila y la respuesta es un 503 que indica que no se archivó nada (un objeto que el almacén aceptó antes del fallo queda huérfano y lo elimina el barrido). Idempotente por CONTENIDO: archivar bytes ya archivados para esta deuda responde 200 con la fila existente y no vuelve a llamar a la autoridad ni al almacén. Dos llamadas cuyo ZIP difiere (el paquete se genera con resolución de segundos) son dos archivos. Con tope por deuda y día UTC (DEFENSE_ARCHIVE_MAX_PER_DEBT_PER_DAY, por defecto 5): superado el tope, responde 429 con Retry-After hasta la medianoche UTC. Cada emisión escribe una entrada en el registro de auditoría del tenant.

Con DEFENSE_ARCHIVE_ENABLED desactivado, las tres rutas de archivo responden el mismo 404 que una ruta que no existe.

Parámetros

NombreEnTipoObligatorioDescripción
idpathstring (uuid)síUUID del recurso. Los ids mal formados o de otro tenant se leen como 404.

Cuerpo de la petición (JSON)

CampoTipoObligatorioDescripción
retention_daysintegernoRetención con la que sellar este archivo, en días. Como mínimo el valor por defecto de la plataforma y como máximo el máximo de la plataforma; si se omite, se usa el valor por defecto.
json
{
  "retention_days": 365
}

Respuestas

200 ​

Estos bytes exactos ya estaban archivados; se devuelve la atestación existente.

CampoTipoObligatorioDescripción
archiveobject (DefensePacketArchive)síLa atestación de sólo anexado de UN paquete archivado. retention_mode, retain_until y version_id son lo que respondió el almacén de archivo cuando se leyó el objeto de vuelta tras la escritura.
archive.idstring (uuid)sí
archive.debt_idstring (uuid)sí
archive.artifact_sha256stringsíSHA-256 (hex) de los bytes del ZIP archivado; el digest que la autoridad selló con marca de tiempo.
archive.integrity_digeststringsíEl integrity.digest propio del paquete (hex) en el momento del archivado.
archive.object_keystringsí
archive.version_idstring,nullnoLa versión del objeto que escribió el almacén. Nula sólo para bucket_lock.
archive.storestringsí
archive.retention_modeCOMPLIANCE · GOVERNANCE · bucket_locksí
archive.retain_untilstring,null (date-time)noFin de retención que confirmó el almacén. Nulo sólo para bucket_lock, donde rige la regla del bucket.
archive.tsa_urlstring (uri)sí
archive.tsa_gen_timestring (date-time)síLa hora que la autoridad puso en el token (genTime).
archive.created_bystringsí
archive.created_atstring (date-time)sí

201 ​

Archivado; la nueva atestación.

CampoTipoObligatorioDescripción
archiveobject (DefensePacketArchive)síLa atestación de sólo anexado de UN paquete archivado. retention_mode, retain_until y version_id son lo que respondió el almacén de archivo cuando se leyó el objeto de vuelta tras la escritura.
archive.idstring (uuid)sí
archive.debt_idstring (uuid)sí
archive.artifact_sha256stringsíSHA-256 (hex) de los bytes del ZIP archivado; el digest que la autoridad selló con marca de tiempo.
archive.integrity_digeststringsíEl integrity.digest propio del paquete (hex) en el momento del archivado.
archive.object_keystringsí
archive.version_idstring,nullnoLa versión del objeto que escribió el almacén. Nula sólo para bucket_lock.
archive.storestringsí
archive.retention_modeCOMPLIANCE · GOVERNANCE · bucket_locksí
archive.retain_untilstring,null (date-time)noFin de retención que confirmó el almacén. Nulo sólo para bucket_lock, donde rige la regla del bucket.
archive.tsa_urlstring (uri)sí
archive.tsa_gen_timestring (date-time)síLa hora que la autoridad puso en el token (genTime).
archive.created_bystringsí
archive.created_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"
  }
}

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 — 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 ​

Se alcanzó el tope diario de archivado por deuda (code archive_daily_cap). Retry-After es el número de segundos hasta la medianoche UTC. Esta operación también puede responder el 429 de la cuota por clave (rate_limited).

json
{
  "error": {
    "code": "archive_daily_cap",
    "message": "the daily archive cap for this debt is reached; retry after the interval in Retry-After"
  }
}

503 ​

No se archivó nada (code archive_unavailable): el archivado no está configurado (periodo de retención, almacén o autoridad de marca de tiempo sin definir), o la autoridad, el almacén o la lectura de vuelta de la retención fallaron. Se puede reintentar con seguridad.

json
{
  "error": {
    "code": "archive_unavailable",
    "message": "the archive store did not confirm the retention; nothing was archived"
  }
}

Kit de atestación de un paquete de defensa ante litigios archivado ​

http
GET /v1/debts/{id}/defense-packet/archives/{aid}/attestation

Todo lo que un tercero necesita para comprobar el archivo sin confiar en esta plataforma: la fila de atestación (attestation.json), la marca de tiempo RFC 3161 como TimeStampResp DER en base64 (artifact.tsr una vez decodificado) y una URL prefirmada de corta duración del ZIP archivado (el version_id exacto). Para verificar: el sha256sum del ZIP descargado debe ser igual a attestation.artifact_sha256, y openssl ts -verify -digest <that sha256> -in artifact.tsr debe aceptarlo frente a la cadena de la autoridad. La URL se genera por petición y caduca en artifact.expires_at. Mismo permiso que el GET del paquete.

Parámetros

NombreEnTipoObligatorioDescripción
idpathstring (uuid)síUUID del recurso. Los ids mal formados o de otro tenant se leen como 404.
aidpathstring (uuid)síEl id de la atestación (archives[].id).

Respuestas

200 ​

El kit de atestación.

CampoTipoObligatorioDescripción
attestationobject (DefensePacketArchive)síLa atestación de sólo anexado de UN paquete archivado. retention_mode, retain_until y version_id son lo que respondió el almacén de archivo cuando se leyó el objeto de vuelta tras la escritura.
attestation.idstring (uuid)sí
attestation.debt_idstring (uuid)sí
attestation.artifact_sha256stringsíSHA-256 (hex) de los bytes del ZIP archivado; el digest que la autoridad selló con marca de tiempo.
attestation.integrity_digeststringsíEl integrity.digest propio del paquete (hex) en el momento del archivado.
attestation.object_keystringsí
attestation.version_idstring,nullnoLa versión del objeto que escribió el almacén. Nula sólo para bucket_lock.
attestation.storestringsí
attestation.retention_modeCOMPLIANCE · GOVERNANCE · bucket_locksí
attestation.retain_untilstring,null (date-time)noFin de retención que confirmó el almacén. Nulo sólo para bucket_lock, donde rige la regla del bucket.
attestation.tsa_urlstring (uri)sí
attestation.tsa_gen_timestring (date-time)síLa hora que la autoridad puso en el token (genTime).
attestation.created_bystringsí
attestation.created_atstring (date-time)sí
tsrstring (byte)síLa marca de tiempo RFC 3161 como TimeStampResp DER (estado granted), en base64.
artifactobjectsí
artifact.urlstring (uri)sí
artifact.expires_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"
  }
}

503 ​

No hay almacén de archivo configurado, así que no se puede generar ninguna URL (code archive_unavailable).

json
{
  "error": {
    "code": "archive_unavailable",
    "message": "archiving is not configured: no archive store is set; nothing was archived"
  }
}

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