A API GraphQL do modelo
As APIs de Tabela e o schema GraphQL gerado do modelo de dados — operações CRUD, filtros, ordenação, paginação, totais e enums.
Cada app do Keplin serve uma API GraphQL num endpoint próprio. O schema dessa API não se escreve à mão: é gerado a partir de duas fontes — as APIs que constróis no construtor e o modelo de dados da app. As tabelas do modelo tornam-se tipos GraphQL com os seus campos e relações; os enums do modelo tornam-se enums GraphQL; e uma API de Tabela transforma uma tabela em operações de leitura e escrita completas, com filtros, ordenação e paginação, sem escreveres uma linha de SQL.
Esta página cobre o endpoint, as APIs de Tabela e a linguagem de consulta que elas oferecem aos clientes.
O endpoint da app
Todas as operações de uma app são servidas num único endpoint GraphQL:
POST /api/graphql/<endereco-da-app>
Na app de exemplo, POST /api/graphql/gestao-clientes. O pedido é um
JSON com query e variables, como em qualquer serviço GraphQL — o
separador Docs de cada API dá-te exemplos prontos a copiar. Quando a
app está publicada num endereço próprio, o mesmo serviço responde também
em /api/graphql desse endereço.
Quem pode chamar o endpoint:
| Quem chama | Como se autentica | O que vê |
|---|---|---|
| Os ecrãs da app | Sessão do utilizador da app (automático) | APIs publicadas |
| Sistemas externos | Header x-api-key — ver Chaves de API |
APIs publicadas dentro do scope da key |
| Quem constrói | Sessão na plataforma | APIs publicadas E rascunhos (marcados como rascunho) |
| Anónimos | Nada | Apenas APIs públicas |
Nota
As versões da app também contam: uma API key e os pedidos anónimos falam sempre com a versão principal (ou com a versão publicada do endereço usado); quem constrói vê a SUA versão de trabalho. Uma key nunca apanha, por acaso, aquilo que um developer está a meio de mudar.
Criar uma API de Tabela
- Cria uma API (Nova API) com o nome
que vai servir de base às operações — por exemplo
contas. - No separador Construir, secção Pipeline, carrega no botão Tabela. O bloco Tabela ocupa o pipeline inteiro — não se combina com passos SQL, HTTP ou Script.
- Escolhe a tabela no selector Escolhe a tabela… — as tabelas aparecem agrupadas por datasource, com pesquisa por nome de tabela ou de datasource.
- Activa as Acções expostas e ajusta os Campos incluídos (ver abaixo).
- Gravar. Para publicar, precisa de tabela escolhida e pelo menos uma acção activa.


Nota
O selector só mostra tabelas importadas para o modelo de dados. Se estiver vazio, importa tabelas no separador Modelo de um datasource primeiro.
Acções expostas
Cada acção activa gera uma operação no schema, com o nome derivado da
base — para a API contas:
| Acção | Operação gerada | O que faz |
|---|---|---|
| Select | getContas (query) |
Lista com filtros/ordenação/paginação. Traz consigo countContas, o total. |
| Insert | addContas (mutation) |
Cria uma linha. |
| Update | updateContas (mutation) |
Actualiza uma linha pela chave primária — parcial: só muda o que enviares. |
| Delete | deleteContas (mutation) |
Apaga uma linha pela chave primária e devolve true. |
A linha Acesso público (sem sessão) controla, acção a acção, o que os ecrãs públicos podem chamar — detalhes em APIs públicas.
Campos incluídos
A árvore Campos incluídos define a forma da resposta: desmarca os
campos que não queres expor, e expande os campos de navegação para
incluir entidades relacionadas — recursivamente, como num editor GraphQL
visual. Numa API contas, expandir o navegador contactos faz com que
os clientes possam pedir os contactos de cada conta na mesma chamada.

