Appearance
Autenticación
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.
Inicio de sesión en el dashboard (email + contraseña -> una sesión ddu_ revocable; sha256 en reposo, texto plano mostrado una sola vez) y recuperación de contraseña (un enlace de un solo uso que caduca — la mitad de la petición nunca revela si una dirección tiene cuenta).
Endpoints
Inicio de sesión con email + contraseña
http
POST /v1/auth/loginEmite una sesión de dashboard ddu_ revocable. Sin enumeración: email desconocido, contraseña incorrecta y usuario deshabilitado dan UN mismo 401 idéntico, con el mismo coste de PBKDF2; solo las credenciales demostradas como correctas de un tenant/cuenta suspendido reciben el 403. Con límite de uso por email (ventana fija) además del factor de trabajo del hash. El token en texto plano se muestra exactamente una vez — solo se almacena su sha256. Un tenant que tiene un IdP SAML HABILITADO y el flag gobernado por el founder sso_required_enforced activado ha cerrado esta vía: las credenciales demostradas como correctas de ese tenant reciben un 403sso_required (la entrada es POST /v1/auth/sso/start). Con el flag gobernado por el founder single_session_enforced activado, un inicio de sesión de CUALQUIER vía (contraseña, segundo factor, alta forzada, SSO) termina las demás sesiones ddu_ vivas de la persona; la pantalla que tenía una recibe 401 session_superseded en su siguiente petición.
Un tenant que publicó una política de inicio de sesión (direcciones y horarios permitidos por rol, ADR 227) en modo enforce rechaza un inicio de sesión desde una dirección fuera de ella o a una hora fuera de ella con UN solo 403 login_policy_denied, el mismo para la dirección y la hora: cuál de las dos falló lo dice únicamente el registro de inicios de sesión del tenant (GET /v1/auth/sign-in-events). Se consulta DESPUÉS de que la credencial resultó correcta y antes de emitir nada. Con la política desactivada o ausente, el inicio de sesión es el de antes.
Autenticación: ninguna — se autentica por la firma de la petición (ver abajo).
Cuerpo de la petición (JSON, obligatorio)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
email | string (email) | sí | |
password | string (password) | sí |
json
{
"email": "[email protected]",
"password": "local-dev-password"
}Respuestas
200
O bien la sesión (token es la única vez que el texto plano es visible) o, cuando la cuenta tiene un segundo factor activo, mfa_required: true y un challenge de un solo uso para canjear en POST /v1/auth/mfa/verify — AÚN NO existe ninguna sesión y el desafío no abre ninguna otra ruta. Cuando el tenant EXIGE un segundo factor (flag mfa_required_enforced, desactivado por defecto) y la cuenta aún no tiene ninguno, la respuesta es mfa_required: true con purpose: enroll y un desafío que solo abre POST /v1/auth/mfa/enroll y POST /v1/auth/mfa/enroll/confirm.
session
json
{
"token": "ddu_example_plaintext_shown_once",
"session": {
"id": "2f6a1c58-9d43-4b1a-8e4f-6c1a2b3d4e5f",
"user_id": "7c1d2e3f-4a5b-4c6d-8e9f-0a1b2c3d4e5f",
"expires_at": "2026-07-10T22:00:00Z",
"revoked_at": null,
"created_at": "2026-07-10T10:00:00Z"
},
"user": {
"id": "7c1d2e3f-4a5b-4c6d-8e9f-0a1b2c3d4e5f",
"email": "[email protected]",
"name": "Local Dev Admin",
"status": "active",
"password_updated_at": "2026-07-01T09:00:00Z",
"last_login_at": "2026-07-10T10:00:00Z",
"created_at": "2026-07-01T09:00:00Z",
"updated_at": "2026-07-10T10:00:00Z"
},
"tenant": {
"id": "0d4f4f9e-1f2a-4b53-9d3c-8a5e2f7b1c10",
"name": "Acme Collections",
"status": "active"
}
}mfa_required
json
{
"mfa_required": true,
"challenge": "ddm_example_challenge_shown_once",
"expires_at": "2026-07-10T10:05:00Z",
"methods": [
"totp",
"recovery_code"
]
}mfa_enrollment_required
json
{
"mfa_required": true,
"purpose": "enroll",
"challenge": "ddm_example_enroll_challenge_shown_once",
"expires_at": "2026-07-10T10:05: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
Credenciales no válidas — email desconocido, contraseña incorrecta y usuario deshabilitado son indistinguibles.
json
{
"error": {
"code": "unauthorized",
"message": "invalid email or password"
}
}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"
}
}429 — LoginRateLimited
El freno de inicio de sesión por CUENTA rechazó este intento (ventana fija; se puede reintentar más tarde). code rate_limited. Se aplica por email (/v1/auth/login) o por {tenant_id, email} (/v1/auth/agent-login), de modo que limita muchas contraseñas contra UNA sola cuenta. El spraying de credenciales sobre muchas cuentas actualmente se CUENTA Y SE ALERTA pero no se rechaza, porque la única clave por origen disponible para core queda colapsada por el proxy inverso del dashboard; un intento de spraying por tanto sigue devolviendo el 401 ordinario. Retry-After es el tiempo que queda de la ventana fija.
/v1/auth/login tiene un segundo motivo, ACUMULATIVO, para el mismo 429 (core#2335, NIST SP 800-63B-4 §3.2.2): cuando una persona acumula login_progressive_threshold_failures (20 por defecto) contraseñas incorrectas desde su último inicio de sesión exitoso, dentro de 24 h, el inicio de sesión se retiene incluso con la contraseña CORRECTA durante 15 min tras el último fallo, y se duplica por cada conjunto adicional de fallos del umbral (con tope de 6 h); Retry-After es el tiempo que queda. Una pausa, nunca un bloqueo permanente. Los intentos retenidos no se registran, así que no la alargan.
json
{
"error": {
"code": "rate_limited",
"message": "too many login attempts; retry later"
}
}Revocar la sesión presentada
http
POST /v1/auth/logoutRevoca la sesión ddu_ que viaja en la cabecera Authorization. IDEMPOTENTE y sin enumeración: cualquier bearer bien formado da 204 nombrara o no una sesión viva; solo una cabecera ausente o mal formada da 401. Las sesiones se revocan, nunca se eliminan.
Una sesión nacida de SSO cuyo proveedor de identidad publicó un SingleLogoutService (slo_url de la configuración del IdP) responde 200 con sso_logout_url: la URL de la LogoutRequest (HTTP-Redirect, sin firmar) que el navegador puede seguir para terminar también la sesión en el IdP. La revocación local YA ocurrió y nunca depende del IdP; cualquier otra sesión da 204.
Respuestas
200
La sesión está revocada y el IdP ofrece un Single Logout. no-store.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
sso_logout_url | string (uri) | sí |
204
La sesión (si la hay) está revocada.
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"
}
}Registro de auditoría de los inicios de sesión del tenant
http
GET /v1/auth/sign-in-eventsEl registro de solo anexado de cada intento de inicio de sesión de las personas del tenant — contraseña, segundo factor y SSO; concedidos y rechazados — del más reciente al más antiguo, paginado por keyset. Cada fila lleva el method, el outcome, un reason cerrado para un rechazo, el ip y el user_agent de quien llama (truncado a 256 caracteres) y, para una concesión que emitió una sesión, su session_id. Nunca lleva una contraseña, un código ni un token.
reason es null en una concesión, salvo mfa_pending: la contraseña era correcta pero el inicio de sesión aún debe el segundo factor (o su alta), por lo que no se emitió ninguna sesión; y observed_ip_outside / observed_hours_outside, escritos cuando la política de inicio de sesión del tenant está en modo observe: el inicio de sesión se concedió y la fila dice que habría sido rechazado. En un rechazo es uno de bad_password, unknown_user, disabled, suspended, sso_required, mfa_failed, sso_rejected o, por la política de inicio de sesión del tenant, ip_not_allowed y outside_hours (modo enforce) y policy_unavailable (la política no se pudo leer o entender, en cualquiera de los dos modos: el inicio de sesión se rechaza). Una dirección que no coincidió con nadie en ESTE tenant aparece solo como email_attempted, un digest sha256 de la dirección normalizada tomado con un prefijo de propósito fijo (no el sha256 simple de la dirección) — nunca el texto.
Una concesión se escribe en la misma transacción que emite la sesión, de modo que una sesión sin su fila no puede existir; un rechazo se escribe justo después y es de mejor esfuerzo. Una respuesta SSO cuya firma no se verifica NO se registra (el endpoint es anónimo); una que sí se verifica y luego se rechaza, sí. Las filas nunca se editan ni se eliminan.
Filtros: user_id, outcome (success | failure), since (inclusive) y until (exclusivo), ambos RFC-3339 sobre occurred_at. Un filtro que no se puede interpretar da 400 con los valores aceptados — nunca una página vacía en silencio. Acotado al tenant: las filas de otro tenant nunca son visibles, sea cual sea el filtro.
Solo inicio de sesión de account_admin en el dashboard (véase 403).
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
user_id | query | string (uuid) | no | |
outcome | query | success · failure | no | |
since | query | string (date-time) | no | Solo eventos en este instante o posteriores. |
until | query | string (date-time) | no | Solo eventos estrictamente anteriores a este instante. |
limit | query | integer | no | |
cursor | query | string | no | Cursor keyset opaco: el next_cursor de la página anterior. |
Respuestas
200
Una página de eventos de inicio de sesión, del más reciente al más antiguo.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
events | arreglo de SignInEvent | sí | |
events[].id | string (uuid) | sí | |
events[].user_id | string (uuid) | null | sí | La persona a la que se atribuye el intento; null cuando la dirección no coincidió con nadie. |
events[].email_attempted | string | null | sí | sha256 (hex) de la dirección normalizada tomado con un prefijo de propósito fijo (no el sha256 simple de la dirección); solo cuando ninguna persona coincidió. Nunca la dirección. |
events[].method | password · mfa · sso | sí | |
events[].outcome | success · failure | sí | |
events[].reason | bad_password · unknown_user · disabled · suspended · sso_required · mfa_failed · sso_rejected · mfa_pending · ip_not_allowed · outside_hours · policy_unavailable · observed_ip_outside · observed_hours_outside | null | sí | |
events[].ip | string | null | sí | La dirección de quien llama tal como la atribuye la plataforma (solo proxies de confianza). |
events[].user_agent | string | null | sí | |
events[].session_id | string (uuid) | null | sí | La sesión que emitió la concesión; null en un rechazo y en mfa_pending. |
events[].occurred_at | string (date-time) | sí | |
next_cursor | string | null | sí | Cursor de la página siguiente; null en la última. |
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
role_forbidden: cualquier cosa que no sea un inicio de sesión de dashboard (ddu_) cuya persona tenga el rol account_admin — incluidos una clave de API dd_ y un inicio de sesión member; forbidden: un token de widget (ddw_) o un tenant suspendido.
json
{
"error": {
"code": "role_forbidden",
"message": "the sign-in audit trail takes a dashboard login with the account_admin role"
}
}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"
}
}Segundo paso de un inicio de sesión con segundo factor
http
POST /v1/auth/mfa/verifyCanjea el challenge devuelto por POST /v1/auth/login con un code TOTP o UN recovery_code de un solo uso (exactamente uno de los dos) y emite la sesión ddu_ que el primer paso retuvo. El cuerpo de la respuesta es el mismo que el de un inicio de sesión en un solo paso.
El desafío es de un solo uso y muere tras 5 minutos, tras 5 códigos incorrectos o cuando la contraseña de la persona cambia entretanto; un desafío desconocido, caducado, gastado o sin intentos restantes da UN mismo 401 idéntico. Un código TOTP se acepta como mucho una vez (se rechaza la repetición del mismo código) y un código de recuperación se quema con su primer uso. Los códigos incorrectos tienen límite de uso por USUARIO (429).
Un tenant que publicó una política de inicio de sesión (direcciones y horarios permitidos por rol, ADR 227) en modo enforce rechaza un inicio de sesión desde una dirección fuera de ella o a una hora fuera de ella con UN solo 403 login_policy_denied, el mismo para la dirección y la hora: cuál de las dos falló lo dice únicamente el registro de inicios de sesión del tenant (GET /v1/auth/sign-in-events). Se consulta DESPUÉS de que la credencial resultó correcta y antes de emitir nada. Con la política desactivada o ausente, el inicio de sesión es el de antes. Se consulta de nuevo aquí, antes de que se compare el código, de modo que un rechazo no gasta ni el desafío ni un intento.
Autenticación: ninguna — se autentica por la firma de la petición (ver abajo).
Cuerpo de la petición (JSON, obligatorio)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
challenge | string | sí | El desafío ddm_ del inicio de sesión. |
code | string | no | Código TOTP, 6 dígitos. |
recovery_code | string | no | Un código de recuperación, XXXX-XXXX-XXXX-XXXX. |
Respuestas
200
Sesión emitida; token es la única vez que el texto plano es visible.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
token | string | sí | Bearer ddu_ en texto plano, mostrado exactamente una vez. |
session | object (UserSession) | sí | Una sesión de inicio de sesión ddu_ — el hash del token nunca se expone. |
session.id | string (uuid) | no | |
session.user_id | string (uuid) | no | |
session.expires_at | string (date-time) | no | Fijada al iniciar sesión (sin renovación deslizante); tope del motor de 24h. |
session.revoked_at | string (date-time) | null | no | |
session.created_at | string (date-time) | no | |
user | object (User) | sí | Un usuario del dashboard; el hash de la contraseña nunca se expone. |
user.id | string (uuid) | no | |
user.email | string (email) | no | Identificador de inicio de sesión global, almacenado en minúsculas. |
user.name | string | no | |
user.status | active · disabled | no | |
user.role | member · account_admin | no | Privilegio dentro de la cuenta. Solo un account_admin puede crear o modificar las filas de toda la empresa que heredan todas las sedes hermanas (scope: "empresa" en carriers y proveedores de SMS); un member recibe 403. Solo lectura aquí: la promoción es un gesto del operador, nunca una petición. |
user.password_updated_at | string (date-time) | no | |
user.last_login_at | string (date-time) | null | no | |
user.mfa_enabled | boolean | no | Si la persona tiene un segundo factor ACTIVO (una inscripción pendiente de confirmación es false). Se emite SOLO cuando quien llama es un inicio de sesión de dashboard account_admin, el que puede actuar sobre él con POST /v1/users/{id}/mfa/remove; para un member, una clave dd_, o cuando no se puede determinar el rol de quien llama, el campo está AUSENTE (no es false). Dondequiera que se renderice un User rige la misma regla: GET /v1/users, GET /v1/me, el usuario devuelto por DELETE /v1/users/{id} y POST /v1/users/{id}/enable, y el usuario en las respuestas de sesión (POST /v1/auth/login, POST /v1/auth/sso/exchange y las dos rutas de finalización de MFA); en ellas, para el account_admin que acaba de iniciar sesión. |
user.created_at | string (date-time) | no | |
user.updated_at | string (date-time) | no | |
tenant | object (Tenant) | sí | |
tenant.id | string (uuid) | no | |
tenant.name | string | no | |
tenant.account_id | string (uuid) | no | La EMPRESA a la que pertenece este tenant (sede) (DA-2). |
tenant.billing_account_id | string (uuid) | no | La cuenta de facturación bajo la que se factura la sede (DA-3). Compartida por todas las sedes de una empresa consolidated; una por sede cuando es per_site. |
tenant.status | active · suspended | no | |
tenant.synthetic | boolean | no | true para un tenant de demostración. No origina tráfico: la marcación manual, el envío de SMS, la pierna externa de una transferencia y el sandbox responden 403 synthetic_tenant. Siempre presente, nunca nulo, para que una consola pueda avisar ANTES de que la acción falle (core#2512). |
tenant.retention_months | integer | no | |
tenant.seats | integer | no | |
tenant.tier | starter · growth · scale · enterprise · dialer_core · compliance_pro · audit_shield | no | |
tenant.voice_ai_enabled | boolean | no | |
tenant.ai_monthly_budget_usd | number | null | no | Techo de integridad de costes (lo fija el admin; null = sin tope). |
tenant.ai_max_call_seconds | integer | null | no | Techo estricto de duración por llamada de IA (segundos) fijado por el admin. null significa que se aplica el valor por defecto de la PLATAFORMA, nunca "sin techo" (core#922, ADR #115). |
tenant.ai_max_call_seconds_effective | integer | no | El techo que realmente se entrega a FreeSWITCH para las piernas de IA de este tenant: ai_max_call_seconds cuando está definido; si no, el valor por defecto de plataforma de la celda (AI_MAX_CALL_SECONDS_DEFAULT, valor de reserva compilado 900). Siempre presente, nunca nulo. Solo lectura (derivado). |
tenant.sms_monthly_budget_usd | number | null | no | Tope de gasto en SMS acumulado en el mes hasta la fecha. |
tenant.human_initiated_enabled | boolean | no | Postura de consentimiento D17 de iniciativa humana (la fija el admin; por defecto false). Cuando es true, una marcación de voz EN VIVO de vista previa/manual/CTI sin voz artificial y sin voz de IA no requiere consentimiento expreso previo (sin ATDS). Todas las demás compuertas (DNC, horas de silencio, Reg F, matriz estatal, voz artificial) siguen aplicándose sin cambios. |
tenant.max_cps | integer | null | no | Techo de CPS por tenant (lo fija el admin; null = sin techo). ADR #12. |
tenant.max_concurrent_channels | integer | null | no | Techo de llamadas concurrentes por tenant, una unidad por llamada con o sin agente (lo fija el admin; null = sin techo). ADR #12, ADR #185. |
tenant.abandon_seller_name | string | null | no | Nombre del vendedor para el mensaje de identificación de llamada abandonada de la FTC TSR (lo fija el admin; null = no configurado, el sobremarcado predictivo permanece apagado). ADR #14. |
tenant.abandon_seller_phone | string | null | no | Número de rellamada E.164 para el mensaje de identificación de llamada abandonada (lo fija el admin; null = no configurado). ADR #14. |
tenant.tax_country | string | null | no | País ISO-3166-1 alpha-2 de la jurisdicción de impuesto sobre ventas del cliente (lo fija el admin; null = no declarado). ADR #77. |
tenant.tax_region | string | null | no | Código de estado/provincia de la jurisdicción de impuesto sobre ventas del cliente (lo fija el admin; null = no declarado). ADR #77. |
tenant.tax_postal_code | string | null | no | Código postal de la jurisdicción de impuesto sobre ventas del cliente (lo fija el admin; null = no declarado). ADR #77. |
tenant.kyc_verified_at | string (date-time) | null | no | Cuándo verificó la plataforma a este cliente (lo fija el admin; null = aún no). Una cuenta de 1 asiento que no está verificada no puede crear ni iniciar una campaña que se ejecute sin agente (403 verification_required, ADR #279). |
tenant.created_at | string (date-time) | no | |
tenant.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
Código incorrecto, o un desafío desconocido/caducado/gastado.
json
{
"error": {
"code": "unauthorized",
"message": "invalid code"
}
}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"
}
}429 — MfaVerifyThrottled
Demasiados intentos FALLIDOS de segundo factor para este USUARIO dentro de la ventana fija (se puede reintentar más tarde). code rate_limited. Se aplica sobre la persona, nunca sobre una dirección de red, se evalúa ANTES de comparar el código y es COMPARTIDO por POST /v1/auth/mfa/verify, las rutas de alta forzada (/v1/auth/mfa/enroll*) y toda ruta /v1/me/mfa/* que compare un código o una contraseña. Un intento correcto no se cuenta una vez que se completa; mientras se verifica, ocupa una plaza, de modo que las peticiones simultáneas nunca obtienen más comparaciones que el presupuesto (ADR #144). Retry-After es el tiempo que queda de la ventana fija.
También es la respuesta del freno ACUMULATIVO (core#1572): cuando una persona acumula mfa_progressive_threshold_failures códigos rechazados (20 por defecto) dentro de mfa_progressive_window_seconds (24 h por defecto), un TOTP ni siquiera se compara hasta que hayan pasado mfa_progressive_cooldown_seconds (15 min por defecto) desde el último fallo, y Retry-After es esa espera. Un código de recuperación nunca queda retenido por él, y un account_admin puede levantarlo (POST /v1/users/{id}/mfa/reset-failures). No es un bloqueo: la persona va más lenta, no queda fuera.
json
{
"error": {
"code": "rate_limited",
"message": "too many failed second-factor attempts; retry later"
}
}Iniciar el alta del segundo factor que un tenant exige al iniciar sesión
http
POST /v1/auth/mfa/enrollPrimera mitad del alta FORZADA: presenta el challenge de propósito enroll que POST /v1/auth/login devolvió (mfa_required: true, purpose: enroll) y obtiene un secreto TOTP nuevo, que queda pending hasta POST /v1/auth/mfa/enroll/confirm. No se vuelve a pedir la contraseña: el desafío existe solo porque acaba de demostrarse la contraseña correcta.
Iniciar no gasta ningún intento del desafío; repetirlo reemplaza el secreto pendiente. El secreto se muestra exactamente una vez. Un desafío de propósito login (y uno desconocido, caducado, gastado o sin intentos restantes) da UN mismo 401 idéntico. Una cuenta que ya tiene un factor activo da 409. Con límite de uso por USUARIO con el mismo presupuesto que POST /v1/auth/mfa/verify.
Un tenant que publicó una política de inicio de sesión (direcciones y horarios permitidos por rol, ADR 227) en modo enforce rechaza un inicio de sesión desde una dirección fuera de ella o a una hora fuera de ella con UN solo 403 login_policy_denied, el mismo para la dirección y la hora: cuál de las dos falló lo dice únicamente el registro de inicios de sesión del tenant (GET /v1/auth/sign-in-events). Se consulta DESPUÉS de que la credencial resultó correcta y antes de emitir nada. Con la política desactivada o ausente, el inicio de sesión es el de antes. El secreto no se entrega a una dirección u hora rechazada.
Autenticación: ninguna — se autentica por la firma de la petición (ver abajo).
Cuerpo de la petición (JSON, obligatorio)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
challenge | string | sí | El desafío de alta ddm_ del inicio de sesión. |
Respuestas
200
Secreto pendiente; codifica otpauth_uri como el QR.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
secret | string | sí | Secreto TOTP, base32; se muestra exactamente una vez. |
otpauth_uri | string | sí | otpauth://totp/... — codifícalo como el QR. |
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
Desafío desconocido, caducado, gastado, sin intentos restantes o de propósito incorrecto.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or expired challenge"
}
}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"
}
}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 — MfaVerifyThrottled
Demasiados intentos FALLIDOS de segundo factor para este USUARIO dentro de la ventana fija (se puede reintentar más tarde). code rate_limited. Se aplica sobre la persona, nunca sobre una dirección de red, se evalúa ANTES de comparar el código y es COMPARTIDO por POST /v1/auth/mfa/verify, las rutas de alta forzada (/v1/auth/mfa/enroll*) y toda ruta /v1/me/mfa/* que compare un código o una contraseña. Un intento correcto no se cuenta una vez que se completa; mientras se verifica, ocupa una plaza, de modo que las peticiones simultáneas nunca obtienen más comparaciones que el presupuesto (ADR #144). Retry-After es el tiempo que queda de la ventana fija.
También es la respuesta del freno ACUMULATIVO (core#1572): cuando una persona acumula mfa_progressive_threshold_failures códigos rechazados (20 por defecto) dentro de mfa_progressive_window_seconds (24 h por defecto), un TOTP ni siquiera se compara hasta que hayan pasado mfa_progressive_cooldown_seconds (15 min por defecto) desde el último fallo, y Retry-After es esa espera. Un código de recuperación nunca queda retenido por él, y un account_admin puede levantarlo (POST /v1/users/{id}/mfa/reset-failures). No es un bloqueo: la persona va más lenta, no queda fuera.
json
{
"error": {
"code": "rate_limited",
"message": "too many failed second-factor attempts; retry later"
}
}Confirmar el alta forzada y terminar el inicio de sesión
http
POST /v1/auth/mfa/enroll/confirmSegunda mitad del alta FORZADA: el primer code TOTP del secreto pendiente activa el factor, emite los códigos de recuperación, consume el desafío y emite la sesión ddu_ que el inicio de sesión retuvo — todo en una sola transacción. El cuerpo es el cuerpo de sesión del inicio de sesión más recovery_codes, mostrados en claro exactamente una vez.
El desafío muere tras 5 minutos, tras 5 códigos incorrectos o cuando la contraseña de la persona cambia entretanto. Un código incorrecto es 401 invalid code y cuenta contra el presupuesto por USUARIO (429); un desafío desconocido, caducado, gastado o de propósito incorrecto es 401 invalid or expired challenge. 409 cuando no se inició ningún alta.
Un tenant que publicó una política de inicio de sesión (direcciones y horarios permitidos por rol, ADR 227) en modo enforce rechaza un inicio de sesión desde una dirección fuera de ella o a una hora fuera de ella con UN solo 403 login_policy_denied, el mismo para la dirección y la hora: cuál de las dos falló lo dice únicamente el registro de inicios de sesión del tenant (GET /v1/auth/sign-in-events). Se consulta DESPUÉS de que la credencial resultó correcta y antes de emitir nada. Con la política desactivada o ausente, el inicio de sesión es el de antes. Un rechazo no gasta ni el desafío ni un intento.
Autenticación: ninguna — se autentica por la firma de la petición (ver abajo).
Cuerpo de la petición (JSON, obligatorio)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
challenge | string | sí | El desafío de alta ddm_ del inicio de sesión. |
code | string | sí | Código TOTP, 6 dígitos. |
Respuestas
200
Factor activo, sesión emitida; token es la única vez que el texto plano es visible.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
token | string | sí | Bearer ddu_ en texto plano, mostrado exactamente una vez. |
session | object (UserSession) | sí | Una sesión de inicio de sesión ddu_ — el hash del token nunca se expone. |
session.id | string (uuid) | no | |
session.user_id | string (uuid) | no | |
session.expires_at | string (date-time) | no | Fijada al iniciar sesión (sin renovación deslizante); tope del motor de 24h. |
session.revoked_at | string (date-time) | null | no | |
session.created_at | string (date-time) | no | |
user | object (User) | sí | Un usuario del dashboard; el hash de la contraseña nunca se expone. |
user.id | string (uuid) | no | |
user.email | string (email) | no | Identificador de inicio de sesión global, almacenado en minúsculas. |
user.name | string | no | |
user.status | active · disabled | no | |
user.role | member · account_admin | no | Privilegio dentro de la cuenta. Solo un account_admin puede crear o modificar las filas de toda la empresa que heredan todas las sedes hermanas (scope: "empresa" en carriers y proveedores de SMS); un member recibe 403. Solo lectura aquí: la promoción es un gesto del operador, nunca una petición. |
user.password_updated_at | string (date-time) | no | |
user.last_login_at | string (date-time) | null | no | |
user.mfa_enabled | boolean | no | Si la persona tiene un segundo factor ACTIVO (una inscripción pendiente de confirmación es false). Se emite SOLO cuando quien llama es un inicio de sesión de dashboard account_admin, el que puede actuar sobre él con POST /v1/users/{id}/mfa/remove; para un member, una clave dd_, o cuando no se puede determinar el rol de quien llama, el campo está AUSENTE (no es false). Dondequiera que se renderice un User rige la misma regla: GET /v1/users, GET /v1/me, el usuario devuelto por DELETE /v1/users/{id} y POST /v1/users/{id}/enable, y el usuario en las respuestas de sesión (POST /v1/auth/login, POST /v1/auth/sso/exchange y las dos rutas de finalización de MFA); en ellas, para el account_admin que acaba de iniciar sesión. |
user.created_at | string (date-time) | no | |
user.updated_at | string (date-time) | no | |
tenant | object (Tenant) | sí | |
tenant.id | string (uuid) | no | |
tenant.name | string | no | |
tenant.account_id | string (uuid) | no | La EMPRESA a la que pertenece este tenant (sede) (DA-2). |
tenant.billing_account_id | string (uuid) | no | La cuenta de facturación bajo la que se factura la sede (DA-3). Compartida por todas las sedes de una empresa consolidated; una por sede cuando es per_site. |
tenant.status | active · suspended | no | |
tenant.synthetic | boolean | no | true para un tenant de demostración. No origina tráfico: la marcación manual, el envío de SMS, la pierna externa de una transferencia y el sandbox responden 403 synthetic_tenant. Siempre presente, nunca nulo, para que una consola pueda avisar ANTES de que la acción falle (core#2512). |
tenant.retention_months | integer | no | |
tenant.seats | integer | no | |
tenant.tier | starter · growth · scale · enterprise · dialer_core · compliance_pro · audit_shield | no | |
tenant.voice_ai_enabled | boolean | no | |
tenant.ai_monthly_budget_usd | number | null | no | Techo de integridad de costes (lo fija el admin; null = sin tope). |
tenant.ai_max_call_seconds | integer | null | no | Techo estricto de duración por llamada de IA (segundos) fijado por el admin. null significa que se aplica el valor por defecto de la PLATAFORMA, nunca "sin techo" (core#922, ADR #115). |
tenant.ai_max_call_seconds_effective | integer | no | El techo que realmente se entrega a FreeSWITCH para las piernas de IA de este tenant: ai_max_call_seconds cuando está definido; si no, el valor por defecto de plataforma de la celda (AI_MAX_CALL_SECONDS_DEFAULT, valor de reserva compilado 900). Siempre presente, nunca nulo. Solo lectura (derivado). |
tenant.sms_monthly_budget_usd | number | null | no | Tope de gasto en SMS acumulado en el mes hasta la fecha. |
tenant.human_initiated_enabled | boolean | no | Postura de consentimiento D17 de iniciativa humana (la fija el admin; por defecto false). Cuando es true, una marcación de voz EN VIVO de vista previa/manual/CTI sin voz artificial y sin voz de IA no requiere consentimiento expreso previo (sin ATDS). Todas las demás compuertas (DNC, horas de silencio, Reg F, matriz estatal, voz artificial) siguen aplicándose sin cambios. |
tenant.max_cps | integer | null | no | Techo de CPS por tenant (lo fija el admin; null = sin techo). ADR #12. |
tenant.max_concurrent_channels | integer | null | no | Techo de llamadas concurrentes por tenant, una unidad por llamada con o sin agente (lo fija el admin; null = sin techo). ADR #12, ADR #185. |
tenant.abandon_seller_name | string | null | no | Nombre del vendedor para el mensaje de identificación de llamada abandonada de la FTC TSR (lo fija el admin; null = no configurado, el sobremarcado predictivo permanece apagado). ADR #14. |
tenant.abandon_seller_phone | string | null | no | Número de rellamada E.164 para el mensaje de identificación de llamada abandonada (lo fija el admin; null = no configurado). ADR #14. |
tenant.tax_country | string | null | no | País ISO-3166-1 alpha-2 de la jurisdicción de impuesto sobre ventas del cliente (lo fija el admin; null = no declarado). ADR #77. |
tenant.tax_region | string | null | no | Código de estado/provincia de la jurisdicción de impuesto sobre ventas del cliente (lo fija el admin; null = no declarado). ADR #77. |
tenant.tax_postal_code | string | null | no | Código postal de la jurisdicción de impuesto sobre ventas del cliente (lo fija el admin; null = no declarado). ADR #77. |
tenant.kyc_verified_at | string (date-time) | null | no | Cuándo verificó la plataforma a este cliente (lo fija el admin; null = aún no). Una cuenta de 1 asiento que no está verificada no puede crear ni iniciar una campaña que se ejecute sin agente (403 verification_required, ADR #279). |
tenant.created_at | string (date-time) | no | |
tenant.updated_at | string (date-time) | no | |
recovery_codes | arreglo de string | sí | Códigos de un solo uso, mostrados en claro exactamente una vez. |
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
Código incorrecto, o un desafío desconocido/caducado/gastado/de propósito incorrecto.
json
{
"error": {
"code": "unauthorized",
"message": "invalid code"
}
}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"
}
}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 — MfaVerifyThrottled
Demasiados intentos FALLIDOS de segundo factor para este USUARIO dentro de la ventana fija (se puede reintentar más tarde). code rate_limited. Se aplica sobre la persona, nunca sobre una dirección de red, se evalúa ANTES de comparar el código y es COMPARTIDO por POST /v1/auth/mfa/verify, las rutas de alta forzada (/v1/auth/mfa/enroll*) y toda ruta /v1/me/mfa/* que compare un código o una contraseña. Un intento correcto no se cuenta una vez que se completa; mientras se verifica, ocupa una plaza, de modo que las peticiones simultáneas nunca obtienen más comparaciones que el presupuesto (ADR #144). Retry-After es el tiempo que queda de la ventana fija.
También es la respuesta del freno ACUMULATIVO (core#1572): cuando una persona acumula mfa_progressive_threshold_failures códigos rechazados (20 por defecto) dentro de mfa_progressive_window_seconds (24 h por defecto), un TOTP ni siquiera se compara hasta que hayan pasado mfa_progressive_cooldown_seconds (15 min por defecto) desde el último fallo, y Retry-After es esa espera. Un código de recuperación nunca queda retenido por él, y un account_admin puede levantarlo (POST /v1/users/{id}/mfa/reset-failures). No es un bloqueo: la persona va más lenta, no queda fuera.
json
{
"error": {
"code": "rate_limited",
"message": "too many failed second-factor attempts; retry later"
}
}Solicitar un enlace de restablecimiento de contraseña de un solo uso
http
POST /v1/auth/password-resetEnvía por correo a la dirección un enlace de un solo uso que caduca SI pertenece a una cuenta de dashboard activa, y no dice NADA sobre si pertenece.
La respuesta es SIEMPRE 202 con el MISMO cuerpo — byte por byte — tenga la dirección una cuenta, no tenga ninguna o pertenezca a un usuario deshabilitado. Cualquier otra cosa convertiría esta ruta no autenticada en un enumerador de clientes. Estar limitado por límite de uso también responde ese mismo 202; simplemente no envía correo, porque un 429 sería un oráculo por sí mismo. Solo un cuerpo mal formado (sin email, o que no sea una cadena) da 400.
Solicitarlo de nuevo invalida cualquier enlace vivo anterior de esa cuenta: solo UN enlace es canjeable en cada momento. Un fallo de entrega NO cambia el 202 y deja el token válido para que la persona pueda reintentar.
Autenticación: ninguna — se autentica por la firma de la petición (ver abajo).
Cuerpo de la petición (JSON, obligatorio)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
email | string (email) | sí | |
locale | es · en | no | Idioma en el que la persona tiene la consola; el correo se envía en él. Ausente o cualquier otro: el idioma de la celda (DEFAULT_LOCALE). Nunca cambia la respuesta — el 202 es el mismo byte por byte. |
json
{
"email": "[email protected]",
"locale": "es"
}Respuestas
202
Aceptada. Idéntica para todos los resultados (véase la descripción).
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
status | accepted | sí | |
message | string | sí |
json
{
"status": "accepted",
"message": "if that address has an account, a reset link is on its way"
}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"
}
}Canjear un enlace de restablecimiento o de invitación y fijar la contraseña
http
POST /v1/auth/password-reset/confirmQuema el token de un solo uso, escribe la nueva contraseña y REVOCA todas las sesiones ddu_ vivas de ese usuario — todo en UNA transacción, de modo que ninguna sesión emitida con la contraseña anterior sobrevive al cambio. La persona inicia sesión de nuevo con la nueva.
El token también puede provenir de una INVITACIÓN (POST /v1/users, el enlace /accept-invite; ADR #182): entonces esta llamada CREA a la persona, en el tenant que invita, con esta contraseña, y quema la invitación.
Fail-closed y de un solo valor: un token desconocido, manipulado, caducado, reemplazado o ya canjeado — y una invitación cuya dirección pasó a ser usuario en cualquier lugar entretanto — dan UN mismo 401 idéntico. La política de contraseñas (12–128 caracteres, la misma que sigue toda contraseña de dashboard) se comprueba ANTES de tocar el token, así que una contraseña demasiado corta responde 422 con el enlace todavía canjeable.
Autenticación: ninguna — se autentica por la firma de la petición (ver abajo).
Cuerpo de la petición (JSON, obligatorio)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
token | string | sí | El texto plano del enlace enviado por correo. De un solo uso. |
new_password | string (password) | sí |
json
{
"token": "ddr_example_plaintext_from_the_link",
"new_password": "an-even-better-password"
}Respuestas
200
Contraseña escrita (o, en una invitación, la persona creada), token quemado, todas las sesiones vivas revocadas.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
status | ok | sí |
json
{
"status": "ok"
}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
Token desconocido, manipulado, caducado, reemplazado o ya canjeado, o una invitación cuya dirección ya está en uso — indistinguibles por diseño.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or expired token"
}
}422
La nueva contraseña se rechaza y NO cambia NADA (el enlace sigue siendo canjeable): unprocessable cuando no cumple la política de longitud, o password_blocklisted (core#2335, NIST SP 800-63B-4 §3.1.1.2) cuando está en la lista de bloqueo local de contraseñas de uso común o comprometidas, contiene el email de la persona o el nombre del espacio de trabajo, o es trivialmente repetitiva. El mensaje nombra el motivo (common, repetitive, contains_email, contains_tenant). Las contraseñas existentes nunca se comprueban.
json
{
"error": {
"code": "password_blocklisted",
"message": "password is on the list of commonly used or compromised passwords (reason: common)"
}
}Inicio de sesión del agente en el widget (email + contraseña -> un token ddw_)
http
POST /v1/auth/agent-loginInicia sesión a un AGENTE en el widget embebible (incrustado en el CRM del cliente) con su propia credencial, y emite el token de navegador restringido ddw_ vinculado a ese agente — de modo que las acciones de llamada son atribuibles a la persona, en tiempo real, SIN que el cliente maneje nunca la clave dd_ del tenant ni una sesión ddu_ del dashboard.
tenant_id es el id PÚBLICO de espacio de trabajo del widget (incrustado en el onboarding; un uuid no secreto, como una clave publicable) — SELECCIONA el tenant; el Origin NUNCA resuelve un tenant. La cabecera Origin obligatoria es el WIDGET-HOST — el documento del navegador que sirve el frame, una CONSTANTE DE DESPLIEGUE (WIDGET_HOST_ORIGINS / Dialer.Widgets.host_origins/0) — y debe coincidir por valor con uno de los orígenes widget-host configurados. Eso es DISTINTO de la lista de permitidos de EMBED por tenant (widget_origins, que gobierna CSP frame-ancestors / postMessage); las dos listas de permitidos divergen deliberadamente. Fail-closed: un conjunto widget-host vacío no autentica a nadie. El ddw_ emitido está SIN FIJAR — el Origin widget-host se vuelve a comparar por valor en cada petición posterior del widget; el token no está vinculado a un origen del CRM. Cross-origin (CORS): el Origin se refleja; el límite duro es la coincidencia del widget-host.
Plano en enumeración y en tiempo: un tenant desconocido, un origen que no es widget-host, un tenant sin la vía de widget, email desconocido, contraseña incorrecta y un agente sin inicio de sesión habilitado dan UN mismo 401 idéntico con el mismo coste de PBKDF2, y ninguno de ellos emite un token; solo las credenciales demostradas como correctas de un tenant/cuenta suspendido reciben el 403. Freno de ventana fija por (tenant, email). Protegido por flag en la vía de widget (clave gobernada widget_auth_enabled, por tenant bajo un techo de celda): mientras el techo de la celda esté cerrado, esta ruta da 404.
El ddw_ emitido llega SOLO a la superficie de widget con denegación por defecto (WidgetAllowlist): los verbos de control de llamada del propio agente, disposición, marcado, rellamadas y el renew/introspect del propio token — nunca la superficie de gestión del tenant.
La puerta no es más blanda que la de la consola (core#2406, adenda del ADR #227; flag de tenant widget_login_honors_policy, ACTIVADO por defecto). Se consulta DESPUÉS de que la contraseña resultó correcta y antes de emitir nada: la política de inicio de sesión del tenant (dirección y horarios) — el rechazo es 403 login_policy_denied, el mismo cuerpo que el de la consola, y la misma pregunta se vuelve a hacer en cada petición ddw_ posterior, de modo que un token vivo que se mueve a una dirección rechazada deja de funcionar allí. Un asiento vinculado a una persona se juzga con el rol de esa persona; un asiento sin persona con el rol agent (queda sujeto a la lista de direcciones, y a ninguna ventana horaria, que pertenecen a una persona). Y, en un tenant con mfa_required_enforced ACTIVADO, el segundo factor: un asiento vinculado a una persona debe enviar el TOTP actual de esa persona como mfa_code (uno incorrecto da el mismo 401 que una contraseña incorrecta y cuenta en el freno); sin mfa_code, un asiento sin persona, o una persona sin factor activo da 403 mfa_required — un widget no puede dar de alta uno. Con el flag DESACTIVADO, o un tenant sin política y sin segundo factor, el inicio de sesión es el de antes.
Autenticación: ninguna — se autentica por la firma de la petición (ver abajo).
Cuerpo de la petición (JSON, obligatorio)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
tenant_id | string (uuid) | sí | El id público de espacio de trabajo del widget (no secreto). |
email | string (email) | sí | |
password | string (password) | sí | |
mfa_code | string | no | El TOTP actual de la persona vinculada al asiento. Necesario solo cuando el tenant tiene mfa_required_enforced ACTIVADO. |
json
{
"tenant_id": "0d4f4f9e-1f2a-4b53-9d3c-8a5e2f7b1c10",
"email": "[email protected]",
"password": "the-agents-passphrase"
}Respuestas
200
Sesión iniciada; token (el texto plano ddw_) se muestra exactamente una vez.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
token | string | sí | Token de navegador ddw_ en texto plano, mostrado exactamente una vez. |
widget_token | object (WidgetToken) | sí | Un token de widget de navegador ddw_ — el texto plano y el hash nunca se exponen aquí. |
widget_token.id | string (uuid) | no | |
widget_token.agent_id | string (uuid) | no | |
widget_token.label | string | no | |
widget_token.allowed_origin_id | string (uuid) | null | no | Fijación opcional a una fila de widget_origins — reduce los orígenes de EMBED expuestos al frame (CSP/postMessage) a solo ese; null = todos los orígenes de embed habilitados del tenant. NO es el límite de autenticación (ese es el Origin widget-host). El inicio de sesión del agente emite SIN FIJAR. |
widget_token.expires_at | string (date-time) | no | |
widget_token.session_expires_at | string (date-time) | no | |
widget_token.revoked_at | string (date-time) | null | no | |
widget_token.last_used_at | string (date-time) | null | no | Reloj de higiene de rotación, con el mismo significado que ApiKey.last_used_at: la última vez que este token se resolvió CON ÉXITO (superadas todas las compuertas), escrito como mucho una vez por minuto por token, de modo que una lectura reciente puede ir retrasada hasta ese tiempo. null significa que no se ha resuelto con éxito desde que la columna adquirió un escritor en la ruta caliente — no que el token nunca se haya presentado. La renovación también lo marca. |
widget_token.created_at | string (date-time) | no | |
widget_token.updated_at | string (date-time) | no | Fecha la FILA, no la emisión: el reloj de uso anterior y las escrituras del ciclo de vida (renovación, baja) lo mueven todos. |
agent | object (Agent) | sí | |
agent.id | string (uuid) | no | |
agent.tenant_id | string (uuid) | no | |
agent.name | string | no | |
agent.email | string | no | |
agent.role | agent · supervisor · admin | no | |
agent.status | active · inactive | no | Indicador de plantilla — NO es presencia. |
agent.sip_extension | string | null | no | |
agent.device_mode | browser · external | no | |
agent.login_enabled | boolean | no | Estado de autoinicio de sesión del widget — verdadero si y solo si hay una credencial de contraseña establecida. El hash de la contraseña nunca se expone. |
agent.presence | offline · available · ringing · on_call · wrap_up · paused | no | Presencia en vivo del asiento desde el AgentFSM en memoria. wrap_up es el trabajo posterior a la llamada (se entra al colgar cuando el tope agent_wrap_up_seconds del tenant es superior a 0); paused es un asiento retirado de la rotación con un motivo (ADR #101 D1, MT-ROAD-R11). |
agent.pause_reason | manual · break · lunch · training · meeting | null | no | Por qué el asiento está en pausa; null salvo que presence sea paused (core#2542). |
agent.state_since | string (date-time) | null | no | Cuándo el asiento entró en su presence actual (RFC 3339); null cuando el agente no tiene asiento (core#2542). |
agent.pause_armed | boolean | no | Hay una pausa armada para cuando termine la llamada actual (POST /v1/agents/{id}/pause con on_release: true). |
agent.created_at | string (date-time) | no | |
agent.updated_at | string (date-time) | no | |
tenant | object (Tenant) | sí | |
tenant.id | string (uuid) | no | |
tenant.name | string | no | |
tenant.account_id | string (uuid) | no | La EMPRESA a la que pertenece este tenant (sede) (DA-2). |
tenant.billing_account_id | string (uuid) | no | La cuenta de facturación bajo la que se factura la sede (DA-3). Compartida por todas las sedes de una empresa consolidated; una por sede cuando es per_site. |
tenant.status | active · suspended | no | |
tenant.synthetic | boolean | no | true para un tenant de demostración. No origina tráfico: la marcación manual, el envío de SMS, la pierna externa de una transferencia y el sandbox responden 403 synthetic_tenant. Siempre presente, nunca nulo, para que una consola pueda avisar ANTES de que la acción falle (core#2512). |
tenant.retention_months | integer | no | |
tenant.seats | integer | no | |
tenant.tier | starter · growth · scale · enterprise · dialer_core · compliance_pro · audit_shield | no | |
tenant.voice_ai_enabled | boolean | no | |
tenant.ai_monthly_budget_usd | number | null | no | Techo de integridad de costes (lo fija el admin; null = sin tope). |
tenant.ai_max_call_seconds | integer | null | no | Techo estricto de duración por llamada de IA (segundos) fijado por el admin. null significa que se aplica el valor por defecto de la PLATAFORMA, nunca "sin techo" (core#922, ADR #115). |
tenant.ai_max_call_seconds_effective | integer | no | El techo que realmente se entrega a FreeSWITCH para las piernas de IA de este tenant: ai_max_call_seconds cuando está definido; si no, el valor por defecto de plataforma de la celda (AI_MAX_CALL_SECONDS_DEFAULT, valor de reserva compilado 900). Siempre presente, nunca nulo. Solo lectura (derivado). |
tenant.sms_monthly_budget_usd | number | null | no | Tope de gasto en SMS acumulado en el mes hasta la fecha. |
tenant.human_initiated_enabled | boolean | no | Postura de consentimiento D17 de iniciativa humana (la fija el admin; por defecto false). Cuando es true, una marcación de voz EN VIVO de vista previa/manual/CTI sin voz artificial y sin voz de IA no requiere consentimiento expreso previo (sin ATDS). Todas las demás compuertas (DNC, horas de silencio, Reg F, matriz estatal, voz artificial) siguen aplicándose sin cambios. |
tenant.max_cps | integer | null | no | Techo de CPS por tenant (lo fija el admin; null = sin techo). ADR #12. |
tenant.max_concurrent_channels | integer | null | no | Techo de llamadas concurrentes por tenant, una unidad por llamada con o sin agente (lo fija el admin; null = sin techo). ADR #12, ADR #185. |
tenant.abandon_seller_name | string | null | no | Nombre del vendedor para el mensaje de identificación de llamada abandonada de la FTC TSR (lo fija el admin; null = no configurado, el sobremarcado predictivo permanece apagado). ADR #14. |
tenant.abandon_seller_phone | string | null | no | Número de rellamada E.164 para el mensaje de identificación de llamada abandonada (lo fija el admin; null = no configurado). ADR #14. |
tenant.tax_country | string | null | no | País ISO-3166-1 alpha-2 de la jurisdicción de impuesto sobre ventas del cliente (lo fija el admin; null = no declarado). ADR #77. |
tenant.tax_region | string | null | no | Código de estado/provincia de la jurisdicción de impuesto sobre ventas del cliente (lo fija el admin; null = no declarado). ADR #77. |
tenant.tax_postal_code | string | null | no | Código postal de la jurisdicción de impuesto sobre ventas del cliente (lo fija el admin; null = no declarado). ADR #77. |
tenant.kyc_verified_at | string (date-time) | null | no | Cuándo verificó la plataforma a este cliente (lo fija el admin; null = aún no). Una cuenta de 1 asiento que no está verificada no puede crear ni iniciar una campaña que se ejecute sin agente (403 verification_required, ADR #279). |
tenant.created_at | string (date-time) | no | |
tenant.updated_at | string (date-time) | no | |
origins | arreglo de string | sí | Los orígenes de EMBED habilitados del tenant (widget_origins) para el frame-ancestors de CSP del frame / el par de postMessage — NO es el límite de autenticación (ese es el Origin widget-host). |
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
Inicio de sesión no válido — tenant/email desconocido, contraseña incorrecta, un Origin que no es widget-host, un tenant sin la vía de widget y un agente con el inicio de sesión deshabilitado son indistinguibles.
json
{
"error": {
"code": "unauthorized",
"message": "invalid email or password"
}
}403
El tenant está suspendido (forbidden); o las credenciales correctas fueron rechazadas: login_policy_denied (la política de inicio de sesión del tenant rechaza esta dirección u hora, el mismo cuerpo que el de la consola) o mfa_required (el tenant exige un segundo factor y el asiento no puede presentar uno).
json
{
"error": {
"code": "mfa_required",
"message": "this workspace requires a second factor for this sign-in"
}
}404
El techo de celda de la vía de widget está cerrado (WIDGET_AUTH_ENABLED desactivado en este despliegue).
429 — LoginRateLimited
El freno de inicio de sesión por CUENTA rechazó este intento (ventana fija; se puede reintentar más tarde). code rate_limited. Se aplica por email (/v1/auth/login) o por {tenant_id, email} (/v1/auth/agent-login), de modo que limita muchas contraseñas contra UNA sola cuenta. El spraying de credenciales sobre muchas cuentas actualmente se CUENTA Y SE ALERTA pero no se rechaza, porque la única clave por origen disponible para core queda colapsada por el proxy inverso del dashboard; un intento de spraying por tanto sigue devolviendo el 401 ordinario. Retry-After es el tiempo que queda de la ventana fija.
/v1/auth/login tiene un segundo motivo, ACUMULATIVO, para el mismo 429 (core#2335, NIST SP 800-63B-4 §3.2.2): cuando una persona acumula login_progressive_threshold_failures (20 por defecto) contraseñas incorrectas desde su último inicio de sesión exitoso, dentro de 24 h, el inicio de sesión se retiene incluso con la contraseña CORRECTA durante 15 min tras el último fallo, y se duplica por cada conjunto adicional de fallos del umbral (con tope de 6 h); Retry-After es el tiempo que queda. Una pausa, nunca un bloqueo permanente. Los intentos retenidos no se registran, así que no la alargan.
json
{
"error": {
"code": "rate_limited",
"message": "too many login attempts; retry later"
}
}Configuración pública de arranque del widget (los orígenes habilitados del tenant)
http
GET /v1/auth/widget-configLos orígenes de EMBED HABILITADOS del tenant (widget_origins) — la lista de permitidos por tenant de orígenes de CRM que pueden incrustar el widget de este tenant. PÚBLICO (security: []): estas cadenas ya viajan dentro de cabeceras CSP. Alimenta las dos compuertas del lado EMBED desde UNA sola fuente: el Content-Security-Policy: frame-ancestors del host del frame (quién puede INCRUSTAR el widget) y la validación de origen del par de postMessage del frame (con quién hablará). Este es SOLO el límite de EMBED — DISTINTO del límite de AUTENTICACIÓN, que es el Origin widget-host (WIDGET_HOST_ORIGINS, una constante de despliegue) vuelto a comparar por valor en cada petición ddw_; las dos listas de permitidos divergen deliberadamente. Reflejado por CORS como agent-login; cacheable 60 s. Un tenant_id desconocido y un tenant sin la vía de widget responden origins: [] — sin oráculo de existencia de tenant. Protegido por flag en la vía de widget (clave gobernada widget_auth_enabled): mientras el techo de la celda esté cerrado, esta ruta da 404.
Autenticación: ninguna — se autentica por la firma de la petición (ver abajo).
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
tenant_id | query | string (uuid) | sí | El id público de espacio de trabajo del widget (no secreto). |
Respuestas
200
Los orígenes habilitados del tenant (posiblemente vacíos).
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
origins | arreglo de string | sí |
json
{
"origins": [
"https://crm.acme-collections.test"
]
}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"
}
}404
El techo de celda de la vía de widget está cerrado (WIDGET_AUTH_ENABLED desactivado en este despliegue).
Rotar la contraseña propia de quien llama
http
POST /v1/me/passwordRequiere una sesión de inicio de sesión ddu_ (una clave de API dd_ no tiene persona que rotar — 403). Verifica la contraseña actual, almacena el nuevo hash y revoca todas las DEMÁS sesiones vivas del usuario; la sesión que realiza la rotación sobrevive.
Como esta ruta verifica la contraseña REAL de quien llama, las confirmaciones incorrectas tienen límite de uso por USUARIO (429): una sesión robada no puede usarse para extraer la contraseña por fuerza bruta. Una confirmación correcta nunca se cuenta, así que la rotación normal no se ve afectada.
Cuerpo de la petición (JSON, obligatorio)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
current_password | string (password) | sí | |
new_password | string (password) | sí |
Respuestas
204
Contraseña rotada; demás sesiones revocadas.
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"
}
}422
La contraseña actual es incorrecta (unprocessable), o la nueva está en la lista de bloqueo local de contraseñas de uso común o comprometidas, contiene el email de la persona o el nombre del espacio de trabajo, o es trivialmente repetitiva (password_blocklisted, core#2335, NIST SP 800-63B-4 §3.1.1.2; el mensaje nombra el motivo). No cambió nada, y una contraseña nueva en la lista de bloqueo no gasta el presupuesto de confirmaciones incorrectas.
json
{
"error": {
"code": "unprocessable",
"message": "current password is incorrect"
}
}429 — PasswordConfirmThrottled
Demasiadas confirmaciones FALLIDAS de la contraseña actual para este USUARIO dentro de la ventana fija (se puede reintentar más tarde). code rate_limited. Se aplica sobre el usuario autenticado — nunca sobre una dirección de red, que un proxy inverso delante de core colapsaría — y se evalúa antes de verificar la contraseña. Una confirmación correcta no se cuenta una vez que se completa, pero mientras cualquier confirmación se está verificando ocupa una plaza del presupuesto, de modo que las peticiones simultáneas nunca pueden obtener más verificaciones que el presupuesto (core#1114); una ráfaga mayor que el presupuesto responde 429 al exceso aunque parte de ella llevara la contraseña correcta. Retry-After es el tiempo que queda de la ventana fija.
json
{
"error": {
"code": "rate_limited",
"message": "too many failed password confirmations; retry later"
}
}El estado del segundo factor propio de quien llama
http
GET /v1/me/mfaRequiere una sesión de inicio de sesión ddu_ (403 para una clave dd_).
Respuestas
200
Estado.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
enabled | boolean | sí | |
recovery_codes_remaining | integer | sí |
401 — Unauthorized
Clave de API ausente, desconocida o revocada (o, en /v1/admin/*, un token de administración incorrecto o sin definir; en webhooks, una firma incorrecta o ausente). Una sesión del dashboard (ddu_) que un inicio de sesión más reciente de la misma persona haya cerrado responde en cambio con el código session_superseded (indicador de tenant single_session_enforced), para que la pantalla pueda explicar por qué se cerró la sesión; un cierre de sesión, un token vencido o uno desconocido siguen siendo unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — Forbidden
El tenant está suspendido. Una sesión del dashboard (ddu_) de un tenant cuya política de inicio de sesión (ADR 227) está en modo enforce responde 403 con el código login_policy_denied ante una solicitud desde una dirección o en una hora que la política no permite — el mismo cuerpo en ambos casos; la sesión no se revoca y una solicitud posterior dentro de la política se procesa. Un token de widget (ddw_) de un puesto queda sujeto a la misma política en cada solicitud (core#2406, indicador widget_login_honors_policy). Un tenant sintético (demo) responde 403 con el código synthetic_tenant en toda ruta que llegaría a un tercero — marcación manual, pierna de transferencia externa o de conferencia, envío de SMS (core#2251, adenda del ADR #280). Ningún pago lo cambia.
json
{
"error": {
"code": "forbidden",
"message": "tenant is suspended"
}
}Iniciar el alta de TOTP
http
POST /v1/me/mfa/enrollEmite un secreto TOTP nuevo, PENDIENTE hasta que POST /v1/me/mfa/confirm demuestre que el autenticador lo posee. El secreto y su URI otpauth:// (la carga útil del QR) se muestran exactamente una vez; el secreto se almacena cifrado. Repetir la llamada antes de confirmar reemplaza el secreto pendiente. Con un factor ya activo da 409: quítalo primero. Requiere la password actual (aún no hay factor al que pedírselo), de modo que una sesión robada por sí sola no puede dejar fuera al titular con un factor propio; una contraseña incorrecta tiene límite de uso por USUARIO (429). Requiere una sesión ddu_.
Cuerpo de la petición (JSON, obligatorio)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
password | string (password) | sí |
Respuestas
200
Secreto y URI, mostrados una vez.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
secret | string | sí | Secreto TOTP, base32; se muestra exactamente una vez. |
otpauth_uri | string | sí | otpauth://totp/... — codifícalo como el QR. |
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"
}
}409
Ya hay un segundo factor activo.
json
{
"error": {
"code": "conflict",
"message": "a second factor is already active; remove it first"
}
}422
La contraseña es incorrecta.
json
{
"error": {
"code": "unprocessable",
"message": "the password is incorrect"
}
}429 — MfaVerifyThrottled
Demasiados intentos FALLIDOS de segundo factor para este USUARIO dentro de la ventana fija (se puede reintentar más tarde). code rate_limited. Se aplica sobre la persona, nunca sobre una dirección de red, se evalúa ANTES de comparar el código y es COMPARTIDO por POST /v1/auth/mfa/verify, las rutas de alta forzada (/v1/auth/mfa/enroll*) y toda ruta /v1/me/mfa/* que compare un código o una contraseña. Un intento correcto no se cuenta una vez que se completa; mientras se verifica, ocupa una plaza, de modo que las peticiones simultáneas nunca obtienen más comparaciones que el presupuesto (ADR #144). Retry-After es el tiempo que queda de la ventana fija.
También es la respuesta del freno ACUMULATIVO (core#1572): cuando una persona acumula mfa_progressive_threshold_failures códigos rechazados (20 por defecto) dentro de mfa_progressive_window_seconds (24 h por defecto), un TOTP ni siquiera se compara hasta que hayan pasado mfa_progressive_cooldown_seconds (15 min por defecto) desde el último fallo, y Retry-After es esa espera. Un código de recuperación nunca queda retenido por él, y un account_admin puede levantarlo (POST /v1/users/{id}/mfa/reset-failures). No es un bloqueo: la persona va más lenta, no queda fuera.
json
{
"error": {
"code": "rate_limited",
"message": "too many failed second-factor attempts; retry later"
}
}Confirmar el alta con el primer código
http
POST /v1/me/mfa/confirmActiva el factor pendiente con un código TOTP válido y devuelve los códigos de recuperación, en claro, exactamente una vez (solo se almacena su sha256). Los códigos incorrectos tienen límite de uso por USUARIO (429). Requiere una sesión ddu_.
Cuerpo de la petición (JSON, obligatorio)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
code | string | sí |
Respuestas
200
Factor activo; códigos de recuperación mostrados una vez.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
recovery_codes | arreglo de string | sí | Códigos de un solo uso, mostrados en claro exactamente una vez. |
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"
}
}409
Ningún alta pendiente.
422
El código es incorrecto.
json
{
"error": {
"code": "unprocessable",
"message": "the code is incorrect"
}
}429 — MfaVerifyThrottled
Demasiados intentos FALLIDOS de segundo factor para este USUARIO dentro de la ventana fija (se puede reintentar más tarde). code rate_limited. Se aplica sobre la persona, nunca sobre una dirección de red, se evalúa ANTES de comparar el código y es COMPARTIDO por POST /v1/auth/mfa/verify, las rutas de alta forzada (/v1/auth/mfa/enroll*) y toda ruta /v1/me/mfa/* que compare un código o una contraseña. Un intento correcto no se cuenta una vez que se completa; mientras se verifica, ocupa una plaza, de modo que las peticiones simultáneas nunca obtienen más comparaciones que el presupuesto (ADR #144). Retry-After es el tiempo que queda de la ventana fija.
También es la respuesta del freno ACUMULATIVO (core#1572): cuando una persona acumula mfa_progressive_threshold_failures códigos rechazados (20 por defecto) dentro de mfa_progressive_window_seconds (24 h por defecto), un TOTP ni siquiera se compara hasta que hayan pasado mfa_progressive_cooldown_seconds (15 min por defecto) desde el último fallo, y Retry-After es esa espera. Un código de recuperación nunca queda retenido por él, y un account_admin puede levantarlo (POST /v1/users/{id}/mfa/reset-failures). No es un bloqueo: la persona va más lenta, no queda fuera.
json
{
"error": {
"code": "rate_limited",
"message": "too many failed second-factor attempts; retry later"
}
}Quitar el segundo factor
http
POST /v1/me/mfa/disableRequiere reautenticación: la contraseña actual Y un segundo factor (un code TOTP o un recovery_code, exactamente uno). Revoca los códigos de recuperación sin usar. Las credenciales incorrectas tienen límite de uso por USUARIO (429). Requiere una sesión ddu_.
Cuerpo de la petición (JSON, obligatorio)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
password | string (password) | sí | |
code | string | no | |
recovery_code | string | no |
Respuestas
204
Segundo factor quitado.
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"
}
}409
No hay ningún segundo factor activo.
422
La contraseña o el código son incorrectos.
429 — MfaVerifyThrottled
Demasiados intentos FALLIDOS de segundo factor para este USUARIO dentro de la ventana fija (se puede reintentar más tarde). code rate_limited. Se aplica sobre la persona, nunca sobre una dirección de red, se evalúa ANTES de comparar el código y es COMPARTIDO por POST /v1/auth/mfa/verify, las rutas de alta forzada (/v1/auth/mfa/enroll*) y toda ruta /v1/me/mfa/* que compare un código o una contraseña. Un intento correcto no se cuenta una vez que se completa; mientras se verifica, ocupa una plaza, de modo que las peticiones simultáneas nunca obtienen más comparaciones que el presupuesto (ADR #144). Retry-After es el tiempo que queda de la ventana fija.
También es la respuesta del freno ACUMULATIVO (core#1572): cuando una persona acumula mfa_progressive_threshold_failures códigos rechazados (20 por defecto) dentro de mfa_progressive_window_seconds (24 h por defecto), un TOTP ni siquiera se compara hasta que hayan pasado mfa_progressive_cooldown_seconds (15 min por defecto) desde el último fallo, y Retry-After es esa espera. Un código de recuperación nunca queda retenido por él, y un account_admin puede levantarlo (POST /v1/users/{id}/mfa/reset-failures). No es un bloqueo: la persona va más lenta, no queda fuera.
json
{
"error": {
"code": "rate_limited",
"message": "too many failed second-factor attempts; retry later"
}
}Regenerar los códigos de recuperación
http
POST /v1/me/mfa/recovery-codesLa misma reautenticación que disable. Revoca todos los códigos sin usar y emite un conjunto nuevo, mostrado en claro exactamente una vez. Requiere una sesión ddu_.
Cuerpo de la petición (JSON, obligatorio)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
password | string (password) | sí | |
code | string | no | |
recovery_code | string | no |
Respuestas
200
Códigos nuevos, mostrados una vez.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
recovery_codes | arreglo de string | sí | Códigos de un solo uso, mostrados en claro exactamente una vez. |
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"
}
}409
No hay ningún segundo factor activo.
422
La contraseña o el código son incorrectos.
429 — MfaVerifyThrottled
Demasiados intentos FALLIDOS de segundo factor para este USUARIO dentro de la ventana fija (se puede reintentar más tarde). code rate_limited. Se aplica sobre la persona, nunca sobre una dirección de red, se evalúa ANTES de comparar el código y es COMPARTIDO por POST /v1/auth/mfa/verify, las rutas de alta forzada (/v1/auth/mfa/enroll*) y toda ruta /v1/me/mfa/* que compare un código o una contraseña. Un intento correcto no se cuenta una vez que se completa; mientras se verifica, ocupa una plaza, de modo que las peticiones simultáneas nunca obtienen más comparaciones que el presupuesto (ADR #144). Retry-After es el tiempo que queda de la ventana fija.
También es la respuesta del freno ACUMULATIVO (core#1572): cuando una persona acumula mfa_progressive_threshold_failures códigos rechazados (20 por defecto) dentro de mfa_progressive_window_seconds (24 h por defecto), un TOTP ni siquiera se compara hasta que hayan pasado mfa_progressive_cooldown_seconds (15 min por defecto) desde el último fallo, y Retry-After es esa espera. Un código de recuperación nunca queda retenido por él, y un account_admin puede levantarlo (POST /v1/users/{id}/mfa/reset-failures). No es un bloqueo: la persona va más lenta, no queda fuera.
json
{
"error": {
"code": "rate_limited",
"message": "too many failed second-factor attempts; retry later"
}
}