Asks a question and streams SQL generation, execution, and answer events using server-sent events
| Time | Status | User Agent | |
|---|---|---|---|
Retrieving recent requests… | |||
The POST /v2/stream/ask endpoint lets you ask a single question and stream the whole answer over Server-Sent Events (SSE) — SQL generation and execution state, the streamed natural-language summary (or an explanation for non-SQL questions), and mid-stream clarification checkpoints you can answer without restarting the turn.
This is the v2 (adhoc) streaming ask — the successor to the v1 /stream/ask. Two things are intentionally different from v1:
- Charts are not part of this stream. Once you have the generated SQL, render a chart separately with
POST /v2/generate_chart(Apache ECharts). - Clarification is built in. When the AI needs input, the stream emits a
pendingQuestionframe; you answer it withPOST /v2/stream/ask/respondand the same open stream resumes.
What It Does
The endpoint returns a Server-Sent Events (SSE) stream that includes:
- A leading
initframe carrying thethreadId(use it for follow-ups). - Processing state for SQL generation (understanding → searching → planning → generating → correcting) and execution.
- The generated SQL and, for SQL answers, a streamed natural-language
summary_generationcontent block — or, for non-SQL questions, a streamedexplanationcontent block. pendingQuestionframes when the AI pauses for clarification at theintentorsql_reasoningcheckpoint.
Basic Usage
{
"projectId": 1,
"question": "List the top 5 states with the most customers"
}This simplified stream shows the key phases of a /v2/stream/ask request — from the leading init frame, through understanding and generating SQL, executing it, and finally producing a streamed summary.
// Stream begins
data: { "type": "init", "threadId": "0625991d-1bba-407d-8ad4-dd0210172484", "queryId": null }
data: { "type": "message_start" }
// SQL Generation Stages
data: { "type": "state", "data": { "state": "sql_generation_start" }}
data: { "type": "state", "data": { "state": "sql_generation_understanding" }}
data: { "type": "state", "data": { "state": "sql_generation_searching" }}
data: { "type": "state", "data": { "state": "sql_generation_planning" }}
data: { "type": "state", "data": { "state": "sql_generation_generating" }}
data: { "type": "state", "data": { "state": "sql_generation_success", "sql": "SELECT ... LIMIT 5" }}
// SQL Execution
data: { "type": "state", "data": { "state": "sql_execution_start" }}
data: { "type": "state", "data": { "state": "sql_execution_end" }}
// Summary Generation (streamed content block)
data: { "type": "content_block_start", "content_block": { "type": "text", "name": "summary_generation" }}
data: { "type": "content_block_delta", "delta": { "text": "Here" }}
data: { "type": "content_block_delta", "delta": { "text": " are" }}
data: { "type": "content_block_delta", "delta": { "text": " the top 5 states by customer count." }}
data: { "type": "content_block_stop" }
// Stream ends
data: { "type": "message_stop" }
This example omits several detailed properties (such as question, rephrasedQuestion, intentReasoning, traceId, and timestamp) for clarity. Refer to the full schema for a complete breakdown of all available fields.
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
projectId | number | ✅ | Target project. |
question | string | ✅ | The natural-language question. |
threadId | string | Continue an existing thread. Supplying it also enables the single-active-turn guard (see below). | |
sampleSize | number | Row limit for the SQL execution preview. | |
language | string | Language used for the summary/explanation (e.g. "English"). Defaults to the project language. | |
returnBothSqlDialect | boolean | When true, sql_generation_success also carries the native dialectSql. Defaults to false. | |
customInstruction | string | Extra instruction passed to the AI for this turn. |
Single-active-turn guard. When you supply a
threadId, a second concurrent/v2/stream/askfor the same thread is rejected with 409 (A turn is already in progress for this thread). OmitthreadIdand the server generates one (returned in theinitframe).
Clarification (pendingQuestion → respond)
pendingQuestion → respond)At the intent or sql_reasoning checkpoint the AI may need you to disambiguate. Instead of failing, the stream emits a pendingQuestion frame and keeps the connection open:
data: {
"type": "pendingQuestion",
"data": {
"questionId": "b8f0c1e2-...",
"checkpoint": "intent",
"question": "Which \"customers\" did you mean?",
"options": [
{ "value": "all", "label": "All registered customers" },
{ "value": "active", "label": "Only active customers" }
],
"selectionType": "single",
"rationale": "The question is ambiguous about customer status.",
"queryId": "1f0a3c7d-...",
"threadId": "0625991d-1bba-407d-8ad4-dd0210172484"
},
"timestamp": 1751014955000
}Answer it with a separate call to POST /v2/stream/ask/respond, carrying the queryId and questionId from the frame. This call returns immediately ({ "status": "accepted" }); the ask resumes on the same open SSE stream — you do not reopen /v2/stream/ask.
{
"projectId": 1,
"queryId": "1f0a3c7d-...",
"questionId": "b8f0c1e2-...",
"action": "SUBMIT",
"answers": ["active"],
"threadId": "0625991d-1bba-407d-8ad4-dd0210172484"
}action | Meaning |
|---|---|
SUBMIT | Answer with answers (option values) and/or freeText. |
SKIP | Skip the question and let the AI proceed with its best guess. |
RETRY | Ask the AI to regenerate the clarification question. |
CLOSE | Cancel the clarification and end the turn. |
action is case-insensitive. See the stream/ask/respond reference for the full request/response contract and error codes.
State Lifecycle
graph TD init["init"] message_start["message_start"] sql_start["sql_generation_start"] sql_understanding["sql_generation_understanding"] sql_searching["sql_generation_searching"] sql_planning["sql_generation_planning"] sql_generating["sql_generation_generating"] sql_correcting["sql_generation_correcting"] sql_success["sql_generation_success"] sql_failed["sql_generation_failed"] sql_stopped["sql_generation_stopped"] sql_exec_start["sql_execution_start"] sql_exec_end["sql_execution_end"] pending["pendingQuestion (intent / sql_reasoning)"] respond["POST /v2/stream/ask/respond"] summary_start["content_block_start: summary_generation"] summary_streaming["content_block_delta (text)"] summary_stop["content_block_stop"] explanation_block["content_block_*: explanation (non-SQL)"] error_event["type: error"] message_stop["message_stop"] %% Normal flow init --> message_start -->|"Trigger SQL generation"| sql_start sql_start --> sql_understanding --> sql_searching --> sql_planning --> sql_generating sql_generating -->|"Auto-correct (if needed)"| sql_correcting sql_correcting --> sql_success sql_generating --> sql_success sql_success -->|"Run SQL"| sql_exec_start --> sql_exec_end sql_exec_end -->|"Start LLM summary"| summary_start summary_start --> summary_streaming --> summary_stop --> message_stop %% Non-SQL branch sql_understanding -->|"Non-SQL question"| explanation_block --> message_stop %% Clarification checkpoints (resume on the same open stream) sql_understanding -.->|"Intent checkpoint"| pending sql_generating -.->|"SQL-reasoning checkpoint"| pending pending -.->|"Answer via respond"| respond respond -.->|"Turn resumes"| sql_generating %% Error flows sql_generating -->|"Too many corrections failed"| sql_failed -->|"Emit error event"| error_event --> message_stop sql_generating -->|"User canceled"| sql_stopped --> message_stop sql_exec_start -->|"SQL execution error"| error_event --> message_stop summary_start -->|"LLM failed to summarize"| error_event --> message_stop
Event Types
During a /v2/stream/ask request, the API streams a sequence of events using Server-Sent Events (SSE). Each event provides insight into the system’s current state or output.
init
- Purpose: Leading frame of every stream. Delivers the
threadIdto use for follow-ups. - Payload:
queryIdis alwaysnullhere — the per-turnqueryIdused to answer clarifications is delivered with eachpendingQuestionframe instead.
{
"type": "init",
"threadId": "0625991d-1bba-407d-8ad4-dd0210172484",
"queryId": null
}message_start
- Purpose: Indicates the start of a new streaming response.
- Payload: Contains the timestamp when the process began.
{
"type": "message_start",
"timestamp": 1751014954139
}state
- Purpose: Describes what stage the system is in during processing.
State Lifecycle Overview
state | Description |
|---|---|
sql_generation_start | The system has begun processing the user’s question. |
sql_generation_understanding | The AI is interpreting the question and trying to identify its intent. |
sql_generation_searching | The AI is searching relevant tables and metadata to answer the question. |
sql_generation_planning | A plan for how to generate SQL is being formed, including table joins or filters. |
sql_generation_generating | The SQL is being generated. |
sql_generation_correcting | A generated SQL failed to execute and is being corrected. (Auto-retry phase) |
sql_generation_success | SQL was successfully generated. The sql field will be included. |
sql_generation_failed | SQL generation failed. Followed by an error event. |
sql_generation_stopped | SQL generation was manually canceled or interrupted (e.g. client disconnected). |
sql_generation_finished | Final state for SQL generation (used internally; usually followed by success or failed). |
sql_execution_start | SQL execution has started. |
sql_execution_end | SQL execution has completed (successfully or not). |
data Field Reference ("type": "state")
data Field Reference ("type": "state")🔹 sql_generation_start
| Field | Type | Description |
|---|---|---|
state | string | "sql_generation_start" |
question | string | The original user input. |
threadId | string | Unique thread identifier. |
language | string | Language used for summarization (e.g., "English"). |
🔹 SQL Generation In-Progress States
(e.g., sql_generation_understanding, searching, planning, generating, correcting)
| Field | Type | Description |
|---|---|---|
state | string | Current generation state, such as "sql_generation_searching", "sql_generation_planning", etc. |
pollCount | number | Number of polling attempts so far. |
rephrasedQuestion | string | null | Reformulated version of the user query. |
intentReasoning | string | null | AI’s interpretation of what the user is asking. |
sqlGenerationReasoning | string | null | Step-by-step reasoning of SQL generation. Appears during generating. |
retrievedTables | string[] | null | List of tables determined to be relevant. |
invalidSql | string | null | SQL that failed during correction attempts (optional). |
traceId | string | For backend debugging. |
🔹 sql_generation_success
| Field | Type | Description |
|---|---|---|
state | "sql_generation_success" | Marks the completion of SQL generation. |
sql | string | The generated SQL query. |
dialectSql | string | undefined | Native dialect SQL, included when returnBothSqlDialect is true. |
🔹 sql_execution_start
| Field | Type | Description |
|---|---|---|
state | "sql_execution_start" | SQL execution has begun. |
sql | string | The SQL query being executed. |
🔹 sql_execution_end
| Field | Type | Description |
|---|---|---|
state | "sql_execution_end" | SQL execution completed. No additional fields. |
Example
data: {
"type": "state",
"data": {
"state": "sql_generation_start",
"question": "list 5 customers",
"threadId": "0625991d-1bba-407d-8ad4-dd0210172484",
"language": "English"
},
"timestamp": 1751014954142
}
data: {
"type": "state",
"data": {
"state": "sql_generation_understanding",
"pollCount": 1,
"rephrasedQuestion": null,
"intentReasoning": null,
"sqlGenerationReasoning": null,
"retrievedTables": null,
"invalidSql": null,
"traceId": "f218b1f7-4623-4a56-8b66-18d544797b20"
},
"timestamp": 1751014954165
}
data: {
"type": "state",
"data": {
"state": "sql_generation_searching",
"pollCount": 4,
"rephrasedQuestion": "List 5 customers from the olist_customers_dataset table.",
"intentReasoning": "User wants to retrieve specific customer data, likely using SQL query.",
"sqlGenerationReasoning": null,
"retrievedTables": null,
"invalidSql": null,
"traceId": "f218b1f7-4623-4a56-8b66-18d544797b20"
},
"timestamp": 1751014957183
}
data: {
"type": "state",
"data": {
"state": "sql_generation_planning",
"pollCount": 6,
"rephrasedQuestion": "List 5 customers from the olist_customers_dataset table.",
"intentReasoning": "User wants to retrieve specific customer data, likely using SQL query.",
"sqlGenerationReasoning": null,
"retrievedTables": ["olist_customers_dataset"],
"invalidSql": null,
"traceId": "f218b1f7-4623-4a56-8b66-18d544797b20"
},
"timestamp": 1751014959232
}
data: {
"type": "state",
"data": {
"state": "sql_generation_generating",
"pollCount": 9,
"rephrasedQuestion": "List 5 customers from the olist_customers_dataset table.",
"intentReasoning": "User wants to retrieve specific customer data, likely using SQL query.",
"sqlGenerationReasoning": "1. **Identify the table involved**: The question asks for customer data, so the relevant table is `olist_customers_dataset`.\n\n2. **Determine the number of records needed**: The user requests 5 customers, so we need to select 5 entries from the table.",
"retrievedTables": ["olist_customers_dataset"],
"invalidSql": null,
"traceId": "f218b1f7-4623-4a56-8b66-18d544797b20"
},
"timestamp": 1751014962254
}
data: {
"type": "state",
"data": {
"state": "sql_generation_success",
"sql": "SELECT \"o\".\"customer_id\", \"o\".\"customer_zip_code_prefix\", \"o\".\"customer_city\", \"o\".\"customer_state\" FROM \"olist_customers_dataset\" AS \"o\" LIMIT 5"
},
"timestamp": 1751014963263
}
data: {
"type": "state",
"data": {
"state": "sql_execution_start",
"sql": "SELECT \"o\".\"customer_id\", \"o\".\"customer_zip_code_prefix\", \"o\".\"customer_city\", \"o\".\"customer_state\" FROM \"olist_customers_dataset\" AS \"o\" LIMIT 5"
},
"timestamp": 1751014963263
}
data: {
"type": "state",
"data": {
"state": "sql_execution_end"
},
"timestamp": 1751014963339
}
content_block_start
- Purpose: Signals the beginning of a streamed text block. The
nametells you which one:summary_generation— a natural-language summary of the SQL result.explanation— a natural-language answer to a non-SQL question (no SQL is generated in this case).
- Payload: Describes the block (always
type: "text").
{
"type": "content_block_start",
"content_block": {
"type": "text",
"name": "summary_generation"
}
}content_block_delta
- Purpose: Streams the actual content in parts (e.g., the summary or explanation, chunk by chunk).
- Payload: Includes a
text_deltawith the partial content.
{
"type": "content_block_delta",
"delta": {
"type": "text_delta",
"text": "São Paulo (SP) leads with"
}
}content_block_stop
- Purpose: Indicates the end of a streamed content block.
{
"type": "content_block_stop"
}pendingQuestion
- Purpose: The AI paused at a clarification checkpoint and needs your input before continuing. The stream stays open; answer via
POST /v2/stream/ask/respond. - Payload:
| Field | Type | Description |
|---|---|---|
questionId | string | Identifier of this clarification question. Echo it back in respond. |
checkpoint | "intent" | "sql_reasoning" | Where in the pipeline the AI paused. |
question | string | The clarification question to show the user. |
options | Array<{ value: string; label: string }> | Selectable options (may be empty for free-text answers). |
selectionType | "single" | "multi" | Whether one or multiple options may be selected. |
rationale | string | undefined | Why the AI is asking. |
queryId | string | Per-turn query id. Pass it to respond to route the answer to this turn. |
threadId | string | The thread this turn belongs to. |
{
"type": "pendingQuestion",
"data": {
"questionId": "b8f0c1e2-1a2b-4c3d-9e8f-7a6b5c4d3e2f",
"checkpoint": "intent",
"question": "Which \"customers\" did you mean?",
"options": [
{ "value": "all", "label": "All registered customers" },
{ "value": "active", "label": "Only active customers" }
],
"selectionType": "single",
"rationale": "The question is ambiguous about customer status.",
"queryId": "1f0a3c7d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
"threadId": "0625991d-1bba-407d-8ad4-dd0210172484"
},
"timestamp": 1751014955000
}message_stop
- Purpose: Marks the end of the entire stream. Includes processing
duration(ms) andthreadId.
{
"type": "message_stop",
"data": {
"threadId": "0625991d-1bba-407d-8ad4-dd0210172484",
"duration": 11096
}
}Error Handling
If an error occurs at any stage of the pipeline, the server emits a type: "error" frame (followed by a terminal message_stop). This lets clients gracefully detect and handle failures — e.g., show an error message in the UI, add retry logic, or cancel streaming.
Error Event Structure
{
"type": "error",
"data": {
"code": "INVALID_SQL_ERROR",
"error": "Unrecognized token near FROM"
},
"timestamp": 1751015028888
}| Field | Type | Description |
|---|---|---|
code | string | Machine-readable error code (e.g. INVALID_SQL_ERROR, INTERNAL_SERVER_ERROR). |
error | string | Human-readable error message. |
Additional context fields (such as
traceId) may accompanydatadepending on the error.
Pre-stream errors. Validation failures that happen before the SSE stream opens are returned as a normal JSON error response instead of an
errorframe —400(missingprojectId/question),401(invalid API key),404(project not found), or409(a turn is already in progress for this thread).
