API REST
Descripción general
Sección titulada «Descripción general»Rebase genera automáticamente una API completa a partir de las definiciones de tus colecciones:
- API REST — Endpoints CRUD para cada colección en
/api/data/:slug - Especificación OpenAPI — Especificación legible por máquina en
/api/docs - Swagger UI — Explorador interactivo de API en
/api/swagger(solo en modo de desarrollo)
No se requiere código: define tus colecciones y la API aparecerá automáticamente.
Endpoints REST
Sección titulada «Endpoints REST»Para cada colección, se generan los siguientes endpoints. Cualquier otra ruta que monte el backend (autenticación, almacenamiento, administración, metadatos) se encuentra en el índice de endpoints.
| Método | Ruta | Descripción |
|---|---|---|
GET |
/api/data/:slug |
Listar entidades |
POST |
/api/data/:slug/query |
El mismo listado, con sus parámetros de consulta en un cuerpo JSON, para una consulta demasiado larga para una URL. Consulta Consultas demasiado largas para una URL |
GET |
/api/data/:slug/count |
Contar entidades |
GET |
/api/data/:slug/aggregate |
count(), sum(), avg(), min(), max(), opcionalmente agrupados. Acepta los mismos filtros que el endpoint de listado, y RLS se aplica a las filas que se agregan; consulta Consultas |
GET |
/api/data/:slug/:id |
Obtener una sola entidad |
POST |
/api/data/:slug |
Crear un registro |
PATCH |
/api/data/:slug/:id |
Actualizar un registro (parcial: solo se escriben las propiedades que envíes) |
DELETE |
/api/data/:slug/:id |
Eliminar un registro |
POST |
/api/data/:slug/bulk |
Crear múltiples entidades en una sola transacción |
PATCH |
/api/data/:slug/bulk |
Actualizar múltiples entidades en una sola transacción |
POST |
/api/data/:slug/bulk/delete |
Eliminar múltiples entidades en una sola transacción |
POST |
/api/data/_batch |
Escribir a través de colecciones en una sola transacción |
Rutas de subcolecciones
Sección titulada «Rutas de subcolecciones»Las relaciones anidadas son accesibles a través de rutas URL:
GET /api/data/authors/42/posts → list author's postsPOST /api/data/authors/42/posts/query → the same listing, parameters in the body (0.24+)POST /api/data/authors/42/posts/count → the same count, parameters in the body (0.24+)GET /api/data/authors/42/posts/7 → get a specific post by authorPOST /api/data/authors/42/posts → create a post for authorPATCH /api/data/authors/42/posts/7 → update the postDELETE /api/data/authors/42/posts/7 → delete the postMecánica de enrutamiento y análisis de segmentos
Sección titulada «Mecánica de enrutamiento y análisis de segmentos»Para manejar profundidades arbitrarias de subcolecciones anidadas, Rebase enruta las solicitudes entrantes utilizando la expresión regular del parámetro :rest{.+} de Hono. El motor interno de análisis de segmentos analiza las rutas contando los segmentos separados por barras:
- Número impar de segmentos (por ejemplo,
authors/42/posts-> 3 segmentos) representa una solicitud de listado de colección. - Número par de segmentos (por ejemplo,
authors/42/posts/7-> 4 segmentos) representa una operación sobre un ID de entidad específico. El último segmento se extrae como elentityIdde destino.
El motor filtra los espacios de nombres reservados del sistema (por ejemplo, history) del análisis de segmentos de ruta para evitar colisiones con los endpoints integrados.
Autenticación
Sección titulada «Autenticación»Todos los endpoints de datos requieren autenticación de forma predeterminada. Incluye un token Bearer en el encabezado Authorization:
curl -H "Authorization: Bearer <access-token>" \ https://api.example.com/api/data/productsPara llamadas entre servidores, utiliza la clave de servicio:
curl -H "Authorization: Bearer <service-key>" \ https://api.example.com/api/data/productsEl filtrado de una lista — operadores ?field=op.value, grupos or/and, el dialecto JSON where y cómo se combinan — tiene su propia página.
Ordenación
Sección titulada «Ordenación»Utiliza orderBy con el formato field:direction:
# Sort by price descendingGET /api/data/products?orderBy=price:desc
# Sort by name ascending (default)GET /api/data/products?orderBy=name:ascSi no se especifica una dirección, se asume asc. Una dirección que no sea ni asc ni desc, o un campo que la colección no tenga, resulta en un 400, no en un 200 con las filas en el orden que la base de datos prefiera, lo cual sería indistinguible de una ordenación exitosa.
Múltiples claves
Sección titulada «Múltiples claves»La sintaxis abreviada admite una sola clave. Para más, pasa un array JSON; la segunda clave desempatará las filas que la primera considere iguales:
# By category, and newest first within each categoryGET /api/data/products?orderBy=[{"field":"category"},{"field":"createdAt","direction":"desc"}]Ambas sintaxis están disponibles en todas las rutas que listan filas, incluidas las anidadas (/api/data/authors/:id/posts). Toda ordenación finaliza con el id de fila descendente, tanto si se solicita como si no: esto es lo que hace que el orden sea total, y paginar sobre un orden que no es total repite y omite filas.
Un parámetro ?orderBy= repetido no realiza una ordenación por múltiples claves; el último prevalece, como ocurre con cualquier otro parámetro de consulta. Utiliza el array.
Dónde se ordenan los valores NULL
Sección titulada «Dónde se ordenan los valores NULL»De forma predeterminada, los valores NULL se ordenan al final en orden ascendente y al principio en orden descendente, que es la convención propia de Postgres. Un tercer segmento separado por dos puntos permite cambiar esto:
# Newest first, with the undated rows at the end rather than the topGET /api/data/posts?orderBy=publishedAt:desc:lastLa forma de array JSON acepta una clave "nulls" para lo mismo:
GET /api/data/posts?orderBy=[{"field":"publishedAt","direction":"desc","nulls":"last"}]Cualquier valor distinto de first o last devuelve un 400, en lugar de un orden silenciosamente diferente. El cursor descrito a continuación respeta lo que haya declarado la ordenación, por lo que paginar sobre una clave que acepte valores nulos sigue siendo correcto con cualquiera de las dos ubicaciones.
Paginación
Sección titulada «Paginación»Utiliza limit y offset, o page — uno u otro:
# Limit and offsetGET /api/data/products?limit=20&offset=40
# Page-based (uses the default limit of 50)GET /api/data/products?page=3El límite predeterminado es 50, y el máximo es 1000. Ambos provienen de DEFAULT_LIST_LIMIT / MAX_LIST_LIMIT, que la especificación OpenAPI generada también reporta; un limit superior al máximo se rechaza en lugar de truncarse.
Los tres parámetros de ventana se rechazan en lugar de corregirse, y cada uno indica su error: INVALID_LIMIT, INVALID_OFFSET (un número entero de 0 o más) e INVALID_PAGE (un número entero de 1 o más). Una ventana sutilmente diferente a la solicitada no se puede distinguir de haber llegado al final de la colección, razón por la cual ninguno de ellos se trunca ni se ignora.
Por la misma razón, page y offset juntos son un 400 PAGE_WITH_OFFSET, en lugar de que uno de los dos gane en silencio.
Omitir el conteo
Sección titulada «Omitir el conteo»meta.total es un COUNT(*) sobre todas las filas que coinciden — en una tabla grande, la mayor parte de lo que cuesta una solicitud de listado. ?count=none (GET /api/data/orders?count=none) lo omite: no hay meta.total, mientras que meta.hasMore sigue siendo exacto (la lectura pide una fila más allá de la página). ?count=exact es el valor por defecto; cualquier otro valor es un 400 INVALID_COUNT. Una lectura con ?distinct=true nunca se cuenta, ni tampoco una búsqueda vectorial con un vector_threshold (consulta Búsqueda vectorial).
Paginación por cursor
Sección titulada «Paginación por cursor»offset vuelve a contar las filas en cada solicitud, por lo que una fila insertada o eliminada entre dos páginas desplaza la ventana y la iteración omite o repite filas de forma silenciosa. En cambio, ?after= realiza una búsqueda por cursor: la siguiente página comienza estrictamente después de la última fila servida.
Cada respuesta de listado incluye meta.nextCursor mientras exista otra página. Reenvíalo sin modificaciones:
GET /api/data/orders?orderBy=createdAt:desc&limit=100# → meta.nextCursor = "eyJrIjpbWyJjcmVhdGVkX2F0Iiw…"
GET /api/data/orders?orderBy=createdAt:desc&limit=100&after=eyJrIjpbWyJjcmVhdGVkX2F0Iiw…El cursor es opaco (codifica las claves de ordenación y los valores de la última fila para ellas), por lo que se aplican tres reglas, cada una resultando en un 400 en lugar de una página incorrecta:
| Situación | Código |
|---|---|
after con offset o page |
CURSOR_WITH_OFFSET — ambos indican dónde comienza la página |
after con un orderBy diferente al emitido originalmente |
CURSOR_ORDER_MISMATCH |
| Un cursor que esta API no emitió | INVALID_CURSOR |
Una solicitud que no especifica ningún orderBy adopta el del cursor, por lo que reenviar meta.nextCursor sin volver a declarar la ordenación funciona.
Tanto las ordenaciones por múltiples claves como las claves que admiten valores nulos paginan correctamente: la comparación se construye sobre cada clave en orden, con la ubicación de NULL que declaró la ordenación. El único orden que ningún cursor puede describir es la relevancia (_score), calculada por consulta y no almacenada en ninguna parte; dicho listado simplemente no incluye nextCursor.
Selección de columnas
Sección titulada «Selección de columnas»?fields= restringe una lectura a las columnas que especifiques. Es una proyección que se aplica directamente en la consulta, no un recorte posterior de la respuesta:
GET /api/data/posts?fields=id,title&limit=50La clave primaria siempre se devuelve (una fila a la que no se puede hacer referencia no puede actualizarse, eliminarse ni paginarse, y el cursor se deriva de ella), y las columnas con excludeFromApi permanecen ocultas tanto si se nombran como si no. Una columna desconocida resulta en un error 400 UNKNOWN_FIELD en lugar de una fila a la que silenciosamente le falte un campo.
Una relación nombrada en ?include= se devuelve tanto si ?fields= la nombra como si no:
?include=author&fields=title devuelve title, la clave y author. Nombrar también la
relación en ?fields= está permitido y no cambia nada.
?distinct=true colapsa las filas que sean idénticas en esas columnas:
# The statuses actually in useGET /api/data/posts?fields=status&distinct=trueSe rechaza (400) cuando se usa junto con un searchString clasificado por rango o una búsqueda vectorial, ya que estos añaden una puntuación por fila que hace que cada una sea distinta por definición, y cuando orderBy nombra una columna que fields no devuelve (DISTINCT_ORDER_BY_NOT_SELECTED), puesto que Postgres no puede ordenar una lectura DISTINCT mediante una expresión que no esté en su lista de selección.
?fields= y ?distinct= también funcionan en la ruta de obtención por ID y en las rutas de subcolecciones anidadas.
Formato de respuesta
Sección titulada «Formato de respuesta»Las respuestas de listados incluyen metadatos de paginación:
{ "data": [ { "id": 1, "name": "Widget", "price": 29.99 }, { "id": 2, "name": "Gadget", "price": 49.99 } ], "meta": { "total": 150, "limit": 20, "offset": 0, "hasMore": true, "nextCursor": "eyJrIjpbWyJpZCIsImRlc2MiXV0sInYiOnsiaWQiOjJ9LCJpIjoyfQ" }}nextCursor está presente mientras hasMore sea true y la página haya devuelto al menos una fila; está ausente en la última página y en ordenaciones que ningún cursor pueda describir.
Las respuestas de una sola entidad devuelven un objeto plano:
{ "id": 1, "name": "Widget", "price": 29.99, "createdAt": "2026-01-15T10:30:00Z"}Errores
Sección titulada «Errores»Cada fallo, de cualquier ruta, se devuelve en una misma estructura contenedora:
{ "error": { "message": "Unknown filter operator 'contains' on field 'title'.", "code": "UNKNOWN_FILTER_OPERATOR", "details": { "field": "title", "operator": "contains" }, "requestId": "9f1c0b8e-4d2a-4e1b-9d0f-2c7a5b3e6a11" }}message y code siempre están presentes. details aparece cuando el rechazo se refiere a algo en específico: el campo incorrecto, las rutas que fallaron. requestId aparece cuando la solicitud incluía un encabezado X-Request-ID o se le asignó uno; también se refleja en el encabezado de respuesta y es la referencia que debe citarse en un informe de error.
Bifurca tu lógica según code, nunca según message o el código de estado únicamente. Los códigos están en SCREAMING_SNAKE_CASE y son estables; los mensajes están escritos para una persona que lee una consola y pueden cambiar. El estado HTTP se encuentra en la respuesta, no en el cuerpo.
| Estado | Código típico | Significado |
|---|---|---|
| 400 | BAD_REQUEST, INVALID_INPUT, INVALID_LIMIT, INVALID_OFFSET, INVALID_PAGE |
La solicitud está mal formada o solicita algo imposible |
| 401 | UNAUTHORIZED |
Sin credencial, o una que no identifica a nadie |
| 403 | FORBIDDEN, SCOPE_MISSING, WRITE_DENIED |
Una credencial que identifica a alguien sin los permisos necesarios |
| 404 | NOT_FOUND |
El recurso solicitado no existe |
| 409 | CONFLICT |
Conflicto de estado: una clave duplicada, un árbol desincronizado |
| 501 | varía | La funcionalidad existe pero no está configurada en este despliegue |
| 503 | SERVICE_UNAVAILABLE |
Una dependencia está caída; la solicitud nunca llegó a ella |
Una funcionalidad que no esté disponible porque este despliegue no la habilitó responde 501 con un código y un motivo, no 404; un 404 inexplicable en una ruta a la que la interfaz de usuario acaba de llamar se interpreta como un despliegue defectuoso.
Las rutas añaden sus propios códigos más específicos además de estos (EMAIL_EXISTS, TOKEN_EXPIRED, UNKNOWN_FILTER_OPERATOR, …), por lo que debes considerar la lista de códigos como abierta. El SDK tipado convierte todos ellos en un único RebaseApiError que contiene status, code y details; consulta Manejo de errores.
Búsqueda de texto
Sección titulada «Búsqueda de texto»Utiliza searchString para búsquedas de texto completo en campos de texto:
GET /api/data/products?searchString=wireless%20keyboardBúsqueda vectorial
Sección titulada «Búsqueda vectorial»Una colección con una propiedad vector admite una búsqueda de vecinos más cercanos en el
listado: ?vector_search=embedding&vector=[0.15,0.22,-0.05], con vector_distance y
vector_threshold opcionales. Los parámetros, los umbrales y cómo se comportan los
filtros sobre el índice están en Búsqueda vectorial.
Consultas demasiado largas para una URL
Sección titulada «Consultas demasiado largas para una URL»POST /api/data/:slug/query (este listado) y POST /api/data/:slug/count
toman los parámetros de consulta en un cuerpo JSON, para una consulta cuya URL no cabría — un embedding, una lista in larga.
Consulta Consultas demasiado largas para una URL.
Inclusión de relaciones
Sección titulada «Inclusión de relaciones»Utiliza el parámetro include para incrustar entidades relacionadas:
# Include specific relationsGET /api/data/articles?include=author,categories
# Include all relations, one hop deepGET /api/data/articles?include=*
# A relation of a relation — up to three hopsGET /api/data/articles?include=comments.authorUn nombre que no sea una relación de la colección devuelve un 400 UNKNOWN_RELATION, en cualquier nivel. Anteriormente se ignoraba, respondiendo 200 con el campo simplemente ausente, lo cual era indistinguible de una fila que realmente no tuviera ninguna fila relacionada, haciendo que un error tipográfico pareciera datos vacíos. Una ruta con una profundidad superior a tres saltos devuelve INCLUDE_TOO_DEEP.
Delimitar una relación
Sección titulada «Delimitar una relación»El formato separado por comas no permite especificar un limit por relación, por lo que include también acepta JSON, diferenciándose por una llave de apertura:
GET /api/data/posts?include={"comments":{"limit":5,"where":{"published":["==",true]},"orderBy":"createdAt:desc","fields":["id","body"],"include":{"author":true}}}| Clave | Significado |
|---|---|
limit |
Filas por fila principal, no en toda la página. Lo aplica la consulta, así que las filas que lo superan nunca se leen de la base de datos |
where |
El mismo dialecto de filtrado que utiliza el where de nivel superior |
logical |
Un grupo or/and/not sobre las filas relacionadas |
orderBy |
La misma sintaxis de ordenación, incluida la posición de NULL. Los empates, y un limit sin orderBy, se resuelven por la clave de la fila relacionada, en orden ascendente |
fields |
Columnas de la fila relacionada; su clave primaria siempre se conserva |
include |
Relaciones de la fila relacionada, a su vez |
count |
true devuelve además el total de cada fila principal bajo _count ("_count":{"comments":37}), contado por la misma sentencia. Solo relaciones a muchos. Consulta Contar lo que deja fuera un límite |
true significa “cargarla por completo”, por lo que {"author":true} y author representan la misma solicitud. Ambas sintaxis funcionan en la ruta de listado, en la de obtención por ID y en las rutas de subcolecciones anidadas.
Cada salto se realiza mediante una única consulta por lotes para toda la página, nunca una por fila.
Las relaciones incluidas se incrustan directamente en la respuesta:
{ "id": 1, "title": "Getting Started", "authorId": 42, "author": { "id": 42, "name": "Jane Doe", "email": "jane@example.com", "companyId": 7 }}Una fila incluida es la que sirve la ruta propia del destino (GET /api/data/authors/42): las mismas columnas y codificaciones, claves foráneas como columnas, sin objetos de referencia.
Escritura
Sección titulada «Escritura»Las claves de idempotencia, las escrituras condicionales (ETag / If-Match), las operaciones de campo ($inc, $push, $pull, $merge), el upsert mediante una clave natural, Prefer: return=minimal, y el endpoint entre colecciones POST /api/data/_batch se encuentran detallados en su propia página: Escritura a través de REST.
Pipeline de hooks del ciclo de vida
Sección titulada «Pipeline de hooks del ciclo de vida»En Postgres, cada mutación REST (POST, PATCH, DELETE) ejecuta los callbacks de su colección en orden, dentro de la única transacción que lleva la escritura:
Request ──► BEGIN ──► beforeSave/beforeDelete ──► DB operation ──► afterSave/afterDelete ──► COMMIT ──► ResponseCada hook se espera, y un error en cualquiera de ellos deshace toda la escritura:
beforeSave,beforeDeletese ejecutan antes de la escritura. Pueden cambiar los valores entrantes, validar o rechazar la solicitud lanzando un error: el llamante recibe 400CALLBACK_REJECTEDy no se escribe nada.afterSave,afterDeletese ejecutan después de la escritura pero antes del commit. Un error revierte la fila y responde el mismo 400, condetails.stageindicando el hook. Mantienen la transacción abierta mientras se ejecutan, por eso son el lugar equivocado para un webhook o cualquier otra llamada de red: Hooks explica adónde va ese trabajo.
Endpoints del sistema
Sección titulada «Endpoints del sistema»| Método | Ruta | Autenticación | Descripción |
|---|---|---|---|
GET |
/health and /api/health |
ninguna | Comprobación de estado operativo y disponibilidad (liveness/readiness check) |
GET |
/api/docs |
ninguna; schema:read cuando enableSwagger es false (el valor predeterminado del runtime en producción) |
La especificación JSON de OpenAPI 3.0 |
GET |
/api/swagger |
ninguna | Swagger UI. Se sirve solo fuera de producción; REBASE_ENABLE_SWAGGER no puede activarlo en producción. No se sirve cuando enableSwagger es false, tampoco en desarrollo |
GET |
/api/meta/schema-version |
ninguna | El hash del esquema a partir del cual se construyó este backend y la versión del runtime: deliberadamente sin autenticación, y nada que describa el esquema |
GET |
/api/meta/contract |
schema:read: un administrador, la service key o una clave de API que lo tenga |
El contrato completo de las colecciones, para rebase generate-sdk --from. Cierre por fallo (fail-closed): 404 cuando no hay autenticación configurada |
GET |
/metrics |
REBASE_METRICS_TOKEN cuando esté configurado |
Métricas de Prometheus, cuando REBASE_METRICS=true |
OpenAPI / Swagger
Sección titulada «OpenAPI / Swagger»La especificación OpenAPI se genera automáticamente a partir de las definiciones de tus colecciones: describe los endpoints de listado, consulta (query), recuento, agregación, lectura, creación, actualización, eliminación y operaciones masivas (bulk) de cada colección servida por el backend, sus rutas anidadas y _batch, con sus parámetros de consulta y esquemas de respuesta. No es un mapa completo de toda la superficie HTTP (las rutas de autenticación, almacenamiento, funciones y cron se documentan únicamente en este sitio), y las columnas marcadas con excludeFromApi quedan excluidas de ella.
Para una colección con softDelete, la especificación indica
que una eliminación, individual o masiva, mueve la fila a la papelera, y documenta
?hard=true en ambas eliminaciones y ?deleted=include|only en el listado, la
lectura individual, el conteo y la agregación. El explorador de API de Studio envía
solo los parámetros que nombra la especificación, así que esto es lo que le permite
purgar una fila o listar la papelera.
Los clientes automatizados se autentican mediante una clave con alcance delimitado en lugar de una sesión: Claves de API.
Metadatos del esquema
Sección titulada «Metadatos del esquema»El esquema completo de colecciones del proyecto (cada colección, propiedad y relación) se sirve a un administrador autenticado:
GET /api/meta/contractNecesita el alcance schema:read, que tiene un administrador, y en un despliegue sin autenticación configurada no se sirve en absoluto (404 CONTRACT_UNAVAILABLE) para evitar exponer el esquema a cualquiera. Su endpoint hermano devuelve una cadena de versión que representa el esquema sin describirlo, y es deliberadamente accesible sin credenciales, que es lo que consulta un proceso de CI:
GET /api/meta/schema-versionPara conocer la forma de los endpoints en lugar del esquema subyacente, el documento de OpenAPI está disponible en GET /api/docs, con Swagger UI en /api/swagger fuera de producción.
Próximos pasos
Sección titulada «Próximos pasos»- SDK tipado — Cliente con seguridad de tipos para la API REST
- Colecciones — Define tu esquema de datos
- Reglas de seguridad (RLS) — Controla el acceso por fila