Skip to content

Campañas ​

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.

CRUD de campañas, ciclo de vida, marcación manual y rebobinado.

Endpoints ​

MétodoRutaResumen
GET/v1/campaignsListar campañas
POST/v1/campaignsCrear una campaña (estado draft)
GET/v1/campaigns/{id}Obtener una campaña
PATCH/v1/campaigns/{id}Actualizar la configuración de una campaña (nunca el estado)
POST/v1/campaigns/{id}/startIniciar una campaña
POST/v1/campaigns/{id}/resumeReanudar una campaña en pausa (alias de start)
POST/v1/campaigns/{id}/pausePausar una campaña en ejecución
POST/v1/campaigns/{id}/stopDetener una campaña (estado terminal completed)
POST/v1/campaigns/{id}/archiveArchivar una campaña
GET/v1/campaigns/{id}/experimentsExperimentos A/B de una campaña
POST/v1/campaigns/{id}/experimentsCrear un experimento A/B (borrador)
GET/v1/campaigns/{id}/experiments/{experiment_id}Obtener un experimento A/B
PATCH/v1/campaigns/{id}/experiments/{experiment_id}Editar un experimento A/B en borrador
DELETE/v1/campaigns/{id}/experiments/{experiment_id}Eliminar un experimento A/B en borrador
GET/v1/campaigns/{id}/experiments/{experiment_id}/resultsResultados y veredicto de un experimento A/B
POST/v1/campaigns/{id}/experiments/{experiment_id}/startIniciar un experimento A/B
POST/v1/campaigns/{id}/experiments/{experiment_id}/stopFinalizar un experimento A/B
POST/v1/campaigns/{id}/dialMarcación iniciada por una persona (campañas preview/manual)
POST/v1/dial/manualMarcar un número digitado (teclado de marcación), resuelto a una de las cuentas del tenant
GET/v1/campaigns/{id}/next-leadPrevisualizar el siguiente lead marcable (sin marcar)
GET/v1/campaigns/{id}/compliance-preflightVerificación previa de cumplimiento (simulación) de los leads marcables de una campaña
GET/v1/campaigns/{id}/statsEjecutor en vivo y contadores de ritmo
GET/v1/campaigns/{id}/survey-resultsDistribución de pulsaciones de encuesta / IVR
GET/v1/campaigns/{id}/abandonmentTasa de abandono durable por período de 30 días
GET/v1/campaigns/{id}/email-skipsPor qué una campaña de correo electrónico no envió a un lead
GET/v1/campaigns/{id}/rewind/previewSimulación de rebobinado (filtros en la query)
POST/v1/campaigns/{id}/rewind/previewSimulación de rebobinado (filtros en el cuerpo)
POST/v1/campaigns/{id}/rewindVolver a encolar las deudas agotadas que coincidan

Listar campañas ​

http
GET /v1/campaigns

Respuestas

200 ​

Todas las campañas del tenant.

