Tus usuarios preguntan. Tu sistema responde con SQL seguro.
Datafala recibe la pregunta en lenguaje natural, genera un SELECT validado para PostgreSQL, MySQL o Firebird 2.5+ y devuelve el SQL con sus parámetros. Nunca se conecta a tu base de datos: quien lo ejecuta es tu sistema, con un usuario de solo lectura.
¿cuánto vendí del producto X en los últimos 6 meses?
{
"sql": "SELECT SUM(vi.quantidade * vi.valor_unitario) AS total_vendido FROM venda_itens vi JOIN vendas v ON v.id = vi.venda_id JOIN produtos p ON p.id = vi.produto_id WHERE p.nome ILIKE %(product)s AND v.status <> 'C' AND v.data >= CURRENT_DATE - INTERVAL '6 months'",
"params": { "product": "%producto x%" },
"dialect": "postgres",
"tables": ["venda_itens", "vendas", "produtos"],
"explanation": "Suma cantidad × valor de los ítems del producto X en las ventas no canceladas de los últimos 6 meses."
}
- PostgreSQL
- MySQL
- Firebird 2.5+
- Tu propia clave de LLM
- Sin acceso a tu base de datos
Tres pasos hasta la primera respuesta
Describe tu base de datos
Pega el DDL en el panel, describe tablas y columnas y oculta lo que la IA no debe usar.
Enséñale tu negocio
Escribe reglas y términos del dominio (“una venta cancelada tiene estado C”) y registra preguntas con el SQL aprobado como ejemplos.
Integra con una sola llamada
Tu sistema envía POST /v1/ask con la pregunta, recibe {sql, params} y lo ejecuta en su propia base de datos.
Diseñado para no confiar en el SQL generado
Todo SQL pasa por el Guard antes de llegar a tu sistema.
Solo SELECT
Se rechazan escrituras, DDL y múltiples comandos.
Lista de permitidos
Solo entran en la consulta tablas y columnas existentes y visibles.
Funciones por dialecto
Solo las funciones permitidas para PostgreSQL, MySQL o Firebird 2.5+.
LIMIT garantizado
El límite de filas se reescribe en la consulta.
Parámetros con nombre
El texto del usuario nunca se concatena en el SQL.
Claves protegidas
La clave del LLM se guarda cifrada y las claves de API se guardan solo como hash.
Si el SQL no pasa el Guard, la IA recibe el motivo y lo intenta una vez más. Si aun así falla, tu API recibe un error claro, nunca un SQL inseguro.
Un panel para configurar, probar y dar seguimiento
Playground
Prueba preguntas y mira el SQL, los parámetros, los tokens usados y el tiempo de respuesta.
Historial con feedback
Mira las preguntas reales, la valoración 👍/👎 que envía tu sistema y promueve buenas respuestas a ejemplos con un clic. “Ver prompt” muestra exactamente lo que se envió a la IA.
Evaluación
Registra preguntas con el SQL esperado y ejecuta la suite antes de cambiar de modelo o de contexto. Resultado: igual, mismos objetos (revisar) o diferente.
Versiones del schema
Un nuevo DDL crea una versión y conserva las descripciones y los elementos ocultos.
Credenciales
Crea y revoca claves de API para cada integración.
Historial por la API
Tu sistema puede reutilizar respuestas anteriores sin una nueva llamada a la IA.
curl -X POST https://app.datafala.com.br/v1/ask \
-H "Authorization: Bearer $DATAFALA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"question": "cuánto vendí del producto X en los últimos 6 meses", "end_user": "usr_8f3a91c2d4e7b605", "max_rows": 100}'
import httpx, psycopg
resposta = httpx.post(
"https://app.datafala.com.br/v1/ask",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"question": "clientes que más compraron este mes",
"end_user": ID_DO_USUARIO, # id opaco de tu usuario (16 a 128 caracteres)
},
).json()
with psycopg.connect(
DATABASE_URL_SOMENTE_LEITURA,
options="-c statement_timeout=5000 -c default_transaction_read_only=on",
) as conn:
linhas = conn.execute(resposta["sql"], resposta["params"]).fetchall()
import httpx
from django.db import connections
def perguntar(pergunta, id_do_usuario):
resposta = httpx.post(
"https://app.datafala.com.br/v1/ask",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"question": pergunta, "end_user": id_do_usuario},
).json()
# "leitura" es una base de datos configurada con un usuario de solo lectura
with connections["leitura"].cursor() as cursor:
cursor.execute(resposta["sql"], resposta["params"])
return cursor.fetchall()
import httpx, psycopg
from fastapi import FastAPI
app = FastAPI()
@app.get("/perguntar")
async def perguntar(pergunta: str, id_do_usuario: str):
async with httpx.AsyncClient() as client:
r = await client.post(
"https://app.datafala.com.br/v1/ask",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"question": pergunta, "end_user": id_do_usuario},
)
resposta = r.json()
# conexión con un usuario de solo lectura
async with await psycopg.AsyncConnection.connect(DATABASE_URL_SOMENTE_LEITURA) as conn:
cur = await conn.execute(resposta["sql"], resposta["params"])
return await cur.fetchall()
import { Client } from "pg";
interface Resposta {
sql: string;
params: Record<string, unknown>;
}
const r = await fetch("https://app.datafala.com.br/v1/ask", {
method: "POST",
headers: {
Authorization: "Bearer " + process.env.DATAFALA_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
question: "clientes que más compraron este mes",
end_user: idDoUsuario, // id opaco de tu usuario (16 a 128 caracteres)
}),
});
const resposta = (await r.json()) as Resposta;
// pg usa $1, $2…: reemplaza cada %(name)s por el valor posicional y %% por %
const valores: unknown[] = [];
const sql = resposta.sql.replace(/%%|%\((\w+)\)s/g, (trecho, nome: string) => {
if (trecho === "%%") return "%"; // el %% de los literales vuelve a ser %
valores.push(resposta.params[nome]);
return "$" + valores.length;
});
const db = new Client({ connectionString: process.env.DATABASE_URL_SOMENTE_LEITURA });
await db.connect();
const { rows } = await db.query(sql, valores);
const { Client } = require("pg");
async function perguntar(pergunta, idDoUsuario) {
const r = await fetch("https://app.datafala.com.br/v1/ask", {
method: "POST",
headers: {
Authorization: "Bearer " + process.env.DATAFALA_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({ question: pergunta, end_user: idDoUsuario }),
});
const resposta = await r.json();
// pg usa $1, $2…: reemplaza cada %(name)s por el valor posicional y %% por %
const valores = [];
const sql = resposta.sql.replace(/%%|%\((\w+)\)s/g, (trecho, nome) => {
if (trecho === "%%") return "%"; // el %% de los literales vuelve a ser %
valores.push(resposta.params[nome]);
return "$" + valores.length;
});
// conexión con un usuario de solo lectura
const db = new Client({ connectionString: process.env.DATABASE_URL_SOMENTE_LEITURA });
await db.connect();
const { rows } = await db.query(sql, valores);
await db.end();
return rows;
}
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.RegularExpressions;
using Npgsql;
var http = new HttpClient();
http.DefaultRequestHeaders.Add("Authorization", "Bearer " + apiKey);
var r = await http.PostAsJsonAsync(
"https://app.datafala.com.br/v1/ask",
new
{
question = "clientes que más compraron este mes",
end_user = idDoUsuario, // id opaco de tu usuario (16 a 128 caracteres)
});
var resposta = await r.Content.ReadFromJsonAsync<JsonElement>();
// Npgsql usa @nombre: reemplaza cada %(name)s y deshace el %% de los literales
var sql = Regex.Replace(resposta.GetProperty("sql").GetString()!, @"%%|%\((\w+)\)s",
m => m.Value == "%%" ? "%" : "@" + m.Groups[1].Value);
// conexión con un usuario de solo lectura
await using var conn = new NpgsqlConnection(connStringSomenteLeitura);
await conn.OpenAsync();
await using var cmd = new NpgsqlCommand(sql, conn);
foreach (var p in resposta.GetProperty("params").EnumerateObject())
{
object valor = p.Value.ValueKind switch
{
JsonValueKind.Number => p.Value.GetDecimal(),
JsonValueKind.String => p.Value.GetString()!,
JsonValueKind.Null => DBNull.Value,
_ => p.Value.ToString(),
};
cmd.Parameters.AddWithValue(p.Name, valor);
}
await using var leitor = await cmd.ExecuteReaderAsync();
uses
System.SysUtils, System.Classes, System.JSON, System.Net.HttpClient,
System.RegularExpressions, FireDAC.Comp.Client;
function Perguntar(const Pergunta, IdDoUsuario: string): TFDQuery;
var
Http: THTTPClient;
Json: TJSONObject;
Corpo: TStringStream;
Retorno: IHTTPResponse;
Resposta: TJSONObject;
Par: TJSONPair;
Sql: string;
begin
Json := TJSONObject.Create;
Http := THTTPClient.Create;
try
Json.AddPair('question', Pergunta);
// id opaco de tu usuario (16 a 128 caracteres)
Json.AddPair('end_user', IdDoUsuario);
Corpo := TStringStream.Create(Json.ToJSON, TEncoding.UTF8);
try
Http.ResponseTimeout := 60000;
Http.ContentType := 'application/json';
Http.CustomHeaders['Authorization'] := 'Bearer ' + ApiKey;
Retorno := Http.Post('https://app.datafala.com.br/v1/ask', Corpo);
finally
Corpo.Free;
end;
finally
Http.Free;
Json.Free;
end;
Resposta := TJSONObject.ParseJSONValue(Retorno.ContentAsString(TEncoding.UTF8)) as TJSONObject;
try
if Retorno.StatusCode <> 200 then
raise Exception.Create(Resposta.GetValue<string>('code'));
// FireDAC usa :nombre: reemplaza cada %(name)s y deshace el %% de los literales
Sql := StringReplace(Resposta.GetValue<string>('sql'), '%%', #1, [rfReplaceAll]);
Sql := TRegEx.Replace(Sql, '%\((\w+)\)s', ':$1');
Sql := StringReplace(Sql, #1, '%', [rfReplaceAll]);
// FDConexaoLeitura usa un usuario de solo lectura
Result := TFDQuery.Create(nil);
Result.Connection := FDConexaoLeitura;
Result.ResourceOptions.MacroCreate := False;
Result.ResourceOptions.MacroExpand := False;
Result.ResourceOptions.EscapeExpand := False;
Result.SQL.Text := Sql;
for Par in Resposta.GetValue<TJSONObject>('params') do
if Par.JsonValue is TJSONNumber then
Result.ParamByName(Par.JsonString.Value).AsFloat := TJSONNumber(Par.JsonValue).AsDouble
else
Result.ParamByName(Par.JsonString.Value).AsString := Par.JsonValue.Value;
Result.Open;
finally
Resposta.Free;
end;
end;
Una llamada. Tú ejecutas.
El SQL llega con los parámetros separados. Tu sistema lo ejecuta con el driver que ya usa, con un usuario de solo lectura y con timeout.
-
Errores con código estable (
sql_rejected,no_answer,rate_limited…) - Explicación de la consulta en el idioma del usuario
-
POST /v1/feedbackpara indicar si la respuesta fue correcta
Usa la clave del proveedor que ya tienes.
- OpenAI
- Anthropic
- Google Gemini
- DeepSeek
- Mistral
- Groq
- Cohere
- xAI
- OpenRouter
Pagas los tokens directamente al proveedor. Una pregunta usa como máximo dos llamadas.
Planes por número de usuarios
Mensualidad fija, preguntas ilimitadas, usa tu propia clave de LLM.
| Tramo | Mensualidad | Destacado | Acción |
|---|---|---|---|
| Hasta 100 usuarios | US$ 97/mes | Para empezar a ofrecer la función | |
| De 101 a 300 usuarios | US$ 397/mes | Para productos en crecimiento | |
| De 301 a 700 usuarios | US$ 697/mes | Para bases de clientes más grandes | |
| De 701 a 1500 usuarios | US$ 1197/mes | Para uso intenso en el día a día | |
| Más de 1500 usuarios | A consultar | Condiciones a medida |
- PostgreSQL, MySQL y Firebird 2.5+
- Panel completo (schema, contexto, ejemplos, playground, historial, evaluación)
- Hasta 20 claves de API
El costo de los tokens queda con tu proveedor de LLM, en tu cuenta. Los usuarios son los usuarios finales de tu sistema que hacen preguntas.
Para quien ya tiene los datos y quiere dar respuestas
Software de gestión y ERP
“¿qué clientes no compran hace 90 días?”
SaaS B2B con informes
“ticket promedio por vendedor este trimestre”
Equipos internos de datos
“pedidos atrasados por transportista”
Preguntas frecuentes
Pon preguntas en lenguaje natural en tu sistema
Cuéntanos un poco sobre tu producto y la base de datos que usas. Respondemos por correo electrónico.
