Ir al contenido

Apps, instancias y scopes

El motor sirve a varios sistemas a la vez (un CRM, un bot, otro proyecto) sin que ninguno vea lo del otro. Para eso separa quién pide (una app con su key) de sobre qué (una instancia, un número) y de quién administra (los operadores del motor).

Motor
├── Operadores (panel /admin, cookie + CSRF)
└── Apps cliente
├── API keys ──────── scopes · entorno · instancias (opcional) · vencimiento
├── Instancias ────── un número de WhatsApp cada una
│ └── ledger de eventos, comandos, contactos, chats, multimedia…
├── Suscripciones de webhooks y bridges
└── Destinos permitidos y cupos propios

Una app cliente es tu sistema visto desde el motor. Es la unidad de aislamiento: sus instancias, sus keys, sus suscripciones y su multimedia son solo suyas.

  • La crea un operador superadmin; tiene un nombre único y un estado: activo o suspendido.
  • Suspenderla corta al instante todas sus keys (401) y hace fallar lo que dejó pendiente (app_suspendida). Reactivarla devuelve el acceso.
  • El motor no conoce tus empresas ni tus usuarios. Si tu app sirve a varias empresas, eso vive en tu sistema: el motor solo guarda referencias opacas tuyas, como el owner_ref de cada instancia.

Una instancia es un número de WhatsApp vinculado al motor como dispositivo. Pertenece a una sola app, para siempre.

Campo Qué es
id Su identificador. Lo usan todas las rutas /v1/instances/{id}/….
nombre, owner_ref Tuyos: un nombre visible y una referencia opaca (la sucursal, el vendedor).
estado Lo que el motor observa: conectado, desconectado, esperando_qr…
estado_deseado Lo que se pidió: activo, pausado, desvinculado o archivado.
generacion Sube con cada intento de vinculación. Ver La generación.
numero El teléfono vinculado, en +E.164, o vacío.
config Su configuración propia: ritmo de envío, rechazo de llamadas, historial, reconexión.

Cada instancia tiene su propio ledger de eventos con su propia secuencia (seq) y sus propias proyecciones: contactos, chats, mensajes, grupos. Un mismo contacto tiene un peer_ref distinto en cada instancia (ver Identidades). Archivarla la desvincula y la saca del cupo, pero su ledger se conserva.

Una app tiene una o más keys. Cada key lleva:

  • scopes: qué rutas puede usar;
  • entorno: produccion (mw_live_…) o prueba (mw_test_…), que solo cambia el prefijo;
  • instancias (opcional): a cuáles alcanza;
  • vencimiento (opcional).

El motor guarda solo el prefijo y el HMAC del token: el token completo se ve una vez, al emitir o rotar. Las keys no se editan; se emiten, se rotan o se revocan. Cómo hacerlo, en Crear la app y la key.

Tener varias keys por app es lo normal: una por componente (el CRM web, el worker que recibe webhooks, un script de soporte), cada una con los scopes justos. Si una se filtra, revocas esa sin tocar las demás.

Un scope es un permiso sobre un grupo de rutas. El catálogo es cerrado: 33 scopes en 14 grupos (Instancias, Mensajes, Eventos, Multimedia, Llamadas, Chats, Contactos, Historial, Grupos, Canales, Status, Comercio, Perfil, Privacidad). La lista completa, con lo que permite cada uno, está en Los scopes.

  • Casi todos van en pares de lectura y escritura: chats:read y chats:manage, groups:read y groups:write.
  • Sin el scope, la ruta responde 403 y dice cuál falta.
  • whoami y capabilities no piden scope.

Un comando recuerda qué key lo pidió, y el motor revalida esa key justo antes de ejecutarlo: si la revocaron, venció, perdió el acceso o su app se suspendió, el comando termina fallido sin tocar WhatsApp. Un permiso retirado no deja nada pendiente que se ejecute igual.

Una key sin restricción alcanza a todas las instancias de su app, también a las que se creen después. Una key restringida solo a las que se eligieron al emitirla:

  • las demás instancias de la app son, para ella, 404;
  • sus listas (instancias, suscripciones, entregas, eventos) solo traen lo de sus instancias;
  • no puede crear instancias (403);
  • una suscripción creada con ella tiene que nombrar sus instancias.

Sirve para dar a un sistema acotado (el bot de una sucursal) acceso a su número y a ningún otro.

Algunos límites son por app, para que una no agote lo de las demás; otros son por instancia, para cuidar cada número. Los valores de fábrica se cambian en Ajustes.

Límite Alcance De fábrica Al pasarlo
Instancias Por app (cada app puede tener el suyo) 3 429 al crear. Las archivadas no cuentan.
Consultas de identidad Por app y por día (UTC) 500 429 cuota_agotada hasta las 00:00 UTC.
Consultas del catálogo Por app y por día (UTC) 500 429.
Multimedia guardada Por app 10 240 MB 429 cuota_excedida.
Streams abiertos Por app 5 429 al abrir otro.
Ritmo de envío Por instancia 1 por segundo, ráfaga de 2 429 con el comando guardado: sale solo.
Envíos en cola Por instancia 200 429 sin guardar.
Multimedia guardada Por instancia 2048 MB 429 cuota_excedida.

El cupo de instancias propio de una app se fija en el panel. La base también admite una cuota propia por app para las consultas de identidad y para la multimedia, pero hoy no hay pantalla ni ruta para fijarlas: rigen las de Ajustes. Todos los límites, en Cuotas y límites.

La regla de fondo: la app sale de la credencial, nunca del pedido.

  • Ningún cuerpo elige la app. No hay un cliente_id que mandar: el motor lo toma de la key. Los cuerpos son cerrados: un campo de más es 422.
  • Todo se filtra por app (y por instancia, si la key está restringida): instancias, comandos, eventos, suscripciones, entregas, multimedia, contactos.
  • 404 sin enumerar. Lo de otra app responde igual que lo que no existe: «No existe esa instancia». Así una key no puede averiguar qué IDs hay en otras apps.
  • La base lo asegura dos veces. Los comandos, los eventos y los grants de las keys tienen claves foráneas compuestas (app, instancia): aunque un servicio se equivocara, la base rechazaría un comando sobre la instancia de otra app.
  • Las identidades son por instancia. Lo que una instancia aprendió de un contacto (su teléfono detrás de un LID, por ejemplo) no aparece en otra, ni siquiera de la misma app.
  • Los eventos no dicen de quién son. El sobre trae instance_id, no la app: quién recibe qué lo decide la suscripción.

Son dos identidades que no se mezclan: una cookie de operador no sirve en /v1, y una API key no sirve en /admin/api.

Operador App cliente
Quién es Una persona que administra el motor Un sistema que lo usa
Credencial Correo y contraseña; sesión con cookie mw_sesion y X-CSRF-Token en cada cambio Authorization: Bearer mw_…
Rutas /admin/api/… y el panel en /admin/ /v1/…
Ve Todas las apps y todas las instancias Solo lo suyo
Se crea con motorctl (el primero) y el panel El panel (un superadmin)

Los operadores tienen dos roles:

Rol Puede
superadmin Todo: crear y suspender apps, emitir, rotar y revocar keys, guardar Ajustes, desvincular y archivar instancias.
operador Ver todo y operar instancias (crear, vincular, reconectar, pausar, una prueba de envío). Recibe 403 en lo que administra credenciales y políticas.

Cuando un operador actúa sobre una instancia (una prueba de envío desde la ficha, por ejemplo), el comando queda a nombre del operador, no de una key, y cae en el ledger de esa instancia como cualquier otro: tu app lo ve por sus eventos.