La API GraphQL del modelo
Las APIs de Tabla y el schema GraphQL generado del modelo de datos — operaciones CRUD, filtros, ordenación, paginación, totales y enums.
Cada app de Keplin sirve una API GraphQL en un endpoint propio. El schema de esa API no se escribe a mano: se genera a partir de dos fuentes — las APIs que construyes en el constructor y el modelo de datos de la app. Las tablas del modelo se convierten en tipos GraphQL con sus campos y relaciones; los enums del modelo se convierten en enums GraphQL; y una API de Tabla transforma una tabla en operaciones de lectura y escritura completas, con filtros, ordenación y paginación, sin que escribas una línea de SQL.
Esta página cubre el endpoint, las APIs de Tabla y el lenguaje de consulta que ofrecen a los clientes.
El endpoint de la app
Todas las operaciones de una app se sirven en un único endpoint GraphQL:
POST /api/graphql/<direccion-de-la-app>
En la app de ejemplo, POST /api/graphql/gestao-clientes. La petición es
un JSON con query y variables, como en cualquier servicio GraphQL — la
pestaña Docs de cada API te da ejemplos listos para copiar. Cuando la
app está publicada en una dirección propia, el mismo servicio responde
también en /api/graphql de esa dirección.
Quién puede llamar al endpoint:
| Quién llama | Cómo se autentica | Qué ve |
|---|---|---|
| Las pantallas de la app | Sesión del usuario de la app (automático) | APIs publicadas |
| Sistemas externos | Header x-api-key — ver Claves de API |
APIs publicadas dentro del scope de la clave |
| Quien construye | Sesión en la plataforma | APIs publicadas Y borradores (marcados como borrador) |
| Anónimos | Nada | Solo APIs públicas |
Nota
Las versiones de la app también cuentan: una clave API y las peticiones anónimas hablan siempre con la versión principal (o con la versión publicada de la dirección usada); quien construye ve SU versión de trabajo. Una clave nunca atrapa, por casualidad, lo que un developer está a medio cambiar.
Crear una API de Tabla
- Crea una API (Nueva API) con el
nombre que va a servir de base a las operaciones — por ejemplo
contas. - En la pestaña Construir, sección Pipeline, pulsa el botón Tabla. El bloque Tabla ocupa el pipeline entero — no se combina con pasos SQL, HTTP o Script.
- Elige la tabla en el selector Elige la tabla… — las tablas aparecen agrupadas por datasource, con búsqueda por nombre de tabla o de datasource.
- Activa las Acciones expuestas y ajusta los Campos incluidos (ver abajo).
- Guardar. Para publicar, necesita tabla elegida y al menos una acción activa.


Nota
El selector solo muestra tablas importadas al modelo de datos. Si está vacío, importa tablas en la pestaña Modelo de un datasource primero.
Acciones expuestas
Cada acción activa genera una operación en el schema, con el nombre
derivado de la base — para la API contas:
| Acción | Operación generada | Qué hace |
|---|---|---|
| Select | getContas (query) |
Lista con filtros/ordenación/paginación. Trae consigo countContas, el total. |
| Insert | addContas (mutation) |
Crea una fila. |
| Update | updateContas (mutation) |
Actualiza una fila por la clave primaria — parcial: solo cambia lo que envíes. |
| Delete | deleteContas (mutation) |
Elimina una fila por la clave primaria y devuelve true. |
La fila Acceso público (sin sesión) controla, acción a acción, lo que las pantallas públicas pueden llamar — detalles en APIs públicas.
Campos incluidos
El árbol Campos incluidos define la forma de la respuesta: desmarca los
campos que no quieres exponer, y expande los campos de navegación para
incluir entidades relacionadas — recursivamente, como en un editor GraphQL
visual. En una API contas, expandir el navegador contactos hace que los
clientes puedan pedir los contactos de cada cuenta en la misma llamada.

