---
doc_version: "1.0.0"
last_updated: "2026-07-14T15:00:57.718Z"
title: "How to Use the Chat API"
description: "Send messages to your ChatbotIQ bot programmatically using the REST API, with examples in curl, JavaScript, and Python."
keywords: [Chat API, REST API, programmatic chat, SSE, streaming, integration, ChatbotIQ]
---

The Chat API lets you send messages to your bot and receive streaming responses programmatically. This is the same API that powers the embedded widget, you can use it to build custom integrations, mobile apps, or backend services.

---

## Prerequisites

- Your bot must be set to **Public** privacy mode.
- You need your **bot ID** (find it in the Deploy tab of the Playground).

---

## Send a message (curl)

```bash
curl -N -X POST https://app.chatbotiq.eu/v1/public/chat/stream \
  -H "Content-Type: application/json" \
  -d '{
    "bot_id": "YOUR-BOT-ID",
    "message": "How do I get started?",
    "session_id": "my-session-123"
  }'
```

The `-N` flag disables curl's output buffering so you see the streaming response in real time.

---

## Understand the response stream

The response is a **Server-Sent Events (SSE)** stream. Each line is prefixed with `data: ` followed by a JSON object. Every event has a `type` field that tells you how to handle it:

| `type` | Payload | What to do |
|--------|---------|------------|
| `status` | `{"type":"status","stage":"retrieving"}` then `"generating"` | Progress hints. Safe to ignore, or show a "thinking…" indicator. |
| `content` | `{"type":"content","content":"some text"}` | A chunk of the answer. **Concatenate the `content` fields** to build the full response. |
| `replace` | `{"type":"replace","content":"full text"}` | The answer was post-filtered. **Discard what you've shown so far and replace it** with this `content`. |
| `notice` | `{"type":"notice","code":"rag_unavailable",...}` | A non-fatal warning (e.g. knowledge retrieval temporarily degraded). Optional to surface. |
| `done` | `{"type":"done","conversation_id":"...","assistant_message_id":"...","citations":[...]}` | End of stream. Save `conversation_id` to continue the conversation. `citations` is present only if the bot has source citations enabled. |
| `error` | `{"type":"error","error":"content_filtered","message":"..."}` | Generation failed or the response was blocked. Show `message`. |

The answer text arrives in the `content` field of `content` events: there is **no `token` field**, and `done` is an event *type*, not a boolean flag. `conversation_id` and `citations` appear only on the final `done` event.

A citation object looks like:

```json
{
  "page_title": "Getting Started",
  "source_url": "https://docs.example.com/start",
  "source_id": "…",
  "score": 0.92,
  "from_web_search": false
}
```

---

## Send a message (JavaScript)

```javascript
async function chat(botId, message, sessionId, conversationId) {
  const response = await fetch('https://app.chatbotiq.eu/v1/public/chat/stream', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      bot_id: botId,
      message: message,
      session_id: sessionId,
      conversation_id: conversationId
    })
  });

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let buffer = '';
  let fullResponse = '';

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

    buffer += decoder.decode(value, { stream: true });
    const lines = buffer.split('\n');
    buffer = lines.pop(); // keep any incomplete trailing line for the next read

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

      switch (event.type) {
        case 'content': // a chunk of the answer
          fullResponse += event.content;
          process.stdout.write(event.content);
          break;
        case 'replace': // post-filtered answer - replaces everything shown so far
          fullResponse = event.content;
          break;
        case 'done': // end of stream
          console.log('\nConversation ID:', event.conversation_id);
          if (event.citations) console.log('Citations:', event.citations);
          break;
        case 'error':
          console.error('\nError:', event.message);
          break;
        // 'status' and 'notice' events can be ignored
      }
    }
  }

  return fullResponse;
}
```

---

## Send a message (Python)

```python
import requests
import json

def chat(bot_id, message, session_id, conversation_id=None):
    response = requests.post(
        'https://app.chatbotiq.eu/v1/public/chat/stream',
        json={
            'bot_id': bot_id,
            'message': message,
            'session_id': session_id,
            'conversation_id': conversation_id,
        },
        stream=True
    )

    full_response = ''
    for line in response.iter_lines():
        if not line or not line.startswith(b'data: '):
            continue
        event = json.loads(line[6:])
        etype = event.get('type')

        if etype == 'content':  # a chunk of the answer
            full_response += event['content']
            print(event['content'], end='', flush=True)
        elif etype == 'replace':  # post-filtered answer - replaces what came before
            full_response = event['content']
        elif etype == 'done':  # end of stream
            print(f"\nConversation ID: {event['conversation_id']}")
            if event.get('citations'):
                print(f"\nCitations: {event['citations']}")
            break
        elif etype == 'error':
            print(f"\nError: {event.get('message')}")
            break
        # 'status' and 'notice' events can be ignored

    return full_response
```

---

## Continue a conversation

To send follow-up messages, include the `conversation_id` from the first response:

```bash
curl -N -X POST https://app.chatbotiq.eu/v1/public/chat/stream \
  -H "Content-Type: application/json" \
  -d '{
    "bot_id": "YOUR-BOT-ID",
    "message": "Tell me more about that",
    "session_id": "my-session-123",
    "conversation_id": "CONVERSATION-ID-FROM-FIRST-RESPONSE"
  }'
```

---

## Handle errors

There are two kinds of failures: HTTP errors returned **before** the stream starts, and `error` events delivered **inside** the stream once it has begun (the HTTP status is already `200` at that point).

### HTTP errors (before streaming)

| Status | Meaning | What to do |
|--------|---------|------------|
| 400 | Missing/invalid field, or a required reCAPTCHA token was not provided | Check the request body. |
| 403 | Bot is not public, bot is inactive, request origin not in the bot's allowed domains, free trial expired, or reCAPTCHA failed | Check the bot's privacy mode, active status, and allowed domains. |
| 404 | Bot not found | Check the `bot_id`. |
| 429 | Rate limit exceeded | Wait and retry. Check the bot's rate-limit settings. |

> **reCAPTCHA:** if the deployment is configured with reCAPTCHA, `recaptcha_token` is **required**: omitting it returns `400` and an invalid token returns `403`.

### In-stream errors (after streaming starts)

Once the stream is open, failures arrive as an SSE `error` event rather than an HTTP status:

```
data: {"type":"error","error":"content_filtered","message":"..."}
data: {"type":"error","error":"service_unavailable","message":"Service temporarily unavailable"}
```

`service_unavailable` covers transient backend issues, **including the workspace running out of credits** (there is no `402` status on this endpoint). `content_filtered` means the response was blocked by the safety/grounding filters.

---

## Related

- [Chat API Reference](/reference/chat-api/) - complete endpoint documentation
- [Bot Settings Reference](/reference/bot-settings/) - rate limits and privacy settings

## Sitemap

Full documentation index: [sitemap.md](/sitemap.md) and [llms.txt](/llms.txt).
