> ## Documentation Index
> Fetch the complete documentation index at: https://docs.horneross.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Chat API

> Endpoint principal para conversar con agentes de Horneross.

El endpoint de Chat es la forma principal de interactuar con tus agentes programáticamente.

## Endpoint

```
POST /api/v2/agent/{agentId}/chat
```

## Autenticación

| Tipo de agente | Autenticación requerida         |
| -------------- | ------------------------------- |
| Agente privado | `Authorization: Bearer API_KEY` |
| Agente público | Solo `visitorId` en el body     |

## Path Parameters

<ParamField path="agentId" type="string" required>
  ID único del agente. Lo encontrás en el dashboard o en la URL del agente.
</ParamField>

## Headers

<ParamField header="Authorization" type="string" required>
  Bearer token con tu API key. Formato: `Bearer sk_live_xxx`
</ParamField>

<ParamField header="Content-Type" type="string" required>
  Siempre `application/json`
</ParamField>

<ParamField header="Accept" type="string">
  Usar `text/event-stream` para respuestas en streaming
</ParamField>

## Request Body

<ParamField body="query" type="string" required>
  Mensaje del usuario a enviar al agente.
</ParamField>

<ParamField body="conversationId" type="string" required>
  ID de la conversación. Usá el mismo ID para mantener el contexto entre mensajes.
</ParamField>

<ParamField body="visitorId" type="string">
  ID único del visitante/usuario. Requerido para agentes públicos.
</ParamField>

<ParamField body="streaming" type="boolean" default="false">
  Si es `true`, la respuesta se envía token por token via Server-Sent Events (SSE).
</ParamField>

<ParamField body="channel" type="string" default="api">
  Canal de origen del mensaje. Valores: `api`, `website`, `whatsapp`, `widget`, `dashboard`
</ParamField>

<ParamField body="contact" type="object">
  Datos del contacto para CRM.

  <Expandable title="Propiedades de contact">
    <ParamField body="contact.email" type="string">
      Email del contacto
    </ParamField>

    <ParamField body="contact.firstName" type="string">
      Nombre del contacto
    </ParamField>

    <ParamField body="contact.lastName" type="string">
      Apellido del contacto
    </ParamField>

    <ParamField body="contact.phoneNumber" type="string">
      Teléfono con código de país (ej: `+5491123456789`)
    </ParamField>

    <ParamField body="contact.customFields" type="object">
      Campos personalizados en formato key-value
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="filters" type="object">
  Filtros para limitar la búsqueda en datastores.

  <Expandable title="Propiedades de filters">
    <ParamField body="filters.datasourceIds" type="string[]">
      Array de IDs de datasources específicos donde buscar
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="attachments" type="array">
  Archivos adjuntos al mensaje.

  <Expandable title="Propiedades de attachment">
    <ParamField body="attachments[].url" type="string" required>
      URL del archivo adjunto
    </ParamField>

    <ParamField body="attachments[].name" type="string">
      Nombre del archivo
    </ParamField>

    <ParamField body="attachments[].type" type="string">
      MIME type del archivo (ej: `application/pdf`, `image/png`)
    </ParamField>

    <ParamField body="attachments[].size" type="number">
      Tamaño en bytes
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="selectedModel" type="string">
  Override del modelo a usar. Valores: `gpt-4o`, `gpt-4o-mini`, `claude-3-5-sonnet`, `gemini-1-5-pro`
