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

# Agent - Query

> Send messages to an AI agent and receive responses. Supports text queries, file attachments, real-time streaming, and conversation continuity.

This endpoint allows you to send messages to an AI agent and receive responses. It supports:

* Simple text queries
* File attachments (documents, images, audio)
* Real-time response streaming
* Continuation of existing conversations
* Contact association (CRM)

### Path

<ParamField path="agentId" type="string" required>
  The ID of the agent you want to query (CUID format).
</ParamField>

### Body

#### Required

<ParamField body="query" type="string" required>
  The user's message or question.
</ParamField>

#### Optional - Basic Configuration

<ParamField body="streaming" type="boolean" default="false">
  If `true`, responds with Server-Sent Events in real-time.
</ParamField>

<ParamField body="conversationId" type="string">
  ID to continue an existing conversation. Auto-generated if not provided.
</ParamField>

<ParamField body="visitorId" type="string">
  Unique ID of the visitor/user. Auto-generated if not provided.
</ParamField>

<ParamField body="channel" type="string" default="api">
  Source channel for the message. Valid values: `api`, `dashboard`, `website`, `form`, `whatsapp`, `telegram`, `slack`, `meta`, `crisp`, `zapier`, `mail`, `mercadolibre`, `agent_builder`, `chatwoot`, `crmchatsappai`.
</ParamField>

<ParamField body="context" type="string">
  Additional context for the AI (e.g., specific instructions).
</ParamField>

<ParamField body="systemPrompt" type="string">
  Overrides the agent's system prompt **for this request only**. The agent's saved configuration in the database is not modified.

  Use this to test different prompt variants, run evaluations, or inject custom instructions without editing the agent from the dashboard.

  <Note>
    An empty string `""` is treated as no override — the agent's saved system prompt is used instead. If both `systemPrompt` and `promptType: "raw"` are provided, `promptType: "raw"` takes precedence.
  </Note>
</ParamField>

#### Optional - Webhook (Conversational Mode)

<ParamField body="webhookUrl" type="string">
  URL to receive the AI response asynchronously. **Required when the agent has conversational mode enabled and `channel` is `api`**.

  The webhook URL must:

  * Be a valid URL format
  * Use `http://` or `https://` protocol
  * Be reachable (the server validates connectivity)
  * Not point to localhost or private IPs in production (SSRF protection)
</ParamField>

#### Optional - File Attachments

<ParamField body="attachments" type="array">
  List of file attachments.

  <Expandable title="Attachment object">
    <ParamField body="url" type="string" required>
      URL of the file to attach.
    </ParamField>
  </Expandable>
</ParamField>

#### Optional - Contact (CRM)

<ParamField body="contact" type="object">
  Contact data to associate with the conversation.

  <Expandable title="Contact object">
    <ParamField body="email" type="string">
      Contact's email address.
    </ParamField>

    <ParamField body="phoneNumber" type="string">
      Contact's phone number.
    </ParamField>

    <ParamField body="firstName" type="string">
      Contact's first name.
    </ParamField>

    <ParamField body="lastName" type="string">
      Contact's last name.
    </ParamField>

    <ParamField body="userId" type="string">
      External user ID.
    </ParamField>
  </Expandable>
</ParamField>

### Response (without streaming)

<ResponseField name="answer" type="string">
  The agent's response.
</ResponseField>

<ResponseField name="sources" type="array">
  Sources used to generate the response.

  <Expandable title="Source object">
    <ResponseField name="source" type="string">
      Name of the source document.
    </ResponseField>

    <ResponseField name="chunk" type="string">
      Relevant fragment from the source.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="messageId" type="string">
  ID of the response message.
</ResponseField>

<ResponseField name="conversationId" type="string">
  ID of the conversation (save this to continue the conversation).
</ResponseField>

<ResponseField name="visitorId" type="string">
  ID of the visitor.
</ResponseField>

<ResponseField name="request_human" type="boolean">
  Whether the agent requested human intervention.
</ResponseField>

<ResponseField name="status" type="string">
  Conversation status (e.g., `UNRESOLVED`, `RESOLVED`).
</ResponseField>

<ResponseField name="usage" type="object">
  Token usage and cost information.

  <Expandable title="Usage object" defaultOpen>
    <ResponseField name="completionTokens" type="integer">
      Number of tokens in the completion/response.
    </ResponseField>

    <ResponseField name="promptTokens" type="integer">
      Number of tokens in the prompt/context.
    </ResponseField>

    <ResponseField name="totalTokens" type="integer">
      Total tokens used (prompt + completion).
    </ResponseField>

    <ResponseField name="cost" type="number">
      Estimated cost of the request.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="approvals" type="array">
  List of approvals for tool executions (if any).
