# Send a message

`POST https://api.chat-data.com/api/v2/chat`

Canonical: https://www.chat-data.com/api-reference/send-message

## Authentication

Send an API key as a Bearer token: `Authorization: Bearer $CHAT_DATA_API_KEY`. Workspace keys reach every chatbot in the workspace; chatbot-scoped keys reach only their chatbot. Keep keys on the server.

## Description

Send messages to a chatbot and receive a response. The API supports both streaming and non-streaming modes,
as well as OpenAI-compatible response formats.

When `stream` is set to `true`, the response is a stream of text chunks that should be consumed with a
`ReadableStream` reader and `TextDecoder`.

When `openAIFormat` is set to `true`, responses match OpenAI's `/chat/completions` API format. For streaming
responses, this means Server-Sent Events (SSE) in OpenAI's chunk format. If the request fails after the stream has started, it ends with HTTP 200 and no `data: [DONE]`.

When `skipCache` is set to `true`, the response cache is bypassed and the LLM is invoked even if a cached response
exists for the same `messages` array. This is useful for regenerating answers. Credits, rate limits, AI actions, and the `chat` webhook
all behave identically to a normal send — its payload for a `skipCache: true` request includes
`regenerate: true`. The persisted conversation's trailing assistant message (if any) is replaced by the new response.

## Request body (`application/json`)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| messages | array of object | yes | The messages of the conversation, oldest first. Every role must be `user` or `assistant`, and the last message must not be empty. |
| messages[].role | string | yes | The role of the message sender (`user` or `assistant`). |
| messages[].content | string | yes | The text content of the message. |
| messages[].files | array of object | no | An optional array of file objects attached to the message. Only the chatbot's allowed number of files is kept on the last message (3 by default); extra files are dropped. |
| messages[].files[].name | string | yes | The name of the attached file. |
| messages[].files[].type | string | yes | The MIME type of the file, for example `image/png` or `application/pdf`. Images (`image/*`) are passed to the model as images. Other files are downloaded and parsed to text by the type the file host reports; PDF, DOCX, HTML, plain text and CSV are supported. |
| messages[].files[].url | string | yes | The publicly accessible URL where the file is hosted by you. |
| messages[].files[].content | string | no | The text content of a non-image file. If this field is non-empty, it is used directly without downloading or parsing the file. If it is empty, the file is downloaded and parsed to text when the file type is supported. Files larger than the chatbot's file size limit (3 MB by default) cannot be downloaded, so parse them yourself and provide their content in this field. |
| chatbotId | string | yes | The ID of the chatbot. |
| includeReasoning | boolean | no | Whether to include reasoning in the response. This parameter overrides the chatbot's reasoning settings. If true, the reasoning is included at the beginning of the response within a `<think>` tag, for example `<think>{reasoning}</think>{response}`. |
| baseModel | string | no | Overrides the chatbot's `baseModel` setting when processing the RAG context for this request. One of: `gpt-4o-mini`, `gpt-4o`, `gpt-4`, `gpt-4.1-nano`, `gpt-4.1-mini`, `gpt-4.1`, `gpt-5-nano`, `gpt-5-mini`, `gpt-5`, `gpt-5.1`, `gpt-5.2`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna`, `gpt-6-astra`, `gpt-6.1-sol`, `gpt-6-luna`, `gpt-5.5`, `gpt-5.4`, `gpt-5.4-nano`, `gpt-5.4-mini`, `gpt-o1`, `gpt-o3-mini`, `gpt-o3`, `gpt-o4-mini`, `claude-4-5-sonnet`, `claude-4-6-sonnet`, `claude-4-5-haiku`, `claude-4-opus`, `claude-4.1-opus`, `claude-4.5-opus`, `claude-4.6-opus`, `claude-5-opus`, `claude-5-sonnet`, `claude-5-5-opus`, `claude-5-5-sonnet`, `claude-5-5-haiku`, `gemini-3.0-flash`, `gemini-3.1-flash`, `gemini-3.5-flash-lite`, `gemini-3.7-flash`, `gemini-2.5-pro`, `gemini-3.0-pro`, `gemini-3.1-pro`, `deepseek-r1`, `deepseek-v3.2`, `mancer-deepseek-v4-flash`. |
| basePrompt | string | no | Overrides the chatbot's `basePrompt` when processing the RAG context for this request. |
| stream | boolean | no | Whether to stream back partial progress or wait for the full response. |
| openAIFormat | boolean | no | When set to true, the response is formatted to be compatible with OpenAI's `/chat/completions` API format. Non-streaming responses return a JSON object matching OpenAI's completion format; streaming responses return Server-Sent Events in OpenAI's chunk format. |
| appendMessages | boolean | no | If `true`, `messages` must contain exactly one `user` message, which is appended to the stored messages of the conversation with the same `conversationId`. If `false`, it is assumed that the `messages` parameter contains the entire message history. |
| conversationId | string | no | ID of the current conversation. If omitted, the chatbot generates one, which is not returned in the response. |
| skipCache | boolean | no | When `true`, bypasses the response cache lookup for this request and runs the LLM pipeline normally. On success, the new response overwrites the prior cached entry under the same cache key. This is useful for regenerating answers when the cached response is no longer wanted. Only the literal boolean `true` enables this behavior — loose-truthy values (`"true"`, `1`) are treated as `false` to prevent accidental cache bypass. **Side effects are unchanged from a normal send:** credits are charged, rate limits apply, AI actions fire, and the `chat` webhook emits (the only webhook this endpoint sends). Its payload carries an additional `regenerate: true` field so receivers can dedupe. **Persisted conversation history:** when `skipCache: true` and the persisted conversation's last message is an assistant message, that trailing assistant message is replaced (not appended after) by the newly generated response. If the last persisted message is a user message, the new response is appended normally. Callers managing their own conversation state typically send the trimmed history along with `skipCache: true` (or use a fresh `conversationId` for a branching alternative). |