CampoTipoObligatorioDescripción
campaignsarreglo de Campaignsí
campaigns[].idstring (uuid)no
campaigns[].tenant_idstring (uuid)no
campaigns[].namestringno
campaigns[].statedraft · running · paused · completed · archived · suspended_balance · exhaustedno
campaigns[].campaign_typepredictive · progressive · preview · manual · tts_blast · tts_ivr · ivr_cascade · survey · ai_voice · sms_blast · email_blastno
campaigns[].channelvoice · sms · emailnoDerivado del perfil del tipo: indica a qué ejecutor despacha el ciclo de vida.
campaigns[].sms_bodystring | nullno
campaigns[].email_template_idstring (uuid) | nullnoLa versión aprobada de la plantilla de correo que envía una campaña email_blast.
campaigns[].email_template_variablesobjectnoValores estáticos de las variables declaradas de la plantilla (email_blast).
campaigns[].abandonment_thresholdstring | nullnoDecimal representado como cadena JSON.
campaigns[].min_dial_ratiostring | nullno
campaigns[].max_dial_ratiostring | nullno
campaigns[].calling_window_startstring | nullnoHH:MM:SS.
campaigns[].calling_window_endstring | nullno
campaigns[].default_timezonestring | nullnoZona horaria IANA. Es la zona en la que se lee la ventana de llamadas OPERATIVA mientras calling_window_destination_local es false.
campaigns[].calling_window_destination_localbooleannoLee calling_window_start/_end en la hora local del DESTINO en lugar de default_timezone. Es false por defecto, que es el comportamiento que tenía toda campaña antes de que existiera este campo. Cuando es true, la ventana debe estar abierta en TODAS las zonas horarias en las que podría estar el destino, y un destino cuya zona no se puede resolver no la abre. Es solo un límite definido por el tenant: puede estrechar la ventana LEGAL de horas de silencio, nunca ampliarla.
campaigns[].calling_window_weeklyCampaignWeeklyWindow | nullnoLa ventana por día tal como se almacena, normalizada a HH:MM:SS. null = sin ventana semanal. Consulta CampaignWrite.calling_window_weekly.
campaigns[].starts_atstring | nullnoInstante de reloj de pared, tal como se almacena (sin desfase), en el que la plataforma inicia esta campaña si todavía está en draft; se lee en default_timezone. null = sin inicio programado. Consulta CampaignWrite.starts_at.
campaigns[].ends_atstring | nullnoInstante de reloj de pared, tal como se almacena (sin desfase), en el que la plataforma completa esta campaña si está en running; se lee en default_timezone. null = sin fin programado. Consulta CampaignWrite.ends_at.
campaigns[].record_callsbooleanno
campaigns[].ai_voicebooleanno
campaigns[].amd_enabledboolean | nullnoEl interruptor ALMACENADO de detección de contestadora automática (AMD) por campaña. null = hereda el valor por defecto del perfil del tipo (activado para los tipos blaster/TTS/IVR/IA, desactivado para los tipos con agente).
campaigns[].optout_digitstring | nullnoLa única tecla DTMF con la que el consumidor se da de baja (0-9, *, #); null = sin tecla de baja.
campaigns[].tts_promptstring | nullnoEl guion del operador para los tratamientos tts_*; null = sin mensaje.
campaigns[].voicemail_optout_numberstring | nullnoEl número gratuito NANP de baja que da el mensaje de voz compuesto, tal como se almacena; null = sin mensaje de voz. Consulta CampaignWrite.voicemail_optout_number.
campaigns[].voicemail_messagestring | nullnoEl texto del mensaje de voz tal como se escribió, con su marcador {optout_number}; null = sin mensaje de voz. Consulta CampaignWrite.voicemail_message.
campaigns[].ivr_menuobjectnoEl menú IVR tal como se escribió ({} = sin menú). Consulta CampaignWrite.ivr_menu.
campaigns[].deferred_bridgebooleanno
campaigns[].consult_configobjectnoLa configuración de la línea de consulta tal como se escribió ({} = deshabilitada). Consulta CampaignWrite.consult_config.
campaigns[].carrierstring | nullno
campaigns[].caller_idsarreglo de stringnoPool rotativo anterior de cadenas E.164 (se usa cuando caller_id_pool está vacío).
campaigns[].caller_id_poolarreglo de string (uuid)noIds del registro de DID; se vuelven a resolver en cada tick a los DID ACTIVOS: un pool con todos los DID aparcados NO marca nada (fail-closed).
campaigns[].created_atstring (date-time)no
campaigns[].updated_atstring (date-time)no
truncatedbooleansítrue cuando hay más filas que las 2 000 campañas que devuelve el listado: la respuesta es un prefijo en el orden documentado, no el conjunto completo.

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

Crear una campaña (estado draft) ​

http
POST /v1/campaigns

campaign_type selecciona el perfil de ritmo (predictive por defecto; preview/manual son los modos iniciados por una persona que POST /v1/campaigns/{id}/dial requiere; sms_blast es el tipo del canal :sms; email_blast es el tipo del canal :email, que envía una plantilla de correo aprobada: email_template_id es OBLIGATORIO y email_template_variables lleva sus valores estáticos). sms_body es OBLIGATORIO para sms_blast y debe contener el aviso de baja STOP (Reg F §1006.6(e)); admite las variables de combinación {{debt_ref}}, {{consumer_ref}}, {{account_number}}, {{amount}}, {{state}}. Todo id de caller_id_pool debe existir en el registro /v1/dids del tenant (los ids ajenos son indistinguibles de los desconocidos → 400 invalid). Un name duplicado → 409 conflict.

Una campaña que se ejecuta SIN agente (tts_blast, tts_ivr, ivr_cascade, survey, ai_voice, sms_blast, email_blast) se rechaza con 403verification_required mientras la cuenta sea una cuenta de 1 puesto que no ha sido verificada (macro-tasks#870, punto 7). Las campañas con agente no se ven afectadas.

Cuerpo de la petición (JSON, obligatorio)

CampoTipoObligatorioDescripción
namestringno
campaign_typepredictive · progressive · preview · manual · tts_blast · tts_ivr · ivr_cascade · survey · ai_voice · sms_blast · email_blastno
sms_bodystringnoOBLIGATORIO para sms_blast; debe contener el aviso de baja STOP; admite variables de combinación.
email_template_idstring (uuid)noOBLIGATORIO para email_blast: una versión APROBADA de plantilla de correo de esta cuenta (not a template of this account / must be an approved template → 400 invalid).
email_template_variablesobjectnoValores estáticos (cadena o número) de las variables que declara la plantilla; se validan contra su declaración al escribir (400 invalid).
abandonment_thresholdnumberno
min_dial_rationumberno
max_dial_rationumberno
calling_window_startstringnoHH:MM:SS.
calling_window_endstringno
default_timezonestringno
starts_atstring | nullnoEl inicio de la ventana de calendario (R8, core#1501): un instante de RELOJ DE PARED como 2026-11-02T09:00:00, leído en el default_timezone de la campaña. Un desfase UTC o Z se rechaza (400 invalid): el valor no tiene zona propia, por lo que no puede discrepar de la de la campaña. Mientras el tenant tenga activo el flag campaign_window_enabled, una campaña que sigue en draft es iniciada por la plataforma cuando se alcanza esa hora local (lo registra una fila append-only en campaign_state_events). Una campaña que una persona ha pausado o que la plataforma ha dejado en exhausted nunca se reinicia por este medio. null lo borra. Sin el flag, o sin valor, nada cambia: la campaña solo se inicia cuando POST /v1/campaigns/{id}/start lo indica.
ends_atstring | nullnoEl fin de la ventana de calendario: mismo formato y zona que starts_at, y posterior a este cuando ambos están definidos. Mientras el flag está activo, la plataforma pasa a completed una campaña running cuando se alcanza esa hora local. completed es terminal, así que una campaña que una persona inicia después de su ends_at vuelve a completarse en la siguiente pasada: borra ends_at primero. null lo borra.
calling_window_destination_localbooleanno
calling_window_weeklyCampaignWeeklyWindow | nullnoUna ventana por día de la semana, leída en la MISMA zona y con el MISMO reloj que calling_window_start/_end (default_timezone, o cada zona de destino con calling_window_destination_local). Solo ESTRECHA: se combina con AND con la ventana única y con las horas de silencio legales, que nunca amplía. Un día omitido está CERRADO. Enviarla REEMPLAZA toda la ventana semanal; null la elimina. Un valor que el dialer no pudo leer se rechaza con 422 unprocessable y no se almacena nada: un día desconocido o repetido, un objeto vacío, un día sin exactamente start y end, una hora que no es HH:MM ni HH:MM:SS (un desfase como Z o +05:00, una fracción de segundo), start no anterior a end, o una ventana que sobrepasa el día: la ventana de un día no cruza la medianoche, y el día termina a las 23:59:59 (24:00 no es una hora del día). Es una preferencia del tenant, no una regla legal de día de la semana ni de festivos.
record_callsbooleanno
ai_voicebooleanno
amd_enabledboolean | nullnoInterruptor de detección de contestadora automática (AMD) por campaña; null (el valor por defecto) hereda el perfil del tipo. Una contestadora detectada recibe el mensaje de voz compuesto solo cuando voicemail_optout_number y voicemail_message están completos (consulta ahí); de lo contrario se le cuelga.
optout_digitstringnoExactamente UNA tecla DTMF: al pulsarla se añade el número al DNC del tenant y se termina la llamada. Cualquier otra cosa es 400 invalid con el nombre del campo. "" la borra.
tts_promptstringnoGuion convertido a audio para los tratamientos tts_blast/tts_ivr (una conversión por texto distinto). Limitado a 2000 caracteres (varios minutos de voz) para que al conversor nunca se le entregue un documento sin límite. "" lo borra.
voicemail_optout_numberstringnoEl número gratuito que da el mensaje de voz para que el consumidor pueda devolver la llamada y darse de baja (47 CFR §64.1200(b)(3)). Debe ser un número gratuito NANP en E.164 Y una ruta entrante optout ACTIVA de este tenant (POST /v1/inbound-routes con dest_type: "optout"): al llamarlo se registra a quien llama en la lista de no llamar de este tenant. Cualquier otra cosa es 400 invalid con el nombre del campo, y no se almacena nada. El dialer vuelve a comprobar la ruta en cada tick: deshabilitarla silencia el mensaje de voz. "" lo borra.
voicemail_messagestringnoEl texto que una campaña tts_blast, tts_ivr, ivr_cascade o survey deja en una contestadora automática (veredicto de máquina de AMD); todos los demás tipos cuelgan ante una máquina. Debe contener el marcador {optout_number} donde se dicta el número (de lo contrario 400 invalid). Lo que se reproduce lo COMPONE el dialer, nunca es un archivo: la divulgación de voz artificial almacenada del tenant (su {optout_digit} sustituido por la tecla de esta campaña), un espacio y luego este mensaje con el marcador sustituido por el número leído dígito a dígito (8 0 0, 5 5 5, 0 1 2 3), convertido a audio como el mensaje. Si falta cualquier pieza (AMD desactivado, sin mensaje reproducible, sin número, sin marcador, ruta no activa), no se deja mensaje de voz y se cuelga ante una máquina. "" lo borra.
ivr_menuobjectnoMapa de dígito → acción que se despacha al pulsar una tecla, plano ({"1": "transfer_agent", "9": "optout"}) o anidado ({"prompt": "...", "entries": {"1": {"action": "hangup"}, "2": {"submenu": {...}}}}). Las claves son teclas DTMF individuales; las acciones son transfer_agent, optout, hangup; profundidad ≤ 5, ≤ 64 nodos. Un menú que no valida es 400 invalid con el motivo del motor (Dialer.IVR.Menu.validate/1); nunca se almacena. {} = sin menú.
deferred_bridgebooleannoCampañas con agente: deja al consumidor en espera al contestar y lo conecta con el agente solo tras un veredicto de AMD de HUMANO. Solo es efectivo mientras AMD esté activado para la campaña.
consult_configobjectnoConfiguración IVR de la línea de consulta, esquema CERRADO: factor (uno de account_last4, ssn_last4, dob, pin, zip), max_attempts opcional (1..10), expose opcional (subconjunto de balance, account_status, minimum_payment, due_date, last4_account), factor_opts opcional (length entero positivo, dob_format mmdd|mmddyyyy). Cualquier otra clave es 400 invalid con su nombre. {} = deshabilitada.
carrierstringno
caller_idsarreglo de stringno
caller_id_poolarreglo de string (uuid)no
json
{
  "name": "postman-golden-1751600000",
  "default_timezone": "America/Chicago",
  "calling_window_start": "00:00:00",
  "calling_window_end": "23:59:59",
  "abandonment_threshold": 0.03,
  "caller_ids": [
    "+13125550199"
  ]
}

Respuestas

201 ​

Creada (estado draft).

CampoTipoObligatorioDescripción
campaignobject (Campaign)sí
campaign.idstring (uuid)no
campaign.tenant_idstring (uuid)no
campaign.namestringno
campaign.statedraft · running · paused · completed · archived · suspended_balance · exhaustedno
campaign.campaign_typepredictive · progressive · preview · manual · tts_blast · tts_ivr · ivr_cascade · survey · ai_voice · sms_blast · email_blastno
campaign.channelvoice · sms · emailnoDerivado del perfil del tipo: indica a qué ejecutor despacha el ciclo de vida.
campaign.sms_bodystring | nullno
campaign.email_template_idstring (uuid) | nullnoLa versión aprobada de la plantilla de correo que envía una campaña email_blast.
campaign.email_template_variablesobjectnoValores estáticos de las variables declaradas de la plantilla (email_blast).
campaign.abandonment_thresholdstring | nullnoDecimal representado como cadena JSON.
campaign.min_dial_ratiostring | nullno
campaign.max_dial_ratiostring | nullno
campaign.calling_window_startstring | nullnoHH:MM:SS.
campaign.calling_window_endstring | nullno
campaign.default_timezonestring | nullnoZona horaria IANA. Es la zona en la que se lee la ventana de llamadas OPERATIVA mientras calling_window_destination_local es false.
campaign.calling_window_destination_localbooleannoLee calling_window_start/_end en la hora local del DESTINO en lugar de default_timezone. Es false por defecto, que es el comportamiento que tenía toda campaña antes de que existiera este campo. Cuando es true, la ventana debe estar abierta en TODAS las zonas horarias en las que podría estar el destino, y un destino cuya zona no se puede resolver no la abre. Es solo un límite definido por el tenant: puede estrechar la ventana LEGAL de horas de silencio, nunca ampliarla.
campaign.calling_window_weeklyCampaignWeeklyWindow | nullnoLa ventana por día tal como se almacena, normalizada a HH:MM:SS. null = sin ventana semanal. Consulta CampaignWrite.calling_window_weekly.
campaign.starts_atstring | nullnoInstante de reloj de pared, tal como se almacena (sin desfase), en el que la plataforma inicia esta campaña si todavía está en draft; se lee en default_timezone. null = sin inicio programado. Consulta CampaignWrite.starts_at.
campaign.ends_atstring | nullnoInstante de reloj de pared, tal como se almacena (sin desfase), en el que la plataforma completa esta campaña si está en running; se lee en default_timezone. null = sin fin programado. Consulta CampaignWrite.ends_at.
campaign.record_callsbooleanno
campaign.ai_voicebooleanno
campaign.amd_enabledboolean | nullnoEl interruptor ALMACENADO de detección de contestadora automática (AMD) por campaña. null = hereda el valor por defecto del perfil del tipo (activado para los tipos blaster/TTS/IVR/IA, desactivado para los tipos con agente).
campaign.optout_digitstring | nullnoLa única tecla DTMF con la que el consumidor se da de baja (0-9, *, #); null = sin tecla de baja.
campaign.tts_promptstring | nullnoEl guion del operador para los tratamientos tts_*; null = sin mensaje.
campaign.voicemail_optout_numberstring | nullnoEl número gratuito NANP de baja que da el mensaje de voz compuesto, tal como se almacena; null = sin mensaje de voz. Consulta CampaignWrite.voicemail_optout_number.
campaign.voicemail_messagestring | nullnoEl texto del mensaje de voz tal como se escribió, con su marcador {optout_number}; null = sin mensaje de voz. Consulta CampaignWrite.voicemail_message.
campaign.ivr_menuobjectnoEl menú IVR tal como se escribió ({} = sin menú). Consulta CampaignWrite.ivr_menu.
campaign.deferred_bridgebooleanno
campaign.consult_configobjectnoLa configuración de la línea de consulta tal como se escribió ({} = deshabilitada). Consulta CampaignWrite.consult_config.
campaign.carrierstring | nullno
campaign.caller_idsarreglo de stringnoPool rotativo anterior de cadenas E.164 (se usa cuando caller_id_pool está vacío).
campaign.caller_id_poolarreglo de string (uuid)noIds del registro de DID; se vuelven a resolver en cada tick a los DID ACTIVOS: un pool con todos los DID aparcados NO marca nada (fail-closed).
campaign.created_atstring (date-time)no
campaign.updated_atstring (date-time)no
json
{
  "campaign": {
    "id": "3f2c1b0a-9d8e-4c7b-a6f5-4e3d2c1b0a99",
    "tenant_id": "0d4f4f9e-1f2a-4b53-9d3c-8a5e2f7b1c10",
    "name": "postman-golden-1751600000",
    "state": "draft",
    "campaign_type": "predictive",
    "channel": "voice",
    "sms_body": null,
    "abandonment_threshold": "0.03",
    "min_dial_ratio": "1.0",
    "max_dial_ratio": "3.0",
    "calling_window_start": "00:00:00",
    "calling_window_end": "23:59:59",
    "default_timezone": "America/Chicago",
    "calling_window_weekly": null,
    "record_calls": true,
    "ai_voice": false,
    "carrier": null,
    "caller_ids": [
      "+13125550199"
    ],
    "caller_id_pool": [],
    "created_at": "2026-07-05T10:00:00Z",
    "updated_at": "2026-07-05T10:00:00Z"
  }
}

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

La cuenta es una cuenta de 1 puesto que no ha sido verificada, y la campaña se ejecuta sin agente (code verification_required). Las campañas con agente no se ven afectadas; la cuenta se verifica cuando la plataforma registra su comprobación de identidad (tenants.kyc_verified_at).

json
{
  "error": {
    "code": "verification_required",
    "message": "this account must be verified before it can create or start a campaign that runs without an agent"
  }
}

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

calling_window_weekly está mal formada o sobrepasa el día (consulta CampaignWrite.calling_window_weekly); no se almacenó nada. Cualquier otro campo con error se nombra en el mismo mensaje.

json
{
  "error": {
    "code": "unprocessable",
    "message": "calling_window_weekly mon needs exactly start and end (HH:MM or HH:MM:SS) with start before end; a day's window does not cross midnight, and the day ends at 23:59:59 (24:00 is not a time of day)"
  }
}

429 — ApiKeyRateLimited ​

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

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

Obtener una campaña ​

http
GET /v1/campaigns/{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 campaña.

CampoTipoObligatorioDescripción
campaignobject (Campaign)sí
campaign.idstring (uuid)no
campaign.tenant_idstring (uuid)no
campaign.namestringno
campaign.statedraft · running · paused · completed · archived · suspended_balance · exhaustedno
campaign.campaign_typepredictive · progressive · preview · manual · tts_blast · tts_ivr · ivr_cascade · survey · ai_voice · sms_blast · email_blastno
campaign.channelvoice · sms · emailnoDerivado del perfil del tipo: indica a qué ejecutor despacha el ciclo de vida.
campaign.sms_bodystring | nullno
campaign.email_template_idstring (uuid) | nullnoLa versión aprobada de la plantilla de correo que envía una campaña email_blast.
campaign.email_template_variablesobjectnoValores estáticos de las variables declaradas de la plantilla (email_blast).
campaign.abandonment_thresholdstring | nullnoDecimal representado como cadena JSON.
campaign.min_dial_ratiostring | nullno
campaign.max_dial_ratiostring | nullno
campaign.calling_window_startstring | nullnoHH:MM:SS.
campaign.calling_window_endstring | nullno
campaign.default_timezonestring | nullnoZona horaria IANA. Es la zona en la que se lee la ventana de llamadas OPERATIVA mientras calling_window_destination_local es false.
campaign.calling_window_destination_localbooleannoLee calling_window_start/_end en la hora local del DESTINO en lugar de default_timezone. Es false por defecto, que es el comportamiento que tenía toda campaña antes de que existiera este campo. Cuando es true, la ventana debe estar abierta en TODAS las zonas horarias en las que podría estar el destino, y un destino cuya zona no se puede resolver no la abre. Es solo un límite definido por el tenant: puede estrechar la ventana LEGAL de horas de silencio, nunca ampliarla.
campaign.calling_window_weeklyCampaignWeeklyWindow | nullnoLa ventana por día tal como se almacena, normalizada a HH:MM:SS. null = sin ventana semanal. Consulta CampaignWrite.calling_window_weekly.
campaign.starts_atstring | nullnoInstante de reloj de pared, tal como se almacena (sin desfase), en el que la plataforma inicia esta campaña si todavía está en draft; se lee en default_timezone. null = sin inicio programado. Consulta CampaignWrite.starts_at.
campaign.ends_atstring | nullnoInstante de reloj de pared, tal como se almacena (sin desfase), en el que la plataforma completa esta campaña si está en running; se lee en default_timezone. null = sin fin programado. Consulta CampaignWrite.ends_at.
campaign.record_callsbooleanno
campaign.ai_voicebooleanno
campaign.amd_enabledboolean | nullnoEl interruptor ALMACENADO de detección de contestadora automática (AMD) por campaña. null = hereda el valor por defecto del perfil del tipo (activado para los tipos blaster/TTS/IVR/IA, desactivado para los tipos con agente).
campaign.optout_digitstring | nullnoLa única tecla DTMF con la que el consumidor se da de baja (0-9, *, #); null = sin tecla de baja.
campaign.tts_promptstring | nullnoEl guion del operador para los tratamientos tts_*; null = sin mensaje.
campaign.voicemail_optout_numberstring | nullnoEl número gratuito NANP de baja que da el mensaje de voz compuesto, tal como se almacena; null = sin mensaje de voz. Consulta CampaignWrite.voicemail_optout_number.
campaign.voicemail_messagestring | nullnoEl texto del mensaje de voz tal como se escribió, con su marcador {optout_number}; null = sin mensaje de voz. Consulta CampaignWrite.voicemail_message.
campaign.ivr_menuobjectnoEl menú IVR tal como se escribió ({} = sin menú). Consulta CampaignWrite.ivr_menu.
campaign.deferred_bridgebooleanno
campaign.consult_configobjectnoLa configuración de la línea de consulta tal como se escribió ({} = deshabilitada). Consulta CampaignWrite.consult_config.
campaign.carrierstring | nullno
campaign.caller_idsarreglo de stringnoPool rotativo anterior de cadenas E.164 (se usa cuando caller_id_pool está vacío).
campaign.caller_id_poolarreglo de string (uuid)noIds del registro de DID; se vuelven a resolver en cada tick a los DID ACTIVOS: un pool con todos los DID aparcados NO marca nada (fail-closed).
campaign.created_atstring (date-time)no
campaign.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"
  }
}

Actualizar la configuración de una campaña (nunca el estado) ​

http
PATCH /v1/campaigns/{id}

Acepta los mismos campos que la creación, y SOLO esos: cualquier otra clave, state incluida, se rechaza con 400 bad_request nombrándola, de modo que un error tipográfico nunca devuelva 200 con el campo ignorado. El ciclo de vida es explícito mediante los endpoints start/pause/stop/archive. Cambiar campaign_type a un tipo que se ejecuta sin agente se rechaza con 403verification_required en una cuenta de 1 puesto sin verificar (misma regla que en la creación).

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, obligatorio)

CampoTipoObligatorioDescripción
namestringno
campaign_typepredictive · progressive · preview · manual · tts_blast · tts_ivr · ivr_cascade · survey · ai_voice · sms_blast · email_blastno
sms_bodystringnoOBLIGATORIO para sms_blast; debe contener el aviso de baja STOP; admite variables de combinación.
email_template_idstring (uuid)noOBLIGATORIO para email_blast: una versión APROBADA de plantilla de correo de esta cuenta (not a template of this account / must be an approved template → 400 invalid).
email_template_variablesobjectnoValores estáticos (cadena o número) de las variables que declara la plantilla; se validan contra su declaración al escribir (400 invalid).
abandonment_thresholdnumberno
min_dial_rationumberno
max_dial_rationumberno
calling_window_startstringnoHH:MM:SS.
calling_window_endstringno
default_timezonestringno
starts_atstring | nullnoEl inicio de la ventana de calendario (R8, core#1501): un instante de RELOJ DE PARED como 2026-11-02T09:00:00, leído en el default_timezone de la campaña. Un desfase UTC o Z se rechaza (400 invalid): el valor no tiene zona propia, por lo que no puede discrepar de la de la campaña. Mientras el tenant tenga activo el flag campaign_window_enabled, una campaña que sigue en draft es iniciada por la plataforma cuando se alcanza esa hora local (lo registra una fila append-only en campaign_state_events). Una campaña que una persona ha pausado o que la plataforma ha dejado en exhausted nunca se reinicia por este medio. null lo borra. Sin el flag, o sin valor, nada cambia: la campaña solo se inicia cuando POST /v1/campaigns/{id}/start lo indica.
ends_atstring | nullnoEl fin de la ventana de calendario: mismo formato y zona que starts_at, y posterior a este cuando ambos están definidos. Mientras el flag está activo, la plataforma pasa a completed una campaña running cuando se alcanza esa hora local. completed es terminal, así que una campaña que una persona inicia después de su ends_at vuelve a completarse en la siguiente pasada: borra ends_at primero. null lo borra.
calling_window_destination_localbooleanno
calling_window_weeklyCampaignWeeklyWindow | nullnoUna ventana por día de la semana, leída en la MISMA zona y con el MISMO reloj que calling_window_start/_end (default_timezone, o cada zona de destino con calling_window_destination_local). Solo ESTRECHA: se combina con AND con la ventana única y con las horas de silencio legales, que nunca amplía. Un día omitido está CERRADO. Enviarla REEMPLAZA toda la ventana semanal; null la elimina. Un valor que el dialer no pudo leer se rechaza con 422 unprocessable y no se almacena nada: un día desconocido o repetido, un objeto vacío, un día sin exactamente start y end, una hora que no es HH:MM ni HH:MM:SS (un desfase como Z o +05:00, una fracción de segundo), start no anterior a end, o una ventana que sobrepasa el día: la ventana de un día no cruza la medianoche, y el día termina a las 23:59:59 (24:00 no es una hora del día). Es una preferencia del tenant, no una regla legal de día de la semana ni de festivos.
record_callsbooleanno
ai_voicebooleanno
amd_enabledboolean | nullnoInterruptor de detección de contestadora automática (AMD) por campaña; null (el valor por defecto) hereda el perfil del tipo. Una contestadora detectada recibe el mensaje de voz compuesto solo cuando voicemail_optout_number y voicemail_message están completos (consulta ahí); de lo contrario se le cuelga.
optout_digitstringnoExactamente UNA tecla DTMF: al pulsarla se añade el número al DNC del tenant y se termina la llamada. Cualquier otra cosa es 400 invalid con el nombre del campo. "" la borra.
tts_promptstringnoGuion convertido a audio para los tratamientos tts_blast/tts_ivr (una conversión por texto distinto). Limitado a 2000 caracteres (varios minutos de voz) para que al conversor nunca se le entregue un documento sin límite. "" lo borra.
voicemail_optout_numberstringnoEl número gratuito que da el mensaje de voz para que el consumidor pueda devolver la llamada y darse de baja (47 CFR §64.1200(b)(3)). Debe ser un número gratuito NANP en E.164 Y una ruta entrante optout ACTIVA de este tenant (POST /v1/inbound-routes con dest_type: "optout"): al llamarlo se registra a quien llama en la lista de no llamar de este tenant. Cualquier otra cosa es 400 invalid con el nombre del campo, y no se almacena nada. El dialer vuelve a comprobar la ruta en cada tick: deshabilitarla silencia el mensaje de voz. "" lo borra.
voicemail_messagestringnoEl texto que una campaña tts_blast, tts_ivr, ivr_cascade o survey deja en una contestadora automática (veredicto de máquina de AMD); todos los demás tipos cuelgan ante una máquina. Debe contener el marcador {optout_number} donde se dicta el número (de lo contrario 400 invalid). Lo que se reproduce lo COMPONE el dialer, nunca es un archivo: la divulgación de voz artificial almacenada del tenant (su {optout_digit} sustituido por la tecla de esta campaña), un espacio y luego este mensaje con el marcador sustituido por el número leído dígito a dígito (8 0 0, 5 5 5, 0 1 2 3), convertido a audio como el mensaje. Si falta cualquier pieza (AMD desactivado, sin mensaje reproducible, sin número, sin marcador, ruta no activa), no se deja mensaje de voz y se cuelga ante una máquina. "" lo borra.
ivr_menuobjectnoMapa de dígito → acción que se despacha al pulsar una tecla, plano ({"1": "transfer_agent", "9": "optout"}) o anidado ({"prompt": "...", "entries": {"1": {"action": "hangup"}, "2": {"submenu": {...}}}}). Las claves son teclas DTMF individuales; las acciones son transfer_agent, optout, hangup; profundidad ≤ 5, ≤ 64 nodos. Un menú que no valida es 400 invalid con el motivo del motor (Dialer.IVR.Menu.validate/1); nunca se almacena. {} = sin menú.
deferred_bridgebooleannoCampañas con agente: deja al consumidor en espera al contestar y lo conecta con el agente solo tras un veredicto de AMD de HUMANO. Solo es efectivo mientras AMD esté activado para la campaña.
consult_configobjectnoConfiguración IVR de la línea de consulta, esquema CERRADO: factor (uno de account_last4, ssn_last4, dob, pin, zip), max_attempts opcional (1..10), expose opcional (subconjunto de balance, account_status, minimum_payment, due_date, last4_account), factor_opts opcional (length entero positivo, dob_format mmdd|mmddyyyy). Cualquier otra clave es 400 invalid con su nombre. {} = deshabilitada.
carrierstringno
caller_idsarreglo de stringno
caller_id_poolarreglo de string (uuid)no
json
{
  "record_calls": false
}

Respuestas

200 ​

Campaña actualizada.

CampoTipoObligatorioDescripción
campaignobject (Campaign)sí
campaign.idstring (uuid)no
campaign.tenant_idstring (uuid)no
campaign.namestringno
campaign.statedraft · running · paused · completed · archived · suspended_balance · exhaustedno
campaign.campaign_typepredictive · progressive · preview · manual · tts_blast · tts_ivr · ivr_cascade · survey · ai_voice · sms_blast · email_blastno
campaign.channelvoice · sms · emailnoDerivado del perfil del tipo: indica a qué ejecutor despacha el ciclo de vida.
campaign.sms_bodystring | nullno
campaign.email_template_idstring (uuid) | nullnoLa versión aprobada de la plantilla de correo que envía una campaña email_blast.
campaign.email_template_variablesobjectnoValores estáticos de las variables declaradas de la plantilla (email_blast).
campaign.abandonment_thresholdstring | nullnoDecimal representado como cadena JSON.
campaign.min_dial_ratiostring | nullno
campaign.max_dial_ratiostring | nullno
campaign.calling_window_startstring | nullnoHH:MM:SS.
campaign.calling_window_endstring | nullno
campaign.default_timezonestring | nullnoZona horaria IANA. Es la zona en la que se lee la ventana de llamadas OPERATIVA mientras calling_window_destination_local es false.
campaign.calling_window_destination_localbooleannoLee calling_window_start/_end en la hora local del DESTINO en lugar de default_timezone. Es false por defecto, que es el comportamiento que tenía toda campaña antes de que existiera este campo. Cuando es true, la ventana debe estar abierta en TODAS las zonas horarias en las que podría estar el destino, y un destino cuya zona no se puede resolver no la abre. Es solo un límite definido por el tenant: puede estrechar la ventana LEGAL de horas de silencio, nunca ampliarla.
campaign.calling_window_weeklyCampaignWeeklyWindow | nullnoLa ventana por día tal como se almacena, normalizada a HH:MM:SS. null = sin ventana semanal. Consulta CampaignWrite.calling_window_weekly.
campaign.starts_atstring | nullnoInstante de reloj de pared, tal como se almacena (sin desfase), en el que la plataforma inicia esta campaña si todavía está en draft; se lee en default_timezone. null = sin inicio programado. Consulta CampaignWrite.starts_at.
campaign.ends_atstring | nullnoInstante de reloj de pared, tal como se almacena (sin desfase), en el que la plataforma completa esta campaña si está en running; se lee en default_timezone. null = sin fin programado. Consulta CampaignWrite.ends_at.
campaign.record_callsbooleanno
campaign.ai_voicebooleanno
campaign.amd_enabledboolean | nullnoEl interruptor ALMACENADO de detección de contestadora automática (AMD) por campaña. null = hereda el valor por defecto del perfil del tipo (activado para los tipos blaster/TTS/IVR/IA, desactivado para los tipos con agente).
campaign.optout_digitstring | nullnoLa única tecla DTMF con la que el consumidor se da de baja (0-9, *, #); null = sin tecla de baja.
campaign.tts_promptstring | nullnoEl guion del operador para los tratamientos tts_*; null = sin mensaje.
campaign.voicemail_optout_numberstring | nullnoEl número gratuito NANP de baja que da el mensaje de voz compuesto, tal como se almacena; null = sin mensaje de voz. Consulta CampaignWrite.voicemail_optout_number.
campaign.voicemail_messagestring | nullnoEl texto del mensaje de voz tal como se escribió, con su marcador {optout_number}; null = sin mensaje de voz. Consulta CampaignWrite.voicemail_message.
campaign.ivr_menuobjectnoEl menú IVR tal como se escribió ({} = sin menú). Consulta CampaignWrite.ivr_menu.
campaign.deferred_bridgebooleanno
campaign.consult_configobjectnoLa configuración de la línea de consulta tal como se escribió ({} = deshabilitada). Consulta CampaignWrite.consult_config.
campaign.carrierstring | nullno
campaign.caller_idsarreglo de stringnoPool rotativo anterior de cadenas E.164 (se usa cuando caller_id_pool está vacío).
campaign.caller_id_poolarreglo de string (uuid)noIds del registro de DID; se vuelven a resolver en cada tick a los DID ACTIVOS: un pool con todos los DID aparcados NO marca nada (fail-closed).
campaign.created_atstring (date-time)no
campaign.updated_atstring (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 — VerificationRequired ​

La cuenta es una cuenta de 1 puesto que no ha sido verificada, y la campaña se ejecuta sin agente (code verification_required). Las campañas con agente no se ven afectadas; la cuenta se verifica cuando la plataforma registra su comprobación de identidad (tenants.kyc_verified_at).

json
{
  "error": {
    "code": "verification_required",
    "message": "this account must be verified before it can create or start a campaign that runs without an agent"
  }
}

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

calling_window_weekly está mal formada o sobrepasa el día (consulta CampaignWrite.calling_window_weekly); no se almacenó nada. Cualquier otro campo con error se nombra en el mismo mensaje.

json
{
  "error": {
    "code": "unprocessable",
    "message": "calling_window_weekly mon needs exactly start and end (HH:MM or HH:MM:SS) with start before end; a day's window does not cross midnight, and the day ends at 23:59:59 (24:00 is not a time of day)"
  }
}

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

Iniciar una campaña ​

http
POST /v1/campaigns/{id}/start

Persiste running PRIMERO y luego lanza el ejecutor del canal de la campaña (voz → bucle de marcación, sms_blast → bucle de envío de SMS, email_blast → bucle de envío de correo; misma API de ciclo de vida, espacios de claves de registro distintos). Las opciones opcionales del ejecutor se validan en modo fail-closed (400 ante tipos incorrectos). Idempotente si ya está en ejecución. Transición inválida → 409. Una campaña en exhausted (su alimentación se agotó, R8) vuelve a iniciarse aquí, de regreso a running. Una campaña que se ejecuta sin agente se rechaza con 403 verification_required mientras la cuenta sea una cuenta de 1 puesto sin verificar (macro-tasks#870, punto 7).

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
tick_msintegerno
batch_sizeintegerno
msgs_per_tickintegernoCampañas de SMS: por defecto 1 (≈1 MPS, el caudal 10DLC seguro para long code).
gatewaystringnoIdentificador de perfil Sofia.
agent_domainstringno
agent_sip_proxystringno"" excluye a la campaña del proxy de todo el despliegue.
call_ttl_msintegerno
blocked_cooldown_msintegerno
json
{
  "tick_ms": 1000,
  "batch_size": 2,
  "gateway": "default"
}

Respuestas

200 ​

La campaña, ahora en running.

CampoTipoObligatorioDescripción
campaignobject (Campaign)sí
campaign.idstring (uuid)no
campaign.tenant_idstring (uuid)no
campaign.namestringno
campaign.statedraft · running · paused · completed · archived · suspended_balance · exhaustedno
campaign.campaign_typepredictive · progressive · preview · manual · tts_blast · tts_ivr · ivr_cascade · survey · ai_voice · sms_blast · email_blastno
campaign.channelvoice · sms · emailnoDerivado del perfil del tipo: indica a qué ejecutor despacha el ciclo de vida.
campaign.sms_bodystring | nullno
campaign.email_template_idstring (uuid) | nullnoLa versión aprobada de la plantilla de correo que envía una campaña email_blast.
campaign.email_template_variablesobjectnoValores estáticos de las variables declaradas de la plantilla (email_blast).
campaign.abandonment_thresholdstring | nullnoDecimal representado como cadena JSON.
campaign.min_dial_ratiostring | nullno
campaign.max_dial_ratiostring | nullno
campaign.calling_window_startstring | nullnoHH:MM:SS.
campaign.calling_window_endstring | nullno
campaign.default_timezonestring | nullnoZona horaria IANA. Es la zona en la que se lee la ventana de llamadas OPERATIVA mientras calling_window_destination_local es false.
campaign.calling_window_destination_localbooleannoLee calling_window_start/_end en la hora local del DESTINO en lugar de default_timezone. Es false por defecto, que es el comportamiento que tenía toda campaña antes de que existiera este campo. Cuando es true, la ventana debe estar abierta en TODAS las zonas horarias en las que podría estar el destino, y un destino cuya zona no se puede resolver no la abre. Es solo un límite definido por el tenant: puede estrechar la ventana LEGAL de horas de silencio, nunca ampliarla.
campaign.calling_window_weeklyCampaignWeeklyWindow | nullnoLa ventana por día tal como se almacena, normalizada a HH:MM:SS. null = sin ventana semanal. Consulta CampaignWrite.calling_window_weekly.
campaign.starts_atstring | nullnoInstante de reloj de pared, tal como se almacena (sin desfase), en el que la plataforma inicia esta campaña si todavía está en draft; se lee en default_timezone. null = sin inicio programado. Consulta CampaignWrite.starts_at.
campaign.ends_atstring | nullnoInstante de reloj de pared, tal como se almacena (sin desfase), en el que la plataforma completa esta campaña si está en running; se lee en default_timezone. null = sin fin programado. Consulta CampaignWrite.ends_at.
campaign.record_callsbooleanno
campaign.ai_voicebooleanno
campaign.amd_enabledboolean | nullnoEl interruptor ALMACENADO de detección de contestadora automática (AMD) por campaña. null = hereda el valor por defecto del perfil del tipo (activado para los tipos blaster/TTS/IVR/IA, desactivado para los tipos con agente).
campaign.optout_digitstring | nullnoLa única tecla DTMF con la que el consumidor se da de baja (0-9, *, #); null = sin tecla de baja.
campaign.tts_promptstring | nullnoEl guion del operador para los tratamientos tts_*; null = sin mensaje.
campaign.voicemail_optout_numberstring | nullnoEl número gratuito NANP de baja que da el mensaje de voz compuesto, tal como se almacena; null = sin mensaje de voz. Consulta CampaignWrite.voicemail_optout_number.
campaign.voicemail_messagestring | nullnoEl texto del mensaje de voz tal como se escribió, con su marcador {optout_number}; null = sin mensaje de voz. Consulta CampaignWrite.voicemail_message.
campaign.ivr_menuobjectnoEl menú IVR tal como se escribió ({} = sin menú). Consulta CampaignWrite.ivr_menu.
campaign.deferred_bridgebooleanno
campaign.consult_configobjectnoLa configuración de la línea de consulta tal como se escribió ({} = deshabilitada). Consulta CampaignWrite.consult_config.
campaign.carrierstring | nullno
campaign.caller_idsarreglo de stringnoPool rotativo anterior de cadenas E.164 (se usa cuando caller_id_pool está vacío).
campaign.caller_id_poolarreglo de string (uuid)noIds del registro de DID; se vuelven a resolver en cada tick a los DID ACTIVOS: un pool con todos los DID aparcados NO marca nada (fail-closed).
campaign.created_atstring (date-time)no
campaign.updated_atstring (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 — VerificationRequired ​

La cuenta es una cuenta de 1 puesto que no ha sido verificada, y la campaña se ejecuta sin agente (code verification_required). Las campañas con agente no se ven afectadas; la cuenta se verifica cuando la plataforma registra su comprobación de identidad (tenants.kyc_verified_at).

json
{
  "error": {
    "code": "verification_required",
    "message": "this account must be verified before it can create or start a campaign that runs without an agent"
  }
}

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

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

Reanudar una campaña en pausa (alias de start) ​

http
POST /v1/campaigns/{id}/resume

Reanudar ES iniciar (paused → running): una sola semántica, dos grafías (paridad con la documentación).

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
tick_msintegerno
batch_sizeintegerno
msgs_per_tickintegernoCampañas de SMS: por defecto 1 (≈1 MPS, el caudal 10DLC seguro para long code).
gatewaystringnoIdentificador de perfil Sofia.
agent_domainstringno
agent_sip_proxystringno"" excluye a la campaña del proxy de todo el despliegue.
call_ttl_msintegerno
blocked_cooldown_msintegerno

Respuestas

200 ​

La campaña, ahora en running.

CampoTipoObligatorioDescripción
campaignobject (Campaign)sí
campaign.idstring (uuid)no
campaign.tenant_idstring (uuid)no
campaign.namestringno
campaign.statedraft · running · paused · completed · archived · suspended_balance · exhaustedno
campaign.campaign_typepredictive · progressive · preview · manual · tts_blast · tts_ivr · ivr_cascade · survey · ai_voice · sms_blast · email_blastno
campaign.channelvoice · sms · emailnoDerivado del perfil del tipo: indica a qué ejecutor despacha el ciclo de vida.
campaign.sms_bodystring | nullno
campaign.email_template_idstring (uuid) | nullnoLa versión aprobada de la plantilla de correo que envía una campaña email_blast.
campaign.email_template_variablesobjectnoValores estáticos de las variables declaradas de la plantilla (email_blast).
campaign.abandonment_thresholdstring | nullnoDecimal representado como cadena JSON.
campaign.min_dial_ratiostring | nullno
campaign.max_dial_ratiostring | nullno
campaign.calling_window_startstring | nullnoHH:MM:SS.
campaign.calling_window_endstring | nullno
campaign.default_timezonestring | nullnoZona horaria IANA. Es la zona en la que se lee la ventana de llamadas OPERATIVA mientras calling_window_destination_local es false.
campaign.calling_window_destination_localbooleannoLee calling_window_start/_end en la hora local del DESTINO en lugar de default_timezone. Es false por defecto, que es el comportamiento que tenía toda campaña antes de que existiera este campo. Cuando es true, la ventana debe estar abierta en TODAS las zonas horarias en las que podría estar el destino, y un destino cuya zona no se puede resolver no la abre. Es solo un límite definido por el tenant: puede estrechar la ventana LEGAL de horas de silencio, nunca ampliarla.
campaign.calling_window_weeklyCampaignWeeklyWindow | nullnoLa ventana por día tal como se almacena, normalizada a HH:MM:SS. null = sin ventana semanal. Consulta CampaignWrite.calling_window_weekly.
campaign.starts_atstring | nullnoInstante de reloj de pared, tal como se almacena (sin desfase), en el que la plataforma inicia esta campaña si todavía está en draft; se lee en default_timezone. null = sin inicio programado. Consulta CampaignWrite.starts_at.
campaign.ends_atstring | nullnoInstante de reloj de pared, tal como se almacena (sin desfase), en el que la plataforma completa esta campaña si está en running; se lee en default_timezone. null = sin fin programado. Consulta CampaignWrite.ends_at.
campaign.record_callsbooleanno
campaign.ai_voicebooleanno
campaign.amd_enabledboolean | nullnoEl interruptor ALMACENADO de detección de contestadora automática (AMD) por campaña. null = hereda el valor por defecto del perfil del tipo (activado para los tipos blaster/TTS/IVR/IA, desactivado para los tipos con agente).
campaign.optout_digitstring | nullnoLa única tecla DTMF con la que el consumidor se da de baja (0-9, *, #); null = sin tecla de baja.
campaign.tts_promptstring | nullnoEl guion del operador para los tratamientos tts_*; null = sin mensaje.
campaign.voicemail_optout_numberstring | nullnoEl número gratuito NANP de baja que da el mensaje de voz compuesto, tal como se almacena; null = sin mensaje de voz. Consulta CampaignWrite.voicemail_optout_number.
campaign.voicemail_messagestring | nullnoEl texto del mensaje de voz tal como se escribió, con su marcador {optout_number}; null = sin mensaje de voz. Consulta CampaignWrite.voicemail_message.
campaign.ivr_menuobjectnoEl menú IVR tal como se escribió ({} = sin menú). Consulta CampaignWrite.ivr_menu.
campaign.deferred_bridgebooleanno
campaign.consult_configobjectnoLa configuración de la línea de consulta tal como se escribió ({} = deshabilitada). Consulta CampaignWrite.consult_config.
campaign.carrierstring | nullno
campaign.caller_idsarreglo de stringnoPool rotativo anterior de cadenas E.164 (se usa cuando caller_id_pool está vacío).
campaign.caller_id_poolarreglo de string (uuid)noIds del registro de DID; se vuelven a resolver en cada tick a los DID ACTIVOS: un pool con todos los DID aparcados NO marca nada (fail-closed).
campaign.created_atstring (date-time)no
campaign.updated_atstring (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 — VerificationRequired ​

La cuenta es una cuenta de 1 puesto que no ha sido verificada, y la campaña se ejecuta sin agente (code verification_required). Las campañas con agente no se ven afectadas; la cuenta se verifica cuando la plataforma registra su comprobación de identidad (tenants.kyc_verified_at).

json
{
  "error": {
    "code": "verification_required",
    "message": "this account must be verified before it can create or start a campaign that runs without an agent"
  }
}

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

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

Pausar una campaña en ejecución ​

http
POST /v1/campaigns/{id}/pause

Persiste paused y detiene el ejecutor; las llamadas en curso terminan mediante el pipeline de CDR.

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 campaña, ahora en paused.

CampoTipoObligatorioDescripción
campaignobject (Campaign)sí
campaign.idstring (uuid)no
campaign.tenant_idstring (uuid)no
campaign.namestringno
campaign.statedraft · running · paused · completed · archived · suspended_balance · exhaustedno
campaign.campaign_typepredictive · progressive · preview · manual · tts_blast · tts_ivr · ivr_cascade · survey · ai_voice · sms_blast · email_blastno
campaign.channelvoice · sms · emailnoDerivado del perfil del tipo: indica a qué ejecutor despacha el ciclo de vida.
campaign.sms_bodystring | nullno
campaign.email_template_idstring (uuid) | nullnoLa versión aprobada de la plantilla de correo que envía una campaña email_blast.
campaign.email_template_variablesobjectnoValores estáticos de las variables declaradas de la plantilla (email_blast).
campaign.abandonment_thresholdstring | nullnoDecimal representado como cadena JSON.
campaign.min_dial_ratiostring | nullno
campaign.max_dial_ratiostring | nullno
campaign.calling_window_startstring | nullnoHH:MM:SS.
campaign.calling_window_endstring | nullno
campaign.default_timezonestring | nullnoZona horaria IANA. Es la zona en la que se lee la ventana de llamadas OPERATIVA mientras calling_window_destination_local es false.
campaign.calling_window_destination_localbooleannoLee calling_window_start/_end en la hora local del DESTINO en lugar de default_timezone. Es false por defecto, que es el comportamiento que tenía toda campaña antes de que existiera este campo. Cuando es true, la ventana debe estar abierta en TODAS las zonas horarias en las que podría estar el destino, y un destino cuya zona no se puede resolver no la abre. Es solo un límite definido por el tenant: puede estrechar la ventana LEGAL de horas de silencio, nunca ampliarla.
campaign.calling_window_weeklyCampaignWeeklyWindow | nullnoLa ventana por día tal como se almacena, normalizada a HH:MM:SS. null = sin ventana semanal. Consulta CampaignWrite.calling_window_weekly.
campaign.starts_atstring | nullnoInstante de reloj de pared, tal como se almacena (sin desfase), en el que la plataforma inicia esta campaña si todavía está en draft; se lee en default_timezone. null = sin inicio programado. Consulta CampaignWrite.starts_at.
campaign.ends_atstring | nullnoInstante de reloj de pared, tal como se almacena (sin desfase), en el que la plataforma completa esta campaña si está en running; se lee en default_timezone. null = sin fin programado. Consulta CampaignWrite.ends_at.
campaign.record_callsbooleanno
campaign.ai_voicebooleanno
campaign.amd_enabledboolean | nullnoEl interruptor ALMACENADO de detección de contestadora automática (AMD) por campaña. null = hereda el valor por defecto del perfil del tipo (activado para los tipos blaster/TTS/IVR/IA, desactivado para los tipos con agente).
campaign.optout_digitstring | nullnoLa única tecla DTMF con la que el consumidor se da de baja (0-9, *, #); null = sin tecla de baja.
campaign.tts_promptstring | nullnoEl guion del operador para los tratamientos tts_*; null = sin mensaje.
campaign.voicemail_optout_numberstring | nullnoEl número gratuito NANP de baja que da el mensaje de voz compuesto, tal como se almacena; null = sin mensaje de voz. Consulta CampaignWrite.voicemail_optout_number.
campaign.voicemail_messagestring | nullnoEl texto del mensaje de voz tal como se escribió, con su marcador {optout_number}; null = sin mensaje de voz. Consulta CampaignWrite.voicemail_message.
campaign.ivr_menuobjectnoEl menú IVR tal como se escribió ({} = sin menú). Consulta CampaignWrite.ivr_menu.
campaign.deferred_bridgebooleanno
campaign.consult_configobjectnoLa configuración de la línea de consulta tal como se escribió ({} = deshabilitada). Consulta CampaignWrite.consult_config.
campaign.carrierstring | nullno
campaign.caller_idsarreglo de stringnoPool rotativo anterior de cadenas E.164 (se usa cuando caller_id_pool está vacío).
campaign.caller_id_poolarreglo de string (uuid)noIds del registro de DID; se vuelven a resolver en cada tick a los DID ACTIVOS: un pool con todos los DID aparcados NO marca nada (fail-closed).
campaign.created_atstring (date-time)no
campaign.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"
  }
}

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

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

Detener una campaña (estado terminal completed) ​

http
POST /v1/campaigns/{id}/stop

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 campaña, ahora en completed.

CampoTipoObligatorioDescripción
campaignobject (Campaign)sí
campaign.idstring (uuid)no
campaign.tenant_idstring (uuid)no
campaign.namestringno
campaign.statedraft · running · paused · completed · archived · suspended_balance · exhaustedno
campaign.campaign_typepredictive · progressive · preview · manual · tts_blast · tts_ivr · ivr_cascade · survey · ai_voice · sms_blast · email_blastno
campaign.channelvoice · sms · emailnoDerivado del perfil del tipo: indica a qué ejecutor despacha el ciclo de vida.
campaign.sms_bodystring | nullno
campaign.email_template_idstring (uuid) | nullnoLa versión aprobada de la plantilla de correo que envía una campaña email_blast.
campaign.email_template_variablesobjectnoValores estáticos de las variables declaradas de la plantilla (email_blast).
campaign.abandonment_thresholdstring | nullnoDecimal representado como cadena JSON.
campaign.min_dial_ratiostring | nullno
campaign.max_dial_ratiostring | nullno
campaign.calling_window_startstring | nullnoHH:MM:SS.
campaign.calling_window_endstring | nullno
campaign.default_timezonestring | nullnoZona horaria IANA. Es la zona en la que se lee la ventana de llamadas OPERATIVA mientras calling_window_destination_local es false.
campaign.calling_window_destination_localbooleannoLee calling_window_start/_end en la hora local del DESTINO en lugar de default_timezone. Es false por defecto, que es el comportamiento que tenía toda campaña antes de que existiera este campo. Cuando es true, la ventana debe estar abierta en TODAS las zonas horarias en las que podría estar el destino, y un destino cuya zona no se puede resolver no la abre. Es solo un límite definido por el tenant: puede estrechar la ventana LEGAL de horas de silencio, nunca ampliarla.
campaign.calling_window_weeklyCampaignWeeklyWindow | nullnoLa ventana por día tal como se almacena, normalizada a HH:MM:SS. null = sin ventana semanal. Consulta CampaignWrite.calling_window_weekly.
campaign.starts_atstring | nullnoInstante de reloj de pared, tal como se almacena (sin desfase), en el que la plataforma inicia esta campaña si todavía está en draft; se lee en default_timezone. null = sin inicio programado. Consulta CampaignWrite.starts_at.
campaign.ends_atstring | nullnoInstante de reloj de pared, tal como se almacena (sin desfase), en el que la plataforma completa esta campaña si está en running; se lee en default_timezone. null = sin fin programado. Consulta CampaignWrite.ends_at.
campaign.record_callsbooleanno
campaign.ai_voicebooleanno
campaign.amd_enabledboolean | nullnoEl interruptor ALMACENADO de detección de contestadora automática (AMD) por campaña. null = hereda el valor por defecto del perfil del tipo (activado para los tipos blaster/TTS/IVR/IA, desactivado para los tipos con agente).
campaign.optout_digitstring | nullnoLa única tecla DTMF con la que el consumidor se da de baja (0-9, *, #); null = sin tecla de baja.
campaign.tts_promptstring | nullnoEl guion del operador para los tratamientos tts_*; null = sin mensaje.
campaign.voicemail_optout_numberstring | nullnoEl número gratuito NANP de baja que da el mensaje de voz compuesto, tal como se almacena; null = sin mensaje de voz. Consulta CampaignWrite.voicemail_optout_number.
campaign.voicemail_messagestring | nullnoEl texto del mensaje de voz tal como se escribió, con su marcador {optout_number}; null = sin mensaje de voz. Consulta CampaignWrite.voicemail_message.
campaign.ivr_menuobjectnoEl menú IVR tal como se escribió ({} = sin menú). Consulta CampaignWrite.ivr_menu.
campaign.deferred_bridgebooleanno
campaign.consult_configobjectnoLa configuración de la línea de consulta tal como se escribió ({} = deshabilitada). Consulta CampaignWrite.consult_config.
campaign.carrierstring | nullno
campaign.caller_idsarreglo de stringnoPool rotativo anterior de cadenas E.164 (se usa cuando caller_id_pool está vacío).
campaign.caller_id_poolarreglo de string (uuid)noIds del registro de DID; se vuelven a resolver en cada tick a los DID ACTIVOS: un pool con todos los DID aparcados NO marca nada (fail-closed).
campaign.created_atstring (date-time)no
campaign.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"
  }
}

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

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 una campaña ​

http
POST /v1/campaigns/{id}/archive

draft|completed → archived (terminal). Cualquier otro caso → 409.

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 campaña, ahora en archived.

CampoTipoObligatorioDescripción
campaignobject (Campaign)sí
campaign.idstring (uuid)no
campaign.tenant_idstring (uuid)no
campaign.namestringno
campaign.statedraft · running · paused · completed · archived · suspended_balance · exhaustedno
campaign.campaign_typepredictive · progressive · preview · manual · tts_blast · tts_ivr · ivr_cascade · survey · ai_voice · sms_blast · email_blastno
campaign.channelvoice · sms · emailnoDerivado del perfil del tipo: indica a qué ejecutor despacha el ciclo de vida.
campaign.sms_bodystring | nullno
campaign.email_template_idstring (uuid) | nullnoLa versión aprobada de la plantilla de correo que envía una campaña email_blast.
campaign.email_template_variablesobjectnoValores estáticos de las variables declaradas de la plantilla (email_blast).
campaign.abandonment_thresholdstring | nullnoDecimal representado como cadena JSON.
campaign.min_dial_ratiostring | nullno
campaign.max_dial_ratiostring | nullno
campaign.calling_window_startstring | nullnoHH:MM:SS.
campaign.calling_window_endstring | nullno
campaign.default_timezonestring | nullnoZona horaria IANA. Es la zona en la que se lee la ventana de llamadas OPERATIVA mientras calling_window_destination_local es false.
campaign.calling_window_destination_localbooleannoLee calling_window_start/_end en la hora local del DESTINO en lugar de default_timezone. Es false por defecto, que es el comportamiento que tenía toda campaña antes de que existiera este campo. Cuando es true, la ventana debe estar abierta en TODAS las zonas horarias en las que podría estar el destino, y un destino cuya zona no se puede resolver no la abre. Es solo un límite definido por el tenant: puede estrechar la ventana LEGAL de horas de silencio, nunca ampliarla.
campaign.calling_window_weeklyCampaignWeeklyWindow | nullnoLa ventana por día tal como se almacena, normalizada a HH:MM:SS. null = sin ventana semanal. Consulta CampaignWrite.calling_window_weekly.
campaign.starts_atstring | nullnoInstante de reloj de pared, tal como se almacena (sin desfase), en el que la plataforma inicia esta campaña si todavía está en draft; se lee en default_timezone. null = sin inicio programado. Consulta CampaignWrite.starts_at.
campaign.ends_atstring | nullnoInstante de reloj de pared, tal como se almacena (sin desfase), en el que la plataforma completa esta campaña si está en running; se lee en default_timezone. null = sin fin programado. Consulta CampaignWrite.ends_at.
campaign.record_callsbooleanno
campaign.ai_voicebooleanno
campaign.amd_enabledboolean | nullnoEl interruptor ALMACENADO de detección de contestadora automática (AMD) por campaña. null = hereda el valor por defecto del perfil del tipo (activado para los tipos blaster/TTS/IVR/IA, desactivado para los tipos con agente).
campaign.optout_digitstring | nullnoLa única tecla DTMF con la que el consumidor se da de baja (0-9, *, #); null = sin tecla de baja.
campaign.tts_promptstring | nullnoEl guion del operador para los tratamientos tts_*; null = sin mensaje.
campaign.voicemail_optout_numberstring | nullnoEl número gratuito NANP de baja que da el mensaje de voz compuesto, tal como se almacena; null = sin mensaje de voz. Consulta CampaignWrite.voicemail_optout_number.
campaign.voicemail_messagestring | nullnoEl texto del mensaje de voz tal como se escribió, con su marcador {optout_number}; null = sin mensaje de voz. Consulta CampaignWrite.voicemail_message.
campaign.ivr_menuobjectnoEl menú IVR tal como se escribió ({} = sin menú). Consulta CampaignWrite.ivr_menu.
campaign.deferred_bridgebooleanno
campaign.consult_configobjectnoLa configuración de la línea de consulta tal como se escribió ({} = deshabilitada). Consulta CampaignWrite.consult_config.
campaign.carrierstring | nullno
campaign.caller_idsarreglo de stringnoPool rotativo anterior de cadenas E.164 (se usa cuando caller_id_pool está vacío).
campaign.caller_id_poolarreglo de string (uuid)noIds del registro de DID; se vuelven a resolver en cada tick a los DID ACTIVOS: un pool con todos los DID aparcados NO marca nada (fail-closed).
campaign.created_atstring (date-time)no
campaign.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"
  }
}

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

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

Experimentos A/B de una campaña ​

http
GET /v1/campaigns/{id}/experiments

Todos los experimentos de la campaña (en cualquier estado), del más reciente al más antiguo, cada uno con sus variantes en orden de position.

Parámetros

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

Respuestas

200 ​

Los experimentos de la campaña.

CampoTipoObligatorioDescripción
experimentsarreglo de Experimentsí
experiments[].idstring (uuid)no
experiments[].campaign_idstring (uuid)no
experiments[].namestringno
experiments[].statedraft · running · endedno
experiments[].primary_metriccontact_rate · promise_rateno
experiments[].started_atstring,null (date-time)no
experiments[].ended_atstring,null (date-time)no
experiments[].variantsarreglo de ExperimentVariantno
experiments[].variants[].idstring (uuid)no
experiments[].variants[].positionintegernoOrden en el que Assignment.pick/3 recorre las variantes (base 0).
experiments[].variants[].namestringno
experiments[].variants[].is_controlbooleanno
experiments[].variants[].weightintegerno
experiments[].variants[].overridesobject (ExperimentVariantOverrides)noLo que una variante cambia de su campaña: una lista CERRADA. Cualquier otra clave se rechaza con 422 (nunca se ignora).
experiments[].variants[].overrides.scriptobjectnoPasa por los validadores de escritura propios de la campaña (un sms_body necesita «STOP»).
experiments[].variants[].overrides.windowobjectnoUn SUBCONJUNTO de la ventana de llamadas de la campaña; nunca la amplía.
experiments[].variants[].overrides.min_gap_secondsintegerno
experiments[].variants[].overrides.email_template_idstring (uuid)noUna plantilla APROBADA del tenant.
experiments[].created_atstring (date-time)no
experiments[].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"
  }
}

Crear un experimento A/B (borrador) ​

http
POST /v1/campaigns/{id}/experiments

Crea un experimento draft de 2 a 5 variantes con exactamente un control. Una variante solo puede sobrescribir la lista CERRADA script, window, min_gap_seconds y email_template_id: cualquier otra clave (en particular optout_digit, abandonment_threshold y max_dial_ratio) se rechaza. Un script pasa por los validadores propios de la campaña (un sms_body sin «STOP» se rechaza) y una window debe ser un subconjunto de la de la campaña. window y min_gap_seconds solo se aplican a una campaña de voz, y una campaña de SMS o correo no puede elegir promise_rate (una promesa apunta a una llamada, no a un mensaje): ambos se rechazan. Cada campo rechazado vuelve en UN solo 422, cada uno con su ruta en error.details.

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, obligatorio)

CampoTipoObligatorioDescripción
namestringno
primary_metriccontact_rate · promise_rateno
variantsarreglo de ExperimentVariantWriteno
variants[].namestringsí
variants[].weightintegersíProporción del tráfico, relativa a la suma de los pesos.
variants[].is_controlbooleannoExactamente una variante del experimento es el control.
variants[].overridesobject (ExperimentVariantOverrides)noLo que una variante cambia de su campaña: una lista CERRADA. Cualquier otra clave se rechaza con 422 (nunca se ignora).
variants[].overrides.scriptobjectnoPasa por los validadores de escritura propios de la campaña (un sms_body necesita «STOP»).
variants[].overrides.script.fieldtts_prompt · voicemail_message · sms_bodysí
variants[].overrides.script.textstringsí
variants[].overrides.windowobjectnoUn SUBCONJUNTO de la ventana de llamadas de la campaña; nunca la amplía.
variants[].overrides.window.startstringsí
variants[].overrides.window.endstringsí
variants[].overrides.min_gap_secondsintegerno
variants[].overrides.email_template_idstring (uuid)noUna plantilla APROBADA del tenant.

Respuestas

201 ​

El experimento en borrador.

CampoTipoObligatorioDescripción
experimentobject (Experiment)sí
experiment.idstring (uuid)no
experiment.campaign_idstring (uuid)no
experiment.namestringno
experiment.statedraft · running · endedno
experiment.primary_metriccontact_rate · promise_rateno
experiment.started_atstring,null (date-time)no
experiment.ended_atstring,null (date-time)no
experiment.variantsarreglo de ExperimentVariantno
experiment.variants[].idstring (uuid)no
experiment.variants[].positionintegernoOrden en el que Assignment.pick/3 recorre las variantes (base 0).
experiment.variants[].namestringno
experiment.variants[].is_controlbooleanno
experiment.variants[].weightintegerno
experiment.variants[].overridesobject (ExperimentVariantOverrides)noLo que una variante cambia de su campaña: una lista CERRADA. Cualquier otra clave se rechaza con 422 (nunca se ignora).
experiment.variants[].overrides.scriptobjectnoPasa por los validadores de escritura propios de la campaña (un sms_body necesita «STOP»).
experiment.variants[].overrides.windowobjectnoUn SUBCONJUNTO de la ventana de llamadas de la campaña; nunca la amplía.
experiment.variants[].overrides.min_gap_secondsintegerno
experiment.variants[].overrides.email_template_idstring (uuid)noUna plantilla APROBADA del tenant.
experiment.created_atstring (date-time)no
experiment.updated_atstring (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"
  }
}

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

El cuerpo se interpretó pero no es un experimento legal (2 a 5 variantes, exactamente un control, pesos 1..1000, sobrescrituras dentro de la lista cerrada y de las reglas propias de la campaña). Cada campo rechazado está en error.details, cada uno con su ruta (variants[1].overrides.window, o variants[1].overrides para una clave fuera de la lista, nombrada en message); no se almacenó nada.

json
{
  "error": {
    "code": "unprocessable",
    "message": "variants[1].overrides has keys outside the closed list: optout_digit; allowed: script, window, min_gap_seconds, email_template_id",
    "details": [
      {
        "path": "variants[1].overrides",
        "message": "has keys outside the closed list: optout_digit; allowed: script, window, min_gap_seconds, email_template_id"
      }
    ]
  }
}

429 — ApiKeyRateLimited ​

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

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

Obtener un experimento A/B ​

http
GET /v1/campaigns/{id}/experiments/{experiment_id}

Parámetros

NombreEnTipoObligatorioDescripción
idpathstring (uuid)síUUID del recurso. Los ids mal formados o de otro tenant se leen como 404.
experiment_idpathstring (uuid)síId de un experimento A/B de la campaña.

Respuestas

200 ​

El experimento con sus variantes.

CampoTipoObligatorioDescripción
experimentobject (Experiment)sí
experiment.idstring (uuid)no
experiment.campaign_idstring (uuid)no
experiment.namestringno
experiment.statedraft · running · endedno
experiment.primary_metriccontact_rate · promise_rateno
experiment.started_atstring,null (date-time)no
experiment.ended_atstring,null (date-time)no
experiment.variantsarreglo de ExperimentVariantno
experiment.variants[].idstring (uuid)no
experiment.variants[].positionintegernoOrden en el que Assignment.pick/3 recorre las variantes (base 0).
experiment.variants[].namestringno
experiment.variants[].is_controlbooleanno
experiment.variants[].weightintegerno
experiment.variants[].overridesobject (ExperimentVariantOverrides)noLo que una variante cambia de su campaña: una lista CERRADA. Cualquier otra clave se rechaza con 422 (nunca se ignora).
experiment.variants[].overrides.scriptobjectnoPasa por los validadores de escritura propios de la campaña (un sms_body necesita «STOP»).
experiment.variants[].overrides.windowobjectnoUn SUBCONJUNTO de la ventana de llamadas de la campaña; nunca la amplía.
experiment.variants[].overrides.min_gap_secondsintegerno
experiment.variants[].overrides.email_template_idstring (uuid)noUna plantilla APROBADA del tenant.
experiment.created_atstring (date-time)no
experiment.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"
  }
}

Editar un experimento A/B en borrador ​

http
PATCH /v1/campaigns/{id}/experiments/{experiment_id}

name y/o variants (que REEMPLAZA todo el conjunto); primary_metric solo puede repetirse, nunca cambiarse. Solo se puede editar un draft: una vez que se ejecuta (o termina) sus variantes quedan congeladas y esto es 409.

Parámetros

NombreEnTipoObligatorioDescripción
idpathstring (uuid)síUUID del recurso. Los ids mal formados o de otro tenant se leen como 404.
experiment_idpathstring (uuid)síId de un experimento A/B de la campaña.

Cuerpo de la petición (JSON, obligatorio)

CampoTipoObligatorioDescripción
namestringno
primary_metriccontact_rate · promise_rateno
variantsarreglo de ExperimentVariantWriteno
variants[].namestringsí
variants[].weightintegersíProporción del tráfico, relativa a la suma de los pesos.
variants[].is_controlbooleannoExactamente una variante del experimento es el control.
variants[].overridesobject (ExperimentVariantOverrides)noLo que una variante cambia de su campaña: una lista CERRADA. Cualquier otra clave se rechaza con 422 (nunca se ignora).
variants[].overrides.scriptobjectnoPasa por los validadores de escritura propios de la campaña (un sms_body necesita «STOP»).
variants[].overrides.script.fieldtts_prompt · voicemail_message · sms_bodysí
variants[].overrides.script.textstringsí
variants[].overrides.windowobjectnoUn SUBCONJUNTO de la ventana de llamadas de la campaña; nunca la amplía.
variants[].overrides.window.startstringsí
variants[].overrides.window.endstringsí
variants[].overrides.min_gap_secondsintegerno
variants[].overrides.email_template_idstring (uuid)noUna plantilla APROBADA del tenant.

Respuestas

200 ​

El experimento editado.

CampoTipoObligatorioDescripción
experimentobject (Experiment)sí
experiment.idstring (uuid)no
experiment.campaign_idstring (uuid)no
experiment.namestringno
experiment.statedraft · running · endedno
experiment.primary_metriccontact_rate · promise_rateno
experiment.started_atstring,null (date-time)no
experiment.ended_atstring,null (date-time)no
experiment.variantsarreglo de ExperimentVariantno
experiment.variants[].idstring (uuid)no
experiment.variants[].positionintegernoOrden en el que Assignment.pick/3 recorre las variantes (base 0).
experiment.variants[].namestringno
experiment.variants[].is_controlbooleanno
experiment.variants[].weightintegerno
experiment.variants[].overridesobject (ExperimentVariantOverrides)noLo que una variante cambia de su campaña: una lista CERRADA. Cualquier otra clave se rechaza con 422 (nunca se ignora).
experiment.variants[].overrides.scriptobjectnoPasa por los validadores de escritura propios de la campaña (un sms_body necesita «STOP»).
experiment.variants[].overrides.windowobjectnoUn SUBCONJUNTO de la ventana de llamadas de la campaña; nunca la amplía.
experiment.variants[].overrides.min_gap_secondsintegerno
experiment.variants[].overrides.email_template_idstring (uuid)noUna plantilla APROBADA del tenant.
experiment.created_atstring (date-time)no
experiment.updated_atstring (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"
  }
}

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

El cuerpo se interpretó pero no es un experimento legal (2 a 5 variantes, exactamente un control, pesos 1..1000, sobrescrituras dentro de la lista cerrada y de las reglas propias de la campaña). Cada campo rechazado está en error.details, cada uno con su ruta (variants[1].overrides.window, o variants[1].overrides para una clave fuera de la lista, nombrada en message); no se almacenó nada.

json
{
  "error": {
    "code": "unprocessable",
    "message": "variants[1].overrides has keys outside the closed list: optout_digit; allowed: script, window, min_gap_seconds, email_template_id",
    "details": [
      {
        "path": "variants[1].overrides",
        "message": "has keys outside the closed list: optout_digit; allowed: script, window, min_gap_seconds, email_template_id"
      }
    ]
  }
}

429 — ApiKeyRateLimited ​

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

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

Eliminar un experimento A/B en borrador ​

http
DELETE /v1/campaigns/{id}/experiments/{experiment_id}

Solo se puede eliminar un draft; un experimento en ejecución o finalizado es 409.

Parámetros

NombreEnTipoObligatorioDescripción
idpathstring (uuid)síUUID del recurso. Los ids mal formados o de otro tenant se leen como 404.
experiment_idpathstring (uuid)síId de un experimento A/B de la campaña.

Respuestas

204 ​

Eliminado.

401 — Unauthorized ​

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

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

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

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

Resultados y veredicto de un experimento A/B ​

http
GET /v1/campaigns/{id}/experiments/{experiment_id}/results

Se recalcula desde el libro mayor en cada lectura (sin contadores): los intentos originales marcados con el experimento, sus correcciones y las promesas que apuntan a ellos. Para una campaña de SMS o correo el libro mayor es su libro de mensajes: los mensajes salientes originales y sus transiciones de estado, donde un mensaje que el proveedor nunca aceptó es not_placed, un contacto es un mensaje delivered y promises y abandoned son 0 (sus tasas null). Por variante: deudas asignadas, intentos, intentos colocados (los not_placed quedan fuera de todo denominador), contactos, promesas, abandonos y las tasas. verdict.status es insufficient_data (algún brazo tiene menos de 100 intentos colocados o 10 éxitos), no_significant_difference o winner (prueba z de dos proporciones, alfa 0.05, Bonferroni con más de dos brazos). Cuando la distribución observada de deudas se desvía de los pesos (chi-cuadrado, alfa 0.001) allocation_warning es true y no se emite ningún winner. Un draft responde con ceros e insufficient_data.

Parámetros

NombreEnTipoObligatorioDescripción
idpathstring (uuid)síUUID del recurso. Los ids mal formados o de otro tenant se leen como 404.
experiment_idpathstring (uuid)síId de un experimento A/B de la campaña.

Respuestas

200 ​

Los resultados del experimento.

CampoTipoObligatorioDescripción
resultsobject (ExperimentResults)sí
results.experimentobjectsí
results.experiment.idstring (uuid)no
results.experiment.campaign_idstring (uuid)no
results.experiment.namestringno
results.experiment.statedraft · running · endedno
results.experiment.primary_metriccontact_rate · promise_rateno
results.experiment.started_atstring,null (date-time)no
results.experiment.ended_atstring,null (date-time)no
results.variantsarreglo de ExperimentVariantResultssí
results.variants[].variant_idstring (uuid)no
results.variants[].namestringno
results.variants[].is_controlbooleanno
results.variants[].weightintegerno
results.variants[].assigned_debtsintegernoDeudas distintas con un intento en esta variante (la unidad de aleatorización).
results.variants[].attemptsintegerno
results.variants[].not_placedintegernoIntentos que nunca salieron (fuera de todo denominador).
results.variants[].placedintegernoattempts menos not_placed; el denominador de las tasas.
results.variants[].contactsintegernoIntentos colocados que contestó una persona (una contestadora automática no es un contacto). Para una campaña de SMS o correo, mensajes colocados que fueron delivered.
results.variants[].promisesintegernoIntentos colocados con al menos una promesa no cancelada. Siempre 0 para una campaña de SMS o correo (una promesa se atribuye a un intento de llamada).
results.variants[].abandonedintegernoContactos que terminaron sin ningún agente que los atendiera.
results.variants[].successesintegernocontacts o promises, según el primary_metric del experimento.
results.variants[].contact_ratenumber,nullnocontacts / placed; null cuando no se colocó nada.
results.variants[].promise_ratenumber,nullnopromises / placed; null cuando no se colocó nada y siempre null para una campaña de SMS o correo.
results.variants[].abandon_ratenumber,nullnoabandoned / contacts; null cuando no hubo contactos y siempre null para una campaña de SMS o correo.
results.unattributed_attemptsintegersíIntentos marcados con el experimento pero sin variante, o con una que el experimento no tiene. Un valor distinto de cero significa que la marca y el experimento no concuerdan.
results.verdictobject (ExperimentVerdict)sí
results.verdict.statusinsufficient_data · no_significant_difference · winnersí
results.verdict.winnernull | objectsí
results.verdict.withheld_bynull | allocation_warningsíSe establece cuando un posible ganador se retuvo a causa de la advertencia de asignación.
results.verdict.comparisonsarreglo de objectsíUna entrada por retador; vacío mientras no se alcance el mínimo de muestra.
results.verdict.comparisons[].variant_idstring (uuid)no
results.verdict.comparisons[].ratenumberno
results.verdict.comparisons[].differencenumbernoRetador menos control.
results.verdict.comparisons[].znumberno
results.verdict.comparisons[].p_valuenumberno
results.verdict.comparisons[].adjusted_p_valuenumberno
results.verdict.comparisons[].ci_lownumberno
results.verdict.comparisons[].ci_highnumberno
results.verdict.comparisons[].significantbooleanno
results.verdict.sample_floorobjectsí
results.verdict.sample_floor.min_placedintegerno
results.verdict.sample_floor.min_successesintegerno
results.verdict.sample_floor.metbooleanno
results.allocation_warningbooleansíLa distribución observada de deudas asignadas se desvía de los pesos (chi-cuadrado, alfa 0.001). Nunca se emite un winner mientras sea true.
results.allocationobjectsí
results.allocation.chi_squarenumberno
results.allocation.degrees_of_freedomintegerno
results.allocation.p_valuenumberno
results.allocation.warningbooleanno

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

Iniciar un experimento A/B ​

http
POST /v1/campaigns/{id}/experiments/{experiment_id}/start

draft → running. Las variantes se validan de nuevo contra la campaña tal como está ahora (422 con details si ya no se cumple). Los experimentos se ejecutan en campañas de voz, SMS y correo cuyos mensajes o llamadas envía la propia plataforma. Una campaña preview o manual (details[].path = "campaign", «experiments need an auto-dialed campaign; preview and manual are not») es 422 aquí, aunque la creación del borrador se acepte. Una campaña con otro experimento en ejecución, o un experimento que no es un draft, es 409.

Parámetros

NombreEnTipoObligatorioDescripción
idpathstring (uuid)síUUID del recurso. Los ids mal formados o de otro tenant se leen como 404.
experiment_idpathstring (uuid)síId de un experimento A/B de la campaña.

Respuestas

200 ​

El experimento, ahora en running.

CampoTipoObligatorioDescripción
experimentobject (Experiment)sí
experiment.idstring (uuid)no
experiment.campaign_idstring (uuid)no
experiment.namestringno
experiment.statedraft · running · endedno
experiment.primary_metriccontact_rate · promise_rateno
experiment.started_atstring,null (date-time)no
experiment.ended_atstring,null (date-time)no
experiment.variantsarreglo de ExperimentVariantno
experiment.variants[].idstring (uuid)no
experiment.variants[].positionintegernoOrden en el que Assignment.pick/3 recorre las variantes (base 0).
experiment.variants[].namestringno
experiment.variants[].is_controlbooleanno
experiment.variants[].weightintegerno
experiment.variants[].overridesobject (ExperimentVariantOverrides)noLo que una variante cambia de su campaña: una lista CERRADA. Cualquier otra clave se rechaza con 422 (nunca se ignora).
experiment.variants[].overrides.scriptobjectnoPasa por los validadores de escritura propios de la campaña (un sms_body necesita «STOP»).
experiment.variants[].overrides.windowobjectnoUn SUBCONJUNTO de la ventana de llamadas de la campaña; nunca la amplía.
experiment.variants[].overrides.min_gap_secondsintegerno
experiment.variants[].overrides.email_template_idstring (uuid)noUna plantilla APROBADA del tenant.
experiment.created_atstring (date-time)no
experiment.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"
  }
}

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

