Chat API
Stream conversational answers grounded in your indexed documents — designed for embedding in widgets, websites, and documentation portals.
The public chat API uses Server-Sent Events streaming exclusively. There is no non-streaming POST /v1/chat endpoint on the public API key surface; the dashboard's non-streaming chat lives behind JWT auth at /api/v1/teams/:teamID/chat and is not part of this reference.
Streaming chat
Endpoint
POST /v1/chat/stream
Authenticated with the X-ZenSearch-Key header. CORS is wide-open so this endpoint can be called from a widget embedded on any third-party site (auth is header-based, not cookie-based, so this is safe).
Request body
{
"message": "What is our refund policy?",
"conversation_id": "00000000-0000-0000-0000-000000000abc",
"collection": "00000000-0000-0000-0000-000000000def"
}
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
message | string | Yes | User message. Max 4000 characters. |
conversation_id | UUID | No | Continue an existing conversation. Omit to start a new one. |
collection | UUID | No | Pin the search to a single collection. Must be in the API key's allowed collections list. Omit to use the key's default collection. |
The model and system prompt are not selectable per request — they are configured on the API key in the dashboard (widget_model_id, widget_context). This keeps the surface predictable for embedded widgets and prevents prompt-injection of system context from the visitor side.
Response stream
Responses use Server-Sent Events. The agent and chat-platform layers emit the events documented in the features overview. The chat-platform layer additionally emits content, sources, follow_up, and done events that visitor-facing widgets typically consume:
event: content
data: {"delta": "Our refund policy "}
event: content
data: {"delta": "allows returns within 30 days..."}
event: sources
data: {"sources": [{"id": "...", "title": "Refund Policy", "url": "https://..."}]}
event: follow_up
data: {"questions": ["What about international orders?", "How do I start a return?"]}
event: done
data: {"usage": {"prompt_tokens": 1500, "completion_tokens": 200, "total_tokens": 1700}}
Errors emitted on the stream are sanitized to a generic visitor-safe message; the original error is logged server-side.
JavaScript example
const res = await fetch('https://your-domain.com/v1/chat/stream', {
method: 'POST',
headers: {
'X-ZenSearch-Key': 'zsk_pk_xxx...',
'Content-Type': 'application/json',
},
body: JSON.stringify({ message: 'What is our refund policy?' }),
});
const reader = res.body!.getReader();
const decoder = new TextDecoder();
while (true) {
const { value, done } = await reader.read();
if (done) break;
process.stdout.write(decoder.decode(value));
}
Conversation history
Conversation listing, retrieval, and deletion are dashboard-only operations under /api/v1/teams/:teamID/conversations. They are not exposed on the public /v1/... API key surface.
To continue a conversation, store the conversation_id returned in your widget session and pass it back on the next request.
Error responses
| Code | Description |
|---|---|
| 400 | Invalid request body |
| 401 | API key missing or invalid |
| 402 | Billing query limit exceeded |
| 403 | Collection not allowed for this key |
| 429 | Rate limit exceeded |
Next steps
- Search API - Direct document search
- Authentication - API key setup
- Docs SDK - JavaScript SDK that wraps these endpoints