Conversations API
Send messages to the AI assistant and receive contextual responses.
If you're using the Embeddable Widget, the widget handles conversations automatically. It uses dedicated widget endpoints (POST /widget/message) instead of the endpoints below. You don't need to call these endpoints directly. The widget's built-in UI manages the full conversation flow.
Send a message
POST /assistant/message
Authorization: Bearer <token>
Content-Type: application/json
{
"message": "What should I enter in the account number field?",
"context": {
"url": "https://app.example.com/registration",
"title": "Registration Form",
"forms": [
{
"id": "reg-form",
"fields": [
{
"name": "account_number",
"type": "text",
"label": "Account Number",
"required": true,
"placeholder": "Enter your 10-digit account number"
}
]
}
]
},
"language": "en",
"conversation_id": 5231
}
conversation_id is optional: omit it and the turn resolves or creates the
thread for that site. The response carries the assistant's answer along with the
conversation_id the turn was recorded under, so a client that omitted it knows
where the next turn belongs. POST /assistant/message/stream is the streamed
variant of the same call.
The organization the turn runs in is the authenticated caller's, never a field
in the body. If the page named in context.url is a registered domain belonging
to a different organization, the turn is refused rather than served from that
organization's configuration.
Conversation history
The history routes are served under /user, not alongside the message endpoint
above.
List a user's conversations
GET /user/conversations
Authorization: Bearer <token>
Query parameters:
| Parameter | Description |
|---|---|
domain | Exact hostname match. The extension panel passes the hostname of the page the user is on and expects that thread list and no other |
domain_search | Case-insensitive substring match, for a search box. shop finds shop.example.com |
start_date, end_date | Bounds on the last message |
limit | Page size, default 50 |
The response carries conversations, plus total and active_domains
computed over the filters and ignoring limit, so a count shown beside a page
never describes a different set than the page.
Each conversation carries a conversation_key, which is its id as a string.
That is the value the detail, update and delete routes take. It used to be a
user|domain|ordinal composite, and between April and September 2026 the list
emitted a primary key in the ordinal position, so every thread opened from My
Account came back empty. A client holding an old composite key still resolves,
but nothing mints them any more.
Read one conversation
GET /user/conversations/:id
Authorization: Bearer <token>
Returns { conversation, messages }. Ownership is checked against the token.
Delete a conversation
DELETE /user/conversations/:id
Authorization: Bearer <token>
Hard delete of the conversation and every turn in it, including turns that were
written without a parent conversation and would survive a cascade.
DELETE /user/conversations does the same for the caller's whole history. Both
answer 200 with a JSON body rather than 204, because the clients parse the
response and an empty body made a successful delete look like a failure.
Export
POST /user/export
Authorization: Bearer <token>
Returns everything the account holds, conversations and their turns included, with a manifest naming each section, its row count and anything withheld. Two things are withheld on purpose because they carry other people's data: request log bodies, and the stored snapshot of the page a turn ran on.
Context object
The context object tells the AI about the user's current page. The more context you provide, the better the answers:
interface PageContext {
url: string;
title: string;
forms?: FormContext[];
selectedText?: string;
pageStructure?: {
headings: string[];
navigation: string[];
};
}
interface FormContext {
id?: string;
action?: string;
method?: string;
fields: FieldContext[];
}
interface FieldContext {
name: string;
type: string;
label?: string;
placeholder?: string;
required?: boolean;
options?: string[];
value?: string;
}
Avoid sending actual field values in the context unless necessary. Use field metadata (labels, types, placeholders) for context. If you must send values, ensure PII masking is enabled.