Descricao do Projeto
QueryCurious e o servico oficial que apresenta o data warehouse da Lifeapps Solucoes via
OData. Ele entende filtros, agregacoes, ordenacao e paginacao, devolvendo as entidades
que voce ja conhece: DimEmpresa, DimProduto e
DimLoja. Tudo protegido por tenant: se o seu token diz que voce pertence a
uma empresa, voce so enxerga os dados dela, mesmo que tente ser criativo no
$filter.
Objetivo do Projeto
- Entregar dados analiticos prontos para uso sem exigir manutencao extra.
- Garantir isolamento e seguranca por tenant direto no token.
- Oferecer interfaces OData e MCP padronizadas para conexoes plug-and-play.
Fluxo de Autenticacao
O teste definitivo para quem diz que domina APIs:
-
Solicite um token JWT usando client credentials. Exemplo classico:
curl -X POST \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials" \ -d "client_id=<XXX>" \ -d "client_secret=<xxxx>" \ https://sso.lifeappsecommerce.com.br/realms/lifeapps/protocol/openid-connect/token - Copie o valor de
access_tokendevolvido. -
Envie em todas as requisicoes:
Authorization: Bearer <seu_access_token> -
O servico extrai o claim
empresa. Se ele faltar, voce recebe um aviso simpatico; se estiver presente, as consultas fluem.
Dica rapida: tokens expiram. Renove antes do dashboard reclamar.
Integracao MCP para agentes
O servidor remoto QueryCurious Lifeapps esta disponivel em
/mcp para clientes compativeis com Model Context Protocol. Ele entrega o
mesmo acesso analitico tenant-scoped da API OData, agora em formato natural para agentes
de IA que precisam descobrir schemas, montar consultas e explicar resultados sem adivinhar
o data warehouse inteiro.
A autenticacao MCP usa Bearer JWT emitido por uma service account do Keycloak. Cada
cliente/agente recebe client_id e client_secret, busca o token
com grant_type=client_credentials fora do QueryCurious e envia o header
Authorization. O claim empresa continua definindo o tenant, e
daily_query_limit continua valendo. Claims opcionais como
permissions podem faltar em service accounts; nesse caso apenas nao ha bypass
lifegrowth.
O recurso MCP publica um documento PRM em
/.well-known/oauth-protected-resource/mcp. Clientes que seguem a especificacao
MCP recebem esse link no desafio WWW-Authenticate quando chamam
/mcp sem token, descobrem o realm Keycloak e entao usam o Bearer emitido
pela service account. Se seu token ainda vier com aud=account, deixe
MCP_AUDIENCE vazio ate configurar um audience mapper no Keycloak.
O servidor publica tools como list_entity_sets,
describe_entity_set, query_entity_set,
aggregate_entity_set e sample_entity_set. Tambem expoe
resources de metadata, exemplos OData, politica de billing e prompts prontos como
analyze_sales, build_kpi_query e
explain_dataset.
Configuracao de cliente
Primeiro obtenha o token no Keycloak:
curl -X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=<mcp-client-id>" \
-d "client_secret=<mcp-client-secret>" \
https://sso.lifeapps.com.br/realms/lifeapps/protocol/openid-connect/token
Depois use a URL publica do ambiente e envie o token como Bearer:
{
"mcpServers": {
"querycurious-lifeapps": {
"url": "https://querycurious.lifeapps.com.br/mcp",
"headers": {
"Authorization": "Bearer ${QUERYCURIOUS_MCP_TOKEN}"
}
}
}
}
Renove o token fora do QueryCurious. Nao existe endpoint nem tool MCP para gerar JWT.
OData e MCP compartilham o mesmo billing. O contador nao liga para a interface, so para os megabytes entregues.
Billing sem terrorismo
Cada resposta analitica em /odata ou via /mcp passa por um
contador que mede o volume entregue em megabytes. O total de cada tenant soma ao longo
de 24 horas e depois expira sozinho.
Se o token carregar daily_query_limit, usamos esse valor como limite
diario. Estourou a cota? Retornamos 429 com a mensagem
Daily query limit exceeded e paramos por ai. Precisa de ajuda ou quer ampliar
o limite? Contate o suporte Lifeapps.
Tutorial Definitivo de OData
Tudo o que voce precisa para transformar curiosidade em dashboards. Os EntitySets publicados sao:
/odata/DimEmpresa/odata/DimProduto/odata/DimLoja
Resposta sempre em JSON, com ate 50 linhas por pagina e isolamento por tenant.
$select - dieta de colunas
Sem $select vem tudo. Use apenas o que importa.
GET /odata/DimEmpresa?$select=nome,erp
GET /odata/DimLoja?$select=nome,ativo&$filter=ativo eq true
Nomes invalidos resultam em 400. Com $apply, cite so colunas agrupadas ou aliases.
$filter - onde a logica brilha
Misture comparacoes, funcoes e operacoes sem perder o controle. Strings precisam de
aspas simples, GUIDs devem ser validos, null e case-insensitive.
eq - operador de igualdade. Retorna linhas em que os valores sao iguais.
GET /odata/DimEmpresa?$filter=erp eq 'ERP-X'ne - operador de diferenca. Remove linhas em que os valores coincidem.
GET /odata/DimEmpresa?$filter=erp ne 'ERP-X'gt - compara se o valor de uma coluna e maior que o alvo informado.
GET /odata/DimEmpresa?$filter=valorfixosuporte gt 1000ge - retorna linhas em que o valor e maior ou igual ao limite.
GET /odata/DimEmpresa?$filter=valorfixosuporte ge 1000lt - verifica valores menores que o parametro escolhido.
GET /odata/DimLoja?$filter=notaavaliacao lt 4le - captura resultados com valor menor ou igual ao limite.
GET /odata/DimLoja?$filter=notaavaliacao le 4in - verifica se o valor da coluna existe dentro de uma lista de literais.
GET /odata/DimEmpresa?$filter=erp in ('ERP-X','ERP-Y','ERP-Z')contains - encontra linhas onde o texto contem o trecho informado.
GET /odata/DimProduto?$filter=contains(descricao,'Combo')startswith - filtra textos que iniciam com o prefixo desejado.
GET /odata/DimProduto?$filter=startswith(descricao,'Kit')endswith - valida textos que terminam com o sufixo indicado.
GET /odata/DimProduto?$filter=endswith(descricao,'Premium')year - extrai o ano de uma coluna de data para comparacoes diretas.
GET /odata/DimProduto?$filter=year(data_atualizacao) eq 2025month - extrai o numero do mes (1 a 12) para filtragem.
GET /odata/DimProduto?$filter=month(data_atualizacao) ge 10day - extrai o dia do mes para comparacoes pontuais.
GET /odata/DimLoja?$filter=day(data_inicio_faturamento) lt 15Operadores aritmeticos - permitem somar, subtrair, multiplicar, dividir ou calcular resto antes da comparacao.
GET /odata/DimEmpresa?$filter=valorfixosuporte sub percrepasse gt 50$orderby - colocando ordem no caos
GET /odata/DimProduto?$orderby=data_atualizacao desc
GET /odata/DimLoja?$orderby=ativo desc,nome asc
GET /odata/DimProduto?$filter=disponivel eq true&$orderby=descricao asc
asc e padrao. Depois de $apply, ordene apenas por colunas agrupadas ou aliases.
$top e $skip - paginacao civilizada
GET /odata/DimProduto?$top=20&$orderby=data_atualizacao desc
GET /odata/DimProduto?$skip=50&$top=50
$top maior que 50 rende 400. Combine com $skip para navegar sem drama.
$count - conhecendo o tamanho da festa
GET /odata/DimEmpresa?$filter=erp eq 'ERP-X'&$count=true
A resposta inclui @odata.count com o total antes da paginacao. $apply nao altera o contador.
$apply - agregacoes com estilo
GET /odata/DimProduto?$apply=
groupby((disponivel),
aggregate(multiplo_incremento with sum as TotalMultiplo))
GET /odata/DimProduto?$apply=
groupby((disponivel),
aggregate(id with countdistinct as ProdutosUnicos))
GET /odata/FatoPedido?$filter=year(datapedido) eq 2026 and month(datapedido) eq 6&
$apply=groupby((),
aggregate(dim_cliente with countdistinct as ClientesDistintos))
GET /odata/DimLoja?$filter=ativo eq true&
$apply=groupby((ativo),
aggregate(notaavaliacao with avg as AvgRating))
GET /odata/DimProduto?
$filter=disponivel eq true and multiplo_incremento gt 0&
$apply=groupby((disponivel),
aggregate(multiplo_incremento with sum as TotalMultiplo))&
$select=disponivel,TotalMultiplo&
$orderby=TotalMultiplo desc&
$top=10
Use parenteses duplos em groupby, filtre antes de agregar e, apos
$apply, selecione apenas colunas agrupadas ou aliases.
Combos dignos de daily
GET /odata/DimEmpresa?
$select=nome,erp&
$filter=erp eq 'ERP-X' or contains(nome,'Beta')&
$orderby=nome asc&
$top=25&
$skip=25&
$count=true
GET /odata/DimProduto?
$filter=disponivel eq true and multiplo_incremento gt 0&
$apply=groupby((disponivel),
aggregate(multiplo_incremento with sum as TotalMultiplo))&
$select=disponivel,TotalMultiplo&
$orderby=TotalMultiplo desc&
$top=10
Erros mais comuns? Coluna inexistente (400), sintaxe torta em $filter
(400), $apply sem groupby valido (400) e
$top acima de 50 (400). Tudo em modo somente leitura: o maximo que voce
trava e o proprio script.
Links uteis
Swagger e ReDoc pedem o token Bearer diretamente nos formularios. Para MCP, configure a
URL remota no cliente e envie Authorization: Bearer ${QUERYCURIOUS_MCP_TOKEN}.