API Documentation
Use Denser.ai's API to query a chatbot programmatically, keep conversations per end user, and list them. Build custom chat UIs and connect your own apps.
Query a Denser chatbot from your own code, keep each of your users' conversations, and read them back. Before you start, generate an API key and copy your chatbot ID — see the REST API quick start.
| Endpoint | What it does |
|---|---|
POST /api/query | Ask the chatbot a question and get its answer, optionally as one of your users and within an ongoing conversation. |
POST /api/v2/conversations/api | List the chatbot's conversations, optionally only one user's, with their messages. |
Authentication
Each chatbot has its own API key, generated in its API channel (see the quick start). Regenerating the key immediately invalidates the old one.
/api/querytakes the key in thekeyfield of the JSON body./api/v2/conversations/apitakes the key in thex-api-keyheader.
Keep the key on your server
Call the API from your backend, never from a browser. The key gives access to the chatbot and its conversations, and the userId you send is trusted because the key vouches for it.
Both endpoints stop working when the chatbot's API channel is turned off. Every answer from /api/query uses one query from your workspace's plan.
Query a chatbot
Endpoint
POST https://denser.ai/api/query
Send the body as Content-Type: application/json.
Request body
Prop
Type
Example request
curl -X POST "https://denser.ai/api/query" \
-H "Content-Type: application/json" \
-d '{
"question": "What can you help me with?",
"chatbotId": "11111111-1111-1111-1111-111111111111",
"key": "00000000-0000-0000-0000-000000000000",
"userId": "user_8842"
}'Response
A successful request returns HTTP 200 with:
Prop
Type
{
"statusCode": "200",
"answer": "I can help you with questions about our products, orders and returns...",
"passages": [
{
"source": "https://example.com/help/returns",
"text": "Returns are accepted within 30 days of delivery...",
"score": 7.904721
}
],
"conversationId": "1791582688917-qzltde6lii",
"messageId": "1791582688915-hq4r1oxjrz"
}Errors
Some outcomes are reported in the body's statusCode with HTTP 200; others use the HTTP status.
| HTTP | statusCode / body | When |
|---|---|---|
| 200 | "404", answer "Chatbot not found" | The chatbot does not exist. |
| 200 | "404", answer "Conversation not found" | conversationId is not this chatbot's, or not the same userId's. |
| 200 | "429", answer "Quota exceeded" | The workspace has used all its queries. |
| 400 | message lists the invalid fields | A field is missing or invalid, such as a userId over 256 characters. |
| 401 | "Invalid API Key" | The key does not match the chatbot. |
| 500 | "Internal Server Error" | Anything else, including a chatbot whose API channel is turned off. |
{
"statusCode": 400,
"error": "Bad Request",
"message": ["question must be a string"]
}End users and conversations
Pass userId to tie each question to one of your users, and conversationId to keep a conversation going. Both are optional: without them every call is a conversation of its own.
-
Ask, with your user's ID. The response's
conversationIdandmessageIdidentify the conversation and the answer.{ "chatbotId": "…", "key": "…", "question": "What is your refund policy?", "userId": "user_8842" } -
Follow up in the same conversation by sending its
conversationIdwith the sameuserId. Withoutcontext, the conversation's recent turns are the history, so a follow-up such as "Does that apply to sale items?" is understood.{ "chatbotId": "…", "key": "…", "question": "Does that apply to sale items?", "userId": "user_8842", "conversationId": "1791582688917-qzltde6lii" } -
Record feedback on your side, such as thumbs up or down, keyed to the answer's
messageId. -
List the user's conversations with List conversations and their
userId.
Conversations made through the API also appear in the chatbot's Chat logs, with the userId as the client ID.
List conversations
Endpoint
POST https://denser.ai/api/v2/conversations/api
Send the API key in the x-api-key header and the filters as a JSON body. Up to 60 requests a minute per key.
Request body
Prop
Type
Example request
curl -X POST "https://denser.ai/api/v2/conversations/api" \
-H "Content-Type: application/json" \
-H "x-api-key: 00000000-0000-0000-0000-000000000000" \
-d '{ "chatbotId": "11111111-1111-1111-1111-111111111111", "userId": "user_8842" }'Response
{
"success": true,
"data": {
"conversations": [
{
"id": "1791582688917-qzltde6lii",
"title": "",
"status": "active",
"clientType": "api",
"userId": "user_8842",
"currentAgent": "bot",
"createdAt": "2026-10-09T18:24:48.917Z",
"updatedAt": "2026-10-09T18:26:05.403Z",
"lastMessage": { "content": "…", "role": "assistant", "createdAt": "2026-10-09T18:26:07.120Z" },
"messages": [
{ "id": "1791582688917-hihu5fongia", "role": "user", "content": "What is your refund policy?", "createdAt": "2026-10-09T18:24:48.917Z" },
{ "id": "1791582688915-hq4r1oxjrz", "role": "assistant", "content": "…", "createdAt": "2026-10-09T18:24:52.310Z" }
]
}
],
"pagination": { "nextCursor": null, "hasMore": false, "count": 1, "limit": 20 }
}
}Each conversation has userId when one was sent (or the website widget's client ID), and each message carries userFeedback when a visitor rated it in the widget. When pagination.hasMore is true, pass pagination.nextCursor back as cursor for the next page.
Errors
Errors have success: false and an error.code:
| HTTP | error.code | When |
|---|---|---|
| 400 | — | A filter is invalid, such as a limit over 100. |
| 401 | INVALID_API_KEY | The key does not match the chatbot. |
| 401 | DATE_RANGE_EXCEEDED | createdAfter to createdBefore spans more than 90 days. |
| 401 | REQUEST_FAILED | The request could not be completed; error.message says why. |
| 403 | CHANNEL_DISABLED | The chatbot's API channel is turned off. |
| 429 | — | More than 60 requests in a minute. |