Pular para o conteúdo

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:

  1. Profile uma conexão ou um arquivo enviado. Amostramos sua estrutura de forma barata e retornamos um SchemaProfile.
  2. Propose um cubo. Enviamos o profile + uma intenção opcional em inglês simples para o Claude e retornamos um CubeProposal estruturado.
  3. 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:

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: apenas TABLE.

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, .csv ou .json.
  • tableTypes — default TABLE (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 header Retry-After e um campo reset_at mostrando 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:

Terminal window
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. Propose
PROPOSAL=$(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.xml

Substitua 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).