API de AI inference
A API de AI inference alimenta o Schema designer no dashboard. Ela expõe o mesmo fluxo de três etapas — profile, propose, render — como endpoints que você pode chamar a partir do seu próprio código. Útil quando você quer automatizar a geração de cubos em vários data warehouses, incorporar a autoria de cubos no seu próprio produto ou integrar o Saiku Cloud em um fluxo de onboarding upstream.
Todos os endpoints exigem uma API key Bearer. Veja Autenticação para o básico.
O fluxo
O Schema designer do dashboard é a implementação de referência canônica:
- Profile uma conexão ou um arquivo enviado. Amostramos sua
estrutura de forma barata e retornamos um
SchemaProfile. - Propose um cubo. Enviamos o profile + uma intenção opcional em
inglês simples para o Claude e retornamos um
CubeProposalestruturado. - Render a proposta como XML de schema do Mondrian. O resultado é uma string pronta para salvar como schema no seu workspace.
Você pode chamar as etapas independentemente — fazer profile uma vez e propor
várias vezes com diferentes intenções, ou pular a etapa de proposta e
montar um CubeProposal à mão para o renderer.
POST /me/inference/profile/connection/{id}
Faça profile de uma conexão de data warehouse salva. Lê o information_schema,
amostra algumas linhas por coluna, retorna um profile estruturado das
tabelas e colunas do data warehouse.
Parâmetros de caminho:
id— o ID da conexão deGET /me/connections.
Parâmetros de query opcionais:
schema— restringe a um schema específico do banco (ex.:public,analytics). Default: o schema sob o qual a conexão foi salva.maxTables— limita o número de tabelas analisadas. Default 20.tableTypes— lista separada por vírgula (TABLE,VIEW,MATERIALIZED VIEW). Default: apenasTABLE.
Resposta (200):
{ "databaseProductName": "PostgreSQL", "databaseProductVersion": "16.6", "tables": [ { "schema": "public", "name": "fact_sales", "rowCount": 1245678, "columns": [ { "name": "sale_id", "type": "BIGINT", "nullable": false, "distinctApprox": 1245678, "sampleValues": [1, 2, 3, 4, 5] }, { "name": "customer_id", "type": "BIGINT", "nullable": false, "distinctApprox": 25000, "sampleValues": [101, 102, 103, 104, 105] } ] } ], "sampledAt": "2026-05-23T18:00:00Z", "sampleDurationMillis": 1832, "sampleCostUsd": 0.001}Custo: tipicamente abaixo de $0,05 por profile contra um data warehouse PostgreSQL. A maior parte do custo são consultas de metadados, não scans de dados.
Modos de falha:
404 not_found— o ID da conexão não existe ou não é visível para o seu tenant.502 warehouse_unreachable— não conseguimos conectar ao data warehouse (credenciais erradas, host fora, problema de rede).
GET /me/inference/profile/connection/{id}/sample
Busca algumas linhas de exemplo de uma tabela. Mesmo formato de auth e alcance do endpoint de profile, com escopo mais restrito.
Parâmetros de caminho + query:
id— ID da conexão.table— nome da tabela (obrigatório).schema— schema do banco (default: o default da conexão).rows— número de linhas (default 5, máximo 50).
Resposta (200):
{ "schema": "public", "table": "fact_sales", "columns": ["sale_id", "customer_id", "amount", "sale_date"], "rows": [ [1, 101, "29.99", "2024-01-15"], [2, 102, "149.00", "2024-01-15"] ]}Útil para mostrar ao usuário como os dados realmente são antes de ele se comprometer com um design de cubo.
POST /me/inference/profile/file
Faça profile de um arquivo enviado em vez de uma tabela do data warehouse. Mesmo formato do profiler de conexão, com o ID do arquivo substituindo o ID da conexão.
Body multipart:
file— a parte do arquivo..parquet,.csvou.json.tableTypes— defaultTABLE(mesmo formato do profiler de conexão, permite expansão futura).
Resposta (200): mesmo formato SchemaProfile do profiler de
conexão. Arquivos aparecem como uma única entrada tables[0].
O httpfs do DuckDB lê o arquivo coluna por coluna, então um arquivo Parquet
de vários GB é analisado em segundos sem que precisemos puxar o arquivo
inteiro para a memória.
POST /me/inference/propose
Envia um profile para o Claude e recebe de volta uma proposta de cubo.
Body da requisição:
{ "profile": { /* SchemaProfile from the profile step */ }, "intent": "Sales facts joined to customer and product dimensions, sum of revenue, count of orders.", "factTable": { "schema": "public", "name": "fact_sales" }}profile(obrigatório) — o JSON retornado por qualquer um dos endpoints de profile.intent(opcional mas fortemente recomendado) — descrição em inglês simples em uma linha do que você quer. Sem isso o Claude tem muito menos com que trabalhar.factTable(opcional) — fixa uma tabela de fatos específica. Sem isso o Claude escolhe uma com base em heurísticas de nomes e formatos de colunas.
Resposta (200):
{ "traceId": "ac2ab729-7fc3-4c3a-8e36-7cd77be87267", "proposal": { "schemaName": "Sales Analytics", "cubes": [ { "name": "Sales", "factTableSchema": "public", "factTableName": "fact_sales", "measures": [ { "name": "Revenue", "column": "amount", "aggregator": "sum" }, { "name": "Orders", "column": "sale_id", "aggregator": "count" } ], "dimensions": [ { "name": "Customer", "foreignKey": "customer_id", "tableSchema": "public", "tableName": "dim_customer", "primaryKey": "customer_id", "levels": [ { "name": "Name", "column": "customer_name", "type": "String", "uniqueMembers": false } ] } ] } ] }, "mondrianXml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Schema name=\"Sales Analytics\">\n <Cube name=\"Sales\">...</Cube>\n</Schema>", "validation": { "valid": true, "cubeCount": 1, "warnings": [] }, "summary": { /* condensed proposal — measures + dim names + join graph */ }, "detectorFindings": { /* heuristic insights about date hierarchies, etc */ }, "usage": { "model": "claude-sonnet-4-6", "inputTokens": 4321, "outputTokens": 892, "totalTokens": 5213, "estimatedCostUsd": 0.018, "isPricingExact": true }, "quota": { /* monthly LLM budget snapshot */ }}Ponto chave a notar: a resposta já inclui o mondrianXml
renderizado para o caminho feliz. Você não precisa chamar
/render separadamente, a menos que tenha editado a proposta antes.
O traceId permite recuperar a conversa completa com o LLM mais tarde
via GET /me/inference/trace/{traceId}
— útil para auditoria ou debug de propostas inesperadas.
Modos de falha:
400 invalid_proposal— o Claude retornou uma proposta estruturalmente inválida que nosso validador rejeitou. O Trace ID ainda é retornado.429 over_budget— seu tenant excedeu o orçamento mensal de LLM. Vem com um headerRetry-Aftere um camporeset_atmostrando quando o orçamento reseta.502 upstream_error— o Claude retornou erro de rede ou recusou a requisição.
POST /me/inference/render
Converte um SchemaProposal em XML de schema do Mondrian.
Body da requisição — o JSON da proposta diretamente (não envolvido em
{proposal: …}). Use o campo proposal de uma resposta anterior de /propose,
com quaisquer edições locais aplicadas:
{ "schemaName": "Sales Analytics", "cubes": [ { "name": "Sales", "factTableSchema": "public", "factTableName": "fact_sales", "measures": [ /* … */ ], "dimensions": [ /* … */ ] } ]}Resposta (200):
{ "mondrianXml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Schema name=\"Sales Analytics\">\n <Cube name=\"Sales\">...</Cube>\n</Schema>", "validation": { "valid": true, "cubeCount": 1, "warnings": [] }, "summary": { /* condensed view — measures + dim names + join graph */ }}Transformação pura — sem chamada de LLM, sem interação com data warehouse. Grátis.
Você pode chamar render várias vezes na mesma proposta enquanto a edita — é exatamente o que o Schema designer faz no loop de edição do dashboard.
Modos de falha:
400 invalid_proposal— a proposta não satisfaz as restrições de XML do Mondrian (campo obrigatório ausente, joins contraditórios, etc). Body da resposta:{ error, kind, message }.400 empty_proposal— body da requisição estava vazio.
POST /me/inference/try-query
Executa uma consulta MDX de exemplo contra um cubo rascunho antes de salvá-lo. Útil para confirmar que os joins caem onde você espera.
Body da requisição:
{ "proposal": { /* CubeProposal */ }, "connectionId": "uuid-of-saved-connection", "mdx": "SELECT { [Measures].[Revenue] } ON COLUMNS, { [Customer].[Name].MEMBERS } ON ROWS FROM [Sales]"}Resposta (200):
{ "columns": ["Customer", "Revenue"], "rows": [ ["Acme Corp", "1234.56"], ["Beta Inc", "987.65"] ], "executionMillis": 234}O cubo é compilado em memória; nada é salvo. Use isso no seu próprio loop de iteração, do mesmo jeito que o Schema designer do dashboard faz.
GET /me/inference/trace/{traceId}
Recupera a conversa com o LLM de uma chamada propose anterior. Cada
resposta de propose inclui um traceId; passe-o aqui para obter o
prompt + resposta completos.
Resposta (200):
{ "traceId": "ac2ab729-7fc3-4c3a-8e36-7cd77be87267", "createdAt": "2026-05-23T18:00:00Z", "status": "success", "model": "claude-sonnet-4-6", "inputTokens": 4321, "outputTokens": 892, "estimatedCostUsd": 0.018, "promptText": "...full system + user prompt (often 20+ KB)...", "responseText": "...full LLM response, parsed and unparsed..."}Útil para:
- Debug de propostas inesperadas. Veja exatamente o que enviamos ao Claude e o que veio de volta.
- Trilha de auditoria. Equipes de compliance às vezes querem um registro do que a IA gerou para revisão humana.
- Iteração. Compare duas propostas consecutivas para ver o que o Claude fez de diferente.
Traces são retidos por 30 dias, depois purgados. Têm escopo RLS para o seu tenant — você só pode recuperar seus próprios traces.
Exemplo de ponta a ponta
Um script completo — fazer profile de um data warehouse, propor um cubo, renderizar o XML, salvar:
KEY="$SAIKU_API_KEY"CONN_ID="abc-123-def"
# 1. Profile (extract the `profile` field — propose wants the# profile object, not the whole response).PROFILE=$(curl -sS https://api.saiku.bi/me/inference/profile/connection/$CONN_ID \ -X POST -H "Authorization: Bearer $KEY" -d '{}' \ -H "Content-Type: application/json" | jq '.profile')
# 2. ProposePROPOSAL=$(curl -sS https://api.saiku.bi/me/inference/propose \ -X POST -H "Authorization: Bearer $KEY" \ -H "Content-Type: application/json" \ -d "{\"profile\": $PROFILE, \"intent\": \"Sales revenue and order count by customer and date.\"}")
# Happy path: the propose response already includes mondrianXml.echo "$PROPOSAL" | jq -r '.mondrianXml' > sales-cube.xml
# 3. Re-render (only needed if you've edited the proposal).# Important: the render endpoint takes the proposal JSON# directly — NOT wrapped in {proposal: ...}.XML=$(echo "$PROPOSAL" | jq '.proposal' | \ curl -sS https://api.saiku.bi/me/inference/render \ -X POST -H "Authorization: Bearer $KEY" \ -H "Content-Type: application/json" \ -d @- | jq -r '.mondrianXml')
# 4. Save (via /me/schemas — not covered on this page yet)echo "$XML" > sales-cube.xmlSubstitua o passo 4 pelo que seu workflow precisar — salvar no seu próprio repositório, commitar no git, entregar a um revisor humano.
Para onde ir agora
- Schema designer — a UI do dashboard construída sobre esta API.
- Autenticação — Bearer tokens, limites de taxa.
- Servidor MCP — para agentes LLM que querem consultar seus cubos (esta página é sobre autorá-los).