</ResponseField>

### Response (Conversational Mode with Webhook)

When the agent has **conversational mode enabled** and you provide a `webhookUrl`, the endpoint returns immediately with a queued status:

<ResponseField name="status" type="string">
  Always `"queued"` for webhook mode.
</ResponseField>

<ResponseField name="conversationId" type="string">
  ID of the conversation.
</ResponseField>

<ResponseField name="visitorId" type="string">
  ID of the visitor.
</ResponseField>

<ResponseField name="inputMessageId" type="string">
  ID of the user's input message.
</ResponseField>

<ResponseField name="message" type="string">
  Confirmation message indicating the request was queued.
</ResponseField>

<ResponseField name="webhookUrl" type="string">
  The webhook URL where the response will be sent.
</ResponseField>

#### Webhook Payload

After processing (typically 8 seconds delay to batch multiple messages), a POST request is sent to your `webhookUrl` with the following payload:

```json theme={null}
{
  "conversationId": "clxxxxxxxxxxxxxxxxx",
  "visitorId": "visitor-abc123",
  "status": "success",
  "messages": [
    {
      "id": "msg_001",
      "text": "First part of the response...",
      "createdAt": "2024-01-15T10:30:00.000Z"
    },
    {
      "id": "msg_002",
      "text": "Second part of the response...",
      "createdAt": "2024-01-15T10:30:00.000Z"
    }
  ],
  "agentResponse": {
    "answer": "Complete response without splitting...",
    "sources": [],
    "usage": {
      "completionTokens": 150,
      "promptTokens": 500,
      "totalTokens": 650,
      "cost": 0.0065
    },
    "metadata": {}
  },
  "messageId": "msg_answer456"
}
```

**Webhook Headers:**

* `Content-Type: application/json`
* `X-Laburen-Event: agent.response`

<Note>
  In conversational mode, long responses are automatically split into multiple messages (maximum 3 for most channels, up to 10 for Instagram/Meta).
</Note>

### Streaming Response

When `streaming: true`, the endpoint responds with Server-Sent Events:

```
Content-Type: text/event-stream

event: answer
data: Hello,

event: answer
data:  how can

event: answer
data:  I help you?

event: endpoint_response
data: {"messageId":"...","answer":"...","conversationId":"..."}

data: [DONE]
```

**Events:**

* `answer`: Partial response text (concatenate to build the full answer)
* `endpoint_response`: Full response object (JSON) with all metadata

### Error Responses

| Status Code | Type           | Description                                                                             |
| ----------- | -------------- | --------------------------------------------------------------------------------------- |
| 400         | Bad Request    | Invalid channel value. Returns list of valid channels.                                  |
| 400         | Bad Request    | Missing `webhookUrl` when agent has conversational mode enabled and `channel` is `api`. |
| 400         | Bad Request    | Invalid `webhookUrl` (malformed URL, invalid protocol, unreachable server).             |
| 401         | UNAUTHORIZED   | Invalid API Key or insufficient permissions                                             |
| 404         | NOT\_FOUND     | Agent not found                                                                         |
| 500         | Internal Error | Error processing the message                                                            |

<RequestExample>
  ```bash cURL theme={null}
  curl --location --request POST 'https://dashboard.laburen.com/api/agents/<agentId>/query' \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer <API_KEY>' \
  --data-raw '{
      "query": "Hello, I need help with my order",
      "conversationId": "clxxxxxxxxxxxxxxxxx",
      "visitorId": "clxxxxxxxxxxxxxxxxx"
  }'
  ```

  ```javascript JavaScript theme={null}
  const apiUrl = 'https://dashboard.laburen.com/api';
  const apiKey = '<API_KEY>';
  const agentId = '<agentId>';

  const response = await fetch(`${apiUrl}/agents/${agentId}/query`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${apiKey}`,
    },
    body: JSON.stringify({
      query: 'Hello, I need help with my order',
      conversationId: 'clxxxxxxxxxxxxxxxxx', // Optional: to continue conversation
      visitorId: 'clxxxxxxxxxxxxxxxxx',       // Optional: user identifier
      // systemPrompt: 'You are a helpful assistant that only answers in English.', // Optional override
    }),
  });

  const data = await response.json();
  console.log(data.answer);
  // Save data.conversationId to continue the conversation
  ```

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

  api_url = "https://dashboard.laburen.com/api"
  api_key = "<API_KEY>"
  agent_id = "<agentId>"

  response = requests.post(
      f"{api_url}/agents/{agent_id}/query",
      headers={
          "Content-Type": "application/json",
          "Authorization": f"Bearer {api_key}",
      },
      json={
          "query": "Hello, I need help with my order",
          "conversationId": "clxxxxxxxxxxxxxxxxx",   # Optional: to continue conversation
          "visitorId": "clxxxxxxxxxxxxxxxxx",        # Optional: user identifier
          # "systemPrompt": "You are a helpful assistant.",  # Optional override
      },
  )

  data = response.json()
  print(data["answer"])
  # Save data["conversationId"] to continue the conversation
  ```