## Example request

```bash
curl 'https://api.chat-data.com/api/v2/chat' \
  -H "Authorization: Bearer $CHAT_DATA_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
  "messages": [
    {
      "role": "assistant",
      "content": "Hi! How can I help you today?"
    },
    {
      "role": "user",
      "content": "What are your opening hours?"
    }
  ],
  "chatbotId": "6650f1c2a9e4b83d2c7a1f05"
}'
```

## Example response (200)

```text
We're open Monday to Friday, 9am to 6pm EST.
Outside those hours you can email support@example.com and we'll reply the next business day.
```

## Responses

| Status | Description |
| --- | --- |
| 200 | The chatbot's answer as plain text, streamed in chunks when `stream` is true. With `openAIFormat` set, an OpenAI-compatible JSON object instead, or `text/event-stream` chunks when streaming. |
| 400 | The message could not be answered. For example: `chatbotId` is missing, `baseModel` is not an allowed value or is not allowed in a HIPAA workspace, `basePrompt` is empty, `messages` is not an array, is empty or ends with an empty message, a role is not `user` or `assistant`, `appendMessages` is true but `messages` is not a single user message, the content or an attached file is too long or could not be processed, the workspace has run out of message credits, or the chatbot has reached its credit limit. |
| 401 | Authentication failed (the `Authorization` header is missing or malformed, the API key does not exist, its workspace is unavailable or has no owner, or the plan does not include API access), or the chatbot uses `custom-model` and the plan does not include custom models (`Custom model is unavailable in the current plan.`). |
| 403 | The API key cannot access the requested chatbot. A chatbot-scoped key used with another chatbot's ID gets `Access denied to this chatbot`. A workspace key gets `Chatbot <id> was not found in this API key's workspace. It may have been deleted or moved to another workspace.` when the chatbot does not exist or belongs to another workspace. |
| 429 | The workspace sent more than 600 API requests in the last minute. Wait the number of seconds given in the `Retry-After` header (and in `message`) before retrying. |
| 503 | The request waited about 130 seconds behind earlier requests for the same conversation (`Server busy, please retry.`). Retry the request. |