Atención
Con Insert o Update activos, los campos obligatorios de la tabla (no nulos, sin valor automático) quedan siempre incluidos — sin ellos no sería posible crear filas válidas. El constructor los muestra marcados y bloqueados.
Leer datos: filtros, ordenación, paginación
La query de lista acepta cuatro argumentos: where, order, take y
skip. Un ejemplo completo en la app Gestión de Clientes:
query {
getContas(
where: { cidade: { eq: "Lisboa" }, estado: { neq: "ARQUIVADA" } }
order: [{ nome: ASC }]
take: 20
skip: 0
) {
id
nome
cidade
contactos {
nome
email
}
}
}
El argumento where
Cada campo filtrable acepta operadores según el tipo:
| Tipo del campo | Operadores |
|---|---|
| Texto (y 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 |
Y dos combinadores para condiciones compuestas: and y or, que reciben
listas de filtros.
where: {
or: [
{ cidade: { eq: "Lisboa" } }
{ cidade: { eq: "Porto" } }
]
valorAnual: { gte: 10000 }
}
Reglas útiles:
eq: nullencuentra los registros con el campo vacío;neq: null, los rellenos.- Intervalos de fechas: las fechas guardadas como texto ISO (ej.
2026-08-11) ordenan alfabéticamente como ordenan en el tiempo, por esogt/lt/gte/lteen texto bastan para filtrar intervalos —dataCriacao: { gte: "2026-01-01", lt: "2026-07-01" }. inrecibe una lista de valores;ninla excluye.- Los valores del filtro van siempre parametrizados a la base de datos —
un
containscon texto malicioso no es un riesgo. contains,startsWithyendsWithbuscan el texto tal cual: un%o un_escrito es texto, no un comodín, y las mayúsculas y minúsculas no cuentan en ninguna base de datos ("ana"encuentra"Ana").
Ordenar y paginar
orderes una lista de{ campo: ASC }o{ campo: DESC }— varios elementos ordenan por varios campos, en el orden dado.takelimita el número de filas (techo de 10 000 por petición) yskipsalta las primeras N — juntos hacen la paginación clásica.
El total: count
Cada API de Tabla con Select activo gana también count<Nombre>, que
devuelve el total de filas del MISMO where. El par natural de una tabla
paginada es pedir la página y el total en una sola operación, con aliases:
query {
items: getContas(take: 10, skip: 0) { id nome }
total: countContas
}
Con filtro, pasa el mismo where a los dos campos — el total cuenta
exactamente las filas que la lista devolvería sin paginación.
Escribir datos
addContasrecibe los campos incluidos como argumentos (los obligatorios de la tabla son obligatorios en la mutation). En algunas bases de datos la respuesta es la fila creada; en otras,true— la pestaña Docs de la API muestra la forma exacta en tu caso. Una columna no nula con valor por omisión en la base de datos, o generada por ella, es opcional: si no la envías, queda el valor de la base.updateContasrecibe la clave primaria (obligatoria) y los demás campos como opcionales — solo actualiza lo que envíes — y devuelve la fila actualizada.deleteContasrecibe la clave primaria y devuelvetrue.
mutation ($nome: String!, $cidade: String) {
addContas(nome: $nome, cidade: $cidade) {
id
nome
}
}
Nota
Los permisos de la app se aplican aquí, siempre: si el usuario de la app
solo puede ver las cuentas de su equipo, getContas y countContas
devuelven — y cuentan — solo esas, llame quien llame (pantalla, informe
o workflow).
Una columna de fecha y hora sin zona horaria (timestamp, datetime) sale de
la API tal como está en la base de datos, en ISO sin zona
(2026-09-25T09:30:00): es la hora que está escrita allí, y llega igual a
quien la lee en cualquier zona horaria. Una columna solo de día sale como el
día (2026-09-25). Una columna con zona horaria (timestamptz,
datetimeoffset, el timestamp de MySQL) sale como instante, en ISO con zona
(2026-09-25T09:30:00.000Z), y las pantallas la muestran en la zona horaria
de quien la lee. Un Date devuelto por un paso SQL de una API de pipeline
sale también en ISO con zona. Las pantallas las muestran todas en el idioma de
la app. Un valor escrito con el tipo equivocado se acepta cuando no deja
dudas: «12» en un Int, 1000 en un String, «true» en un Boolean.
Tres reglas más de las escrituras:
- La clave primaria también puede ir en el
add. Con una clave generada por la base queda fuera; con una clave natural (un código de artículo, un código de país) es así como se crea el registro. - Un
updatea un registro que no existe, o que está fuera del ámbito de quien lo pide, no cambia nada y devuelvenull; undeleteen las mismas condiciones devuelvefalse. - Un rechazo de la base (clave repetida, registro con dependientes, campo obligatorio vacío, valor demasiado largo) llega como un mensaje claro, con el campo cuando la base lo indica.
Una petición puede encadenar hasta 12 niveles de relaciones y usar hasta 100 alias. Las relaciones se leen por lotes: las filas pedidas a la vez van en una sola consulta.
Enums
Un enum expone un conjunto fijo de valores en el schema GraphQL — el estado de una cuenta, la fase de una oportunidad. Se gestionan en la página APIs, pestaña Enums:
- Pulsa Nuevo enum.
- Dale un nombre (ej.
EstadoConta) y, si ayuda, una descripción. - Añade valores con Añadir valor — cada valor tiene un identificador (value), un label y un color opcionales. El color y el label los usan las pantallas; el value es lo que viaja en la API.
- Guardar.


Un enum se usa en dos sitios: como tipo de un campo del modelo (el campo pasa a aceptar solo esos valores, y en los filtros se comporta como texto) y como tipo de argumento de una API. En el schema, los clientes ven el enum con sus valores — el autocompletado del entorno de prueba los sugiere.
Atención
Eliminar un enum es permanente y los campos/argumentos que lo usaban dejan de referenciarlo. Prefiere editar los valores antes que eliminar el enum.
Al escribir un valor de enum, la API acepta el nombre que muestra el schema, sin
comillas (estado: Em_curso), y también el valor tal como está guardado, entre
comillas (estado: "Em curso"). Así lo envían los formularios y
keplin.api.mutate. Un valor que no pertenece al enum sigue siendo rechazado.
Explorar el schema en GraphiQL
La pestaña Probar de cualquier API incluye el GraphiQL — el entorno interactivo del endpoint de la app. Escribes la operación a la izquierda, ejecutas, y ves la respuesta a la derecha; el autocompletado conoce el schema entero, operaciones de Tabla incluidas. El botón Abrir en ventana te da el mismo entorno a pantalla completa.

Como estás autenticado en la plataforma, el GraphiQL ejecuta como un cliente pero ve también los borradores — cada operación en borrador aparece con la descripción «BORRADOR» en la documentación del schema. Y responde sobre tu versión de trabajo: lo que estás diseñando es lo que estás probando.
¿Por qué no…?
- ¿Por qué no veo la operación
getContasdesde fuera? O la API está en borrador (publícala), o la acción Select no está activa, o tu clave no tiene ese endpoint en el scope. - ¿Por qué no aparece un campo en la respuesta? No está marcado en Campos incluidos — los clientes solo pueden seleccionar lo que la API incluya.
- ¿Por qué
addContasdevuelve untrueen vez de la fila? Depende de la base de datos detrás de la tabla. Cuando necesites siempre la fila, sigue con ungetContasfiltrado por la clave. - ¿Por qué cambió el schema sin que yo tocara las APIs? El schema se genera del modelo: importar columnas nuevas, cambiar un enum o desactivar una entidad se refleja en la API en la llamada siguiente.