</RequestExample>

<ResponseExample>
  ```json Response theme={null}
  {
    "answer": "Hello! I'd be happy to help you with your order. Could you please provide your order number so I can look it up?",
    "usage": {
      "completionTokens": 28,
      "promptTokens": 1250,
      "totalTokens": 1278,
      "cost": 0.0064
    },
    "sources": [
      {
        "source": "FAQ.pdf",
        "chunk": "To check your order status, please provide your order number..."
      }
    ],
    "approvals": [],
    "messageId": "clxxxxxxxxxxxxxxxxx",
    "conversationId": "clxxxxxxxxxxxxxxxxx",
    "visitorId": "clxxxxxxxxxxxxxxxxx",
    "request_human": false,
    "status": "UNRESOLVED"
  }
  ```
</ResponseExample>

### Streaming Example (JavaScript)

```javascript theme={null}
import {
  EventStreamContentType,
  fetchEventSource,
} from '@microsoft/fetch-event-source';

const apiUrl = 'https://dashboard.laburen.com/api';
const apiKey = '<API_KEY>';
const agentId = '<agentId>';

let answer = '';
let endpointResponse = '';
const ctrl = new AbortController();

await fetchEventSource(`${apiUrl}/agents/${agentId}/query`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${apiKey}`,
  },
  signal: ctrl.signal,
  body: JSON.stringify({
    streaming: true,
    query: 'Hello, I need help',
    conversationId: 'optional-conversation-id',
    visitorId: 'optional-visitor-id',
  }),

  async onopen(response) {
    if (response.status === 401) {
      throw new Error('Unauthorized');
    }
    if (response.status === 402) {
      throw new Error('Usage limit exceeded');
    }
  },

  onmessage: (event) => {
    if (event.data === '[DONE]') {
      // End of stream
      ctrl.abort();

      // Parse the full response
      const fullResponse = JSON.parse(endpointResponse);
      console.log('Full response:', fullResponse);
    } else if (event.data?.startsWith('[ERROR]')) {
      console.error('Stream error:', event.data);
    } else if (event.event === 'endpoint_response') {
      endpointResponse += event.data;
    } else if (event.event === 'answer') {
      answer += event.data;
      // Update UI with partial answer
      console.log('Partial answer:', answer);
    }
  },

  onerror: (error) => {
    console.error('Connection error:', error);
  },
});
```

### Webhook Example (Conversational Mode)

When your agent has conversational mode enabled, use `webhookUrl` to receive the AI response asynchronously:

```bash cURL theme={null}
curl --location --request POST 'https://dashboard.laburen.com/api/agents/<agentId>/query' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <API_KEY>' \
--data-raw '{
    "query": "Hello, I need help",
    "channel": "api",
    "webhookUrl": "https://your-server.com/webhook/laburen"
}'
```

**Immediate Response:**

```json theme={null}
{
  "status": "queued",
  "conversationId": "clxxxxxxxxxxxxxxxxx",
  "visitorId": "visitor-abc123",
  "inputMessageId": "msg_input123",
  "message": "Your message has been queued. Response will be sent to the provided webhookUrl after processing.",
  "webhookUrl": "https://your-server.com/webhook/laburen"
}
```

**Webhook Receiver Example (Node.js/Express):**

```javascript theme={null}
const express = require('express');
const app = express();

app.use(express.json());

app.post('/webhook/laburen', (req, res) => {
  const event = req.headers['x-laburen-event'];

  if (event === 'agent.response') {
    const { conversationId, messages, agentResponse } = req.body;

    console.log('Conversation:', conversationId);
    console.log('Messages:', messages.length);

    // Process each split message
    messages.forEach((msg, i) => {
      console.log(`Message ${i + 1}:`, msg.text);
    });

    // Full answer available in agentResponse
    console.log('Full answer:', agentResponse.answer);
    console.log('Usage:', agentResponse.usage);
  }

  res.status(200).send('OK');
});

app.listen(4000, () => {
  console.log('Webhook server listening on port 4000');
});
```
