# whatsapp-bridge — referencia rápida de endpoints Base URL: `https://whatsapp.nucleoriofrio.com` Auth: `Authorization: Bearer $WA_BRIDGE_TOKEN` (excepto `/healthz`) ## Diagnóstico | Método | Ruta | Respuesta | |---|---|---| | GET | `/healthz` | `{ ok: true, ts }` | | GET | `/api/auth/status` | `{ status, hasSession, user, pairingCode, lastError }` | `status` ∈ `idle | connecting | open | closed | needs-pair`. ## Auth / pairing | Método | Ruta | Body / Headers | |---|---|---| | POST | `/api/auth/pair` | — → `{ code, hint }` | | POST | `/api/auth/logout` | Header `X-Confirm-Wipe: yes` (destructivo) | ## Chats ``` GET /api/chats ?type=group|individual|all (default all) &q= (busca en name + jid, ILIKE) &limit=<1..500> (default 100) &offset= (default 0) ``` Respuesta: ```json { "count": N, "chats": [ { "jid": "...@g.us", "isGroup": true, "name": "...", "lastMessageAt": "ISO", "participants": 42 } ] } ``` ## Grupo específico ``` GET /api/groups/:jid (URL-encode el jid, termina en @g.us) ``` Respuesta: `{ jid, stored: {…}, live: GroupMetadata | null }`. ``` GET /api/groups/:jid/messages ?since= (timestamp >=) &until= (timestamp <=) &q= (ILIKE en content) &sender= (filtra por participant) &order=asc|desc (default desc) &limit=<1..1000> (default 100) &offset= ``` Respuesta: ```json { "jid": "...@g.us", "count": N, "filters": { ... }, "messages": [ { "msgId": "...", "fromMe": false, "senderJid": "...@s.whatsapp.net", "pushName": "Don Fulano", "messageType": "extendedTextMessage", "content": "texto extraído", "contextInfo": { "quotedMsgId": "...", "mentionedJid": ["..."] } | null, "timestamp": "ISO", "media": null | { "type": "image"|"video"|"audio"|"document"|"sticker", "mimetype": "image/jpeg", "size": 12345, "fileName": "... (solo document)", "caption": "... (image/video/document)", "durationSec": 30 (video/audio), "ptt": true (solo audio = voice note), "width": 800 (image/video/sticker), "height": 600, "pages": 5 (solo document), "isAnimated": true (solo sticker), "downloadUrl": "/api/messages/media//" } } ] } ``` El campo `media` aparece en cada mensaje que contiene archivo; `downloadUrl` es la ruta relativa al endpoint que descarga + desencripta. ## Mensajes individuales ``` GET /api/messages/individual/:jid (termina en @s.whatsapp.net) ?since=&until=&q=&order=&limit=&offset= ``` ## Búsqueda cross-chat ``` GET /api/messages/search ?q= (requerido, min 1 char, ILIKE) &since= &until= &chatType=group|individual|all (default all) &limit=<1..500> (default 100) ``` Respuesta: ```json { "query": "quintal", "count": N, "matches": [ { "chatJid": "...", "msgId": "...", "senderJid": "...", "pushName": "...", "isGroup": true, "content": "...", "timestamp": "ISO" } ] } ``` ## Envío de texto (gated por ENABLE_SEND) ``` POST /api/messages/send Content-Type: application/json { "jid": "...@g.us" | "...@s.whatsapp.net" | "...@lid", "text": "...", "quotedChatJid": "...", // opcional; default = mismo jid destino "quotedMsgId": "...", // opcional; responder a mensaje previo "mentions": ["jid1", ...] // opcional; notifica a esos JIDs } ``` Respuesta: `{ ok: true, msgId: "..." }` o 403 si ENABLE_SEND=false. ## Reenvío (forward) ``` POST /api/messages/forward { "sourceChatJid": "...", // chat donde está el mensaje original "sourceMsgId": "...", // id del mensaje "destJid": "..." // destino } ``` Requiere que el mensaje esté en la DB del bridge (escuchado mientras el socket estaba vivo). ## Reacción (emoji) ``` POST /api/messages/react { "chatJid": "...", "msgId": "...", "emoji": "👍" // vacío "" = quitar reacción } ``` ## Envío de multimedia ``` POST /api/messages/send-media { "jid": "...", "url": "https://...", // URL pública del archivo "type": "image" | "video" | "audio" | "document", "caption": "...", // opcional (solo image/video/document) "fileName": "...", // opcional (solo document) "mimetype": "...", // opcional (override del content-type) "ptt": false, // solo audio: true = voice note "quotedChatJid": "...", // opcional "quotedMsgId": "..." // opcional } ``` Límites y guards: - **URL debe ser HTTP/HTTPS pública** — localhost, 127.0.0.1, 10.x, 192.168.x, 172.16-31.x son bloqueadas (SSRF protection). - **Tamaño máximo ~100 MB** (aunque WhatsApp limita más bajo según tipo). - Timeout del fetch: 60s. ## Envío de ubicación ``` POST /api/messages/send-location { "jid": "...", "latitude": 14.123, "longitude": -88.456, "name": "...", // opcional (ej: "Beneficio Rio Frio") "address": "...", // opcional (ej: "Santa Bárbara, Honduras") "quotedChatJid": "...", "quotedMsgId": "..." } ``` ## Envío de encuesta (poll) ``` POST /api/messages/send-poll { "jid": "...", "name": "Pregunta", "values": ["Opción A", "Opción B", "Opción C"], // 2-12 opciones "selectableCount": 1, // 1=single (default), 0=multi-choice, N=hasta N "quotedChatJid": "...", "quotedMsgId": "..." } ``` ## Envío de contacto(s) (vCard) ``` POST /api/messages/send-contact { "jid": "...", "contacts": [ { "displayName": "Don Fulano", "firstName": "Fulano", // opcional "lastName": "Pérez", // opcional "phone": "50499999999", // opcional, E.164 sin + → vinculable a WA "phoneDisplay": "+504 9999-9999", // opcional, default = "+" + phone "email": "...", // opcional "organization": "...", // opcional "title": "...", // opcional "vcard": "BEGIN:VCARD..." // opcional, override total } ], "quotedChatJid": "...", "quotedMsgId": "..." } ``` Hasta 20 contactos por mensaje. Si `phone` está en formato E.164 (sin `+`), WhatsApp lo detecta y permite iniciar chat con un tap. ## Descarga de media (desencriptado server-side) Los mensajes con media incluyen `media.downloadUrl` en las respuestas. Ese path apunta a un endpoint que, al llamarlo con el bearer, descarga el archivo del CDN de WhatsApp, lo desencripta con la `mediaKey` del mensaje, y devuelve el binario listo para usar. ``` GET /api/messages/media/:chatJid/:msgId[?disposition=inline|attachment] ``` Headers de respuesta: - `Content-Type`: MIME correcto (image/jpeg, video/mp4, etc.) - `Content-Length`: tamaño en bytes - `Content-Disposition`: `inline` (default) o `attachment` - `X-Wa-Media-Type`: `image|video|audio|document|sticker` Solo metadata (sin bajar el binario): ``` GET /api/messages/media-info/:chatJid/:msgId → { chatJid, msgId, media: { type, mimetype, size, fileName, caption, ..., downloadUrl } } ``` Errores: - 404 si el mensaje no existe o no tiene media - 503 si el socket de WhatsApp está caído - 500 si el CDN de WA ya no tiene el archivo (muy viejo → no se puede recuperar) ## Eventos (no soportado todavía) Baileys 6.17 no expone una API estable para crear event messages (los puede recibir/parsear pero no enviar via `sendMessage`). Sería necesario construir `proto.Message.IEventMessage` manualmente y usar `relayMessage` low-level — todavía no implementado en el bridge. Si lo necesitás urgente, abrir issue. Errores típicos: 400 con `fetch failed: ...` si la URL no responde bien o es privada. ## Formatos de JID | JID | Tipo | Ejemplo | |---|---|---| | `@s.whatsapp.net` | chat individual | `50499999999@s.whatsapp.net` | | `-@g.us` | grupo | `50499999999-1614182736@g.us` | | `status@broadcast` | estados | (ignorado por el bridge) | URL-encoding en path params: `@` → `%40`, `.` se deja tal cual. ## Códigos de error | Status | Significado | |---|---| | 400 | parámetros inválidos (ver body con `.error`) | | 401 | bearer ausente o inválido | | 403 | endpoint gated (ej send cuando ENABLE_SEND=false) | | 404 | chat/grupo no existe en storage | | 500 | error interno (revisar logs del contenedor) | | 503 | socket no listo (status != open) |