Atenção
Com Insert ou Update activos, os campos obrigatórios da tabela (não-nulos, sem valor automático) ficam sempre incluídos — sem eles não era possível criar linhas válidas. O construtor mostra-os marcados e bloqueados.
Ler dados: filtros, ordenação, paginação
A query de lista aceita quatro argumentos: where, order, take e
skip. Um exemplo completo na app Gestão de Clientes:
query {
getContas(
where: { cidade: { eq: "Lisboa" }, estado: { neq: "ARQUIVADA" } }
order: [{ nome: ASC }]
take: 20
skip: 0
) {
id
nome
cidade
contactos {
nome
email
}
}
}
O argumento where
Cada campo filtrável aceita operadores conforme o tipo:
| Tipo do campo | Operadores |
|---|---|
| Texto (e enums) | eq, neq, contains, startsWith, endsWith, gt, gte, lt, lte, in, nin |
Números (Int, Float) |
eq, neq, gt, gte, lt, lte, in, nin |
Boolean |
eq, neq |
ID |
eq, neq, in, nin |
E dois combinadores para condições compostas: and e or, que recebem
listas de filtros.
where: {
or: [
{ cidade: { eq: "Lisboa" } }
{ cidade: { eq: "Porto" } }
]
valorAnual: { gte: 10000 }
}
Regras úteis:
eq: nullencontra os registos com o campo vazio;neq: null, os preenchidos.- Intervalos de datas: datas guardadas como texto ISO (ex.
2026-08-11) ordenam alfabeticamente como ordenam no tempo, por issogt/lt/gte/lteem texto chegam para filtrar intervalos —dataCriacao: { gte: "2026-01-01", lt: "2026-07-01" }. inrecebe uma lista de valores;ninexclui-a.- Os valores do filtro vão sempre parametrizados para a base de dados —
um
containscom texto malicioso não é um risco.
Ordenar e paginar
orderé uma lista de{ campo: ASC }ou{ campo: DESC }— vários itens ordenam por vários campos, pela ordem dada.takelimita o número de linhas (tecto de 10 000 por pedido) eskipsalta as primeiras N — juntos fazem a paginação clássica.
O total: count
Cada API de Tabela com Select activo ganha também count<Nome>, que
devolve o total de linhas do MESMO where. O par natural de uma tabela
paginada é pedir a página e o total numa só operação, com aliases:
query {
items: getContas(take: 10, skip: 0) { id nome }
total: countContas
}
Com filtro, passa o mesmo where aos dois campos — o total conta
exactamente as linhas que a lista devolveria sem paginação.
Escrever dados
addContasrecebe os campos incluídos como argumentos (os obrigatórios da tabela são obrigatórios na mutation). Em algumas bases de dados a resposta é a linha criada; noutras,true— o separador Docs da API mostra a forma exacta no teu caso.updateContasrecebe a chave primária (obrigatória) e os restantes campos como opcionais — só actualiza o que enviares — e devolve a linha actualizada.deleteContasrecebe a chave primária e devolvetrue.
mutation ($nome: String!, $cidade: String) {
addContas(nome: $nome, cidade: $cidade) {
id
nome
}
}
Nota
As permissões da app aplicam-se aqui, sempre: se o utilizador da app
só pode ver as contas da sua equipa, getContas e countContas
devolvem — e contam — apenas essas, chame quem chamar (ecrã, relatório
ou workflow).
Enums
Um enum expõe um conjunto fixo de valores no schema GraphQL — o estado de uma conta, a fase de uma oportunidade. Gerem-se na página APIs, separador Enums:
- Carrega em Novo enum.
- Dá um nome (ex.
EstadoConta) e, se ajudar, uma descrição. - Acrescenta valores com Adicionar valor — cada valor tem um identificador (value), um label e uma cor opcionais. A cor e o label são usados pelos ecrãs; o value é o que viaja na API.
- Gravar.


Um enum usa-se em dois sítios: como tipo de um campo do modelo (o campo passa a aceitar apenas aqueles valores, e nos filtros comporta-se como texto) e como tipo de argumento de uma API. No schema, os clientes vêem o enum com os seus valores — o autocompletar do ambiente de teste sugere-os.
Atenção
Eliminar um enum é permanente e os campos/argumentos que o usavam deixam de o referenciar. Prefere editar os valores a eliminar o enum.
Explorar o schema no GraphiQL
O separador Testar de qualquer API inclui o GraphiQL — o ambiente interactivo do endpoint da app. Escreves a operação à esquerda, executas, e vês a resposta à direita; o autocompletar conhece o schema inteiro, operações de Tabela incluídas. O botão Abrir em janela dá-te o mesmo ambiente a ecrã inteiro.

Como estás autenticado na plataforma, o GraphiQL executa como um cliente mas vê também os rascunhos — cada operação em rascunho aparece com a descrição "RASCUNHO" na documentação do schema. E responde sobre a tua versão de trabalho: o que estás a desenhar é o que estás a testar.
Porque não…?
- Porque não vejo a operação
getContasde fora? Ou a API está em rascunho (publica-a), ou a acção Select não está activa, ou a tua key não tem esse endpoint no scope. - Porque não aparece um campo na resposta? Não está marcado em Campos incluídos — os clientes só podem seleccionar o que a API incluir.
- Porque devolve
addContasumtrueem vez da linha? Depende da base de dados por trás da tabela. Quando precisares sempre da linha, segue com umgetContasfiltrado pela chave. - Porque mudou o schema sem eu tocar nas APIs? O schema é gerado do modelo: importar colunas novas, mudar um enum ou desactivar uma entidade reflecte-se na API na chamada seguinte.