El cuerpo se interpretó pero no es un experimento legal (2 a 5 variantes, exactamente un control, pesos 1..1000, sobrescrituras dentro de la lista cerrada y de las reglas propias de la campaña). Cada campo rechazado está en error.details, cada uno con su ruta (variants[1].overrides.window, o variants[1].overrides para una clave fuera de la lista, nombrada en message); no se almacenó nada.

json
{
  "error": {
    "code": "unprocessable",
    "message": "variants[1].overrides has keys outside the closed list: optout_digit; allowed: script, window, min_gap_seconds, email_template_id",
    "details": [
      {
        "path": "variants[1].overrides",
        "message": "has keys outside the closed list: optout_digit; allowed: script, window, min_gap_seconds, email_template_id"
      }
    ]
  }
}

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

Finalizar un experimento A/B ​

http
POST /v1/campaigns/{id}/experiments/{experiment_id}/stop

running → ended (terminal). Cualquier otro caso → 409.

Parámetros

NombreEnTipoObligatorioDescripción
idpathstring (uuid)síUUID del recurso. Los ids mal formados o de otro tenant se leen como 404.
experiment_idpathstring (uuid)síId de un experimento A/B de la campaña.

Respuestas

200 ​

El experimento, ahora en ended.

CampoTipoObligatorioDescripción
experimentobject (Experiment)sí
experiment.idstring (uuid)no
experiment.campaign_idstring (uuid)no
experiment.namestringno
experiment.statedraft · running · endedno
experiment.primary_metriccontact_rate · promise_rateno
experiment.started_atstring,null (date-time)no
experiment.ended_atstring,null (date-time)no
experiment.variantsarreglo de ExperimentVariantno
experiment.variants[].idstring (uuid)no
experiment.variants[].positionintegernoOrden en el que Assignment.pick/3 recorre las variantes (base 0).
experiment.variants[].namestringno
experiment.variants[].is_controlbooleanno
experiment.variants[].weightintegerno
experiment.variants[].overridesobject (ExperimentVariantOverrides)noLo que una variante cambia de su campaña: una lista CERRADA. Cualquier otra clave se rechaza con 422 (nunca se ignora).
experiment.variants[].overrides.scriptobjectnoPasa por los validadores de escritura propios de la campaña (un sms_body necesita «STOP»).
experiment.variants[].overrides.windowobjectnoUn SUBCONJUNTO de la ventana de llamadas de la campaña; nunca la amplía.
experiment.variants[].overrides.min_gap_secondsintegerno
experiment.variants[].overrides.email_template_idstring (uuid)noUna plantilla APROBADA del tenant.
experiment.created_atstring (date-time)no
experiment.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"
  }
}

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

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

