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

# Chat API — Send Messages Programmatically

> Send a message to an InsiteChat chatbot via the REST API and receive an AI-generated reply grounded in your training content. Hybrid retrieval, RRF fusion, JSON response.

The InsiteChat **Chat API** runs your message through the same [RAG](/concepts/what-is-rag) pipeline as the web embed — [hybrid vector + BM25 retrieval](/concepts/hybrid-search) with Reciprocal Rank Fusion, [system prompt](/concepts/system-prompts), identity rules, and conversation history — then returns the chatbot's reply as a single JSON response. Streaming is not supported.

## Send a Message

### Request

```
POST /v1/chatbots/{chatbot_id}/chat
```

| Path parameter | Type | Required | Description                                                  |
| -------------- | ---- | -------- | ------------------------------------------------------------ |
| `chatbot_id`   | UUID | yes      | The chatbot to message. Must be owned by the API key's user. |

### Body

| Field        | Type   | Required | Description                                                                                                                                                                                                                               |
| ------------ | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `message`    | string | yes      | The user's message.                                                                                                                                                                                                                       |
| `session_id` | string | no       | A session identifier so the chatbot can track conversation history across multiple turns. If omitted, defaults to `api-<your-user-id>` (so all keyless calls share one conversation per user — usually not what you want for production). |

