Appearance
Campañas
Generado desde
DialerDigital/corecommit0fc18008b3c8— especificaciónopenapi/dialer-v1.yamlv0.134.0. No se edita a mano; consulta Cómo se construye esta referencia. Descargar la especificación OpenAPI.
Traducción en curso
Esta referencia se genera desde la especificación OpenAPI. Las cadenas que todavía no tienen traducción aparecen en inglés.
CRUD de campañas, ciclo de vida, marcación manual y rebobinado.
Endpoints
Listar campañas
http
GET /v1/campaignsRespuestas
200
Todas las campañas del tenant.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
campaigns | arreglo de Campaign | sí | |
campaigns[].id | string (uuid) | no | |
campaigns[].tenant_id | string (uuid) | no | |
campaigns[].name | string | no | |
campaigns[].state | draft · running · paused · completed · archived · suspended_balance · exhausted | no | |
campaigns[].campaign_type | predictive · progressive · preview · manual · tts_blast · tts_ivr · ivr_cascade · survey · ai_voice · sms_blast · email_blast | no | |
campaigns[].channel | voice · sms · email | no | Derivado del perfil del tipo: indica a qué ejecutor despacha el ciclo de vida. |
campaigns[].sms_body | string | null | no | |
campaigns[].email_template_id | string (uuid) | null | no | La versión aprobada de la plantilla de correo que envía una campaña email_blast. |
campaigns[].email_template_variables | object | no | Valores estáticos de las variables declaradas de la plantilla (email_blast). |
campaigns[].abandonment_threshold | string | null | no | Decimal representado como cadena JSON. |
campaigns[].min_dial_ratio | string | null | no | |
campaigns[].max_dial_ratio | string | null | no | |
campaigns[].calling_window_start | string | null | no | HH:MM:SS. |
campaigns[].calling_window_end | string | null | no | |
campaigns[].default_timezone | string | null | no | Zona 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_local | boolean | no | Lee 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_weekly | CampaignWeeklyWindow | null | no | La ventana por día tal como se almacena, normalizada a HH:MM:SS. null = sin ventana semanal. Consulta CampaignWrite.calling_window_weekly. |
campaigns[].starts_at | string | null | no | Instante 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_at | string | null | no | Instante 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_calls | boolean | no | |
campaigns[].ai_voice | boolean | no | |
campaigns[].amd_enabled | boolean | null | no | El 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_digit | string | null | no | La única tecla DTMF con la que el consumidor se da de baja (0-9, *, #); null = sin tecla de baja. |
campaigns[].tts_prompt | string | null | no | El guion del operador para los tratamientos tts_*; null = sin mensaje. |
campaigns[].voicemail_optout_number | string | null | no | El 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_message | string | null | no | El 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_menu | object | no | El menú IVR tal como se escribió ({} = sin menú). Consulta CampaignWrite.ivr_menu. |
campaigns[].deferred_bridge | boolean | no | |
campaigns[].consult_config | object | no | La configuración de la línea de consulta tal como se escribió ({} = deshabilitada). Consulta CampaignWrite.consult_config. |
campaigns[].carrier | string | null | no | |
campaigns[].caller_ids | arreglo de string | no | Pool rotativo anterior de cadenas E.164 (se usa cuando caller_id_pool está vacío). |
campaigns[].caller_id_pool | arreglo de string (uuid) | no | Ids 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_at | string (date-time) | no | |
campaigns[].updated_at | string (date-time) | no | |
truncated | boolean | sí | 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/campaignscampaign_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)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
name | string | no | |
campaign_type | predictive · progressive · preview · manual · tts_blast · tts_ivr · ivr_cascade · survey · ai_voice · sms_blast · email_blast | no | |
sms_body | string | no | OBLIGATORIO para sms_blast; debe contener el aviso de baja STOP; admite variables de combinación. |
email_template_id | string (uuid) | no | OBLIGATORIO 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_variables | object | no | Valores estáticos (cadena o número) de las variables que declara la plantilla; se validan contra su declaración al escribir (400 invalid). |
abandonment_threshold | number | no | |
min_dial_ratio | number | no | |
max_dial_ratio | number | no | |
calling_window_start | string | no | HH:MM:SS. |
calling_window_end | string | no | |
default_timezone | string | no | |
starts_at | string | null | no | El 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_at | string | null | no | El 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_local | boolean | no | |
calling_window_weekly | CampaignWeeklyWindow | null | no | Una 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_calls | boolean | no | |
ai_voice | boolean | no | |
amd_enabled | boolean | null | no | Interruptor 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_digit | string | no | Exactamente 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_prompt | string | no | Guion 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_number | string | no | El 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_message | string | no | El 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_menu | object | no | Mapa 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_bridge | boolean | no | Campañ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_config | object | no | Configuració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. |
carrier | string | no | |
caller_ids | arreglo de string | no | |
caller_id_pool | arreglo 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).
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
campaign | object (Campaign) | sí | |
campaign.id | string (uuid) | no | |
campaign.tenant_id | string (uuid) | no | |
campaign.name | string | no | |
campaign.state | draft · running · paused · completed · archived · suspended_balance · exhausted | no | |
campaign.campaign_type | predictive · progressive · preview · manual · tts_blast · tts_ivr · ivr_cascade · survey · ai_voice · sms_blast · email_blast | no | |
campaign.channel | voice · sms · email | no | Derivado del perfil del tipo: indica a qué ejecutor despacha el ciclo de vida. |
campaign.sms_body | string | null | no | |
campaign.email_template_id | string (uuid) | null | no | La versión aprobada de la plantilla de correo que envía una campaña email_blast. |
campaign.email_template_variables | object | no | Valores estáticos de las variables declaradas de la plantilla (email_blast). |
campaign.abandonment_threshold | string | null | no | Decimal representado como cadena JSON. |
campaign.min_dial_ratio | string | null | no | |
campaign.max_dial_ratio | string | null | no | |
campaign.calling_window_start | string | null | no | HH:MM:SS. |
campaign.calling_window_end | string | null | no | |
campaign.default_timezone | string | null | no | Zona 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_local | boolean | no | Lee 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_weekly | CampaignWeeklyWindow | null | no | La ventana por día tal como se almacena, normalizada a HH:MM:SS. null = sin ventana semanal. Consulta CampaignWrite.calling_window_weekly. |
campaign.starts_at | string | null | no | Instante 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_at | string | null | no | Instante 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_calls | boolean | no | |
campaign.ai_voice | boolean | no | |
campaign.amd_enabled | boolean | null | no | El 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_digit | string | null | no | La única tecla DTMF con la que el consumidor se da de baja (0-9, *, #); null = sin tecla de baja. |
campaign.tts_prompt | string | null | no | El guion del operador para los tratamientos tts_*; null = sin mensaje. |
campaign.voicemail_optout_number | string | null | no | El 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_message | string | null | no | El 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_menu | object | no | El menú IVR tal como se escribió ({} = sin menú). Consulta CampaignWrite.ivr_menu. |
campaign.deferred_bridge | boolean | no | |
campaign.consult_config | object | no | La configuración de la línea de consulta tal como se escribió ({} = deshabilitada). Consulta CampaignWrite.consult_config. |
campaign.carrier | string | null | no | |
campaign.caller_ids | arreglo de string | no | Pool rotativo anterior de cadenas E.164 (se usa cuando caller_id_pool está vacío). |
campaign.caller_id_pool | arreglo de string (uuid) | no | Ids 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_at | string (date-time) | no | |
campaign.updated_at | string (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
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
Respuestas
200
La campaña.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
campaign | object (Campaign) | sí | |
campaign.id | string (uuid) | no | |
campaign.tenant_id | string (uuid) | no | |
campaign.name | string | no | |
campaign.state | draft · running · paused · completed · archived · suspended_balance · exhausted | no | |
campaign.campaign_type | predictive · progressive · preview · manual · tts_blast · tts_ivr · ivr_cascade · survey · ai_voice · sms_blast · email_blast | no | |
campaign.channel | voice · sms · email | no | Derivado del perfil del tipo: indica a qué ejecutor despacha el ciclo de vida. |
campaign.sms_body | string | null | no | |
campaign.email_template_id | string (uuid) | null | no | La versión aprobada de la plantilla de correo que envía una campaña email_blast. |
campaign.email_template_variables | object | no | Valores estáticos de las variables declaradas de la plantilla (email_blast). |
campaign.abandonment_threshold | string | null | no | Decimal representado como cadena JSON. |
campaign.min_dial_ratio | string | null | no | |
campaign.max_dial_ratio | string | null | no | |
campaign.calling_window_start | string | null | no | HH:MM:SS. |
campaign.calling_window_end | string | null | no | |
campaign.default_timezone | string | null | no | Zona 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_local | boolean | no | Lee 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_weekly | CampaignWeeklyWindow | null | no | La ventana por día tal como se almacena, normalizada a HH:MM:SS. null = sin ventana semanal. Consulta CampaignWrite.calling_window_weekly. |
campaign.starts_at | string | null | no | Instante 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_at | string | null | no | Instante 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_calls | boolean | no | |
campaign.ai_voice | boolean | no | |
campaign.amd_enabled | boolean | null | no | El 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_digit | string | null | no | La única tecla DTMF con la que el consumidor se da de baja (0-9, *, #); null = sin tecla de baja. |
campaign.tts_prompt | string | null | no | El guion del operador para los tratamientos tts_*; null = sin mensaje. |
campaign.voicemail_optout_number | string | null | no | El 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_message | string | null | no | El 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_menu | object | no | El menú IVR tal como se escribió ({} = sin menú). Consulta CampaignWrite.ivr_menu. |
campaign.deferred_bridge | boolean | no | |
campaign.consult_config | object | no | La configuración de la línea de consulta tal como se escribió ({} = deshabilitada). Consulta CampaignWrite.consult_config. |
campaign.carrier | string | null | no | |
campaign.caller_ids | arreglo de string | no | Pool rotativo anterior de cadenas E.164 (se usa cuando caller_id_pool está vacío). |
campaign.caller_id_pool | arreglo de string (uuid) | no | Ids 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_at | string (date-time) | no | |
campaign.updated_at | string (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
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
Cuerpo de la petición (JSON, obligatorio)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
name | string | no | |
campaign_type | predictive · progressive · preview · manual · tts_blast · tts_ivr · ivr_cascade · survey · ai_voice · sms_blast · email_blast | no | |
sms_body | string | no | OBLIGATORIO para sms_blast; debe contener el aviso de baja STOP; admite variables de combinación. |
email_template_id | string (uuid) | no | OBLIGATORIO 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_variables | object | no | Valores estáticos (cadena o número) de las variables que declara la plantilla; se validan contra su declaración al escribir (400 invalid). |
abandonment_threshold | number | no | |
min_dial_ratio | number | no | |
max_dial_ratio | number | no | |
calling_window_start | string | no | HH:MM:SS. |
calling_window_end | string | no | |
default_timezone | string | no | |
starts_at | string | null | no | El 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_at | string | null | no | El 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_local | boolean | no | |
calling_window_weekly | CampaignWeeklyWindow | null | no | Una 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_calls | boolean | no | |
ai_voice | boolean | no | |
amd_enabled | boolean | null | no | Interruptor 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_digit | string | no | Exactamente 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_prompt | string | no | Guion 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_number | string | no | El 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_message | string | no | El 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_menu | object | no | Mapa 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_bridge | boolean | no | Campañ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_config | object | no | Configuració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. |
carrier | string | no | |
caller_ids | arreglo de string | no | |
caller_id_pool | arreglo de string (uuid) | no |
json
{
"record_calls": false
}Respuestas
200
Campaña actualizada.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
campaign | object (Campaign) | sí | |
campaign.id | string (uuid) | no | |
campaign.tenant_id | string (uuid) | no | |
campaign.name | string | no | |
campaign.state | draft · running · paused · completed · archived · suspended_balance · exhausted | no | |
campaign.campaign_type | predictive · progressive · preview · manual · tts_blast · tts_ivr · ivr_cascade · survey · ai_voice · sms_blast · email_blast | no | |
campaign.channel | voice · sms · email | no | Derivado del perfil del tipo: indica a qué ejecutor despacha el ciclo de vida. |
campaign.sms_body | string | null | no | |
campaign.email_template_id | string (uuid) | null | no | La versión aprobada de la plantilla de correo que envía una campaña email_blast. |
campaign.email_template_variables | object | no | Valores estáticos de las variables declaradas de la plantilla (email_blast). |
campaign.abandonment_threshold | string | null | no | Decimal representado como cadena JSON. |
campaign.min_dial_ratio | string | null | no | |
campaign.max_dial_ratio | string | null | no | |
campaign.calling_window_start | string | null | no | HH:MM:SS. |
campaign.calling_window_end | string | null | no | |
campaign.default_timezone | string | null | no | Zona 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_local | boolean | no | Lee 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_weekly | CampaignWeeklyWindow | null | no | La ventana por día tal como se almacena, normalizada a HH:MM:SS. null = sin ventana semanal. Consulta CampaignWrite.calling_window_weekly. |
campaign.starts_at | string | null | no | Instante 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_at | string | null | no | Instante 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_calls | boolean | no | |
campaign.ai_voice | boolean | no | |
campaign.amd_enabled | boolean | null | no | El 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_digit | string | null | no | La única tecla DTMF con la que el consumidor se da de baja (0-9, *, #); null = sin tecla de baja. |
campaign.tts_prompt | string | null | no | El guion del operador para los tratamientos tts_*; null = sin mensaje. |
campaign.voicemail_optout_number | string | null | no | El 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_message | string | null | no | El 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_menu | object | no | El menú IVR tal como se escribió ({} = sin menú). Consulta CampaignWrite.ivr_menu. |
campaign.deferred_bridge | boolean | no | |
campaign.consult_config | object | no | La configuración de la línea de consulta tal como se escribió ({} = deshabilitada). Consulta CampaignWrite.consult_config. |
campaign.carrier | string | null | no | |
campaign.caller_ids | arreglo de string | no | Pool rotativo anterior de cadenas E.164 (se usa cuando caller_id_pool está vacío). |
campaign.caller_id_pool | arreglo de string (uuid) | no | Ids 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_at | string (date-time) | no | |
campaign.updated_at | string (date-time) | no |
400 — BadRequest
Petición mal formada (errores de forma/tipo, filtros no válidos, marcas de tiempo erróneas).
json
{
"error": {
"code": "bad_request",
"message": "from must be RFC-3339"
}
}401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — 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}/startPersiste 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
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
Cuerpo de la petición (JSON)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
tick_ms | integer | no | |
batch_size | integer | no | |
msgs_per_tick | integer | no | Campañas de SMS: por defecto 1 (≈1 MPS, el caudal 10DLC seguro para long code). |
gateway | string | no | Identificador de perfil Sofia. |
agent_domain | string | no | |
agent_sip_proxy | string | no | "" excluye a la campaña del proxy de todo el despliegue. |
call_ttl_ms | integer | no | |
blocked_cooldown_ms | integer | no |
json
{
"tick_ms": 1000,
"batch_size": 2,
"gateway": "default"
}Respuestas
200
La campaña, ahora en running.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
campaign | object (Campaign) | sí | |
campaign.id | string (uuid) | no | |
campaign.tenant_id | string (uuid) | no | |
campaign.name | string | no | |
campaign.state | draft · running · paused · completed · archived · suspended_balance · exhausted | no | |
campaign.campaign_type | predictive · progressive · preview · manual · tts_blast · tts_ivr · ivr_cascade · survey · ai_voice · sms_blast · email_blast | no | |
campaign.channel | voice · sms · email | no | Derivado del perfil del tipo: indica a qué ejecutor despacha el ciclo de vida. |
campaign.sms_body | string | null | no | |
campaign.email_template_id | string (uuid) | null | no | La versión aprobada de la plantilla de correo que envía una campaña email_blast. |
campaign.email_template_variables | object | no | Valores estáticos de las variables declaradas de la plantilla (email_blast). |
campaign.abandonment_threshold | string | null | no | Decimal representado como cadena JSON. |
campaign.min_dial_ratio | string | null | no | |
campaign.max_dial_ratio | string | null | no | |
campaign.calling_window_start | string | null | no | HH:MM:SS. |
campaign.calling_window_end | string | null | no | |
campaign.default_timezone | string | null | no | Zona 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_local | boolean | no | Lee 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_weekly | CampaignWeeklyWindow | null | no | La ventana por día tal como se almacena, normalizada a HH:MM:SS. null = sin ventana semanal. Consulta CampaignWrite.calling_window_weekly. |
campaign.starts_at | string | null | no | Instante 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_at | string | null | no | Instante 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_calls | boolean | no | |
campaign.ai_voice | boolean | no | |
campaign.amd_enabled | boolean | null | no | El 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_digit | string | null | no | La única tecla DTMF con la que el consumidor se da de baja (0-9, *, #); null = sin tecla de baja. |
campaign.tts_prompt | string | null | no | El guion del operador para los tratamientos tts_*; null = sin mensaje. |
campaign.voicemail_optout_number | string | null | no | El 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_message | string | null | no | El 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_menu | object | no | El menú IVR tal como se escribió ({} = sin menú). Consulta CampaignWrite.ivr_menu. |
campaign.deferred_bridge | boolean | no | |
campaign.consult_config | object | no | La configuración de la línea de consulta tal como se escribió ({} = deshabilitada). Consulta CampaignWrite.consult_config. |
campaign.carrier | string | null | no | |
campaign.caller_ids | arreglo de string | no | Pool rotativo anterior de cadenas E.164 (se usa cuando caller_id_pool está vacío). |
campaign.caller_id_pool | arreglo de string (uuid) | no | Ids 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_at | string (date-time) | no | |
campaign.updated_at | string (date-time) | no |
400 — BadRequest
Petición mal formada (errores de forma/tipo, filtros no válidos, marcas de tiempo erróneas).
json
{
"error": {
"code": "bad_request",
"message": "from must be RFC-3339"
}
}401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — 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}/resumeReanudar ES iniciar (paused → running): una sola semántica, dos grafías (paridad con la documentación).
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
Cuerpo de la petición (JSON)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
tick_ms | integer | no | |
batch_size | integer | no | |
msgs_per_tick | integer | no | Campañas de SMS: por defecto 1 (≈1 MPS, el caudal 10DLC seguro para long code). |
gateway | string | no | Identificador de perfil Sofia. |
agent_domain | string | no | |
agent_sip_proxy | string | no | "" excluye a la campaña del proxy de todo el despliegue. |
call_ttl_ms | integer | no | |
blocked_cooldown_ms | integer | no |
Respuestas
200
La campaña, ahora en running.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
campaign | object (Campaign) | sí | |
campaign.id | string (uuid) | no | |
campaign.tenant_id | string (uuid) | no | |
campaign.name | string | no | |
campaign.state | draft · running · paused · completed · archived · suspended_balance · exhausted | no | |
campaign.campaign_type | predictive · progressive · preview · manual · tts_blast · tts_ivr · ivr_cascade · survey · ai_voice · sms_blast · email_blast | no | |
campaign.channel | voice · sms · email | no | Derivado del perfil del tipo: indica a qué ejecutor despacha el ciclo de vida. |
campaign.sms_body | string | null | no | |
campaign.email_template_id | string (uuid) | null | no | La versión aprobada de la plantilla de correo que envía una campaña email_blast. |
campaign.email_template_variables | object | no | Valores estáticos de las variables declaradas de la plantilla (email_blast). |
campaign.abandonment_threshold | string | null | no | Decimal representado como cadena JSON. |
campaign.min_dial_ratio | string | null | no | |
campaign.max_dial_ratio | string | null | no | |
campaign.calling_window_start | string | null | no | HH:MM:SS. |
campaign.calling_window_end | string | null | no | |
campaign.default_timezone | string | null | no | Zona 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_local | boolean | no | Lee 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_weekly | CampaignWeeklyWindow | null | no | La ventana por día tal como se almacena, normalizada a HH:MM:SS. null = sin ventana semanal. Consulta CampaignWrite.calling_window_weekly. |
campaign.starts_at | string | null | no | Instante 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_at | string | null | no | Instante 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_calls | boolean | no | |
campaign.ai_voice | boolean | no | |
campaign.amd_enabled | boolean | null | no | El 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_digit | string | null | no | La única tecla DTMF con la que el consumidor se da de baja (0-9, *, #); null = sin tecla de baja. |
campaign.tts_prompt | string | null | no | El guion del operador para los tratamientos tts_*; null = sin mensaje. |
campaign.voicemail_optout_number | string | null | no | El 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_message | string | null | no | El 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_menu | object | no | El menú IVR tal como se escribió ({} = sin menú). Consulta CampaignWrite.ivr_menu. |
campaign.deferred_bridge | boolean | no | |
campaign.consult_config | object | no | La configuración de la línea de consulta tal como se escribió ({} = deshabilitada). Consulta CampaignWrite.consult_config. |
campaign.carrier | string | null | no | |
campaign.caller_ids | arreglo de string | no | Pool rotativo anterior de cadenas E.164 (se usa cuando caller_id_pool está vacío). |
campaign.caller_id_pool | arreglo de string (uuid) | no | Ids 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_at | string (date-time) | no | |
campaign.updated_at | string (date-time) | no |
400 — BadRequest
Petición mal formada (errores de forma/tipo, filtros no válidos, marcas de tiempo erróneas).
json
{
"error": {
"code": "bad_request",
"message": "from must be RFC-3339"
}
}401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — 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}/pausePersiste paused y detiene el ejecutor; las llamadas en curso terminan mediante el pipeline de CDR.
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
Respuestas
200
La campaña, ahora en paused.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
campaign | object (Campaign) | sí | |
campaign.id | string (uuid) | no | |
campaign.tenant_id | string (uuid) | no | |
campaign.name | string | no | |
campaign.state | draft · running · paused · completed · archived · suspended_balance · exhausted | no | |
campaign.campaign_type | predictive · progressive · preview · manual · tts_blast · tts_ivr · ivr_cascade · survey · ai_voice · sms_blast · email_blast | no | |
campaign.channel | voice · sms · email | no | Derivado del perfil del tipo: indica a qué ejecutor despacha el ciclo de vida. |
campaign.sms_body | string | null | no | |
campaign.email_template_id | string (uuid) | null | no | La versión aprobada de la plantilla de correo que envía una campaña email_blast. |
campaign.email_template_variables | object | no | Valores estáticos de las variables declaradas de la plantilla (email_blast). |
campaign.abandonment_threshold | string | null | no | Decimal representado como cadena JSON. |
campaign.min_dial_ratio | string | null | no | |
campaign.max_dial_ratio | string | null | no | |
campaign.calling_window_start | string | null | no | HH:MM:SS. |
campaign.calling_window_end | string | null | no | |
campaign.default_timezone | string | null | no | Zona 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_local | boolean | no | Lee 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_weekly | CampaignWeeklyWindow | null | no | La ventana por día tal como se almacena, normalizada a HH:MM:SS. null = sin ventana semanal. Consulta CampaignWrite.calling_window_weekly. |
campaign.starts_at | string | null | no | Instante 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_at | string | null | no | Instante 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_calls | boolean | no | |
campaign.ai_voice | boolean | no | |
campaign.amd_enabled | boolean | null | no | El 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_digit | string | null | no | La única tecla DTMF con la que el consumidor se da de baja (0-9, *, #); null = sin tecla de baja. |
campaign.tts_prompt | string | null | no | El guion del operador para los tratamientos tts_*; null = sin mensaje. |
campaign.voicemail_optout_number | string | null | no | El 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_message | string | null | no | El 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_menu | object | no | El menú IVR tal como se escribió ({} = sin menú). Consulta CampaignWrite.ivr_menu. |
campaign.deferred_bridge | boolean | no | |
campaign.consult_config | object | no | La configuración de la línea de consulta tal como se escribió ({} = deshabilitada). Consulta CampaignWrite.consult_config. |
campaign.carrier | string | null | no | |
campaign.caller_ids | arreglo de string | no | Pool rotativo anterior de cadenas E.164 (se usa cuando caller_id_pool está vacío). |
campaign.caller_id_pool | arreglo de string (uuid) | no | Ids 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_at | string (date-time) | no | |
campaign.updated_at | string (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}/stopParámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
Respuestas
200
La campaña, ahora en completed.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
campaign | object (Campaign) | sí | |
campaign.id | string (uuid) | no | |
campaign.tenant_id | string (uuid) | no | |
campaign.name | string | no | |
campaign.state | draft · running · paused · completed · archived · suspended_balance · exhausted | no | |
campaign.campaign_type | predictive · progressive · preview · manual · tts_blast · tts_ivr · ivr_cascade · survey · ai_voice · sms_blast · email_blast | no | |
campaign.channel | voice · sms · email | no | Derivado del perfil del tipo: indica a qué ejecutor despacha el ciclo de vida. |
campaign.sms_body | string | null | no | |
campaign.email_template_id | string (uuid) | null | no | La versión aprobada de la plantilla de correo que envía una campaña email_blast. |
campaign.email_template_variables | object | no | Valores estáticos de las variables declaradas de la plantilla (email_blast). |
campaign.abandonment_threshold | string | null | no | Decimal representado como cadena JSON. |
campaign.min_dial_ratio | string | null | no | |
campaign.max_dial_ratio | string | null | no | |
campaign.calling_window_start | string | null | no | HH:MM:SS. |
campaign.calling_window_end | string | null | no | |
campaign.default_timezone | string | null | no | Zona 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_local | boolean | no | Lee 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_weekly | CampaignWeeklyWindow | null | no | La ventana por día tal como se almacena, normalizada a HH:MM:SS. null = sin ventana semanal. Consulta CampaignWrite.calling_window_weekly. |
campaign.starts_at | string | null | no | Instante 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_at | string | null | no | Instante 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_calls | boolean | no | |
campaign.ai_voice | boolean | no | |
campaign.amd_enabled | boolean | null | no | El 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_digit | string | null | no | La única tecla DTMF con la que el consumidor se da de baja (0-9, *, #); null = sin tecla de baja. |
campaign.tts_prompt | string | null | no | El guion del operador para los tratamientos tts_*; null = sin mensaje. |
campaign.voicemail_optout_number | string | null | no | El 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_message | string | null | no | El 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_menu | object | no | El menú IVR tal como se escribió ({} = sin menú). Consulta CampaignWrite.ivr_menu. |
campaign.deferred_bridge | boolean | no | |
campaign.consult_config | object | no | La configuración de la línea de consulta tal como se escribió ({} = deshabilitada). Consulta CampaignWrite.consult_config. |
campaign.carrier | string | null | no | |
campaign.caller_ids | arreglo de string | no | Pool rotativo anterior de cadenas E.164 (se usa cuando caller_id_pool está vacío). |
campaign.caller_id_pool | arreglo de string (uuid) | no | Ids 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_at | string (date-time) | no | |
campaign.updated_at | string (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}/archivedraft|completed → archived (terminal). Cualquier otro caso → 409.
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
Respuestas
200
La campaña, ahora en archived.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
campaign | object (Campaign) | sí | |
campaign.id | string (uuid) | no | |
campaign.tenant_id | string (uuid) | no | |
campaign.name | string | no | |
campaign.state | draft · running · paused · completed · archived · suspended_balance · exhausted | no | |
campaign.campaign_type | predictive · progressive · preview · manual · tts_blast · tts_ivr · ivr_cascade · survey · ai_voice · sms_blast · email_blast | no | |
campaign.channel | voice · sms · email | no | Derivado del perfil del tipo: indica a qué ejecutor despacha el ciclo de vida. |
campaign.sms_body | string | null | no | |
campaign.email_template_id | string (uuid) | null | no | La versión aprobada de la plantilla de correo que envía una campaña email_blast. |
campaign.email_template_variables | object | no | Valores estáticos de las variables declaradas de la plantilla (email_blast). |
campaign.abandonment_threshold | string | null | no | Decimal representado como cadena JSON. |
campaign.min_dial_ratio | string | null | no | |
campaign.max_dial_ratio | string | null | no | |
campaign.calling_window_start | string | null | no | HH:MM:SS. |
campaign.calling_window_end | string | null | no | |
campaign.default_timezone | string | null | no | Zona 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_local | boolean | no | Lee 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_weekly | CampaignWeeklyWindow | null | no | La ventana por día tal como se almacena, normalizada a HH:MM:SS. null = sin ventana semanal. Consulta CampaignWrite.calling_window_weekly. |
campaign.starts_at | string | null | no | Instante 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_at | string | null | no | Instante 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_calls | boolean | no | |
campaign.ai_voice | boolean | no | |
campaign.amd_enabled | boolean | null | no | El 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_digit | string | null | no | La única tecla DTMF con la que el consumidor se da de baja (0-9, *, #); null = sin tecla de baja. |
campaign.tts_prompt | string | null | no | El guion del operador para los tratamientos tts_*; null = sin mensaje. |
campaign.voicemail_optout_number | string | null | no | El 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_message | string | null | no | El 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_menu | object | no | El menú IVR tal como se escribió ({} = sin menú). Consulta CampaignWrite.ivr_menu. |
campaign.deferred_bridge | boolean | no | |
campaign.consult_config | object | no | La configuración de la línea de consulta tal como se escribió ({} = deshabilitada). Consulta CampaignWrite.consult_config. |
campaign.carrier | string | null | no | |
campaign.caller_ids | arreglo de string | no | Pool rotativo anterior de cadenas E.164 (se usa cuando caller_id_pool está vacío). |
campaign.caller_id_pool | arreglo de string (uuid) | no | Ids 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_at | string (date-time) | no | |
campaign.updated_at | string (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}/experimentsTodos 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
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
Respuestas
200
Los experimentos de la campaña.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
experiments | arreglo de Experiment | sí | |
experiments[].id | string (uuid) | no | |
experiments[].campaign_id | string (uuid) | no | |
experiments[].name | string | no | |
experiments[].state | draft · running · ended | no | |
experiments[].primary_metric | contact_rate · promise_rate | no | |
experiments[].started_at | string,null (date-time) | no | |
experiments[].ended_at | string,null (date-time) | no | |
experiments[].variants | arreglo de ExperimentVariant | no | |
experiments[].variants[].id | string (uuid) | no | |
experiments[].variants[].position | integer | no | Orden en el que Assignment.pick/3 recorre las variantes (base 0). |
experiments[].variants[].name | string | no | |
experiments[].variants[].is_control | boolean | no | |
experiments[].variants[].weight | integer | no | |
experiments[].variants[].overrides | object (ExperimentVariantOverrides) | no | Lo que una variante cambia de su campaña: una lista CERRADA. Cualquier otra clave se rechaza con 422 (nunca se ignora). |
experiments[].variants[].overrides.script | object | no | Pasa por los validadores de escritura propios de la campaña (un sms_body necesita «STOP»). |
experiments[].variants[].overrides.window | object | no | Un SUBCONJUNTO de la ventana de llamadas de la campaña; nunca la amplía. |
experiments[].variants[].overrides.min_gap_seconds | integer | no | |
experiments[].variants[].overrides.email_template_id | string (uuid) | no | Una plantilla APROBADA del tenant. |
experiments[].created_at | string (date-time) | no | |
experiments[].updated_at | string (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}/experimentsCrea 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
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
Cuerpo de la petición (JSON, obligatorio)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
name | string | no | |
primary_metric | contact_rate · promise_rate | no | |
variants | arreglo de ExperimentVariantWrite | no | |
variants[].name | string | sí | |
variants[].weight | integer | sí | Proporción del tráfico, relativa a la suma de los pesos. |
variants[].is_control | boolean | no | Exactamente una variante del experimento es el control. |
variants[].overrides | object (ExperimentVariantOverrides) | no | Lo que una variante cambia de su campaña: una lista CERRADA. Cualquier otra clave se rechaza con 422 (nunca se ignora). |
variants[].overrides.script | object | no | Pasa por los validadores de escritura propios de la campaña (un sms_body necesita «STOP»). |
variants[].overrides.script.field | tts_prompt · voicemail_message · sms_body | sí | |
variants[].overrides.script.text | string | sí | |
variants[].overrides.window | object | no | Un SUBCONJUNTO de la ventana de llamadas de la campaña; nunca la amplía. |
variants[].overrides.window.start | string | sí | |
variants[].overrides.window.end | string | sí | |
variants[].overrides.min_gap_seconds | integer | no | |
variants[].overrides.email_template_id | string (uuid) | no | Una plantilla APROBADA del tenant. |
Respuestas
201
El experimento en borrador.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
experiment | object (Experiment) | sí | |
experiment.id | string (uuid) | no | |
experiment.campaign_id | string (uuid) | no | |
experiment.name | string | no | |
experiment.state | draft · running · ended | no | |
experiment.primary_metric | contact_rate · promise_rate | no | |
experiment.started_at | string,null (date-time) | no | |
experiment.ended_at | string,null (date-time) | no | |
experiment.variants | arreglo de ExperimentVariant | no | |
experiment.variants[].id | string (uuid) | no | |
experiment.variants[].position | integer | no | Orden en el que Assignment.pick/3 recorre las variantes (base 0). |
experiment.variants[].name | string | no | |
experiment.variants[].is_control | boolean | no | |
experiment.variants[].weight | integer | no | |
experiment.variants[].overrides | object (ExperimentVariantOverrides) | no | Lo que una variante cambia de su campaña: una lista CERRADA. Cualquier otra clave se rechaza con 422 (nunca se ignora). |
experiment.variants[].overrides.script | object | no | Pasa por los validadores de escritura propios de la campaña (un sms_body necesita «STOP»). |
experiment.variants[].overrides.window | object | no | Un SUBCONJUNTO de la ventana de llamadas de la campaña; nunca la amplía. |
experiment.variants[].overrides.min_gap_seconds | integer | no | |
experiment.variants[].overrides.email_template_id | string (uuid) | no | Una plantilla APROBADA del tenant. |
experiment.created_at | string (date-time) | no | |
experiment.updated_at | string (date-time) | no |
400 — BadRequest
Petición mal formada (errores de forma/tipo, filtros no válidos, marcas de tiempo erróneas).
json
{
"error": {
"code": "bad_request",
"message": "from must be RFC-3339"
}
}401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}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
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
experiment_id | path | string (uuid) | sí | Id de un experimento A/B de la campaña. |
Respuestas
200
El experimento con sus variantes.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
experiment | object (Experiment) | sí | |
experiment.id | string (uuid) | no | |
experiment.campaign_id | string (uuid) | no | |
experiment.name | string | no | |
experiment.state | draft · running · ended | no | |
experiment.primary_metric | contact_rate · promise_rate | no | |
experiment.started_at | string,null (date-time) | no | |
experiment.ended_at | string,null (date-time) | no | |
experiment.variants | arreglo de ExperimentVariant | no | |
experiment.variants[].id | string (uuid) | no | |
experiment.variants[].position | integer | no | Orden en el que Assignment.pick/3 recorre las variantes (base 0). |
experiment.variants[].name | string | no | |
experiment.variants[].is_control | boolean | no | |
experiment.variants[].weight | integer | no | |
experiment.variants[].overrides | object (ExperimentVariantOverrides) | no | Lo que una variante cambia de su campaña: una lista CERRADA. Cualquier otra clave se rechaza con 422 (nunca se ignora). |
experiment.variants[].overrides.script | object | no | Pasa por los validadores de escritura propios de la campaña (un sms_body necesita «STOP»). |
experiment.variants[].overrides.window | object | no | Un SUBCONJUNTO de la ventana de llamadas de la campaña; nunca la amplía. |
experiment.variants[].overrides.min_gap_seconds | integer | no | |
experiment.variants[].overrides.email_template_id | string (uuid) | no | Una plantilla APROBADA del tenant. |
experiment.created_at | string (date-time) | no | |
experiment.updated_at | string (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
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
experiment_id | path | string (uuid) | sí | Id de un experimento A/B de la campaña. |
Cuerpo de la petición (JSON, obligatorio)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
name | string | no | |
primary_metric | contact_rate · promise_rate | no | |
variants | arreglo de ExperimentVariantWrite | no | |
variants[].name | string | sí | |
variants[].weight | integer | sí | Proporción del tráfico, relativa a la suma de los pesos. |
variants[].is_control | boolean | no | Exactamente una variante del experimento es el control. |
variants[].overrides | object (ExperimentVariantOverrides) | no | Lo que una variante cambia de su campaña: una lista CERRADA. Cualquier otra clave se rechaza con 422 (nunca se ignora). |
variants[].overrides.script | object | no | Pasa por los validadores de escritura propios de la campaña (un sms_body necesita «STOP»). |
variants[].overrides.script.field | tts_prompt · voicemail_message · sms_body | sí | |
variants[].overrides.script.text | string | sí | |
variants[].overrides.window | object | no | Un SUBCONJUNTO de la ventana de llamadas de la campaña; nunca la amplía. |
variants[].overrides.window.start | string | sí | |
variants[].overrides.window.end | string | sí | |
variants[].overrides.min_gap_seconds | integer | no | |
variants[].overrides.email_template_id | string (uuid) | no | Una plantilla APROBADA del tenant. |
Respuestas
200
El experimento editado.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
experiment | object (Experiment) | sí | |
experiment.id | string (uuid) | no | |
experiment.campaign_id | string (uuid) | no | |
experiment.name | string | no | |
experiment.state | draft · running · ended | no | |
experiment.primary_metric | contact_rate · promise_rate | no | |
experiment.started_at | string,null (date-time) | no | |
experiment.ended_at | string,null (date-time) | no | |
experiment.variants | arreglo de ExperimentVariant | no | |
experiment.variants[].id | string (uuid) | no | |
experiment.variants[].position | integer | no | Orden en el que Assignment.pick/3 recorre las variantes (base 0). |
experiment.variants[].name | string | no | |
experiment.variants[].is_control | boolean | no | |
experiment.variants[].weight | integer | no | |
experiment.variants[].overrides | object (ExperimentVariantOverrides) | no | Lo que una variante cambia de su campaña: una lista CERRADA. Cualquier otra clave se rechaza con 422 (nunca se ignora). |
experiment.variants[].overrides.script | object | no | Pasa por los validadores de escritura propios de la campaña (un sms_body necesita «STOP»). |
experiment.variants[].overrides.window | object | no | Un SUBCONJUNTO de la ventana de llamadas de la campaña; nunca la amplía. |
experiment.variants[].overrides.min_gap_seconds | integer | no | |
experiment.variants[].overrides.email_template_id | string (uuid) | no | Una plantilla APROBADA del tenant. |
experiment.created_at | string (date-time) | no | |
experiment.updated_at | string (date-time) | no |
400 — BadRequest
Petición mal formada (errores de forma/tipo, filtros no válidos, marcas de tiempo erróneas).
json
{
"error": {
"code": "bad_request",
"message": "from must be RFC-3339"
}
}401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}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
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
experiment_id | path | string (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}/resultsSe 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
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
experiment_id | path | string (uuid) | sí | Id de un experimento A/B de la campaña. |
Respuestas
200
Los resultados del experimento.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
results | object (ExperimentResults) | sí | |
results.experiment | object | sí | |
results.experiment.id | string (uuid) | no | |
results.experiment.campaign_id | string (uuid) | no | |
results.experiment.name | string | no | |
results.experiment.state | draft · running · ended | no | |
results.experiment.primary_metric | contact_rate · promise_rate | no | |
results.experiment.started_at | string,null (date-time) | no | |
results.experiment.ended_at | string,null (date-time) | no | |
results.variants | arreglo de ExperimentVariantResults | sí | |
results.variants[].variant_id | string (uuid) | no | |
results.variants[].name | string | no | |
results.variants[].is_control | boolean | no | |
results.variants[].weight | integer | no | |
results.variants[].assigned_debts | integer | no | Deudas distintas con un intento en esta variante (la unidad de aleatorización). |
results.variants[].attempts | integer | no | |
results.variants[].not_placed | integer | no | Intentos que nunca salieron (fuera de todo denominador). |
results.variants[].placed | integer | no | attempts menos not_placed; el denominador de las tasas. |
results.variants[].contacts | integer | no | Intentos 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[].promises | integer | no | Intentos 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[].abandoned | integer | no | Contactos que terminaron sin ningún agente que los atendiera. |
results.variants[].successes | integer | no | contacts o promises, según el primary_metric del experimento. |
results.variants[].contact_rate | number,null | no | contacts / placed; null cuando no se colocó nada. |
results.variants[].promise_rate | number,null | no | promises / placed; null cuando no se colocó nada y siempre null para una campaña de SMS o correo. |
results.variants[].abandon_rate | number,null | no | abandoned / contacts; null cuando no hubo contactos y siempre null para una campaña de SMS o correo. |
results.unattributed_attempts | integer | sí | 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.verdict | object (ExperimentVerdict) | sí | |
results.verdict.status | insufficient_data · no_significant_difference · winner | sí | |
results.verdict.winner | null | object | sí | |
results.verdict.withheld_by | null | allocation_warning | sí | Se establece cuando un posible ganador se retuvo a causa de la advertencia de asignación. |
results.verdict.comparisons | arreglo de object | sí | Una entrada por retador; vacío mientras no se alcance el mínimo de muestra. |
results.verdict.comparisons[].variant_id | string (uuid) | no | |
results.verdict.comparisons[].rate | number | no | |
results.verdict.comparisons[].difference | number | no | Retador menos control. |
results.verdict.comparisons[].z | number | no | |
results.verdict.comparisons[].p_value | number | no | |
results.verdict.comparisons[].adjusted_p_value | number | no | |
results.verdict.comparisons[].ci_low | number | no | |
results.verdict.comparisons[].ci_high | number | no | |
results.verdict.comparisons[].significant | boolean | no | |
results.verdict.sample_floor | object | sí | |
results.verdict.sample_floor.min_placed | integer | no | |
results.verdict.sample_floor.min_successes | integer | no | |
results.verdict.sample_floor.met | boolean | no | |
results.allocation_warning | boolean | sí | 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.allocation | object | sí | |
results.allocation.chi_square | number | no | |
results.allocation.degrees_of_freedom | integer | no | |
results.allocation.p_value | number | no | |
results.allocation.warning | boolean | 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"
}
}Iniciar un experimento A/B
http
POST /v1/campaigns/{id}/experiments/{experiment_id}/startdraft → 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
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
experiment_id | path | string (uuid) | sí | Id de un experimento A/B de la campaña. |
Respuestas
200
El experimento, ahora en running.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
experiment | object (Experiment) | sí | |
experiment.id | string (uuid) | no | |
experiment.campaign_id | string (uuid) | no | |
experiment.name | string | no | |
experiment.state | draft · running · ended | no | |
experiment.primary_metric | contact_rate · promise_rate | no | |
experiment.started_at | string,null (date-time) | no | |
experiment.ended_at | string,null (date-time) | no | |
experiment.variants | arreglo de ExperimentVariant | no | |
experiment.variants[].id | string (uuid) | no | |
experiment.variants[].position | integer | no | Orden en el que Assignment.pick/3 recorre las variantes (base 0). |
experiment.variants[].name | string | no | |
experiment.variants[].is_control | boolean | no | |
experiment.variants[].weight | integer | no | |
experiment.variants[].overrides | object (ExperimentVariantOverrides) | no | Lo que una variante cambia de su campaña: una lista CERRADA. Cualquier otra clave se rechaza con 422 (nunca se ignora). |
experiment.variants[].overrides.script | object | no | Pasa por los validadores de escritura propios de la campaña (un sms_body necesita «STOP»). |
experiment.variants[].overrides.window | object | no | Un SUBCONJUNTO de la ventana de llamadas de la campaña; nunca la amplía. |
experiment.variants[].overrides.min_gap_seconds | integer | no | |
experiment.variants[].overrides.email_template_id | string (uuid) | no | Una plantilla APROBADA del tenant. |
experiment.created_at | string (date-time) | no | |
experiment.updated_at | string (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}/stoprunning → ended (terminal). Cualquier otro caso → 409.
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
experiment_id | path | string (uuid) | sí | Id de un experimento A/B de la campaña. |
Respuestas
200
El experimento, ahora en ended.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
experiment | object (Experiment) | sí | |
experiment.id | string (uuid) | no | |
experiment.campaign_id | string (uuid) | no | |
experiment.name | string | no | |
experiment.state | draft · running · ended | no | |
experiment.primary_metric | contact_rate · promise_rate | no | |
experiment.started_at | string,null (date-time) | no | |
experiment.ended_at | string,null (date-time) | no | |
experiment.variants | arreglo de ExperimentVariant | no | |
experiment.variants[].id | string (uuid) | no | |
experiment.variants[].position | integer | no | Orden en el que Assignment.pick/3 recorre las variantes (base 0). |
experiment.variants[].name | string | no | |
experiment.variants[].is_control | boolean | no | |
experiment.variants[].weight | integer | no | |
experiment.variants[].overrides | object (ExperimentVariantOverrides) | no | Lo que una variante cambia de su campaña: una lista CERRADA. Cualquier otra clave se rechaza con 422 (nunca se ignora). |
experiment.variants[].overrides.script | object | no | Pasa por los validadores de escritura propios de la campaña (un sms_body necesita «STOP»). |
experiment.variants[].overrides.window | object | no | Un SUBCONJUNTO de la ventana de llamadas de la campaña; nunca la amplía. |
experiment.variants[].overrides.min_gap_seconds | integer | no | |
experiment.variants[].overrides.email_template_id | string (uuid) | no | Una plantilla APROBADA del tenant. |
experiment.created_at | string (date-time) | no | |
experiment.updated_at | string (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}/dialUn 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
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
Cuerpo de la petición (JSON, obligatorio)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
debt_id | string (uuid) | sí | |
agent_id | string (uuid) | no | El 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.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
status | dialing · blocked | sí | |
call_id | string (uuid) | no | Presente cuando status es dialing. |
reason | tcpa_no_consent · quiet_hours · regf_7in7 · regf_post_contact · state_limit · dnc_listed · consent_revoked · cease_and_desist · number_reassigned · tenant_policy · artificial_voice_no_consent | no | El vocabulario de códigos de motivo del motor de cumplimiento. |
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/manualEl 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)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
number | string | sí | El destino digitado, E.164 (+13125550100). |
agent_id | string (uuid) | no | El 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.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
status | dialing · blocked | sí | |
call_id | string (uuid) | no | Presente cuando status es dialing. |
reason | tcpa_no_consent · quiet_hours · regf_7in7 · regf_post_contact · state_limit · dnc_listed · consent_revoked · cease_and_desist · number_reassigned · tenant_policy · artificial_voice_no_consent | no | El vocabulario de códigos de motivo del motor de cumplimiento. |
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-leadParámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
Respuestas
200
El siguiente lead marcable, o null cuando la alimentación está vacía.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
lead | PreviewLead | null | sí |
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-preflightEjecuta 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
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
Respuestas
200
Agregado de la verificación previa
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
campaign_id | string (uuid) | sí | |
unknown_state_policy | strict · federal_fallback | sí | Postura de la matriz de estados para un consumer_state fuera de su forma canónica, según el flag del tenant. |
tenant_gates | object | sí | Postura de cada compuerta para este tenant. Hoy solo state_matrix tiene interruptor de apagado. |
tenant_gates.dnc | active | no | |
tenant_gates.consent | active | no | |
tenant_gates.quiet_hours | active | no | |
tenant_gates.reg_f | active | no | |
tenant_gates.state_matrix | active · federal_fallback | no | |
totals | object | sí | |
totals.leads | integer | no | Tamaño exacto de la población marcable. |
totals.evaluated | integer | no | Leads sobre los que se ejecutó el motor (≤ leads). |
totals.eligible | integer | no | |
totals.suppressed | integer | no | |
totals.unknown_state | integer | no | Leads evaluados con us_state vacío. |
truncated | boolean | sí | true cuando evaluated < leads (se alcanzó el límite de evaluación). |
states | object | sí | Indexado por el us_state CRUDO que ve el motor ("unknown" para la cadena vacía). |
list_health | object | sí | Salud por señal de los leads evaluados, sin cortocircuito (core#1181). |
list_health.signals | object | sí | |
list_health.signals.consent | object (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.status | ok · blocking · no_verificable | sí | |
list_health.signals.consent.blocked | integer | sí | |
list_health.signals.consent.no_verificable | integer | sí | |
list_health.signals.consent.rules | object | sí | Leads bloqueados por id de regla (para state, la etiqueta de la matriz de estados bajo la que cae el lead). |
list_health.signals.state | object (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.status | ok · blocking · no_verificable | sí | |
list_health.signals.state.blocked | integer | sí | |
list_health.signals.state.no_verificable | integer | sí | |
list_health.signals.state.rules | object | sí | Leads bloqueados por id de regla (para state, la etiqueta de la matriz de estados bajo la que cae el lead). |
list_health.signals.dnc | object (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.status | ok · blocking · no_verificable | sí | |
list_health.signals.dnc.blocked | integer | sí | |
list_health.signals.dnc.no_verificable | integer | sí | |
list_health.signals.dnc.rules | object | sí | Leads bloqueados por id de regla (para state, la etiqueta de la matriz de estados bajo la que cae el lead). |
list_health.signals.rnd | object (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.status | ok · blocking · no_verificable | sí | |
list_health.signals.rnd.blocked | integer | sí | |
list_health.signals.rnd.no_verificable | integer | sí | |
list_health.signals.rnd.rules | object | sí | Leads bloqueados por id de regla (para state, la etiqueta de la matriz de estados bajo la que cae el lead). |
list_health.dnc_snapshots | object | sí | Las tres listas DNC globales, cada una frente al techo de 31 días. |
list_health.dnc_snapshots.federal | object (PreflightDncSnapshot) | sí | |
list_health.dnc_snapshots.federal.status | provisioned · stale · absent | sí | |
list_health.dnc_snapshots.federal.age_days | integer,null | sí | Días desde que se importó la instantánea; null cuando no existe. |
list_health.dnc_snapshots.federal.max_age_days | integer | sí | Techo de frescura para esta lista. |
list_health.dnc_snapshots.federal.source | string | sí | Cadena de procedencia de la instantánea (none cuando no existe). |
list_health.dnc_snapshots.federal.record_count | integer,null | sí | |
list_health.dnc_snapshots.state | object (PreflightDncSnapshot) | sí | |
list_health.dnc_snapshots.state.status | provisioned · stale · absent | sí | |
list_health.dnc_snapshots.state.age_days | integer,null | sí | Días desde que se importó la instantánea; null cuando no existe. |
list_health.dnc_snapshots.state.max_age_days | integer | sí | Techo de frescura para esta lista. |
list_health.dnc_snapshots.state.source | string | sí | Cadena de procedencia de la instantánea (none cuando no existe). |
list_health.dnc_snapshots.state.record_count | integer,null | sí | |
list_health.dnc_snapshots.internal_global | object (PreflightDncSnapshot) | sí | |
list_health.dnc_snapshots.internal_global.status | provisioned · stale · absent | sí | |
list_health.dnc_snapshots.internal_global.age_days | integer,null | sí | Días desde que se importó la instantánea; null cuando no existe. |
list_health.dnc_snapshots.internal_global.max_age_days | integer | sí | Techo de frescura para esta lista. |
list_health.dnc_snapshots.internal_global.source | string | sí | Cadena de procedencia de la instantánea (none cuando no existe). |
list_health.dnc_snapshots.internal_global.record_count | integer,null | sí | |
list_health.dnc_state_registries | object | sí | Cada registro DNC por estado declarado, indexado por código de estado, cada uno frente a su propio techo. |
list_health.rnd_provider | object | sí | |
list_health.rnd_provider.status | declared · stub · no_verificable | sí | 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.provider | string,null | sí | 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}/statsrunning: 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
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
Respuestas
200
Estado de la campaña + estadísticas en vivo.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
campaign_id | string (uuid) | sí | |
state | draft · running · paused · completed · archived · suspended_balance · exhausted | sí | |
running | boolean | sí | |
stats | VoiceRunnerStats | SmsRunnerStats | null | sí |
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-resultsEl 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
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
from | query | string (date-time) | no | Límite inferior RFC 3339 (campo específico del endpoint; por defecto los últimos 30 días UTC donde se indica). |
to | query | string (date-time) | no | Límite superior RFC 3339. |
question_id | query | string | no | Acota a una pregunta (cadena no vacía). Omitido = todas las preguntas. |
format | query | csv | no | csv para el artefacto descargable (equivalente: Accept: text/csv). |
Respuestas
200
Distribución por dígito (JSON) o el artefacto CSV.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
campaign_id | string (uuid) | sí | |
from | string (date-time) | sí | |
to | string (date-time) | sí | |
question_id | string | null | sí | El filtro que se aplicó; null = todas las preguntas. |
total | integer | sí | |
distribution | arreglo de object | sí | Una fila por dígito pulsado al menos una vez, ordenada por dígito. |
distribution[].digit | string | sí | |
distribution[].count | integer | sí |
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}/abandonmentLos 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
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
format | query | csv | no | csv para el artefacto descargable (equivalente: Accept: text/csv). |
Respuestas
200
El informe (JSON) o el artefacto CSV.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
campaign_id | string (uuid) | sí | |
measured_at | string (date-time) | sí | |
period_days | 30 | sí | |
ceiling_percent | 3 | sí | |
anchored_at | string (date-time) | null | sí | La primera marcación realizada de la campaña; null = aún sin marcaciones (y sin período). |
predicate | object | sí | |
predicate.counts | arreglo de no_agent · ai_stream_lost · agent_bridge_failed · handoff_failed · late_bridge | sí | Los motivos por los que la tasa cuenta como abandonada. |
predicate.reported_apart | arreglo de no_agent · ai_stream_lost · agent_bridge_failed · handoff_failed · late_bridge | sí | Motivos informados por período y que NO se cuentan en la tasa. |
periods | arreglo de AbandonmentPeriod | sí | 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[].index | integer | sí | [anchored_at + 30·index days, + 30 days). |
periods[].starts_at | string (date-time) | sí | |
periods[].ends_at | string (date-time) | sí | Exclusivo. |
periods[].complete | boolean | sí | ends_at no es posterior a measured_at. |
periods[].placed | integer | sí | |
periods[].answered | integer | sí | |
periods[].answered_machine | integer | sí | Contestada con un veredicto AMD machine. |
periods[].answered_live | integer | sí | El denominador: answered - answered_machine. |
periods[].abandoned | integer | sí | El numerador; la suma de abandoned_by_reason. |
periods[].abandoned_by_reason | object | sí | 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_failed | integer | sí | Traspasos de IA de voz sin puesto disponible; no entran en la tasa. |
periods[].late_bridge | integer | sí | 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[].rate | number | null | sí | abandoned / answered_live, 4 decimales; null si no hay respuesta en vivo. |
periods[].exceeds_ceiling | boolean | sí | 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-skipsEl 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
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
reason | query | debt_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_tenant | no | Acota samples a un motivo. Omitido = todos los motivos. reasons nunca se acota. |
limit | query | integer | no | Tamaño de samples (20 por defecto, máximo 100). |
Respuestas
200
Números por motivo y las filas más recientes.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
campaign_id | string (uuid) | sí | |
total_leads | integer | sí | Deudas distintas descartadas por CUALQUIER motivo (una deuda con dos motivos cuenta una vez). |
reasons | arreglo de EmailLeadSkipReasonCount | sí | Todos los motivos del vocabulario cerrado, incluidos los ceros, en el orden del vocabulario. |
reasons[].reason | debt_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_tenant | sí | |
reasons[].leads | integer | sí | Deudas distintas descartadas por este motivo. |
reasons[].days | integer | sí | Días UTC con al menos una fila para este motivo. |
reasons[].first_at | string,null (date-time) | sí | |
reasons[].last_at | string,null (date-time) | sí | |
samples | arreglo de EmailLeadSkipSample | sí | |
samples[].debt_id | string (uuid) | sí | |
samples[].reason | debt_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_tenant | sí | |
samples[].occurred_at | string (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/previewEquivalente 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
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
dispositions | query | string | no | Lista separada por comas que se compara con la disposición MÁS RECIENTE de cada deuda. |
max_attempts | query | integer | no | |
last_attempt_older_than_days | query | integer | no | |
amd_verdicts | query | string | no | Veredictos AMD separados por comas que se comparan con el último intento realizado de cada deuda. |
segment_id | query | string (uuid) | no | Un 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.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
matching | integer | sí | |
excluded_by_compliance | integer | sí | Deudas coincidentes que el motor bloquearía AHORA MISMO (informativo). |
debt_ids | arreglo 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/previewLa misma simulación que el GET, con los filtros en el cuerpo JSON (forma del traspaso B1).
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
Cuerpo de la petición (JSON, obligatorio)
json
{
"max_attempts": 1
}Respuestas
200
Resultado de la simulación.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
matching | integer | sí | |
excluded_by_compliance | integer | sí | Deudas coincidentes que el motor bloquearía AHORA MISMO (informativo). |
debt_ids | arreglo 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}/rewindEstablece 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
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
id | path | string (uuid) | sí | UUID del recurso. Los ids mal formados o de otro tenant se leen como 404. |
Cuerpo de la petición (JSON, obligatorio)
json
{
"max_attempts": 1,
"keep_scheduled_callbacks": true
}Respuestas
200
Rebobinado aplicado.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
requeued | integer | sí | |
callbacks_cancelled | integer | sí | |
kept_closed | integer | sí | 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"
}
}