Overview of the K-SQL Ask API introduced in revision 2026-10-15.
OpenAPI Spec
Machine-readable spec for the Reporting API, which includes the K-SQL Ask API (revision 2026-10-15.pre): beta/categories/reporting.json
Before you begin
Check out our general API overview before getting started with specific endpoints.
The K-SQL Ask API (revision 2026-10-15.pre) is currently in beta.
Send
2026-10-15.preas yourrevisionheader. Endpoint paths and request/response shapes may change before GA. Access is limited to accounts that have been enabled for the API.
Enable the read scopes for the data you want to ask about on your private API key or OAuth token:
campaigns:read,catalogs:read,events:read,lists:read,metrics:read,profiles:read, andsegments:read. See Required scopes.
The K-SQL Ask API lets you ask a question about your Klaviyo account data in plain language and get a plain-language answer back. K-SQL turns your question into read-only queries over your account's data, runs them, and summarizes the results. The result sets behind the answer are included in the response so you can see how it was reached.
Use cases
Here are some example use cases supported by the K-SQL Ask API:
- Answer ad hoc questions about your account data without writing queries, for example, "How many orders were placed last week?"
- Add plain-language data questions to your own internal tools or assistants.
- Show the figures behind an answer by displaying the returned result sets next to it.
Required scopes
You don't need every scope to call the endpoint. An answer only draws on data that your key's scopes allow, so a key with fewer scopes gets answers based on less of your account's data.
| Scope | Data it makes available to answers |
|---|---|
campaigns:read | Campaigns |
catalogs:read | Catalogs |
events:read | Events |
lists:read | Lists |
metrics:read | Metrics |
profiles:read | Profiles |
segments:read | Segments |
How it works
- You send a question to the Ask Analytics Question endpoint.
- K-SQL decides which data it needs and runs read-only queries against it.
- You receive an answer, along with the result sets that support it.
If your question is too ambiguous to answer, you receive a clarifying question instead of an answer. Ask again with more detail. Each response has a status that tells you which case you're in:
| Status | Meaning |
|---|---|
success | answer contains the plain-language answer. |
clarification | answer contains a question for you. result_sets is empty. |
Ask Analytics Question
Send a POST request to /api/ksql-ask with your question.
The request supports the following attributes:
| Attribute | Type | Description |
|---|---|---|
question | string | Required. The question to ask, in 1 to 2000 characters. |
max_rows | integer | The maximum number of rows to return in each result set, from 1 to 1000. Defaults to 100. |
curl --request POST \
--url https://a.klaviyo.com/api/ksql-ask \
--header 'Authorization: Klaviyo-API-Key your-private-api-key' \
--header 'accept: application/vnd.api+json' \
--header 'content-type: application/vnd.api+json' \
--header 'revision: 2026-10-15.pre' \
--data '
{
"data": {
"type": "ksql-ask",
"attributes": {
"question": "How many orders were placed last week?",
"max_rows": 100
}
}
}'
{
"data": {
"type": "ksql-ask",
"id": "5b6e9c0a-3f4d-4e57-9a38-2c1f6d7b8e90",
"attributes": {
"status": "success",
"answer": "42 orders were placed last week.",
"result_sets": [
{
"columns": [
{
"name": "order_count",
"type": "long"
}
],
"rows": [
[
42
]
],
"row_count": 1,
"rows_returned": 1,
"completeness": "complete",
"description": "Number of orders placed last week.",
"sql": "SELECT count(*) FROM klaviyo.orders",
"caveats": []
}
],
"warnings": []
}
}
}
The response id identifies this request.
Clarifying questions
When a question is too ambiguous to answer, the response has status set to clarification and answer holds a question for you. Send a new request with a more specific question. For example, if you send the question "How did my last campaign do?", the response asks which measure you want:
{
"data": {
"type": "ksql-ask",
"id": "0c8d2f61-7a19-4b3e-8e54-6f2a9d1b3c47",
"attributes": {
"status": "clarification",
"answer": "Which measure do you want to see for your last campaign: open rate, click rate, or revenue?",
"result_sets": [],
"warnings": []
}
}
}
You can then ask again with the detail included, for example, "What was the click rate of my last campaign?"
Understanding the response
Each response has the following attributes:
-
statusEither
successorclarification. -
answerThe plain-language answer when
statusissuccess, or a clarifying question whenstatusisclarification. -
result_setsThe result sets that support the answer. This is always empty when
statusisclarification, and it can be empty whenstatusissuccessif the answer didn't require running a query. -
warningsCaveats that change how you should read
answerandresult_sets, for example, a sampled result set or rows left out by a cap. Show these alongside the answer rather than discarding them.
Each result set has the following attributes:
-
columnsThe result set's columns, each with a
nameand atype. The type is one ofboolean,long,double,string,timestamptz, orunknown. -
rowsThe result rows. Each row is a list of values in the same order as
columns. A value is a boolean forbooleancolumns, a number forlonganddoublecolumns, and a string for every other type. A missing value isnull. -
row_countandrows_returnedrow_countis the number of rows in the query's result.rows_returnedis the number of rows included inrows, which is lower thanrow_countwhen rows were left out by a cap. -
completenessWhether the result set contains everything its query matched.
completemeans it does.samplemeans the result may be larger than what the query returned, which is common for list-style questions.truncatedmeans the result reached the maximum result size. -
descriptionA short plain-language description of what the result set contains.
-
sqlThe K-SQL query that produced the result set, so you can see how the figures were derived. It is for explanation only. K-SQL is an internal dialect, and there is no endpoint that runs it. Don't parse it, store it, or build on its syntax.
-
caveatsCaveats that apply to this result set, in plain language. The same sentences also appear in
warnings.
Limits
- Question length: 1 to 2000 characters.
- Rows per result set: up to 1000. Use
max_rowsto request fewer. - Rate limits: burst
1/sand steady15/m. - Composer AI Credits: each request uses Composer AI Credits from your account. See Common errors for what happens when an account runs out.
- Response time: answers can take several seconds, so allow for this in your client's request timeout.
Common errors
The following errors can occur:
400: the question can't be answered as asked. Rephrase it and try again.403with codeinsufficient_ai_credits: the account has no Composer AI Credits left for the current billing period. Increase the account's AI usage limit to continue asking questions. Retrying doesn't help until credits are available.403with codeforbidden: the account isn't enabled for this API.503, including codesdeadline_exceededandunverified: a reliable answer couldn't be produced. Retry the request, and honor theRetry-Afterheader when it's present. TheRetry-Afterheader indicates the number of seconds to wait before retrying.500with codeanswer_failed: the question couldn't be answered because of a server error.
Example error response
{
"errors": [
{
"id": "7d3f1b52-9c64-4e08-a1d7-5e2b8c4f6a93",
"status": 403,
"code": "insufficient_ai_credits",
"title": "Forbidden.",
"detail": "This account has no Composer AI Credits left for the current billing period. Increase the account's AI usage limit to continue asking questions.",
"source": {
"pointer": "/data"
},
"links": {},
"meta": {
"available_credits": "0"
}
}
]
}