Claves de API y uso de la API (para programadores)
¿Para qué sirve?
La API de Bravilo permite que otro sistema (tu CRM, tu sistema de gestión, tu app o un desarrollo propio) trabaje con Bravilo sin que nadie entre al panel: hablar con tus agentes, leer y cerrar sus conversaciones, cargarles información y mandar plantillas de WhatsApp. Esta guía es para la persona que programa esa conexión.
Bravilo tiene tres formas de conectarse con otros sistemas. Elegí la que corresponde:
| Querés que… | Usá |
|---|---|
| Tu sistema le pida algo a Bravilo (mandarle un mensaje al agente, leer una conversación, cargar información) | Esta API, con una clave |
| El agente consulte tu sistema durante una charla (por ejemplo, el stock de un producto) | Conectar APIs |
| Bravilo le avise a tu sistema cuando pasa algo (por ejemplo, un lead nuevo) | Webhooks |
Cómo crear una clave
- Hacé clic en tu inicial, arriba a la derecha, después en Configuración y después en API Keys.
- Hacé clic en Crear API Key. La clave nueva aparece en la lista.
- Pasá el mouse sobre la clave: a la derecha aparecen dos íconos. Con el de copiar la copiás; con la papelera la borrás.
Cómo cuidarlas
- Tratala como una contraseña. Usala solo desde tu servidor: nunca en el código que se ejecuta en el navegador, en una app que se instala en el teléfono ni en un repositorio público.
- Una clave por integración. Si conectás varios sistemas, creá una clave para cada uno. Así podés cortar uno sin afectar a los demás.
- Si se filtró, reemplazala. Creá una clave nueva, cambiala en tu sistema y recién después borrá la vieja con la papelera.
Cuidado: si borrás una clave que está en uso, esa integración deja de funcionar al instante.
Antes de probar los ejemplos: los pedidos de lectura (los
GETy las búsquedas) no cambian nada. Los demás sí:querycrea conversaciones que aparecen en Conversaciones;upsert,updateyDELETEcambian lo que sabe el agente;PATCHcambia el estado de una conversación; ysend-templatele manda un WhatsApp real a un cliente. Probalos con un agente de prueba.
Cómo se usa
Todas las rutas de esta guía empiezan con esta dirección base:
https://app.braviloai.com/api
Por ejemplo, GET /agents es https://app.braviloai.com/api/agents.
En cada pedido mandá la clave en el encabezado Authorization. Los pedidos con cuerpo, además, con Content-Type: application/json:
Authorization: Bearer TU_CLAVE
Content-Type: application/json
Los IDs que vas a usar:
| Nombre en esta guía | Qué es | Dónde lo conseguís |
|---|---|---|
ID_DEL_AGENTE | El agente | En GET /agents (campo id) o en la dirección del navegador cuando abrís el agente en Bravilo |
ID_DE_LA_CONVERSACION | Una conversación | En la respuesta de query (conversationId) |
ID_DEL_VISITANTE | La persona que escribe | En la respuesta de query (visitorId) |
ID_DE_LA_BASE | Una base de conocimiento | En GET /datastores o en GET /agents/ID_DEL_AGENTE (en tools, el datastoreId) |
ID_DE_LA_FUENTE | Una fuente dentro de una base | En la respuesta de upsert (ids) o en GET /datastores/ID_DE_LA_BASE |
Índice de endpoints
| Método | Ruta | Para qué sirve | Datos importantes |
|---|---|---|---|
| Agentes | |||
GET | /agents | Listar los agentes de tu organización | — |
GET | /agents/ID_DEL_AGENTE | Ver nombre, descripción, visibilidad y base de un agente | — |
| Consultas al agente (ver) | |||
POST | /agents/ID_DEL_AGENTE/query | Mandarle un mensaje y recibir la respuesta | query (obligatorio), conversationId, visitorId, streaming |
| Conversaciones (ver) | |||
GET | /conversations/ID_DE_LA_CONVERSACION | Ver la conversación, sus mensajes y su lead | ?cursor= para mensajes anteriores |
GET | /conversations/ID_DE_LA_CONVERSACION/summary | Pedir un resumen hecho con IA | ?refresh=true para uno nuevo |
PATCH | /conversations/ID_DE_LA_CONVERSACION | Cambiar el estado | status (obligatorio) |
| Bases de conocimiento (ver) | |||
GET | /datastores | Listar las bases | — |
GET | /datastores/ID_DE_LA_BASE | Ver una base con sus fuentes y su estado | — |
POST | /datastores/ID_DE_LA_BASE/query | Buscar en una base | query (obligatorio), topK |
POST | /external/datastores/upsert/ID_DE_LA_BASE | Cargar textos | documents, con seis campos de metadata |
POST | /external/datastores/update/ID_DE_LA_BASE | Corregir un texto cargado | id (obligatorio), text, name |
| Fuentes (ver) | |||
GET | /datasources/ID_DE_LA_FUENTE | Ver una fuente | — |
DELETE | /datasources/ID_DE_LA_FUENTE | Borrar una fuente | — |
| Plantillas de WhatsApp (ver) | |||
POST | /v2/agents/ID_DEL_AGENTE/send-template | Mandar una plantilla aprobada por Meta | phoneNumber y templateName (obligatorios). Manda un mensaje real |
| Análisis de archivos (ver) | |||
POST | /agents/ID_DEL_AGENTE/universal | Describir una imagen o extraer el texto de un archivo | fileBuffer, fileType y fileName (obligatorios) |
Hablar con un agente
POST /agents/ID_DEL_AGENTE/query le manda un mensaje al agente, como si fuera un cliente, y devuelve su respuesta.
curl -X POST https://app.braviloai.com/api/agents/ID_DEL_AGENTE/query \
-H "Authorization: Bearer TU_CLAVE" \
-H "Content-Type: application/json" \
-d '{"query": "Hola, ¿hacen envíos a Rosario?"}'
| Campo del cuerpo | Qué es | |
|---|---|---|
query | Obligatorio | El mensaje del cliente |
conversationId | Opcional | Para seguir una conversación que ya existe. Sin él, se crea una nueva |
visitorId | Opcional | La persona que escribe. Sin él, se crea uno nuevo |
streaming | Opcional | true para recibir la respuesta por partes (ver abajo) |
Respuesta (resumida):
{
"answer": "Sí, hacemos envíos a Rosario: llegan en 48 a 72 horas hábiles.",
"sources": [
{"datasource_id": "cmv…", "datasource_name": "Política de envíos", "datasource_type": "text", "score": 1}
],
"conversationId": "cmv…",
"visitorId": "cmv…",
"messageId": "cmv…",
"usage": {"promptTokens": 1906, "completionTokens": 297, "totalTokens": 2203}
}
| Campo | Qué es |
|---|---|
answer | Lo que respondió el agente |
sources | Las fuentes que usó para responder |
conversationId | La conversación. Guardalo para seguirla |
visitorId | La persona que escribe. Guardalo para seguir la conversación |
messageId | El mensaje de la respuesta |
usage | Cuánto procesamiento usó la respuesta |
Seguir la misma conversación
Mandá en cada mensaje siguiente el conversationId y el visitorId que te devolvió la primera respuesta. Así el agente recuerda lo que se habló y todo queda en una sola conversación:
curl -X POST https://app.braviloai.com/api/agents/ID_DEL_AGENTE/query \
-H "Authorization: Bearer TU_CLAVE" \
-H "Content-Type: application/json" \
-d '{"query": "¿Y cuánto cuesta el envío?", "conversationId": "ID_DE_LA_CONVERSACION", "visitorId": "ID_DEL_VISITANTE"}'
Recibir la respuesta por partes (streaming)
Con "streaming": true, la respuesta llega como un flujo de eventos (text/event-stream) mientras el agente escribe:
event: answer
data: S%C3%AD%2C%20hacemos%20env%C3%ADos
event: endpoint_response
data: %7B%22messageId%22%3A%22cmv%E2%80%A6%22%2C%22answer%22%3A%22S%C3%AD%E2%80%A6%22%7D
event:
data: [DONE]
answer: un pedazo del texto de la respuesta.endpoint_response: al final, la respuesta completa con los mismos campos del modo normal (answer,sources,conversationId,visitorId,messageId).[DONE]: la respuesta terminó.
El contenido de cada
datallega codificado para URL (por ejemplo,%20es un espacio). Pasalo pordecodeURIComponentantes de mostrarlo; el deendpoint_response, después, porJSON.parse.
Las conversaciones que se crean así aparecen en Conversaciones con el canal API. Si corresponde, el agente también crea el lead.
Leer y cerrar conversaciones
Ver una conversación
curl https://app.braviloai.com/api/conversations/ID_DE_LA_CONVERSACION \
-H "Authorization: Bearer TU_CLAVE"
Respuesta (resumida):
{
"id": "cmv…",
"status": "UNRESOLVED",
"isAiEnabled": true,
"lead": {"id": "cmv…", "name": "Lead sin nombre", "phone": null, "priority": "medium", "notes": "…"},
"messages": [
{"id": "cmv…", "from": "agent", "text": "Sí, hacemos envíos a Rosario…", "createdAt": "2026-10-21T01:03:49.549Z"},
{"id": "cmv…", "from": "human", "text": "¿Me lo pueden mandar a Rosario?", "createdAt": "2026-10-21T01:03:36.090Z"}
]
}
messagesviene del más nuevo al más viejo.- En
from,agentes el agente yhumanes el cliente.
Mensajes anteriores. Si la conversación es larga, pedí la página siguiente con cursor y el id del último mensaje que recibiste (el más viejo):
curl "https://app.braviloai.com/api/conversations/ID_DE_LA_CONVERSACION?cursor=ID_DEL_MENSAJE_MAS_VIEJO" \
-H "Authorization: Bearer TU_CLAVE"
Devuelve los mensajes anteriores a ese. Cuando messages llega vacío, no quedan más.
Pedir un resumen
curl "https://app.braviloai.com/api/conversations/ID_DE_LA_CONVERSACION/summary?refresh=true" \
-H "Authorization: Bearer TU_CLAVE"
{"summary": "El cliente consulta por 40 bolsas de cemento con envío a Rosario…", "model": "gpt-4o-mini", "tokens_used": 109, "max_tokens": 500, "cached": false}
Sin ?refresh=true devuelve el resumen guardado, si hay uno.
Cambiar el estado
curl -X PATCH https://app.braviloai.com/api/conversations/ID_DE_LA_CONVERSACION \
-H "Authorization: Bearer TU_CLAVE" \
-H "Content-Type: application/json" \
-d '{"status": "RESOLVED"}'
status es obligatorio y acepta:
RESOLVED: resuelta.UNRESOLVED: abierta.HUMAN_REQUESTED: el cliente pidió hablar con una persona.
Respuesta: {"id": "cmv…", "status": "RESOLVED", "isAiEnabled": true}.
Por la API solo se cambia el estado. Para tomar el control de una conversación o devolvérsela a la IA, hacelo desde Conversaciones.
Cargarle información al agente
La información que usa el agente se guarda en bases de conocimiento (en la API, datastores; ver Cargar documentos). Cada base tiene fuentes (en la API, datasources).
1. Encontrá la base
curl https://app.braviloai.com/api/datastores \
-H "Authorization: Bearer TU_CLAVE"
Devuelve {"items": [...]}, con el id y el name de cada base. Para ver una base con todas sus fuentes y el estado de cada una (synched cuando ya se puede usar):
curl https://app.braviloai.com/api/datastores/ID_DE_LA_BASE \
-H "Authorization: Bearer TU_CLAVE"
2. Cargá un texto
curl -X POST https://app.braviloai.com/api/external/datastores/upsert/ID_DE_LA_BASE \
-H "Authorization: Bearer TU_CLAVE" \
-H "Content-Type: application/json" \
-d '{
"documents": [{
"text": "Hacemos envíos a todo el país. A Rosario llegan en 48 a 72 horas hábiles.",
"metadata": {
"datastore_id": "ID_DE_LA_BASE",
"datasource_id": "UN_ID_NUEVO",
"datasource_type": "file",
"datasource_name": "Política de envíos",
"mime_type": "text/plain",
"source_url": "https://tuempresa.com/envios"
}
}]
}'
- Los seis campos de
metadatason obligatorios. datasource_idtiene que ser un identificador nuevo en formato cuid (letras y números, empieza con «c»; por ejemplo, el que genera la libreríacuidde JavaScript).documentsacepta varios textos a la vez.
Respuesta: {"ids": ["ID_DE_LA_FUENTE"]}. Guardá ese ID: lo vas a necesitar para corregir o borrar la fuente. En pocos segundos la fuente queda sincronizada y el agente ya responde con ese dato.
3. Corregí un texto
curl -X POST https://app.braviloai.com/api/external/datastores/update/ID_DE_LA_BASE \
-H "Authorization: Bearer TU_CLAVE" \
-H "Content-Type: application/json" \
-d '{"id": "ID_DE_LA_FUENTE", "name": "Política de envíos", "text": "Hacemos envíos a todo el país. A Rosario llegan en 48 horas hábiles."}'
ides obligatorio, y tiene que ser el que te devolvió elupsert.- Respuesta:
{"id": "ID_DE_LA_FUENTE"}. La fuente se vuelve a procesar.
4. Buscá en una base
Para ver qué información encuentra el agente sobre un tema:
curl -X POST https://app.braviloai.com/api/datastores/ID_DE_LA_BASE/query \
-H "Authorization: Bearer TU_CLAVE" \
-H "Content-Type: application/json" \
-d '{"query": "envíos a Rosario", "topK": 3}'
queryes obligatorio.topK(opcional) es la cantidad máxima de resultados.- Devuelve una lista de fragmentos, del más relacionado al menos:
[{"text": "Hacemos envíos a todo el país…", "score": 0.59, "source": "https://tuempresa.com/envios", "datasource_id": "cmv…", "offset": 0}]
5. Mirá o borrá una fuente
curl https://app.braviloai.com/api/datasources/ID_DE_LA_FUENTE \
-H "Authorization: Bearer TU_CLAVE"
curl -X DELETE https://app.braviloai.com/api/datasources/ID_DE_LA_FUENTE \
-H "Authorization: Bearer TU_CLAVE"
El DELETE borra la fuente y el agente deja de usar esa información.
Para subir archivos (PDF, Word, Excel), por ahora hacelo desde la pestaña Archivos y conocimientos del agente. Ver Cargar documentos.
Mandar una plantilla de WhatsApp
Este pedido le manda un WhatsApp real al número que pongas. No lo uses para probar con números de clientes.
Para escribirle primero a un cliente por WhatsApp hace falta una plantilla aprobada por Meta (ver Campañas), y el agente tiene que tener WhatsApp conectado.
curl -X POST https://app.braviloai.com/api/v2/agents/ID_DEL_AGENTE/send-template \
-H "Authorization: Bearer TU_CLAVE" \
-H "Content-Type: application/json" \
-d '{
"phoneNumber": "5491122334455",
"templateName": "nombre_de_tu_plantilla",
"languageCode": "es",
"components": [
{"type": "body", "parameters": [{"type": "text", "text": "Juan"}]}
]
}'
| Campo | Qué es | |
|---|---|---|
phoneNumber | Obligatorio | El número con código de país, sin «+» ni espacios |
templateName | Obligatorio | El nombre exacto de la plantilla aprobada |
languageCode | Opcional | El idioma de la plantilla, por ejemplo es |
components | Opcional | Los valores de las variables de la plantilla (por ejemplo, el nombre del cliente) |
Si el agente no tiene WhatsApp conectado, responde 400 con {"error": "No WhatsApp integration found for this agent or organization"}.
Analizar una imagen o un documento
POST /agents/ID_DEL_AGENTE/universal recibe un archivo y devuelve lo que entiende de él: la descripción de una imagen o el texto de un documento.
curl -X POST https://app.braviloai.com/api/agents/ID_DEL_AGENTE/universal \
-H "Authorization: Bearer TU_CLAVE" \
-H "Content-Type: application/json" \
-d '{
"fileBuffer": "CONTENIDO_DEL_ARCHIVO_EN_BASE64",
"fileType": "png",
"fileName": "foto.png",
"userQuery": "¿Qué producto es?"
}'
| Campo | Qué es | |
|---|---|---|
fileBuffer | Obligatorio | El archivo en base64 (una forma de escribir un archivo como texto) |
fileType | Obligatorio | La extensión: jpg, jpeg, png, tiff, pdf, mp3, wav, m4a, ogg, webm, mp4, avi o mov |
fileName | Obligatorio | El nombre del archivo |
userQuery | Opcional | Qué querés saber del archivo |
Respuesta: {"success": true, "messageText": "…"}, con el análisis en messageText. Con un formato no aceptado (por ejemplo, txt o docx) responde 400 con la lista de formatos válidos.
Errores
Los errores llegan como JSON con un campo error.
| Código | Cuándo pasa | Ejemplo |
|---|---|---|
| 400 | Falta un campo obligatorio, o un valor o un ID no tiene el formato esperado | {"error": "status: Required"}, {"error": "conversationId: Invalid cuid"} |
| 403 | Falta la clave, no es válida, o el recurso no es de tu organización | {"error": "UNAUTHORIZED"} |
| 404 | El agente o la fuente no existen | {"error": "NOT_FOUND"} |
| 500 | Error del servidor. También pasa si mandás query sin el campo query | {"error": "Internal server error"} |
Preguntas frecuentes
Todo me responde 403 «UNAUTHORIZED».
Revisá que mandes el encabezado Authorization: Bearer TU_CLAVE en cada pedido, que la clave esté completa y que no la hayas borrado. Si es un solo pedido, revisá que el ID sea de tu organización.
El agente responde como si no recordara lo anterior.
Mandá el conversationId y el visitorId de la primera respuesta en cada mensaje siguiente.
Cargué un texto con upsert y me da error 400.
Revisá que estén los seis campos de metadata y que datasource_id sea un identificador nuevo en formato cuid.
Corregí un texto con update y me da 403.
Usá el ID que te devolvió el upsert (el que viene en ids), no uno inventado.
El texto que llega en streaming tiene símbolos raros, como «%20».
Está codificado. Pasalo por decodeURIComponent.
¿Dónde veo las conversaciones que creé por la API? En Conversaciones, con el canal API.
¿Te sirvió esta guía?
Guías relacionadas
¿No lo resolviste?
Escribinos por el chat: te responde el equipo de Bravilo.