</ParamField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://app.horneross.com/api/v2/agent/ag_abc123/chat \
    -H "Authorization: Bearer sk_live_xxx" \
    -H "Content-Type: application/json" \
    -d '{
      "query": "¿Cuáles son los horarios de atención?",
      "conversationId": "conv_usuario_123",
      "contact": {
        "email": "cliente@ejemplo.com",
        "firstName": "María"
      }
    }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    'https://app.horneross.com/api/v2/agent/ag_abc123/chat',
    {
      method: 'POST',
      headers: {
        'Authorization': 'Bearer sk_live_xxx',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        query: '¿Cuáles son los horarios de atención?',
        conversationId: 'conv_usuario_123',
        contact: {
          email: 'cliente@ejemplo.com',
          firstName: 'María'
        }
      }),
    }
  );

  const data = await response.json();
  console.log(data.answer);
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      'https://app.horneross.com/api/v2/agent/ag_abc123/chat',
      headers={
          'Authorization': 'Bearer sk_live_xxx',
          'Content-Type': 'application/json',
      },
      json={
          'query': '¿Cuáles son los horarios de atención?',
          'conversationId': 'conv_usuario_123',
          'contact': {
              'email': 'cliente@ejemplo.com',
              'firstName': 'María'
          }
      }
  )

  data = response.json()
  print(data['answer'])
  ```
</RequestExample>

## Response

<ResponseField name="answer" type="string" required>
  Respuesta generada por el agente.
</ResponseField>

<ResponseField name="conversationId" type="string" required>
  ID de la conversación (mismo que enviaste o uno nuevo si no existía).
</ResponseField>

<ResponseField name="messageId" type="string" required>
  ID único del mensaje generado.
</ResponseField>

<ResponseField name="sources" type="array">
  Fuentes de conocimiento utilizadas para generar la respuesta.

  <Expandable title="Propiedades de source">
    <ResponseField name="sources[].source" type="string">
      Nombre o URL de la fuente
    </ResponseField>

    <ResponseField name="sources[].score" type="number">
      Score de relevancia (0-1)
    </ResponseField>

    <ResponseField name="sources[].datasourceName" type="string">
      Nombre del datasource de origen
    </ResponseField>

    <ResponseField name="sources[].chunk" type="string">
      Fragmento de texto utilizado
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="usage" type="object">
  Métricas de uso de tokens.

  <Expandable title="Propiedades de usage">
    <ResponseField name="usage.promptTokens" type="number">
      Tokens usados en el prompt
    </ResponseField>

    <ResponseField name="usage.completionTokens" type="number">
      Tokens generados en la respuesta
    </ResponseField>

    <ResponseField name="usage.totalTokens" type="number">
      Total de tokens consumidos
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="metadata" type="object">
  Información adicional sobre la respuesta.

  <Expandable title="Propiedades de metadata">
    <ResponseField name="metadata.model" type="string">
      Modelo utilizado (ej: `gpt-4o`)
    </ResponseField>

    <ResponseField name="metadata.latency" type="number">
      Tiempo de respuesta en milisegundos
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseExample>
  ```json 200 - Success theme={null}
  {
    "answer": "Nuestros horarios de atención son de lunes a viernes de 9:00 a 18:00 horas.",
    "conversationId": "conv_usuario_123",
    "messageId": "msg_xyz789",
    "sources": [
      {
        "source": "FAQ - Horarios",
        "score": 0.95,
        "datasourceName": "Preguntas Frecuentes",
        "chunk": "Los horarios de atención al público son de lunes a viernes..."
      }
    ],
    "usage": {
      "promptTokens": 245,
      "completionTokens": 67,
      "totalTokens": 312
    },
    "metadata": {
      "model": "gpt-4o",
      "latency": 1234
    }
  }
  ```

  ```json 401 - Unauthorized theme={null}
  {
    "error": {
      "code": "UNAUTHORIZED",
      "message": "API key inválida o expirada"
    }
  }
  ```

  ```json 404 - Not Found theme={null}
  {
    "error": {
      "code": "NOT_FOUND",
      "message": "Agente no encontrado"
    }
  }
  ```

  ```json 429 - Rate Limited theme={null}
  {
    "error": {
      "code": "RATE_LIMIT_EXCEEDED",
      "message": "Demasiadas requests. Intentá de nuevo en 60 segundos.",
      "retryAfter": 60
    }
  }
  ```
</ResponseExample>

***

## Streaming (SSE)

Para recibir la respuesta token por token en tiempo real, usá `streaming: true`:

<CodeGroup>
  ```javascript JavaScript theme={null}
  const response = await fetch(
    'https://app.horneross.com/api/v2/agent/ag_abc123/chat',
    {
      method: 'POST',
      headers: {
        'Authorization': 'Bearer sk_live_xxx',
        'Content-Type': 'application/json',
        'Accept': 'text/event-stream',
      },
      body: JSON.stringify({
        query: 'Contame sobre tus productos',
        conversationId: 'conv_123',
        streaming: true
      }),
    }
  );

  const reader = response.body.getReader();
  const decoder = new TextDecoder();

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    const chunk = decoder.decode(value);
    const lines = chunk.split('\n');

    for (const line of lines) {
      if (line.startsWith('data: ')) {
        const data = JSON.parse(line.slice(6));

        switch (data.type) {
          case 'token':
            process.stdout.write(data.value);
            break;
          case 'source':
            console.log('Fuente:', data.source);
            break;
          case 'done':
            console.log('\n--- Respuesta completa ---');
            break;
        }
      }
    }
  }
  ```

  ```python Python theme={null}
  import requests
  import json

  response = requests.post(
      'https://app.horneross.com/api/v2/agent/ag_abc123/chat',
      headers={
          'Authorization': 'Bearer sk_live_xxx',
          'Content-Type': 'application/json',
          'Accept': 'text/event-stream',
      },
      json={
          'query': 'Contame sobre tus productos',
          'conversationId': 'conv_123',
          'streaming': True
      },
      stream=True
  )

  for line in response.iter_lines():
      if line:
          line = line.decode('utf-8')
          if line.startswith('data: '):
              data = json.loads(line[6:])
              if data['type'] == 'token':
                  print(data['value'], end='', flush=True)
              elif data['type'] == 'done':
                  print('\n--- Respuesta completa ---')
  ```
</CodeGroup>

### Eventos SSE

| Evento      | Descripción                         |
| ----------- | ----------------------------------- |
| `token`     | Cada token de la respuesta          |
| `source`    | Fuente de conocimiento encontrada   |
| `tool_call` | Herramienta ejecutada por el agente |
| `done`      | Respuesta completada                |
| `error`     | Error durante el procesamiento      |

***

## Agentes públicos (sin API key)

Para agentes con visibilidad pública, no necesitás API key pero sí `visitorId`:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://app.horneross.com/api/v2/agent/ag_public123/chat \
    -H "Content-Type: application/json" \
    -d '{
      "query": "Hola, necesito ayuda",
      "conversationId": "conv_visitor_abc",
      "visitorId": "visitor_unique_id"
    }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    'https://app.horneross.com/api/v2/agent/ag_public123/chat',
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        query: 'Hola, necesito ayuda',
        conversationId: 'conv_visitor_abc',
        visitorId: 'visitor_unique_id',
      }),
    }
  );
  ```
