Chat API
Send a user message to a QSC Agent and get back a structured response (Markdown text, product data, or [[QSCACTION]] buttons).
Calls are handled by the Agent Server. You need an agent configuration in QSC Admin — its tenant and code become the path parameters below. How to set up MCP servers, LLM models, and the system prompt is covered there. For the ready-made web widget, see Chatbot.
To try the endpoints, use the Postman collection (folder 4 – Chat — import steps in the Overview). For the API reference, see the Open API Explorer.
Two endpoints are available:
| Endpoint | Description |
|---|---|
| Chat | Single request/response turn |
| Streaming Chat | Same turn, streamed as newline-delimited JSON |
Authentication
Pass your static API token in the X-QSC-Token header — same header name as the other APIs, different token:
X-QSC-Token: <your-token>
This is the agent-server AUTH_TOKEN — separate from the per-tenant admin and feeding tokens described in Authentication. If it's missing or doesn't match, the request is rejected with 401.
Chat
POST /api/v1/agent/chat/{tenant}/{code}
| Path Parameter | Description |
|---|---|
tenant | Tenant code of the agent configuration (e.g. demo) |
code | The agent config code (e.g. support-bot) |
Request
Starting a conversation
Send type: "init" with no text as the first call. This resets the agent's runtime state for the session and returns the configured welcome_message plus the list of selectable models — no LLM call is made.
You may pass a sessionId (e.g. one you generated client-side or from a previous visit) or omit it — if omitted, the server generates one and returns it as session_id.
{
"type": "init",
"sessionId": "3f8c9e2a-1b4d-4b2e-9c3a-6e7d8f1a2b3c"
}
Sending a message
{
"text": "What running shoes do you have under 100 euros?",
"sessionId": "3f8c9e2a-1b4d-4b2e-9c3a-6e7d8f1a2b3c",
"userId": "user-42",
"id": "msg-1",
"model": "haystack:eu.anthropic.claude-opus-4-8"
}
| Property | Type | Required | Description |
|---|---|---|---|
text | string | Yes (unless init or attachments is non-empty) | The user's message. Not sent when type is init. |
type | string | No | Set to "init" to start/reset a conversation and receive the configured welcome message instead of running the agent. |
sessionId | string | No | Conversation/session identifier. Optional on init (server generates one if omitted). On later calls, send back the session_id from the previous response to keep history. |
userId | string | No | Caller-supplied end-user identifier, forwarded for tracking. |
id | string | No | Client-supplied message id, echoed back in the response. |
model | string | No | Overrides the default LLM from the agent config for this request (must be one of the config's llm_models). |
attachments | array | No | Images or files to send alongside the message — see Sending attachments. |
Sending attachments
Attach images or files to a message by adding an attachments array. Each entry is sent inline as base64 — there's no separate upload endpoint or multipart support.
{
"text": "What's in this image?",
"sessionId": "3f8c9e2a-1b4d-4b2e-9c3a-6e7d8f1a2b3c",
"id": "msg-2",
"model": "haystack:eu.anthropic.claude-opus-4-8",
"attachments": [
{
"name": "shoe.jpg",
"mimeType": "image/jpeg",
"data": "/9j/4AAQSkZJRgABAQAAAQABAAD..."
}
]
}
| Property | Type | Required | Description |
|---|---|---|---|
name | string | No | Original filename. Used for file attachments; ignored for images. |
mimeType | string | No | MIME type (e.g. image/png, application/pdf). Any image/* type is sent to the model as an image; everything else is sent as a generic file. Recommended — if omitted, the server guesses it from the bytes. |
data | string | Yes | Base64-encoded file content. Must be non-empty, valid base64, and under ~11 MB decoded. |
Attachments only work with haystack: models — see Agent Server for how llm_models and its haystack:/openai: prefixes are configured. Sending attachments with an openai: model returns 400. Whether the model actually uses an attachment also depends on the underlying model supporting multimodal input (e.g. a vision-capable model for images) — that's a model capability, not something the endpoint can guarantee.
Attachments apply only to the turn that sends them — conversation history replays prior text, not prior attachments, so resend them if a later message still needs to reference the same file.
Response
{
"type": "markdown",
"data": "Here are a few options under €100:\n\n1. **Trail Boots X** — €89.99\n2. **City Sneaker** — €59.99",
"id": "msg-1",
"date": 1753088400000,
"session_id": "3f8c9e2a-1b4d-4b2e-9c3a-6e7d8f1a2b3c",
"models": [
{ "model": "haystack:eu.anthropic.claude-opus-4-8", "label": "Claude Opus 4.8", "selected": true },
{ "model": "haystack:eu.anthropic.claude-sonnet-4-5", "label": "Claude Sonnet 4.5", "selected": null }
]
}
| Property | Type | Description |
|---|---|---|
type | string | Render hint for the client, e.g. markdown or broadcast |
data | string | The agent's reply (Markdown, may include [[QSCACTION]] tokens) |
id | string | null | Echoes the request's id |
date | integer | Response timestamp, epoch milliseconds |
session_id | string | null | The session id — save this and send it back on the next call |
models | array | Available models from the agent config, with the active one flagged "selected": true |
Streaming Chat
POST /api/v1/agent/chat/streaming/{tenant}/{code}
Same path parameters and request body as Chat. The response is Content-Type: application/json but the body is a stream of newline-delimited JSON objects, not a single JSON document — read and parse it line by line.
-
Zero or more delta lines, emitted as the agent generates content:
{ "data": "Here are a few", "streaming": true }
{ "data": " options under €100:", "streaming": true } -
Exactly one final line, a full Chat response object with
"streaming": false, carrying the resolvedsession_id,id, andmodels:{ "type": "markdown", "data": "", "id": "msg-1", "date": 1753088400000, "session_id": "3f8c9e2a-1b4d-4b2e-9c3a-6e7d8f1a2b3c", "models": [...], "streaming": false }
Concatenate the data of all delta lines to get the full reply; the final line carries no additional content (data is empty) but confirms the turn is complete and supplies the metadata. If the agent fails mid-stream, one delta line with an error message is emitted, followed by a final line with "type": "broadcast".
type: "init" also works on the streaming endpoint and returns the welcome message as a single streamed turn.
Status Codes
| Code | Description |
|---|---|
200 | Request processed successfully |
400 | Invalid request body (e.g. missing text/attachments when type is not init, invalid attachment base64, or attachments sent with a non-haystack: model) |
401 | Missing or invalid token |
403 | Token rejected by the config service |
404 | Agent configuration not found for tenant/code |
500 | Upstream/LLM provider error |