API · Conversations
Conversations
POST/clients/{client_id}/ask/stream
Ask with follow-ups. FactQL remembers earlier questions in the chat, and streams the answer as it works: progress, the SQL, the rows, then the explanation.
Start a chat, then follow up
Send new_session: true with the first question. The first event, session, carries the new session_id. Send that id with each follow-up so “and last month?” is understood in context. Your app doesn't create ids; FactQL does.
curl -N -X POST "https://api.factql.ai/clients/$CLIENT_ID/ask/stream" \ -H "X-API-Key: $FACTQL_API_KEY" \ -H "X-End-User-Id: user-4821" \ -H "Content-Type: application/json" \ -d '{"query": "Top 5 customers by spend", "new_session": true}'
Headers
X-API-Keystring · required
Your API key.
X-End-User-Idstring
A stable, opaque id for the person asking, 1 to 128 characters of letters, digits and
. _ : @ + -. Their chats are private to other end users. A request without the header acts for your whole account and can open any chat. See Serving many users.Acceptstring
text/event-stream (optional).Path
client_idstring · required
Your tenant, from
GET /whoami.Body
querystring · required
The question.
new_sessionboolean
Start a new chat with this question. Can't be combined with
session_id.session_idstring
Continue this chat. Leave both out to use the person's default chat.
workspace_idstring
The workspace to ask. Keys bound to a workspace don't need it.
Events
The response is text/event-stream. Each event is an event: line and a data: line of JSON, separated by a blank line. Lines end in \r\n, so split on either line ending.
sessionThe conversation's session_id. Arrives first when you start a chat.
statusProgress: a stage and a short message, such as “Generating SQL…”.
thinkingPieces of the model's reasoning as it works. Optional to show.
sqlThe SQL about to run.
resultcolumns, rows, total_rows and truncated. Rows are capped at 10 inline.
explanationThe plain-English answer, in text.
errorFactQL couldn't answer: message, plus decline_reason and suggestions when known. The stream still returns 200.
doneThe end of the answer. It can arrive twice; the last one carries duration_ms and session_id.
event: session
data: {"session_id": "3f7b1c40-2a55-4f9e-8d21-0a6f4b9c1d33"}
event: status
data: {"stage": "generating_sql", "message": "Generating SQL..."}
event: sql
data: {"sql": "SELECT month, SUM(revenue) AS revenue FROM orders GROUP BY month ORDER BY month"}
event: result
data: {"columns": ["month", "revenue"], "rows": [["2026-06-01", 184320.5], ["2026-07-01", 201880.0]], "total_rows": 2, "truncated": false}
event: explanation
data: {"text": "Revenue rose from 184,320.50 in June to 201,880 in July."}
event: done
data: {"agent": "text2sql", "duration_ms": 4210, "session_id": "3f7b1c40-2a55-4f9e-8d21-0a6f4b9c1d33"}Errors before the stream starts
400
X-End-User-Id has characters that aren't allowed.401The key is missing, invalid or revoked.
403The key belongs to a different tenant.
404The
session_id doesn't exist, or belongs to a different end user.422Both
new_session and session_id were sent, or query is missing.429Too many requests. Wait
Retry-After seconds.Once the stream has started, problems arrive as an error event, including a reached spend cap (with status: 402 in its data).