Crear la app y la key
Tu sistema habla con el motor como una app cliente con una o más API keys. Esta página amplía el paso 1 del Inicio rápido: qué elegir al emitir una key, cómo restringirla, cómo rotarla sin cortar el servicio y qué pasa con lo pendiente cuando una key deja de valer. El modelo completo está en Apps, instancias y scopes.
Quién crea qué
Sección titulada «Quién crea qué»Las apps y las keys las crea un operador con rol superadmin en el panel. Una app nunca crea
otra app ni emite sus propias keys: /v1 no tiene rutas para eso. Un operador con rol operador
ve las apps y sus keys, pero no puede crearlas, rotarlas ni revocarlas.
1. Crea la app
Sección titulada «1. Crea la app»En Clientes, elige Nueva app:
| Campo | Qué es |
|---|---|
| Nombre | Único en el motor. Lo verás en el panel y en whoami. |
| Descripción | Opcional. |
| Cupo de instancias | Opcional. Sin cupo propio, la app usa el de Ajustes → Límites (3 de fábrica). |
La app nace activo. Lo mismo se puede hacer con la API de administración
(POST /admin/api/clientes, sesión de operador con CSRF): está en
API de administración.
2. Emite una key
Sección titulada «2. Emite una key»En la ficha de la app, elige Emitir key:
| Campo | Qué es |
|---|---|
| Nombre | Para reconocerla: «CRM producción», «worker de webhooks». |
| Entorno | Producción (mw_live_…) o Prueba (mw_test_…). |
| Scopes | Al menos uno. Los permisos de la key: la tabla completa. |
| Instancias | Opcional. Si eliges alguna, la key solo alcanza a esas. Vacío: todas las de la app. |
| Vencimiento | Sin vencimiento, en 30 días, en 90 días o en un año. |
El diálogo Key emitida muestra el token completo una sola vez. El motor guarda solo su prefijo y su HMAC: no puede volver a mostrártelo. Si lo pierdes, rota la key.
El token
Sección titulada «El token»mw_live_1a2b3c4d_<43 caracteres base64url>└──── prefijo ─┘ └──────── secreto ────────┘El prefijo (mw_live_1a2b3c4d) es lo único que vuelve a aparecer: en el panel, en whoami y
en la auditoría. Sirve para saber qué key es sin exponer el secreto. Guarda el token completo en
el gestor de secretos de tu app; nunca en el repositorio, en un log ni en un ticket.
Producción y prueba
Sección titulada «Producción y prueba»El entorno solo cambia el prefijo (mw_live_ o mw_test_) y la etiqueta. Una key de prueba
habla con el mismo motor, las mismas instancias y el mismo WhatsApp real: no hay un modo de
prueba que simule envíos.
Los scopes
Sección titulada «Los scopes»Cada ruta de /v1 pide un scope; sin él, la respuesta es 403 («Tu API key no tiene el scope
…»). Elige los mínimos que necesita cada parte de tu sistema: el worker que solo recibe webhooks
no necesita messages:send.
| Grupo | Scope | Qué permite |
|---|---|---|
| Instancias | instances:read |
Ver sus instancias, su estado y su diagnóstico. |
instances:create |
Crear instancias. | |
instances:manage |
Cambiar el nombre y la configuración, reconectar, desconectar, pausar y reanudar. | |
instances:pair |
Vincular números (QR, código y passkey). | |
instances:logout |
Desvincular o archivar instancias. | |
| Mensajes | messages:send |
Enviar mensajes (y leer el estado de esos comandos). |
messages:read |
Leer los mensajes guardados, sus recibos y el resultado de encuestas e interactivos. | |
presence:write |
Mandar la presencia y seguir la de un contacto. | |
| Eventos | events:read |
Leer el ledger, los streams y las entregas. |
subscriptions:manage |
Crear, cambiar, probar y rotar suscripciones de webhooks. | |
deliveries:replay |
Pedir el reenvío (replay) de eventos ya aceptados. | |
| Multimedia | media:read |
Descargar multimedia. |
media:write |
Subir multimedia. | |
| Llamadas | calls:manage |
Ver y rechazar llamadas entrantes. |
| Chats | chats:read |
Ver el estado de los chats (fijado, archivado, silenciado, no leído) y su sincronización. |
chats:manage |
Fijar, archivar, silenciar, marcar, destacar y borrar chats. | |
labels:read |
Ver las etiquetas del número. | |
labels:manage |
Crear, editar, eliminar y asociar etiquetas. | |
| Contactos | contacts:read |
Ver los contactos del número y resolver identidades (LID↔PN) de una instancia. |
contacts:lookup |
Preguntar al servidor si un número está en WhatsApp y el perfil público de otros, con la cuota diaria de la app. | |
| Historial | history:read |
Ver la política del historial y sus trabajos. |
history:manage |
Pedir e importar historial y cancelar sus trabajos. | |
| Grupos | groups:read |
Consultar grupos y comunidades. |
groups:write |
Administrar grupos y comunidades. | |
| Canales | newsletters:read |
Consultar canales y sus publicaciones. |
newsletters:write |
Crear, seguir, silenciar, ver y reaccionar en canales. Publicar va con messages:send. |
|
| Status | status:read |
Consultar los status recibidos y publicados mientras se retienen. |
status:write |
Publicar status con la audiencia explícita. | |
| Comercio | catalog:read |
Consultar el catálogo y las colecciones de WhatsApp Business, con la cuota diaria de la app. |
| Perfil | profile:read |
Consultar el perfil propio. |
profile:write |
Cambiar el perfil propio. | |
| Privacidad | privacy:read |
Consultar la privacidad y la lista de bloqueados. |
privacy:write |
Cambiar la privacidad y los bloqueos. |
Tres reglas que no se ven en la tabla:
GET /v1/whoamiyGET /v1/capabilitiesfuncionan con cualquier key válida.GET /v1/commands/{id}pide el scope del tipo del comando: el de un mensaje pidemessages:send; el de uninstance.reconnect,instances:manage.- Algunas rutas piden dos: mandar la invitación de un grupo pide
groups:readymessages:send.
La tabla es el catálogo completo: el motor no tiene otros scopes (una prueba del motor compara esta
tabla con su código). Para ver los que tiene tu key, usa GET /v1/whoami o GET /v1/capabilities.
Restringir la key a instancias
Sección titulada «Restringir la key a instancias»Si eliges instancias al emitirla, la key queda restringida:
- Solo ve y opera esas instancias. Las demás de la app responden 404, igual que una instancia que no existe.
- Las listas (
GET /v1/instances, las suscripciones, las entregas) solo traen lo suyo. - No puede crear instancias (403): una instancia nueva no entraría en su alcance. Créala con otra key o pídesela al operador.
- Una suscripción creada con ella tiene que nombrar sus
instancias. - Cada instancia elegida tiene que ser de la app y no estar archivada (si no, 422 en
body.instancias).
Una instancia que se crea después no se suma sola a las keys restringidas. Una key sin restricción alcanza a todas, también a las nuevas.
Usar la key
Sección titulada «Usar la key»Manda el token en cada pedido como Authorization: Bearer <token>. Si falta, la respuesta es
401 «Falta la API key»; si es inválida, venció, fue revocada o su app está suspendida, 401
con un mismo mensaje para todos los casos (el motor no dice cuál).
Al arrancar tu app, comprueba con GET /v1/whoami que la key es la que esperas y que tiene los
scopes que vas a usar. Así un error de configuración aparece en el arranque y no en el primer
envío:
curl -s https://motor.ejemplo.com/v1/whoami \ -H "Authorization: Bearer TU_API_KEY" | jq '.key | {prefijo, entorno, scopes, instancias}'// El SDK de Go no trae whoami: es una petición HTTP con el mismo token.func comprobarKey(ctx context.Context, base, apiKey string, necesarios []string) error { req, _ := http.NewRequestWithContext(ctx, http.MethodGet, base+"/v1/whoami", nil) req.Header.Set("Authorization", "Bearer "+apiKey) res, err := http.DefaultClient.Do(req) if err != nil { return err } defer res.Body.Close() if res.StatusCode != http.StatusOK { return fmt.Errorf("la API key no vale: %d", res.StatusCode) } var w struct { Key struct { Prefijo string `json:"prefijo"` Scopes []string `json:"scopes"` } `json:"key"` } if err := json.NewDecoder(res.Body).Decode(&w); err != nil { return err } for _, s := range necesarios { if !slices.Contains(w.Key.Scopes, s) { return fmt.Errorf("a la key %s le falta el scope %s", w.Key.Prefijo, s) } } return nil}async function comprobarKey(base, apiKey, necesarios) { const res = await fetch(`${base}/v1/whoami`, { headers: { Authorization: `Bearer ${apiKey}` }, }); if (!res.ok) throw new Error(`La API key no vale: ${res.status}`); const { key } = await res.json(); const faltan = necesarios.filter((s) => !key.scopes.includes(s)); if (faltan.length) throw new Error(`A la key ${key.prefijo} le faltan: ${faltan.join(', ')}`);}
await comprobarKey('https://motor.ejemplo.com', apiKey, ['messages:send', 'events:read']);{ "cliente": { "id": "8a1d2c3e-0000-4000-8000-000000000001", "nombre": "Mi CRM" }, "key": { "id": "8a1d2c3e-0000-4000-8000-000000000003", "prefijo": "mw_test_5e6f7a8b", "entorno": "prueba", "scopes": ["events:read", "messages:send"], "instancias": ["8a1d2c3e-0000-4000-8000-0000000000a1"] }}GET /v1/capabilities completa el cuadro: la versión del contrato, el contrato de eventos (los
transportes, cuántos streams puede abrir la app, los días de reanudación), el estado del proveedor
y los scopes de la key.
Rotar una key
Sección titulada «Rotar una key»Rotar (en el panel, o POST /admin/api/keys/{id}/rotar) emite una key nueva con el mismo
nombre, entorno, scopes, instancias y vencimiento, y revoca la anterior en el mismo momento
(motivo rotada). No hay una ventana en la que valgan las dos: desde la rotación, el token viejo
recibe 401. Una key vencida no se rota (409).
Para cambiar de key sin cortar el servicio ni perder lo pendiente:
- Emite una segunda key con los mismos scopes e instancias.
- Despliega tu app con la nueva y confirma con
whoamique la usa (el panel muestra el último uso de cada key). - Espera a que salga lo que la vieja dejó en cola.
- Revoca la vieja.
Rota directamente cuando el token se filtró: ahí importa más cortarlo ya que lo pendiente.
Revocar, vencer y suspender
Sección titulada «Revocar, vencer y suspender»| Qué pasa | Efecto en /v1 |
Efecto en lo pendiente |
|---|---|---|
| Revocar la key (irreversible) | 401 al instante | Sus comandos sin ejecutar fallan con credencial_revocada. |
| La key vence | 401 desde la fecha | Fallan con credencial_vencida. |
| Suspender la app | 401 en todas sus keys al instante | Fallan con app_suspendida. Reactivar la app devuelve el acceso. |
Una key no se edita: para cambiar sus scopes o sus instancias, emite otra y revoca la anterior. El motivo de una revocación es texto libre y queda en la auditoría; si alguien pega un token ahí por error, el motor lo oculta antes de guardarlo. Las suscripciones de webhooks son de la app, no de la key: revocar una key no las toca.
Errores comunes
Sección titulada «Errores comunes»| Respuesta | Causa | Qué hacer |
|---|---|---|
| 401 «Falta la API key» | Sin Authorization o sin el prefijo Bearer . |
Manda Authorization: Bearer mw_…. |
| 401 «La API key no es válida…» | Token mal copiado, revocado, vencido o app suspendida. | Mira el prefijo y el estado de la key en el panel. |
| 403 «…no tiene el scope…» | La key no tiene el scope de esa ruta. | Emite una key con el scope (las keys no se editan). |
| 403 al crear una instancia | La key está restringida a instancias. | Usa una key sin restricción. |
| 404 en una instancia que existe | Es de otra app, o está fuera de las instancias de la key. | Revisa instancias en whoami. |
422 body.instancias al emitir |
Una instancia no es de la app o está archivada. | Elige instancias vivas de esa app. |
Siguiente paso
Sección titulada «Siguiente paso»Con la key lista, vincula un número.