Marcación iniciada por una persona (campañas preview/manual) ​

http
POST /v1/campaigns/{id}/dial

Un agente realiza UNA llamada (sin ritmo automático) al contacto principal de una deuda, a través del MISMO Originator con compuertas que las campañas: se aplican todas las compuertas de cumplimiento y el tope de gasto. Un bloqueo de cumplimiento es un 200 con status: "blocked" (un resultado válido que el agente debe ver, no un error del servidor). Solo los tipos de campaña preview/manual lo aceptan (422 en otro caso).

El identificador de llamada sale del pool de la campaña, resuelto en cada clic con la misma selección de presencia local y el mismo tope anti-reutilización que usa el ritmo automático. Un pool configurado que ahora mismo no da ningún número utilizable (todos los DID en cuarentena o retirados, el tope de reutilización los agotó, o no se pudo leer el registro) es 422 caller_id_unavailable: la llamada no se realiza desde un número sin validar.

El softphone del agente se comprueba antes de marcar al consumidor (core#1395). Cuando el puente pasa por el borde SIP (un puesto de navegador), core pregunta si el softphone del agente está registrado, y no realiza ninguna llamada si no puede conectarse con él: 409 softphone_unregistered: aún no está registrado, dentro de los 30 s posteriores al check-in (reintenta en breve; el puesto se conserva); 409 softphone_unreachable: sigue sin registrarse pasado ese margen: el puesto se cierra y el agente debe volver a hacer check-in; 503 registration_unverifiable: no se pudo comprobar el registro en este momento (reintentable; el puesto se conserva).

Quién puede nombrar a quién (ADR #133, ADR #134). El agent_id lo determina el PRINCIPAL, con la misma regla en todos los verbos que lo leen: una clave de máquina dd_ nombra a cualquier agente de su tenant; una ddw_ solo al suyo; una sesión de login ddu_ vinculada a un puesto de agente actúa como ESE puesto: agent_id puede omitirse (se rellena con el puesto) o repetirlo, y nombrar a otro agente es 403 forbidden; una sesión ddu_ SIN puesto vinculado solo puede nombrar a otro agente con users.role = account_admin (un member recibe 403 role_forbidden) una vez que la clave de catálogo ddu_author_role_enforced del tenant está activada (nace desactivada: hasta entonces la sesión sin puesto conserva el contrato dd_ en este verbo).

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, obligatorio)

CampoTipoObligatorioDescripción
debt_idstring (uuid)sí
agent_idstring (uuid)noEl agente que marca. Obligatorio para una clave dd_ (400 si falta); una sesión ddu_ vinculada a un puesto puede omitirlo (su puesto) y no puede nombrar a otro.
json
{
  "debt_id": "5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
  "agent_id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e"
}

Respuestas

200 ​

Resultado de la marcación: marcando, o bloqueada por una compuerta de cumplimiento.

CampoTipoObligatorioDescripción
statusdialing · blockedsí
call_idstring (uuid)noPresente cuando status es dialing.
reasontcpa_no_consent · quiet_hours · regf_7in7 · regf_post_contact · state_limit · dnc_listed · consent_revoked · cease_and_desist · number_reassigned · tenant_policy · artificial_voice_no_consentnoEl vocabulario de códigos de motivo del motor de cumplimiento.

dialing

json
{
  "status": "dialing",
  "call_id": "6f0a1b2c-3d4e-5f6a-7b8c-9d0e1f2a3b4c"
}

blocked

json
{
  "status": "blocked",
  "reason": "quiet_hours"
}

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

Freno de ritmo de marcación (reintentable más tarde con espera creciente). No se envía la cabecera Retry-After: el limitador no sabe cuándo se recupera la capacidad. Por encima de la cuota por clave esta operación responde rate_limited como en ApiKeyRateLimited.

json
{
  "error": {
    "code": "throttled",
    "message": "dial throttled (rate)"
  }
}

502 — DialError ​

El dialer no pudo realizar la llamada (el switch rechazó el originate).

json
{
  "error": {
    "code": "dial_error",
    "message": "the dialer could not place the call"
  }
}

503 ​

No se pudo verificar el registro del softphone del agente; no se realizó ninguna llamada (reintentable).

json
{
  "error": {
    "code": "registration_unverifiable",
    "message": "the agent's softphone registration could not be verified; no call was placed"
  }
}

Marcar un número digitado (teclado de marcación), resuelto a una de las cuentas del tenant ​

http
POST /v1/dial/manual

El agente DIGITA un número E.164. Se resuelve contra los teléfonos de las cuentas propias del tenant (con alcance RLS). Solo cuando coincide con exactamente UNA cuenta marcable se realiza la llamada, por el MISMO pipeline que POST /v1/campaigns/{id}/dial: la campaña propia de la cuenta, el puesto del agente, la comprobación del softphone y el Originator con compuertas (Consent, DNC, la ventana de llamadas en hora local del destino, frecuencia de Reg F, matriz de estados), de modo que los resultados de ese verbo se aplican sin cambios: 200 dialing, 200 blocked con el reason de la compuerta, 409, 429, 503.

Fail-closed, no se realiza ninguna llamada (422, code legible por máquina): invalid_number (no es E.164), no_account (ninguna cuenta marcable del tenant tiene el número; un número sin cuenta NO es marcable), ambiguous_account (más de una cuenta lo tiene), no_campaign (la cuenta no tiene campaña por la que marcarlo), number_quarantined. 503 audit_unavailable cuando no se pudo registrar la solicitud, y 503 resolution_unavailable cuando falló la consulta de la cuenta.

Cada solicitud se audita en modo append-only (quién digitó qué número, y cada motivo de bloqueo). Quién puede nombrar a quién es la regla de POST /v1/campaigns/{id}/dial (ADR #133, ADR #134): una ddw_ solo su propio agent_id (otro es 403 forbidden), una dd_ cualquier agente de su tenant, una ddu_ con puesto su propio puesto.

Cuerpo de la petición (JSON, obligatorio)

CampoTipoObligatorioDescripción
numberstringsíEl destino digitado, E.164 (+13125550100).
agent_idstring (uuid)noEl agente que marca. Obligatorio para una clave dd_ (400 si falta); una ddw_ debe nombrar a su propio agente (si falta o es otro, 403 forbidden); una ddu_ con puesto puede omitirlo (su puesto) y no puede nombrar a otro.
json
{
  "number": "+13125551001",
  "agent_id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e"
}

Respuestas

200 ​

Resultado de la marcación: marcando, o bloqueada por una compuerta de cumplimiento.

CampoTipoObligatorioDescripción
statusdialing · blockedsí
call_idstring (uuid)noPresente cuando status es dialing.
reasontcpa_no_consent · quiet_hours · regf_7in7 · regf_post_contact · state_limit · dnc_listed · consent_revoked · cease_and_desist · number_reassigned · tenant_policy · artificial_voice_no_consentnoEl vocabulario de códigos de motivo del motor de cumplimiento.

dialing

json
{
  "status": "dialing",
  "call_id": "6f0a1b2c-3d4e-5f6a-7b8c-9d0e1f2a3b4c"
}

blocked

json
{
  "status": "blocked",
  "reason": "dnc"
}

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 ​

El número digitado no se resuelve a exactamente una cuenta marcable; no se realizó ninguna llamada.

json
{
  "error": {
    "code": "no_account",
    "message": "no account of this tenant holds that number; no call was placed"
  }
}

429 — Throttled ​

Freno de ritmo de marcación (reintentable más tarde con espera creciente). No se envía la cabecera Retry-After: el limitador no sabe cuándo se recupera la capacidad. Por encima de la cuota por clave esta operación responde rate_limited como en ApiKeyRateLimited.

json
{
  "error": {
    "code": "throttled",
    "message": "dial throttled (rate)"
  }
}

502 — DialError ​

El dialer no pudo realizar la llamada (el switch rechazó el originate).

json
{
  "error": {
    "code": "dial_error",
    "message": "the dialer could not place the call"
  }
}

503 ​

La marcación no se realizó y es reintentable: no se pudo completar el registro del softphone, la escritura de auditoría o la consulta de la cuenta.

json
{
  "error": {
    "code": "audit_unavailable",
    "message": "the dial could not be recorded, so it was not placed; retry"
  }
}

Previsualizar el siguiente lead marcable (sin marcar) ​

http
GET /v1/campaigns/{id}/next-lead

Parámetros

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

Respuestas

200 ​

El siguiente lead marcable, o null cuando la alimentación está vacía.

CampoTipoObligatorioDescripción
leadPreviewLead | nullsí
json
{
  "lead": {
    "debt_id": "5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
    "debt_ref": "cms-debt-0001",
    "account_number": "ACC-1001",
    "debtor_name": "Ana María Pérez",
    "debt_type": "other",
    "debt_state": "open",
    "contact_phone": "+13125551001",
    "line_type": "mobile",
    "consumer_state": "IL",
    "consumer_timezone": "America/Chicago",
    "last_attempt_at": null
  }
}

401 — Unauthorized ​

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

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

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

Verificación previa de cumplimiento (simulación) de los leads marcables de una campaña ​

http
GET /v1/campaigns/{id}/compliance-preflight

Ejecuta el motor de cumplimiento en SIMULACIÓN sobre los leads que el ritmo automático podría ofrecer para esta campaña (un contacto clasificado por deuda, estado marcable, marca de agua de reintento vencida, cuarentena aplicada, entregas sin agente excluidas) y devuelve UN agregado por estado del consumidor: bajo qué regla de estado cae cada lead, cuántos permitirían las compuertas y cuántos bloquearían, por regla y motivo de bloqueo. No marca nada y no devuelve ningún lead individual. Mismo motor, misma Request y mismos almacenes durables que la marcación, así que los contadores son lo que produciría lanzar la campaña AHORA.

Es honesto sobre la compuerta de estado configurable: con state_matrix_unknown_state_fails_strict DESACTIVADO para el tenant (el valor por defecto del catálogo), un lead sin us_state utilizable conserva solo el federal 7-in-7: unknown_state_policy y tenant_gates.state_matrix dicen federal_fallback, nunca «protected». ACTIVADO, dicen strict / active y el grupo lleva el id de regla propio de la compuerta.

El costo está acotado: se evalúan como máximo 10000 leads por llamada; totals.leads es el recuento exacto de la población y truncated: true indica que la evaluación solo cubrió los primeros totals.evaluated de ella.

Salud de la lista (list_health, core#1181). states cuenta cada lead únicamente bajo su PRIMER bloqueo, así que una lista con deudas sin consentimiento Y con una instantánea DNC vencida muestra solo uno de los dos. list_health.signals lee los mismos leads evaluados una vez por señal, sin que ninguna detenga a las demás: consent, state (el us_state se resuelve a un código canónico), dnc (las listas, sus instantáneas y el registro por estado) y rnd (Reassigned Numbers). Una señal que no puede decidir (una lista DNC sin instantánea reciente, ningún proveedor RND declarado, un RND sin datos o con error, una compuerta que falló, un estado que no se resuelve) es no_verificable y CUENTA COMO BLOQUEO (blocked incluye no_verificable), nunca como sana. Las premisas a nivel de celda viajan al lado (antigüedad de cada instantánea DNC, proveedor RND), de modo que una señal cuya premisa no es verificable dice no_verificable incluso con cero leads. Aditivo: states y totals no cambian.

Parámetros

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

Respuestas

200 ​

Agregado de la verificación previa

CampoTipoObligatorioDescripción
campaign_idstring (uuid)sí
unknown_state_policystrict · federal_fallbacksíPostura de la matriz de estados para un consumer_state fuera de su forma canónica, según el flag del tenant.
tenant_gatesobjectsíPostura de cada compuerta para este tenant. Hoy solo state_matrix tiene interruptor de apagado.
tenant_gates.dncactiveno
tenant_gates.consentactiveno
tenant_gates.quiet_hoursactiveno
tenant_gates.reg_factiveno
tenant_gates.state_matrixactive · federal_fallbackno
totalsobjectsí
totals.leadsintegernoTamaño exacto de la población marcable.
totals.evaluatedintegernoLeads sobre los que se ejecutó el motor (≤ leads).
totals.eligibleintegerno
totals.suppressedintegerno
totals.unknown_stateintegernoLeads evaluados con us_state vacío.
truncatedbooleansítrue cuando evaluated < leads (se alcanzó el límite de evaluación).
statesobjectsíIndexado por el us_state CRUDO que ve el motor ("unknown" para la cadena vacía).
list_healthobjectsíSalud por señal de los leads evaluados, sin cortocircuito (core#1181).
list_health.signalsobjectsí
list_health.signals.consentobject (PreflightSignal)síUna señal de la salud de la lista de la verificación previa, sobre los leads evaluados. blocked cuenta todo lead que esta señal rechaza, INCLUIDOS los no_verificable (una señal que no puede decidir bloquea). status es no_verificable cuando algún lead no fue verificable o lo es la premisa a nivel de celda (lista DNC sin instantánea reciente, ningún proveedor RND), si no blocking cuando blocked > 0, si no ok.
list_health.signals.consent.statusok · blocking · no_verificablesí
list_health.signals.consent.blockedintegersí
list_health.signals.consent.no_verificableintegersí
list_health.signals.consent.rulesobjectsíLeads bloqueados por id de regla (para state, la etiqueta de la matriz de estados bajo la que cae el lead).
list_health.signals.stateobject (PreflightSignal)síUna señal de la salud de la lista de la verificación previa, sobre los leads evaluados. blocked cuenta todo lead que esta señal rechaza, INCLUIDOS los no_verificable (una señal que no puede decidir bloquea). status es no_verificable cuando algún lead no fue verificable o lo es la premisa a nivel de celda (lista DNC sin instantánea reciente, ningún proveedor RND), si no blocking cuando blocked > 0, si no ok.
list_health.signals.state.statusok · blocking · no_verificablesí
list_health.signals.state.blockedintegersí
list_health.signals.state.no_verificableintegersí
list_health.signals.state.rulesobjectsíLeads bloqueados por id de regla (para state, la etiqueta de la matriz de estados bajo la que cae el lead).
list_health.signals.dncobject (PreflightSignal)síUna señal de la salud de la lista de la verificación previa, sobre los leads evaluados. blocked cuenta todo lead que esta señal rechaza, INCLUIDOS los no_verificable (una señal que no puede decidir bloquea). status es no_verificable cuando algún lead no fue verificable o lo es la premisa a nivel de celda (lista DNC sin instantánea reciente, ningún proveedor RND), si no blocking cuando blocked > 0, si no ok.
list_health.signals.dnc.statusok · blocking · no_verificablesí
list_health.signals.dnc.blockedintegersí
list_health.signals.dnc.no_verificableintegersí
list_health.signals.dnc.rulesobjectsíLeads bloqueados por id de regla (para state, la etiqueta de la matriz de estados bajo la que cae el lead).
list_health.signals.rndobject (PreflightSignal)síUna señal de la salud de la lista de la verificación previa, sobre los leads evaluados. blocked cuenta todo lead que esta señal rechaza, INCLUIDOS los no_verificable (una señal que no puede decidir bloquea). status es no_verificable cuando algún lead no fue verificable o lo es la premisa a nivel de celda (lista DNC sin instantánea reciente, ningún proveedor RND), si no blocking cuando blocked > 0, si no ok.
list_health.signals.rnd.statusok · blocking · no_verificablesí
list_health.signals.rnd.blockedintegersí
list_health.signals.rnd.no_verificableintegersí
list_health.signals.rnd.rulesobjectsíLeads bloqueados por id de regla (para state, la etiqueta de la matriz de estados bajo la que cae el lead).
list_health.dnc_snapshotsobjectsíLas tres listas DNC globales, cada una frente al techo de 31 días.
list_health.dnc_snapshots.federalobject (PreflightDncSnapshot)sí
list_health.dnc_snapshots.federal.statusprovisioned · stale · absentsí
list_health.dnc_snapshots.federal.age_daysinteger,nullsíDías desde que se importó la instantánea; null cuando no existe.
list_health.dnc_snapshots.federal.max_age_daysintegersíTecho de frescura para esta lista.
list_health.dnc_snapshots.federal.sourcestringsíCadena de procedencia de la instantánea (none cuando no existe).
list_health.dnc_snapshots.federal.record_countinteger,nullsí
list_health.dnc_snapshots.stateobject (PreflightDncSnapshot)sí
list_health.dnc_snapshots.state.statusprovisioned · stale · absentsí
list_health.dnc_snapshots.state.age_daysinteger,nullsíDías desde que se importó la instantánea; null cuando no existe.
list_health.dnc_snapshots.state.max_age_daysintegersíTecho de frescura para esta lista.
list_health.dnc_snapshots.state.sourcestringsíCadena de procedencia de la instantánea (none cuando no existe).
list_health.dnc_snapshots.state.record_countinteger,nullsí
list_health.dnc_snapshots.internal_globalobject (PreflightDncSnapshot)sí
list_health.dnc_snapshots.internal_global.statusprovisioned · stale · absentsí
list_health.dnc_snapshots.internal_global.age_daysinteger,nullsíDías desde que se importó la instantánea; null cuando no existe.
list_health.dnc_snapshots.internal_global.max_age_daysintegersíTecho de frescura para esta lista.
list_health.dnc_snapshots.internal_global.sourcestringsíCadena de procedencia de la instantánea (none cuando no existe).
list_health.dnc_snapshots.internal_global.record_countinteger,nullsí
list_health.dnc_state_registriesobjectsíCada registro DNC por estado declarado, indexado por código de estado, cada uno frente a su propio techo.
list_health.rnd_providerobjectsí
list_health.rnd_provider.statusdeclared · stub · no_verificablesístub es el proveedor de desarrollo que responde limpio sin consultar un registro; no_verificable significa que no hay ninguno declarado (la compuerta rechaza toda marcación).
list_health.rnd_provider.providerstring,nullsíNombre del proveedor declarado; null cuando no hay ninguno.
json
{
  "campaign_id": "0190b2a4-7c3e-7f21-9d2a-3f1c2b9e8a10",
  "unknown_state_policy": "federal_fallback",
  "tenant_gates": {
    "dnc": "active",
    "consent": "active",
    "quiet_hours": "active",
    "reg_f": "active",
    "state_matrix": "federal_fallback"
  },
  "totals": {
    "leads": 4,
    "evaluated": 4,
    "eligible": 3,
    "suppressed": 1,
    "unknown_state": 1
  },
  "truncated": false,
  "states": {
    "MA": {
      "leads": 1,
      "eligible": 0,
      "suppressed": 1,
      "rules": {
        "state.ma.2in7.v1": 1
      },
      "blocks": {
        "state.ma.2in7.v1": {
          "state_limit": 1
        }
      }
    },
    "WA": {
      "leads": 1,
      "eligible": 1,
      "suppressed": 0,
      "rules": {
        "state.wa.3in7.v1": 1
      },
      "blocks": {}
    },
    "NY": {
      "leads": 1,
      "eligible": 1,
      "suppressed": 0,
      "rules": {
        "city.nyc.2in7.v1": 1
      },
      "blocks": {}
    },
    "unknown": {
      "leads": 1,
      "eligible": 1,
      "suppressed": 0,
      "rules": {
        "federal_fallback": 1
      },
      "blocks": {}
    }
  },
  "list_health": {
    "signals": {
      "consent": {
        "status": "blocking",
        "blocked": 2,
        "no_verificable": 0,
        "rules": {
          "tcpa.consent.v1": 2
        }
      },
      "state": {
        "status": "no_verificable",
        "blocked": 1,
        "no_verificable": 1,
        "rules": {
          "federal_fallback": 1
        }
      },
      "dnc": {
        "status": "blocking",
        "blocked": 1,
        "no_verificable": 0,
        "rules": {
          "dnc.lists.v1": 1
        }
      },
      "rnd": {
        "status": "ok",
        "blocked": 0,
        "no_verificable": 0,
        "rules": {}
      }
    },
    "dnc_snapshots": {
      "federal": {
        "status": "provisioned",
        "age_days": 3,
        "max_age_days": 31,
        "source": "ftc:2026-09-23",
        "record_count": 240000000
      },
      "state": {
        "status": "provisioned",
        "age_days": 3,
        "max_age_days": 31,
        "source": "states:2026-09-23",
        "record_count": 1200000
      },
      "internal_global": {
        "status": "provisioned",
        "age_days": 0,
        "max_age_days": 31,
        "source": "platform",
        "record_count": 1500
      }
    },
    "dnc_state_registries": {
      "FL": {
        "status": "absent",
        "age_days": null,
        "max_age_days": 31,
        "source": "none",
        "record_count": null
      }
    },
    "rnd_provider": {
      "status": "declared",
      "provider": "Example.RND.Provider"
    }
  }
}

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

Ejecutor en vivo y contadores de ritmo ​

http
GET /v1/campaigns/{id}/stats

running: false, stats: null cuando no hay ningún ejecutor activo. Los ejecutores de voz informan {dialed, blocked, finished, reaped, throttled, pool_unavailable, pool_unavailable_reason, no_agent_seat, no_rate, tenant_throttled, tenant_throttle_reason, tenant_throttle_by_reason, tenant_throttle_last_at, prompt_unavailable, prompt_unavailable_reason, overdial_disarmed, thirty_day_abandonment, overdial_margin, in_flight, pacing}; los ejecutores de SMS informan {sent, duplicate, blocked, failed, pending_cooldown, send_unavailable_reason}.

Parámetros

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

Respuestas

200 ​

Estado de la campaña + estadísticas en vivo.

CampoTipoObligatorioDescripción
campaign_idstring (uuid)sí
statedraft · running · paused · completed · archived · suspended_balance · exhaustedsí
runningbooleansí
statsVoiceRunnerStats | SmsRunnerStats | nullsí
json
{
  "campaign_id": "3f2c1b0a-9d8e-4c7b-a6f5-4e3d2c1b0a99",
  "state": "running",
  "running": true,
  "stats": {
    "dialed": 42,
    "blocked": 5,
    "finished": 30,
    "reaped": 0,
    "pool_unavailable": 0,
    "pool_unavailable_reason": null,
    "no_agent_seat": 0,
    "no_rate": 2,
    "tenant_throttled": 120,
    "tenant_throttle_reason": "exposure_limit",
    "tenant_throttle_by_reason": {
      "exposure_limit": 120
    },
    "tenant_throttle_last_at": "2026-09-29T16:58:00Z",
    "in_flight": 7,
    "pacing": {
      "ratio": 1.6,
      "abandoned": 1,
      "connected": 22,
      "abandonment_rate": 0.0435,
      "tokens": 3
    }
  }
}

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

Distribución de pulsaciones de encuesta / IVR ​

http
GET /v1/campaigns/{id}/survey-results

El lado de lectura de las respuestas de encuesta sin agente: cuántos consumidores pulsaron cada tecla DTMF en esta campaña durante [from, to] (ambos inclusivos; por defecto los últimos 30 días UTC), opcionalmente acotado a una question_id (q1 es la pregunta por defecto que registra la FSM de llamada). distribution está ordenada por dígito; total es la suma. Solo lectura y con alcance de tenant: una campaña de otro tenant es 404, nunca un 200 vacío. ?format=csv (o Accept: text/csv) devuelve los mismos números como artefacto CSV con su propio X-Artifact-SHA256, por la misma vía que /v1/stats/ai-usage y /v1/reports/violations-prevented.

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.
question_idquerystringnoAcota a una pregunta (cadena no vacía). Omitido = todas las preguntas.
formatquerycsvnocsv para el artefacto descargable (equivalente: Accept: text/csv).

Respuestas

200 ​

Distribución por dígito (JSON) o el artefacto CSV.

CampoTipoObligatorioDescripción
campaign_idstring (uuid)sí
fromstring (date-time)sí
tostring (date-time)sí
question_idstring | nullsíEl filtro que se aplicó; null = todas las preguntas.
totalintegersí
distributionarreglo de objectsíUna fila por dígito pulsado al menos una vez, ordenada por dígito.
distribution[].digitstringsí
distribution[].countintegersí
json
{
  "campaign_id": "3f2c1b0a-9d8e-4c7b-a6f5-4e3d2c1b0a99",
  "from": "2026-06-01T00:00:00Z",
  "to": "2026-06-30T23:59:59Z",
  "question_id": null,
  "total": 4,
  "distribution": [
    {
      "digit": "1",
      "count": 2
    },
    {
      "digit": "2",
      "count": 1
    },
    {
      "digit": "9",
      "count": 1
    }
  ]
}

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

Tasa de abandono durable por período de 30 días ​

http
GET /v1/campaigns/{id}/abandonment

Los registros de 47 CFR §64.1200(a)(7)(iii) y 16 CFR §310.4(b)(4)(iv): la tasa de abandono de la campaña para cada período sucesivo de 30 días, anclado en su primera marcación realizada (anchored_at), calculado en el momento de lectura a partir del CDR append-only (call_attempts). Ningún contador en la ruta de la llamada lo alimenta. La tasa en vivo de /v1/campaigns/{id}/stats es la ventana de 120 segundos del controlador de ritmo; esta es el registro.

Se mide: las marcaciones salientes que realizó la plataforma (no la liquidación not_placed, una pierna entrante, una pierna de carrier de transferencia externa ni una fila cms_import). Denominador answered_live: contestadas, sin veredicto de AMD machine. Numerador abandoned, por motivo (predicate.counts): no_agent (disposición abandoned: contestada en una campaña con agente y ningún representante se conectó, sin puesto reservado para el intento), ai_stream_lost (se perdió la bifurcación de IA de voz y no se transfirió nada) y agent_bridge_failed (la misma respuesta abandoned, pero el intento lleva un agent_id: se reservó un puesto y falló el puente hacia él). agent_bridge_failed es la parte de lo que no_agent contaba hasta core#1383 que tenía un puesto: las dos causas piden arreglos distintos, y la división no mueve ni abandoned ni rate. Una entrega con voz artificial (delivered, ai_conversation) no es un abandono (§64.1200(a)(7)(ii), §310.4(b)(1)(v)(C)); una transferencia de IA de voz que no encontró puesto se informa aparte como handoff_failed y no entra en la tasa (predicate.reported_apart). También se informa aparte, desde core#1426: late_bridge, una respuesta en vivo no contada como abandonada cuya pierna del consumidor se conectó MÁS de 2 s después de answered_at (§310.4(b)(1)(iv): "within two (2) seconds of the person's completed greeting"; answered_at hace las veces del fin del saludo, así que el retraso se mide por exceso, nunca por defecto). No se cuenta una transferencia que ordenó la IA de voz o el IVR, ni una llamada escrita antes de que se registrara el instante del puente. exceeds_ceiling es "more than three percent", decidido en enteros; rate es null sin respuesta en vivo. Las filas escritas antes de core 0.73.0 conservan la disposición con la que nacieron, así que una entrega sin agente de esa época figura como abandoned y cuenta. Un período sin marcaciones no se lista.

Solo lectura y con alcance de tenant: una campaña de otro tenant es 404, nunca un 200 vacío. ?format=csv (o Accept: text/csv) devuelve los mismos números como artefacto CSV con su propio X-Artifact-SHA256.

Parámetros

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

Respuestas

200 ​

El informe (JSON) o el artefacto CSV.

CampoTipoObligatorioDescripción
campaign_idstring (uuid)sí
measured_atstring (date-time)sí
period_days30sí
ceiling_percent3sí
anchored_atstring (date-time) | nullsíLa primera marcación realizada de la campaña; null = aún sin marcaciones (y sin período).
predicateobjectsí
predicate.countsarreglo de no_agent · ai_stream_lost · agent_bridge_failed · handoff_failed · late_bridgesíLos motivos por los que la tasa cuenta como abandonada.
predicate.reported_apartarreglo de no_agent · ai_stream_lost · agent_bridge_failed · handoff_failed · late_bridgesíMotivos informados por período y que NO se cuentan en la tasa.
periodsarreglo de AbandonmentPeriodsíUna entrada por período de 30 días con al menos una marcación realizada, de la más antigua a la más reciente.
periods[].indexintegersí[anchored_at + 30·index days, + 30 days).
periods[].starts_atstring (date-time)sí
periods[].ends_atstring (date-time)síExclusivo.
periods[].completebooleansíends_at no es posterior a measured_at.
periods[].placedintegersí
periods[].answeredintegersí
periods[].answered_machineintegersíContestada con un veredicto AMD machine.
periods[].answered_liveintegersíEl denominador: answered - answered_machine.
periods[].abandonedintegersíEl numerador; la suma de abandoned_by_reason.
periods[].abandoned_by_reasonobjectsíUna clave por cada motivo de predicate.counts. Los lectores no deben asumir un conjunto cerrado de claves: agent_bridge_failed se añadió en core#1383.
periods[].handoff_failedintegersíTraspasos de IA de voz sin puesto disponible; no entran en la tasa.
periods[].late_bridgeintegersíRespuestas en vivo no contadas como abandonadas cuya pierna del consumidor se puenteó más de 2 s después de answered_at (core#1426); no entran en la tasa.
periods[].ratenumber | nullsíabandoned / answered_live, 4 decimales; null si no hay respuesta en vivo.
periods[].exceeds_ceilingbooleansíabandoned * 100 > 3 * answered_live.
json
{
  "campaign_id": "3f2c1b0a-9d8e-4c7b-a6f5-4e3d2c1b0a99",
  "measured_at": "2026-09-29T22:00:00.000000Z",
  "period_days": 30,
  "ceiling_percent": 3,
  "anchored_at": "2026-08-01T15:00:00.000000Z",
  "predicate": {
    "counts": [
      "no_agent",
      "ai_stream_lost",
      "agent_bridge_failed"
    ],
    "reported_apart": [
      "handoff_failed",
      "late_bridge"
    ]
  },
  "periods": [
    {
      "index": 0,
      "starts_at": "2026-08-01T15:00:00.000000Z",
      "ends_at": "2026-08-31T15:00:00.000000Z",
      "complete": true,
      "placed": 1200,
      "answered": 410,
      "answered_machine": 60,
      "answered_live": 350,
      "abandoned": 7,
      "abandoned_by_reason": {
        "no_agent": 5,
        "ai_stream_lost": 0,
        "agent_bridge_failed": 2
      },
      "handoff_failed": 0,
      "late_bridge": 4,
      "rate": 0.02,
      "exceeds_ceiling": false
    }
  ]
}

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

Por qué una campaña de correo electrónico no envió a un lead ​

http
GET /v1/campaigns/{id}/email-skips

El registro de cada lead que el ejecutor de correo DESCARTÓ sin enviar y sin un bloqueo de cumplimiento, y de cada rechazo que DETUVO su ciclo: una campaña email_blast en ejecución sin nada en /v1/stats/blocked y sin nada en /v1/email/messages se explica aquí. Una fila de solo anexado por (campaña, deuda, motivo, día UTC): el ejecutor vuelve a ofrecer la misma deuda en cada vuelta y eso es una fila por día. Los números se derivan de las filas en el momento de la lectura; nada se cuenta en la ruta de envío.

reasons lista TODOS los motivos del vocabulario cerrado (incluidos los ceros): debt_not_found, debt_not_dialable (la deuda salió de los estados marcables), no_sendable_address (ningún contacto tiene una dirección con procedencia válida que no esté suprimida), email_address_invalid y email_debt_unresolved (el emisor rechazó solo ese lead) y los motivos throttled_* (core#2105): el emisor respondió {:throttled, motivo}, un rechazo que es el mismo para todos los leads, por lo que el ciclo se detiene y la deuda se vuelve a ofrecer: throttled_plan_inactive, throttled_rate_limit, throttled_provider_open, throttled_spend_cap y throttled_no_rate (no hay tarifa de correo configurada para la región de envío, así que no se envía nada: fail-closed) y throttled_synthetic_tenant (core#2251: el tenant es un tenant de demostración sintético, que no origina tráfico saliente). Para un motivo throttled_*, debt_id es el lead con el que se encontró el rechazo, y la fila se escribe cuando cambia el motivo o el día UTC, no una vez por ciclo. leads es el número de deudas distintas descartadas por el motivo; days los días UTC con una fila; first_at / last_at son null si no hay filas. total_leads cuenta una vez una deuda descartada por dos motivos distintos. samples son las filas más recientes, las más nuevas primero, opcionalmente acotadas con reason. Nunca se sirve ninguna dirección.

Solo lectura y con alcance de tenant (una clave dd_ o un inicio de sesión del dashboard): una campaña de otro tenant es 404, nunca un 200 vacío.

Parámetros

NombreEnTipoObligatorioDescripción
idpathstring (uuid)síUUID del recurso. Los ids mal formados o de otro tenant se leen como 404.
reasonquerydebt_not_found · debt_not_dialable · no_sendable_address · email_address_invalid · email_debt_unresolved · throttled_plan_inactive · throttled_rate_limit · throttled_provider_open · throttled_spend_cap · throttled_no_rate · throttled_synthetic_tenantnoAcota samples a un motivo. Omitido = todos los motivos. reasons nunca se acota.
limitqueryintegernoTamaño de samples (20 por defecto, máximo 100).

Respuestas

200 ​

Números por motivo y las filas más recientes.

CampoTipoObligatorioDescripción
campaign_idstring (uuid)sí
total_leadsintegersíDeudas distintas descartadas por CUALQUIER motivo (una deuda con dos motivos cuenta una vez).
reasonsarreglo de EmailLeadSkipReasonCountsíTodos los motivos del vocabulario cerrado, incluidos los ceros, en el orden del vocabulario.
reasons[].reasondebt_not_found · debt_not_dialable · no_sendable_address · email_address_invalid · email_debt_unresolved · throttled_plan_inactive · throttled_rate_limit · throttled_provider_open · throttled_spend_cap · throttled_no_rate · throttled_synthetic_tenantsí
reasons[].leadsintegersíDeudas distintas descartadas por este motivo.
reasons[].daysintegersíDías UTC con al menos una fila para este motivo.
reasons[].first_atstring,null (date-time)sí
reasons[].last_atstring,null (date-time)sí
samplesarreglo de EmailLeadSkipSamplesí
samples[].debt_idstring (uuid)sí
samples[].reasondebt_not_found · debt_not_dialable · no_sendable_address · email_address_invalid · email_debt_unresolved · throttled_plan_inactive · throttled_rate_limit · throttled_provider_open · throttled_spend_cap · throttled_no_rate · throttled_synthetic_tenantsí
samples[].occurred_atstring (date-time)sí
json
{
  "campaign_id": "3f2c1b0a-9d8e-4c7b-a6f5-4e3d2c1b0a99",
  "total_leads": 3,
  "reasons": [
    {
      "reason": "debt_not_found",
      "leads": 0,
      "days": 0,
      "first_at": null,
      "last_at": null
    },
    {
      "reason": "debt_not_dialable",
      "leads": 1,
      "days": 1,
      "first_at": "2026-10-10T15:02:00.000000Z",
      "last_at": "2026-10-10T15:02:00.000000Z"
    },
    {
      "reason": "no_sendable_address",
      "leads": 2,
      "days": 2,
      "first_at": "2026-10-09T15:00:00.000000Z",
      "last_at": "2026-10-10T15:00:00.000000Z"
    },
    {
      "reason": "email_address_invalid",
      "leads": 0,
      "days": 0,
      "first_at": null,
      "last_at": null
    },
    {
      "reason": "email_debt_unresolved",
      "leads": 0,
      "days": 0,
      "first_at": null,
      "last_at": null
    },
    {
      "reason": "throttled_plan_inactive",
      "leads": 0,
      "days": 0,
      "first_at": null,
      "last_at": null
    },
    {
      "reason": "throttled_rate_limit",
      "leads": 0,
      "days": 0,
      "first_at": null,
      "last_at": null
    },
    {
      "reason": "throttled_provider_open",
      "leads": 0,
      "days": 0,
      "first_at": null,
      "last_at": null
    },
    {
      "reason": "throttled_spend_cap",
      "leads": 0,
      "days": 0,
      "first_at": null,
      "last_at": null
    },
    {
      "reason": "throttled_no_rate",
      "leads": 0,
      "days": 0,
      "first_at": null,
      "last_at": null
    },
    {
      "reason": "throttled_synthetic_tenant",
      "leads": 0,
      "days": 0,
      "first_at": null,
      "last_at": null
    }
  ],
  "samples": [
    {
      "debt_id": "8a1d5c0e-2b7f-4e49-9c3a-6f0b1d2e3a44",
      "reason": "debt_not_dialable",
      "occurred_at": "2026-10-10T15:02:00.000000Z"
    },
    {
      "debt_id": "5e9f7a10-3c4d-4b8e-a1f2-7d6c5b4a3921",
      "reason": "no_sendable_address",
      "occurred_at": "2026-10-10T15:00:00.000000Z"
    }
  ]
}

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

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

Simulación de rebobinado (filtros en la query) ​

http
GET /v1/campaigns/{id}/rewind/preview

Equivalente GET, pensado para el dashboard, del preview POST. excluded_by_compliance cuenta las deudas coincidentes que el motor bloquearía AHORA MISMO (informativo; las compuertas se aplican de nuevo en cada originate de todos modos).

Parámetros

NombreEnTipoObligatorioDescripción
idpathstring (uuid)síUUID del recurso. Los ids mal formados o de otro tenant se leen como 404.
dispositionsquerystringnoLista separada por comas que se compara con la disposición MÁS RECIENTE de cada deuda.
max_attemptsqueryintegerno
last_attempt_older_than_daysqueryintegerno
amd_verdictsquerystringnoVeredictos AMD separados por comas que se comparan con el último intento realizado de cada deuda.
segment_idquerystring (uuid)noUn segmento guardado (/v1/rewind-segments) que se usa EN LUGAR de los filtros sueltos; combinarlo con cualquiera de ellos es 400. Se resuelve en el servidor y se reevalúa ahora.

Respuestas

200 ​

Resultado de la simulación.

CampoTipoObligatorioDescripción
matchingintegersí
excluded_by_complianceintegersíDeudas coincidentes que el motor bloquearía AHORA MISMO (informativo).
debt_idsarreglo de string (uuid)sí
json
{
  "matching": 12,
  "excluded_by_compliance": 3,
  "debt_ids": [
    "5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d"
  ]
}

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

Simulación de rebobinado (filtros en el cuerpo) ​

http
POST /v1/campaigns/{id}/rewind/preview

La misma simulación que el GET, con los filtros en el cuerpo JSON (forma del traspaso B1).

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, obligatorio)

json
{
  "max_attempts": 1
}

Respuestas

200 ​

Resultado de la simulación.

CampoTipoObligatorioDescripción
matchingintegersí
excluded_by_complianceintegersíDeudas coincidentes que el motor bloquearía AHORA MISMO (informativo).
debt_idsarreglo de string (uuid)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 — 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"
  }
}

Volver a encolar las deudas agotadas que coincidan ​

http
POST /v1/campaigns/{id}/rewind

Establece la marca de agua requeued_at de ESTA campaña en cada deuda coincidente (el alimentador de leads de la campaña las trata como nunca intentadas; no se mueve la marca de agua de ninguna otra campaña) y reactiva closed → open, salvo una deuda closed que también esté en la población de otra campaña: su estado pertenece a todas las campañas que la contienen, así que permanece closed y se cuenta en kept_closed. paid/settled/disputed NUNCA se rebobinan y el CDR no se toca (solo anexado; los contadores de Reg F siguen contando). keep_scheduled_callbacks: false cancela las rellamadas pendientes de la campaña para las deudas rebobinadas. amd_verdicts acota por el veredicto AMD del último intento realizado; un veredicto desconocido responde 422. segment_id nombra un segmento guardado (/v1/rewind-segments) en lugar de los filtros sueltos.

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, obligatorio)

json
{
  "max_attempts": 1,
  "keep_scheduled_callbacks": true
}

Respuestas

200 ​

Rebobinado aplicado.

CampoTipoObligatorioDescripción
requeuedintegersí
callbacks_cancelledintegersí
kept_closedintegersíDeudas closed coincidentes que siguieron closed porque otra campaña también las tiene en su población. Su marca de agua en esta campaña se movió de todos modos.
json
{
  "requeued": 9,
  "callbacks_cancelled": 0,
  "kept_closed": 0
}

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

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

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

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