La bandeja de entrada por la API
La bandeja de entrada es donde el equipo atiende desde el panel. La API da acceso a las mismas conversaciones, para conectar un helpdesk, un CRM o un bot: lo que responde tu integración aparece para el equipo, y lo que responde el equipo aparece para tu integración.
Leer las conversaciones
curl 'https://api.joinotify.com/inbox/conversations?folder=open&unread=true' \
-H 'Authorization: Bearer sk_live_xxx'
La lista se pagina por cursor: envía el nextCursor de la respuesta en cursor para
la página siguiente, hasta que venga null. Los mensajes de una conversación vienen en
GET /inbox/conversations/{id}/messages, del más reciente al más antiguo, con el cursor en
before.
Cada conversación dice si la ventana de 24 horas está abierta (windowOpen) y hasta
cuándo (windowExpiresAt). Es lo que decide qué se puede responder.
Responder
curl -X POST https://api.joinotify.com/inbox/conversations/cm1c0nv000001/messages \
-H 'Authorization: Bearer sk_live_xxx' \
-H 'Content-Type: application/json' \
-d '{ "kind": "text", "text": "¡Hola, Ana! Tu pedido salió para entrega." }'
kind | Cuándo usarlo |
|---|---|
text | Texto libre, con la ventana abierta |
media | Un archivo, con la ventana abierta — súbelo antes en /media |
template | Ventana cerrada: una plantilla aprobada reabre la conversación |
note | Nota interna para el equipo; nunca va a WhatsApp |
Con la ventana cerrada, text y media se rechazan antes de salir, con
422 window_closed — a diferencia de POST /messages, que acepta y falla después.
Responder reabre una conversación cerrada o pospuesta y pausa las automatizaciones de esa
conversación por un tiempo, como cuando alguien del equipo toma el control.
Para enviar un archivo, sube sus bytes a POST /inbox/conversations/{id}/media (el cuerpo
es el archivo, no multipart, con el nombre en X-File-Name) y responde con
{ "kind": "media", "assetId": "..." }.
Organizar
PATCH /inbox/conversations/{id} cierra, reabre, deja pendiente, pospone hasta una fecha,
asigna a alguien del equipo (los ids vienen de GET /inbox/agents) o pausa las
automatizaciones. Cada cambio queda registrado en la conversación, y cerrar y asignar
disparan los eventos conversation.closed y conversation.assigned.
Una API key no es una persona del equipo: la carpeta mine siempre viene vacía, y en los
eventos assigned_by y closed_by vienen null cuando quien actuó fue la clave.
Tiempo real
GET /inbox/stream es un stream Server-Sent Events. Avisa qué cambió — mensaje nuevo,
estado de mensaje, conversación actualizada, alguien escribiendo —, sin el contenido:
event: message.created
id: 12
data: {"type":"message.created","conversationId":"cm1c0nv000001","phoneNumberId":"106540352242922","messageId":"cm1m3ss4g3001","direction":"in"}
Al recibir un evento, busca la conversación o los mensajes. Tres cuidados:
- No hay reenvío. Un evento perdido durante una caída no vuelve, y
Last-Event-IDno recupera nada. En cada reconexión, vuelve a leer las conversaciones que importan. - El
EventSourcedel navegador no envía la cabeceraAuthorization. Usa un cliente que la envíe — el stream está pensado para servidores. - Cada API key abre como máximo 5 streams a la vez.
Si solo necesitas reaccionar a mensajes recibidos, el webhook de
messages sigue siendo el camino más simple.
Alcance de la clave
Una API key restringida a algunos números solo ve las conversaciones de esos números —
una conversación de otro número responde 404, como si no existiera. GET /inbox/agents,
que lista el equipo de la cuenta con los correos, responde 403 para ella.