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 propiosLa app cliente
Sección titulada «La app cliente»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:activoosuspendido. - 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_refde cada instancia.
La instancia
Sección titulada «La 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.
La API key
Sección titulada «La API key»Una app tiene una o más keys. Cada key lleva:
- scopes: qué rutas puede usar;
- entorno:
produccion(mw_live_…) oprueba(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.
Los scopes
Sección titulada «Los scopes»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:readychats:manage,groups:readygroups:write. - Sin el scope, la ruta responde 403 y dice cuál falta.
whoamiycapabilitiesno 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.
La restricción por instancias
Sección titulada «La restricción por instancias»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.
Cupos y límites por app
Sección titulada «Cupos y límites por app»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.
El aislamiento
Sección titulada «El aislamiento»La regla de fondo: la app sale de la credencial, nunca del pedido.
- Ningún cuerpo elige la app. No hay un
cliente_idque 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.
Operadores y apps
Sección titulada «Operadores y apps»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.