Skip to main content

Generate SQL: POST /v1/ask

Receives a natural language question and returns a SELECT validated by the SQL Guard, with the values in params. Each call makes 1 to 2 calls to the LLM of your tenant (the second one is an automatic correction when the first SQL is rejected).

Request​

Headers:

Authorization: Bearer tsk_1a2b3c4d_...
Content-Type: application/json
Accept-Language: en (optional)

Body:

{
"question": "How many orders were placed yesterday?",
"end_user": "5f0c3a9e7b1d4e2f8a6c0b9d3e7f1a2c4b5d6e7f8091a2b3c4d5e6f708192a3b",
"max_rows": 100,
"locale": "en"
}
FieldTypeRequiredDescription
questionstringyesThe question, up to 2,000 characters.
end_userstringyes*Opaque identifier of the person who asked, 16 to 128 characters (A-Z, a-z, 0-9, _, -). See End-user identifier. *Required for most customers.
max_rowsintegernoMaximum rows, ≥ 1. Default 100, ceiling 1,000. The API applies a LIMIT to the SQL; aggregations that return a single row are left without LIMIT.
localestringnoLanguage of message and explanation: pt-BR, en or es. Variants such as en-US or es-419 fall back to the primary language (en, es). Precedence: this field → Accept-Language header → the tenant's default language (set in the panel) → pt-BR.

Unknown fields are rejected with 422 invalid_request. The whole body can be up to 96 KiB.

Response 200​

{
"sql": "SELECT count(*) AS total FROM public.pedidos WHERE criado_em >= %(start)s AND criado_em < %(end)s",
"params": { "start": "2026-10-22", "end": "2026-10-23" },
"dialect": "postgres",
"tables": ["public.pedidos"],
"explanation": "Counts the orders created yesterday.",
"locale": "en",
"request_id": "req_0f3c9a7d2b6e4f1a8c5d0e9b7a6f3c21"
}
FieldTypeDescription
sqlstringA single SELECT, in your schema's dialect, with %(name)s placeholders.
paramsobjectValues of the placeholders: string, number, boolean or null. Can be {}.
dialectstringpostgres, mysql or firebird.
tableslist of stringsTables used by the query.
explanationstring or nullShort explanation of what the query does, in the resolved language.
localestringLanguage used in the response.
request_idstringreq_ + 32 hexadecimal characters. Use it to send feedback and to fetch the answer from the history.

The same request_id comes in the X-Request-ID header of every response, including error responses.

About the returned SQL​

  • Table and column names are the ones in your database, exactly as they appear in the registered DDL.
  • User text is never concatenated into the SQL: values such as names, dates and search terms arrive in params.
  • Every % that is not a placeholder comes out escaped as %% (for example LIKE 'A%%'), in the style of Python drivers. That is why you should always pass params to the driver, even when empty. See Run the SQL.
  • Relative dates ("yesterday", "last month") are resolved at the moment of the question, with today's date sent to the LLM.

Common errors​

HTTPcodeWhat to do
400end_user_requiredSend end_user.
401invalid_api_keyFix the key.
409schema_not_configured, llm_not_configuredThe administrator needs to finish the configuration in the panel.
422no_answerThe schema does not allow an answer; ask the user to rephrase.
422sql_rejectedThe generated SQL did not pass the Guard, even after the automatic correction.
429rate_limitedWait for the time in the Retry-After header.
502llm_errorFailure at the LLM provider; see details.reason.

The full list is in Errors.

Latency​

A question normally takes a few seconds, and up to twice as long when the automatic correction runs. Set the read timeout of your HTTP client to at least 60 seconds, and do not automatically retry a POST /v1/ask that timed out, because each attempt spends tokens from your LLM.

Example​

curl -sS -X POST "https://app.datafala.com.br/v1/ask" \
-H "Authorization: Bearer $DATAFALA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"question": "Top 5 customers by revenue in 2026", "end_user": "'"$END_USER"'", "max_rows": 5}'

More examples in Integration examples.