Appearance
Errores y cumplimiento
English version: Errors & compliance.
El sobre de error
Todo error, en todo endpoint, tiene la misma forma:
json
{
"error": {
"code": "conflict",
"message": "campaign cannot transition from completed to running"
}
}code es estable y legible por máquina — ramifica por él. message es para personas y puede reformularse en cualquier momento — nunca lo analices.
| HTTP | error.code | Significado |
|---|---|---|
400 | bad_request | JSON mal formado, parámetros de consulta incorrectos, tipos de campo erróneos. |
400 | invalid | Un campo viola una regla de dominio (error de validación). |
401 | unauthorized | Clave API ausente, desconocida o revocada — consulta Autenticación. |
403 | forbidden | Cuenta suspendida. |
404 | not_found | Id desconocido, mal formado o de otra cuenta — nunca se filtra la existencia. |
409 | conflict | Violación de unicidad o de la máquina de estados (nombre duplicado, transición de ciclo de vida inválida, puesto ocupado). |
415 | unsupported_media_type | Petición con cuerpo sin un tipo de contenido JSON. |
422 | unprocessable | La forma es correcta, la semántica no (una regla de dominio rota). |
422 | compliance_blocked | El motor de cumplimiento rechazó la operación — ver más abajo. |
422 | sms_optout_notice_missing / sms_content_prohibited | El cuerpo del SMS se rechazó antes de que corriera ninguna compuerta — ver Revisiones del cuerpo del SMS. |
429 | throttled / sms_spend_cap_exceeded | Frenos de ritmo y de gasto — ver Límites de uso y topes. |
500 | internal | Falla nuestra. Reintenta con backoff y cita el X-Request-ID a soporte. |
502 | provider_error / dial_error / sbc_error | Un tercero (proveedor de SMS, switch, borde SIP) rechazó la operación. |
Los códigos propios de un endpoint (p. ej. sms_provider_unconfigured) se documentan en cada respuesta de la referencia de endpoints.
422 compliance_blocked — la plataforma que se niega a infringir la ley
Dialer Digital tiene exactamente un camino hacia el teléfono de un consumidor: un motor con compuertas que comprueba consentimiento, estado de no llamar (DNC), horas de silencio, reglas de frecuencia de contacto (Reg F 7-en-7 y post-contacto), límites estatales y las políticas de tu propia cuenta — antes de contactar a ningún proveedor. Tus llamadas a la API pasan por las mismas compuertas que nuestras campañas y nuestro dashboard. No hay parámetro para saltárselas, y no se puede añadir uno por error: el motor falla cerrado.
Números reasignados: se rechaza, no se consulta
Comprobar si un número fue reasignado requiere un proveedor de la base de datos de números reasignados (Reassigned Numbers Database), y hoy la plataforma no consulta ninguno. El motor no toma la consulta ausente por un visto bueno: mientras no haya proveedor configurado, rechaza la marcación o el envío, y lo reporta como tenant_policy. No reporta number_reassigned para este rechazo, porque no se preguntó a ningún registro.
Cuando una compuerta rechaza, recibes:
json
{
"error": {
"code": "compliance_blocked",
"message": "the compliance engine refused this send (dnc_listed)",
"gate": "dnc_listed",
"decision_id": "2c1d0e9f-8a7b-6c5d-4e3f-2a1b0c9d8e7f"
}
}gate— qué regla rechazó:tcpa_no_consent,quiet_hours,regf_7in7,regf_post_contact,state_limit,dnc_listed,consent_revoked,cease_and_desist,number_reassignedotenant_policy.number_reassignedsolo se reporta cuando de verdad se consultó un registro de números reasignados — consulta la advertencia de arriba.decision_id— una cita dentro de la cadena de auditoría de solo anexado. La decisión que bloqueó la operación es evidencia duradera: si un consumidor o un regulador pregunta alguna vez «¿intentaron contactar a esta persona?», la respuesta es un registro verificable, a tu favor.
Un bloqueo es evidencia, no un error
No pasó nada: cero llamadas al proveedor, cero cargos. No reintentes sin cambios: la entrada que se rechazó se rechazará de nuevo, y machacar una compuerta solo añade ruido a tu propio rastro de auditoría. Corrige el estado de fondo (obtén el consentimiento, espera a que pasen las horas de silencio, respeta la revocación) o pasa a la siguiente cuenta. Esto es lo que estás pagando: cada bloqueo es una infracción que nunca llegó a un consumidor, empaquetada para tu defensa en GET /v1/reports/violations-prevented.
Dónde aparecen los bloqueos
| Superficie | Cómo se reporta un bloqueo |
|---|---|
SMS individual (POST /v1/sms/messages) | 422 compliance_blocked con gate + decision_id. |
Marcación manual/preview (POST /v1/campaigns/{id}/dial) | 200 con {"status": "blocked", "reason": "<gate>"} — un resultado válido que el agente debe ver, no un error del servidor. |
| Marcación automática de campaña | Los bloqueos nunca llegan a quien llama a la API; se registran y se agregan en GET /v1/stats/blocked. |
Transferencia ciega a un número externo (POST /v1/calls/{call_id}/transfer) | 422 compliance_blocked; gate dice qué comprobación rechazó. gate: "mid_call": la revisión sobre el consumidor rechazó (consentimiento revocado, cease-and-desist, horas de silencio) — lleva reason_code y rule_id, y queda armado el corte automático de la llamada (auto_hangup_at, cuando está presente, dice cuándo). gate: "pre_dial": se rechazó el nuevo tramo hacia el destino — lleva reason_code, rule_id y, cuando se escribió la fila de evidencia, decision_id; no se marcó nada y el consumidor sigue en la llamada. |
Revisiones del cuerpo del SMS
Antes de que el motor vea un SMS — antes también de tus frenos de plan, ritmo y gasto — la plataforma lee el cuerpo por sí misma. Dos reglas, tanto en el envío individual como en toda campaña de SMS:
error.code | El cuerpo… | Qué lo arregla |
|---|---|---|
sms_optout_notice_missing | no nombra ninguna palabra clave de baja que la plataforma respete en las respuestas entrantes (STOP, ALTO, UNSUBSCRIBE, CANCEL, QUIT, END). Reg F, 12 CFR 1006.6(e). | Dile al consumidor cómo parar: «Reply STOP to opt out.» |
sms_content_prohibited | nombra contenido que los operadores de EE. UU. sancionan con independencia del consentimiento: SHAFT (sex, hate, alcohol, firearm, firearms, tobacco) o alivio de deuda (debt relief, consolidation, forgiveness, credit repair). | Reformula el mensaje. |
json
{
"error": {
"code": "sms_content_prohibited",
"message": "body must not contain carrier-prohibited content (SHAFT: sex, hate, alcohol, firearms, tobacco; debt relief, consolidation, forgiveness, credit repair)"
}
}- Palabras completas, en cualquier caja. «Essex» no coincide con
sex; «Debt-Relief» y «debt relief» coinciden ambos condebt relief. «debt» y «credit» por sí solas son vocabulario ordinario de cobranza y no están en la lista. - Bloquea más de lo necesario, a propósito. Una palabra de la lista se rechaza en cualquier sentido — «we hate to bother you» se rechaza. Una infracción de contenido ante el operador se le cobra a la plataforma; reformular te cuesta una frase.
- No pasó nada. Cero llamadas al proveedor, cero filas en el libro mayor, cero cargos. No hay
decision_idni fila de evidencia: el motor nunca evaluó el envío, así que estos rechazos no aparecen enGET /v1/stats/blockedni en el reporte de infracciones evitadas. - Sin interruptor. Ningún parámetro de cuenta, campaña o petición se salta ninguna de las dos revisiones. Reintentar el mismo cuerpo se rechaza de nuevo.
- Las campañas reciben la misma respuesta antes.
POSTyPATCH /v1/campaignsrechazan unsms_bodyque incumple cualquiera de las dos reglas con400 invalidy el mismo mensaje, de modo que una campaña nunca arranca con un cuerpo que cada envío rechazaría. Una campaña guardada antes de que existiera una regla deja de enviar, y no acepta ningún cambio (ni siquiera una pausa) hasta que se corrija susms_body.
Despliegue
sms_content_prohibited está activo desde core v0.76.0 (core#1351, core ADR #181). sms_optout_notice_missing es anterior: ya estaba en core v0.75.0.
Bloqueos tenant_policy y su rule_id
tenant_policy cubre más de una regla — el rechazo por números reasignados de arriba es una de ellas. El rule_id de la decisión (GET /v1/compliance/decisions/{id}, y las muestras de GET /v1/reports/violations-prevented) dice cuál. Tres de ellas:
rule_id | Por qué se rechazó la llamada | Qué lo arregla |
|---|---|---|
tcpa.artificial_voice.optout.v1 | Una campaña de TTS, IVR o encuesta no tiene optout_digit. | Define esa clave en la campaña — consulta campañas de voz artificial. |
egress.destination_not_allowed.v1 | El número está fuera de la lista de destinos permitidos de uno de tus troncales BYOC. | Añádelo a la lista, o no está pensado para llamarse — consulta troncales BYOC: lista de destinos permitidos. |
egress.destination_allowlist_unavailable.v1 | No se pudo leer la lista de destinos permitidos, así que la llamada se rechazó en lugar de arriesgarla. | Reintenta más tarde; si persiste, contacta a soporte. |
Convertir los bloqueos en papeleo
GET /v1/stats/blocked— conteos por compuerta y una serie diaria, calculados desde la tabla de evidencia de solo anexado.GET /v1/reports/violations-prevented— el reporte empaquetado (JSON o CSV) que puedes entregar a un cliente, un auditor o un tribunal.GET /v1/debts/{id}/defense-packet— el paquete sellado de litigio por cuenta; cadadecision_idque hayas recibido es citable contra él.
404 y multitenencia
Los ids de otra cuenta, los UUID mal formados y los ids genuinamente desconocidos son todos el mismo 404 not_found. Lo impone la seguridad a nivel de fila de la base de datos — la API no puede decirte si existe el recurso de otra persona, por diseño.
Invitar a un usuario: una sola respuesta para una dirección libre y una ocupada
POST /v1/users invita a una persona al dashboard (core v0.77.0, core ADR #182). Sigue la misma regla que el 404 de arriba: nunca te cuenta nada de otra cuenta.
| HTTP | error.code | Significado |
|---|---|---|
202 | — | Invitación registrada: {"status": "invited", "message": "invitation recorded"}. El mismo cuerpo, byte a byte, tanto si la dirección está libre como si ya es usuaria de otra cuenta. |
400 | bad_request | El cuerpo lleva un password: la contraseña la elige la persona invitada. |
403 | role_forbidden | La cuenta tiene un account_admin activo y quien llama es un inicio de sesión member o una clave dd_: invitar exige un inicio de sesión account_admin. |
409 | conflict | La dirección ya es usuaria de tu cuenta, activa o deshabilitada. |
429 | rate_limited | Tu cuenta envió demasiadas invitaciones en poco tiempo. Reintenta más tarde. |
Canjear el enlace (POST /v1/auth/password-reset/confirm) falla de una sola manera: un enlace desconocido, caducado, usado o reemplazado, o uno cuya dirección pasó a ser usuaria entretanto, es el mismo 401 unauthorized. Una contraseña demasiado corta es 422 y deja el enlace válido. El flujo completo, y cómo migrar una integración que todavía envía password, está en Usuarios del dashboard.