</CodeGroup>

***

## Listar conversaciones del agente

```
GET /api/v2/agent/{agentId}/conversations
```

<ParamField query="visitorId" type="string">
  Filtrar por visitante. Requerido para agentes públicos.
</ParamField>

<ParamField query="limit" type="number" default="10">
  Cantidad de resultados. Máximo 50.
</ParamField>

<ResponseExample>
  ```json 200 - Success theme={null}
  {
    "conversations": [
      {
        "id": "conv_abc123",
        "createdAt": "2024-01-21T10:00:00Z",
        "messagesCount": 5,
        "lastMessage": "Gracias por la ayuda",
        "status": "resolved"
      }
    ]
  }
  ```
</ResponseExample>

***

## Errores comunes

| Código | Error                 | Causa                            | Solución                                   |
| ------ | --------------------- | -------------------------------- | ------------------------------------------ |
| `401`  | `UNAUTHORIZED`        | API key inválida                 | Verificá tu API key en Settings → API Keys |
| `404`  | `NOT_FOUND`           | Agent ID incorrecto              | Verificá el agentId en la URL del agente   |
| `400`  | `BAD_REQUEST`         | Falta `query` o `conversationId` | Incluí los campos requeridos               |
| `429`  | `RATE_LIMIT_EXCEEDED` | Rate limit excedido              | Esperá el tiempo indicado en `retryAfter`  |
| `500`  | `INTERNAL_ERROR`      | Error del servidor               | Reintentá en unos segundos                 |
