# Claves de API y uso de la API (para programadores)

> Cómo crear y cuidar las claves de API, y qué podés hacer con la API de Bravilo: hablar con un agente, leer conversaciones, cargar información y mandar plantillas de WhatsApp.

Guía del Centro de ayuda de Bravilo · Tema: Integraciones · Fuente: https://help.braviloai.com/a/api-keys

## ¿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](https://help.braviloai.com/a/conectar-apis) |
| **Bravilo** le avise a tu sistema cuando pasa algo (por ejemplo, un lead nuevo) | [Webhooks](https://help.braviloai.com/a/webhooks) |

## Cómo crear una clave

1. Hacé clic en tu inicial, arriba a la derecha, después en **Configuración** y después en **API Keys**.
2. Hacé clic en **Crear API Key**. La clave nueva aparece en la lista.
3. 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.

![Pantalla API Keys: Claves de API, el enlace Documentación, la lista de claves y el botón Crear API Key](https://help.braviloai.com/capturas/api-keys-01-lista.png?v=70eac0408d)

## 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 `GET` y las búsquedas) no cambian nada. Los demás sí: `query` crea conversaciones que aparecen en [Conversaciones](https://help.braviloai.com/a/conversaciones); `upsert`, `update` y `DELETE` cambian lo que sabe el agente; `PATCH` cambia el estado de una conversación; y **`send-template` le 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](https://help.braviloai.com/a/api-keys#hablar-con-un-agente)) | | | |
| `POST` | `/agents/ID_DEL_AGENTE/query` | Mandarle un mensaje y recibir la respuesta | `query` (obligatorio), `conversationId`, `visitorId`, `streaming` |
| **Conversaciones** ([ver](https://help.braviloai.com/a/api-keys#leer-y-cerrar-conversaciones)) | | | |
| `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](https://help.braviloai.com/a/api-keys#cargarle-información-al-agente)) | | | |
| `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](https://help.braviloai.com/a/api-keys#5-mirá-o-borrá-una-fuente)) | | | |
| `GET` | `/datasources/ID_DE_LA_FUENTE` | Ver una fuente | — |
| `DELETE` | `/datasources/ID_DE_LA_FUENTE` | Borrar una fuente | — |
| **Plantillas de WhatsApp** ([ver](https://help.braviloai.com/a/api-keys#mandar-una-plantilla-de-whatsapp)) | | | |
| `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](https://help.braviloai.com/a/api-keys#analizar-una-imagen-o-un-documento)) | | | |
| `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.

```bash
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):

```json
{
  "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:

```bash
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 `data` llega **codificado para URL** (por ejemplo, `%20` es un espacio). Pasalo por `decodeURIComponent` antes de mostrarlo; el de `endpoint_response`, después, por `JSON.parse`.

Las conversaciones que se crean así aparecen en [Conversaciones](https://help.braviloai.com/a/conversaciones) con el canal **API**. Si corresponde, el agente también crea el lead.

## Leer y cerrar conversaciones

### Ver una conversación

```bash
curl https://app.braviloai.com/api/conversations/ID_DE_LA_CONVERSACION \
  -H "Authorization: Bearer TU_CLAVE"
```

Respuesta (resumida):

```json
{
  "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"}
  ]
}
```

- `messages` viene **del más nuevo al más viejo**.
- En `from`, `agent` es el agente y `human` es 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):

```bash
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

```bash
curl "https://app.braviloai.com/api/conversations/ID_DE_LA_CONVERSACION/summary?refresh=true" \
  -H "Authorization: Bearer TU_CLAVE"
```

```json
{"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

```bash
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](https://help.braviloai.com/a/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](https://help.braviloai.com/a/documentos)). Cada base tiene **fuentes** (en la API, `datasources`).

### 1. Encontrá la base

```bash
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):

```bash
curl https://app.braviloai.com/api/datastores/ID_DE_LA_BASE \
  -H "Authorization: Bearer TU_CLAVE"
```

### 2. Cargá un texto

```bash
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 `metadata` son **obligatorios**.
- `datasource_id` tiene que ser un identificador **nuevo** en formato **cuid** (letras y números, empieza con «c»; por ejemplo, el que genera la librería `cuid` de JavaScript).
- `documents` acepta 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

```bash
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."}'
```

- `id` es **obligatorio**, y tiene que ser el que te devolvió el `upsert`.
- 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:

```bash
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}'
```

- `query` es **obligatorio**. `topK` (opcional) es la cantidad máxima de resultados.
- Devuelve una lista de fragmentos, del más relacionado al menos:

```json
[{"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

```bash
curl https://app.braviloai.com/api/datasources/ID_DE_LA_FUENTE \
  -H "Authorization: Bearer TU_CLAVE"
```

```bash
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](https://help.braviloai.com/a/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](https://help.braviloai.com/a/campanas)), y el agente tiene que tener [WhatsApp conectado](https://help.braviloai.com/a/whatsapp).

```bash
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.

```bash
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](https://help.braviloai.com/a/conversaciones), con el canal **API**.

## Guías relacionadas

- [Conectar APIs (herramientas HTTP)](https://help.braviloai.com/a/conectar-apis.md)
- [Webhooks](https://help.braviloai.com/a/webhooks.md)
- [Conversaciones: leer, responder y cerrar](https://help.braviloai.com/a/conversaciones.md)
- [Cargar documentos para que el agente aprenda](https://help.braviloai.com/a/documentos.md)