### Example

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://backend.insitechat.ai/api/v1/chatbots/0a1b2c3d-4e5f-6789-abcd-ef0123456789/chat \
    -H "Authorization: Bearer ic_your-api-key" \
    -H "Content-Type: application/json" \
    -d '{
      "message": "What is your return policy?",
      "session_id": "user-42-session-abc"
    }'
  ```

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

  CHATBOT_ID = "0a1b2c3d-4e5f-6789-abcd-ef0123456789"
  API_KEY = "ic_your-api-key"

  response = requests.post(
      f"https://backend.insitechat.ai/api/v1/chatbots/{CHATBOT_ID}/chat",
      headers={
          "Authorization": f"Bearer {API_KEY}",
          "Content-Type": "application/json",
      },
      json={
          "message": "What is your return policy?",
          "session_id": "user-42-session-abc",
      },
      timeout=30,
  )
  response.raise_for_status()
  print(response.json()["response"])
  ```

  ```javascript Node.js theme={null}
  const CHATBOT_ID = "0a1b2c3d-4e5f-6789-abcd-ef0123456789";
  const API_KEY = "ic_your-api-key";

  const res = await fetch(
    `https://backend.insitechat.ai/api/v1/chatbots/${CHATBOT_ID}/chat`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        message: "What is your return policy?",
        session_id: "user-42-session-abc",
      }),
    }
  );
  if (!res.ok) throw new Error(`InsiteChat ${res.status}: ${await res.text()}`);
  const { response } = await res.json();
  console.log(response);
  ```

  ```typescript TypeScript theme={null}
  const CHATBOT_ID = "0a1b2c3d-4e5f-6789-abcd-ef0123456789";
  const API_KEY = process.env.INSITECHAT_API_KEY!;

  interface ChatResponse {
    response: string;
  }

  export async function askChatbot(
    message: string,
    sessionId: string
  ): Promise<string> {
    const res = await fetch(
      `https://backend.insitechat.ai/api/v1/chatbots/${CHATBOT_ID}/chat`,
      {
        method: "POST",
        headers: {
          Authorization: `Bearer ${API_KEY}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ message, session_id: sessionId }),
      }
    );
    if (!res.ok) {
      throw new Error(`InsiteChat ${res.status}: ${await res.text()}`);
    }
    const data: ChatResponse = await res.json();
    return data.response;
  }
  ```

  ```go Go theme={null}
  package main

  import (
      "bytes"
      "encoding/json"
      "fmt"
      "io"
      "net/http"
  )

  const (
      chatbotID = "0a1b2c3d-4e5f-6789-abcd-ef0123456789"
      apiKey    = "ic_your-api-key"
  )

  type chatRequest struct {
      Message   string `json:"message"`
      SessionID string `json:"session_id"`
  }

  type chatResponse struct {
      Response string `json:"response"`
  }

  func main() {
      body, _ := json.Marshal(chatRequest{
          Message:   "What is your return policy?",
          SessionID: "user-42-session-abc",
      })
      req, _ := http.NewRequest(
          "POST",
          fmt.Sprintf("https://backend.insitechat.ai/api/v1/chatbots/%s/chat", chatbotID),
          bytes.NewReader(body),
      )
      req.Header.Set("Authorization", "Bearer "+apiKey)
      req.Header.Set("Content-Type", "application/json")
      resp, err := http.DefaultClient.Do(req)
      if err != nil { panic(err) }
      defer resp.Body.Close()
      data, _ := io.ReadAll(resp.Body)
      var out chatResponse
      json.Unmarshal(data, &out)
      fmt.Println(out.Response)
  }
  ```
</CodeGroup>

### Response

```json theme={null}
{
  "response": "Our return policy allows returns within 30 days of purchase. Items must be in their original condition with tags attached. To start a return, go to your order history and click \"Request Return\" on the item you'd like to send back."
}
```

| Field      | Type   | Description          |
| ---------- | ------ | -------------------- |
| `response` | string | The chatbot's reply. |

The endpoint **does not** return a `conversation_id` — the conversation is keyed by your `session_id`. If you need to correlate the reply with a server-side conversation record, subscribe to the [`conversation.started`](/api-reference/webhooks) webhook.

## Multi-Turn Conversations

Pass the same `session_id` on every call to give the chatbot the last 10 turns of history:

```bash theme={null}
# Turn 1
curl -X POST https://backend.insitechat.ai/api/v1/chatbots/0a1b2c3d-.../chat \
  -H "Authorization: Bearer ic_your-api-key" \
  -H "Content-Type: application/json" \
  -d '{"message": "What is your return policy?", "session_id": "user-42-session-abc"}'

# Turn 2 — same session_id
curl -X POST https://backend.insitechat.ai/api/v1/chatbots/0a1b2c3d-.../chat \
  -H "Authorization: Bearer ic_your-api-key" \
  -H "Content-Type: application/json" \
  -d '{"message": "Does that apply to sale items too?", "session_id": "user-42-session-abc"}'
```

<Tip>
  Use one `session_id` per end-user session (e.g. derived from your own user ID + a UUID per visit). Without a `session_id`, every request shares one default per-key conversation, which mixes up history.
</Tip>

## Error Responses

Error bodies use Django Ninja's default `{"detail": "..."}` shape — no machine-readable `code` field.

| Status | When                                                                                                                                                                                               |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401`  | Missing, invalid, or revoked API key                                                                                                                                                               |
| `404`  | No chatbot exists with that UUID **or** the chatbot belongs to a different user                                                                                                                    |
| `429`  | Per-key rate limit (`API rate limit exceeded. Max 60 requests per minute.`) **or** plan message quota exhausted (e.g. `Monthly message limit reached.`) — distinguish by reading the `detail` text |
| `500`  | LLM provider error or internal fault                                                                                                                                                               |

<Info>
  Every successful chat request counts toward your plan's **monthly message quota**. Only `role='user'` messages count — assistant replies are free. Track usage in **Dashboard** → **Analytics**, or upgrade your plan in **Plans & Pricing**.
</Info>

## What Happens Under the Hood

Each call:

1. Enforces your plan's monthly message quota and per-conversation rate limit (raises `429` if exceeded).
2. Looks up or creates a `Conversation` keyed by `(chatbot_id, session_id)`.
3. If a human agent has taken over this conversation, persists the inbound message but skips LLM generation (no reply is returned in that case — the dashboard surfaces it for the agent).
4. Saves the user message and fires the `conversation.started` webhook (if newly created) and `message.received` webhook.
5. Retrieves relevant context via hybrid vector + BM25 search with RRF fusion.
6. Builds the prompt (your chatbot's system prompt + identity rules + retrieved context + last 10 turns).
7. Calls the configured LLM provider (Gemini / OpenAI / Ollama) and saves + returns the reply.
