Zum Inhalt springen

REST API

Rebase generiert automatisch eine vollständige API aus Ihren Collection-Definitionen:

  • REST-API — CRUD-Endpunkte für jede Collection unter /api/data/:slug
  • OpenAPI-Spezifikation — Maschinenlesbare Spezifikation unter /api/docs
  • Swagger UI — Interaktiver API-Explorer unter /api/swagger (nur im Entwicklungsmodus)

Es ist kein Code erforderlich — definieren Sie Ihre Collections und die API steht automatisch zur Verfügung.

Für jede Collection werden die folgenden Endpunkte generiert. Alle anderen vom Backend bereitgestellten Routen — Auth, Storage, Admin, Meta — finden Sie im Endpunkt-Index.

Methode Pfad Beschreibung
GET /api/data/:slug Entitäten auflisten
POST /api/data/:slug/query Dieselbe Auflistung, ihre Query-Parameter in einem JSON-Body — für eine Abfrage, die für eine URL zu lang ist. Siehe Abfragen, die für eine URL zu lang sind
GET /api/data/:slug/count Entitäten zählen
GET /api/data/:slug/aggregate count(), sum(), avg(), min(), max(), optional gruppiert. Akzeptiert dieselben Filter wie der Listen-Endpunkt, und RLS gilt für die aggregierten Zeilen — siehe Abfragen
GET /api/data/:slug/:id Eine einzelne Entität abrufen
POST /api/data/:slug Einen Datensatz erstellen
PATCH /api/data/:slug/:id Einen Datensatz aktualisieren (partiell — es werden nur die gesendeten Eigenschaften geschrieben)
DELETE /api/data/:slug/:id Einen Datensatz löschen
POST /api/data/:slug/bulk Mehrere Entitäten in einer Transaktion erstellen
PATCH /api/data/:slug/bulk Mehrere Entitäten in einer Transaktion aktualisieren
POST /api/data/:slug/bulk/delete Mehrere Entitäten in einer Transaktion löschen
POST /api/data/_batch Collection-übergreifend in einer Transaktion schreiben

Verschachtelte Relationen sind über URL-Pfade zugänglich:

GET /api/data/authors/42/posts → list author's posts
POST /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 author
POST /api/data/authors/42/posts → create a post for author
PATCH /api/data/authors/42/posts/7 → update the post
DELETE /api/data/authors/42/posts/7 → delete the post

Um beliebige Verschachtelungstiefen von Subcollections zu verarbeiten, routet Rebase eingehende Anfragen mithilfe des :rest{.+}-Parameter-Regex von Hono. Die interne Segment-Parsing-Engine analysiert Pfade durch das Zählen von durch Schrägstriche getrennten Segmenten:

  • Ungerade Segmentanzahl (z. B. authors/42/posts -> 3 Segmente) stellt eine Anfrage zum Auflisten einer Collection dar.
  • Gerade Segmentanzahl (z. B. authors/42/posts/7 -> 4 Segmente) stellt eine Operation auf einer bestimmten Entitäts-ID dar. Das letzte Segment wird als Ziel-entityId extrahiert.

Die Engine filtert reservierte System-Namespaces (z. B. history) aus der Pfadsegmentanalyse heraus, um Kollisionen mit integrierten Endpunkten zu verhindern.

Alle Datenendpunkte erfordern standardmäßig eine Authentifizierung. Fügen Sie ein Bearer-Token in den Authorization-Header ein:

curl -H "Authorization: Bearer <access-token>" \
https://api.example.com/api/data/products

Verwenden Sie für Server-zu-Server-Aufrufe den Service-Key:

curl -H "Authorization: Bearer <service-key>" \
https://api.example.com/api/data/products

Das Filtern einer Liste — ?field=op.value-Operatoren, or/and-Gruppen, der JSON-Dialekt where und wie sie zusammenwirken — hat eine eigene Seite.

Verwenden Sie orderBy mit dem Format field:direction:

# Sort by price descending
GET /api/data/products?orderBy=price:desc
# Sort by name ascending (default)
GET /api/data/products?orderBy=name:asc

Eine fehlende Richtung entspricht asc. Eine Richtung, die weder asc noch desc ist, oder ein Feld, das die Collection nicht besitzt, führt zu einem 400 — nicht zu einem 200 mit den Zeilen in einer beliebigen Reihenfolge der Datenbank, was von einer funktionierenden Sortierung nicht zu unterscheiden wäre.

Die Kurzschreibweise unterstützt einen Schlüssel. Für mehrere übergeben Sie ein JSON-Array — der zweite Schlüssel entscheidet bei Zeilen, die nach dem ersten gleich sind:

# By category, and newest first within each category
GET /api/data/products?orderBy=[{"field":"category"},{"field":"createdAt","direction":"desc"}]

Beide Schreibweisen funktionieren bei jeder Route, die Zeilen auflistet, einschließlich verschachtelter Routen (/api/data/authors/:id/posts). Jede Sortierung endet mit der absteigenden Zeilen-ID, unabhängig davon, ob Sie dies angegeben haben: Das macht die Sortierung total, und das Paginieren über eine nicht-totale Ordnung wiederholt oder überspringt Zeilen.

Ein wiederholter ?orderBy=-Parameter ist keine Sortierung nach mehreren Schlüsseln — der letzte gewinnt, wie bei jedem anderen Query-Parameter. Verwenden Sie das Array.

Standardmäßig werden NULL-Werte aufsteigend als Letztes und absteigend als Erstes sortiert, was der Konvention von Postgres entspricht. Ein drittes, durch Doppelpunkt abgetrenntes Segment bestimmt dies anders:

# Newest first, with the undated rows at the end rather than the top
GET /api/data/posts?orderBy=publishedAt:desc:last

Die JSON-Array-Form akzeptiert für denselben Zweck den Schlüssel "nulls":

GET /api/data/posts?orderBy=[{"field":"publishedAt","direction":"desc","nulls":"last"}]

Alles andere als first oder last ist ein 400-Fehler, keine stillschweigend abweichende Reihenfolge. Der unten beschriebene Cursor berücksichtigt die deklarierte Sortierung, sodass die Paginierung über einen Nullable-Schlüssel bei beiden Platzierungen korrekt bleibt.

Verwenden Sie limit und offset, oder page — das eine oder das andere:

# Limit and offset
GET /api/data/products?limit=20&offset=40
# Page-based (uses the default limit of 50)
GET /api/data/products?page=3

Das Standardlimit ist 50, das Maximum liegt bei 1000. Beide Werte stammen aus DEFAULT_LIST_LIMIT / MAX_LIST_LIMIT, was auch die generierte OpenAPI-Spezifikation ausweist — ein limit über dem Maximum wird abgelehnt und nicht gekappt.

Alle drei Fenster-Parameter werden abgelehnt statt korrigiert, und jeder benennt sich selbst: INVALID_LIMIT, INVALID_OFFSET (eine ganze Zahl von 0 oder mehr) und INVALID_PAGE (eine ganze Zahl von 1 oder mehr). Ein Fenster, das sich stillschweigend von dem angeforderten unterscheidet, lässt sich nicht vom Erreichen des Endes der Collection unterscheiden, weshalb keines davon gekappt oder ignoriert wird.

Aus demselben Grund sind page und offset zusammen ein 400 PAGE_WITH_OFFSET, statt dass eines von beiden stillschweigend gewinnt.

meta.total ist ein COUNT(*) über alle passenden Zeilen — bei einer großen Tabelle der größte Teil dessen, was eine Listenanfrage kostet. ?count=none (GET /api/data/orders?count=none) überspringt sie: kein meta.total, während meta.hasMore exakt bleibt (der Read fragt eine Zeile über die Seite hinaus ab). ?count=exact ist der Standard; jeder andere Wert ist ein 400 INVALID_COUNT. Ein ?distinct=true-Read wird nie gezählt, ebenso wenig eine Vektorsuche mit einem vector_threshold (siehe Vektorsuche).

offset zählt Zeilen bei jeder Anfrage neu, sodass eine zwischen zwei Seiten eingefügte oder gelöschte Zeile das Fenster verschiebt und der Abruf stillschweigend Zeilen überspringt oder wiederholt. ?after= sucht stattdessen direkt (Seek-Verfahren): Die nächste Seite beginnt strikt nach der zuletzt ausgelieferten Zeile.

Jede Listen-Antwort enthält meta.nextCursor, solange es eine weitere Seite gibt. Senden Sie diesen unverändert zurück:

GET /api/data/orders?orderBy=createdAt:desc&limit=100
# → meta.nextCursor = "eyJrIjpbWyJjcmVhdGVkX2F0Iiw…"
GET /api/data/orders?orderBy=createdAt:desc&limit=100&after=eyJrIjpbWyJjcmVhdGVkX2F0Iiw…

Der Cursor ist opak — er kodiert die Sortierschlüssel und die Werte der letzten Zeile für diese —, woraus drei Regeln folgen, die jeweils zu einem 400-Fehler anstelle einer falschen Seite führen:

Situation Code
after mit offset oder page CURSOR_WITH_OFFSET — beide geben an, wo die Seite beginnt
after mit einem anderen orderBy als dem, unter dem er ausgestellt wurde CURSOR_ORDER_MISMATCH
Ein Cursor, der nicht von dieser API ausgestellt wurde INVALID_CURSOR

Eine Anfrage, die kein orderBy angibt, übernimmt die Sortierung des Cursors, sodass das Zurücksenden von meta.nextCursor ohne erneute Angabe der Sortierung funktioniert.

Sortierungen nach mehreren Schlüsseln und Nullable-Schlüssel werden beide korrekt paginiert: Der Vergleich wird über jeden Schlüssel der Reihe nach aufgebaut, unter Berücksichtigung der für die Sortierung deklarierten NULL-Platzierung. Die einzige Sortierung, die kein Cursor beschreiben kann, ist Relevanz (_score) — pro Abfrage berechnet und nirgends gespeichert —, und eine solche Auflistung enthält schlicht keinen nextCursor.

?fields= schränkt einen Lesevorgang auf die von Ihnen angegebenen Spalten ein. Es handelt sich um eine Projektion, die in die Datenbankabfrage verschoben wird, nicht um ein Kürzen der Antwort:

GET /api/data/posts?fields=id,title&limit=50

Der Primärschlüssel wird immer zurückgegeben (eine Zeile, die nicht adressiert werden kann, kann weder aktualisiert, gelöscht noch per Paginierung überschritten werden — und der Cursor wird daraus abgeleitet), und Spalten mit excludeFromApi bleiben verborgen, unabhängig davon, ob sie angegeben sind. Eine unbekannte Spalte führt zu einem 400 UNKNOWN_FIELD statt zu einer Zeile, bei der stillschweigend ein Feld fehlt.

Eine Relation, die in ?include= genannt wird, kommt zurück, unabhängig davon, ob ?fields= sie nennt: ?include=author&fields=title liefert title, den Schlüssel und author. Die Relation zusätzlich in ?fields= zu nennen, ist erlaubt und ändert nichts.

?distinct=true fasst Zeilen zusammen, die bezüglich dieser Spalten identisch sind:

# The statuses actually in use
GET /api/data/posts?fields=status&distinct=true

Es wird zusammen mit einem gerankten searchString oder einer Vektorsuche abgelehnt (400), da diese einen Score pro Zeile anhängen, der jede Zeile per Konstruktion eindeutig macht, sowie wenn orderBy eine Spalte benennt, die fields nicht zurückgibt (DISTINCT_ORDER_BY_NOT_SELECTED) — Postgres kann einen DISTINCT-Lesevorgang nicht nach einem Ausdruck sortieren, der außerhalb seiner SELECT-Liste liegt.

?fields= und ?distinct= funktionieren auch auf der Get-by-ID-Route und den verschachtelten Subcollection-Routen.

Listen-Antworten enthalten Paginierungs-Metadaten:

{
"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 ist vorhanden, solange hasMore wahr ist und die Seite mindestens eine Zeile zurückgegeben hat; auf der letzten Seite und bei einer Sortierung, die kein Cursor beschreiben kann, fehlt er.

Antworten für einzelne Entitäten geben ein flaches Objekt zurück:

{
"id": 1,
"name": "Widget",
"price": 29.99,
"createdAt": "2026-01-15T10:30:00Z"
}

Jeder Fehler, von jeder Route, wird in einem einheitlichen Envelope zurückgegeben:

{
"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 und code sind immer vorhanden. details erscheint, wenn sich die Ablehnung auf etwas Konkretes bezieht — das fehlerhafte Feld, die fehlgeschlagenen Pfade. requestId erscheint, wenn die Anfrage einen X-Request-ID-Header enthielt oder ihr einer zugewiesen wurde; er wird auch im Response-Header zurückgegeben und sollte in einem Fehlerbericht angegeben werden.

Verzweigen Sie nach code, niemals nach message oder allein nach dem Status. Codes sind in SCREAMING_SNAKE_CASE und stabil; Meldungen sind für Personen verfasst, die eine Konsole lesen, und können sich ändern. Der HTTP-Status befindet sich in der Antwort, nicht im Body.

Status Typischer Code Bedeutung
400 BAD_REQUEST, INVALID_INPUT, INVALID_LIMIT, INVALID_OFFSET, INVALID_PAGE Die Anfrage ist fehlerhaft oder fordert etwas Unmögliches an
401 UNAUTHORIZED Keine Anmeldedaten oder solche, die niemanden identifizieren
403 FORBIDDEN, SCOPE_MISSING, WRITE_DENIED Anmeldedaten, die jemanden ohne die erforderlichen Rechte identifizieren
404 NOT_FOUND Das adressierte Objekt existiert nicht
409 CONFLICT Der Zustand steht in Konflikt — ein doppelter Schlüssel, ein veränderter Baum (Dirty Tree)
501 variiert Die Schnittstelle existiert, ist aber auf diesem Deployment nicht konfiguriert
503 SERVICE_UNAVAILABLE Eine Abhängigkeit ist nicht verfügbar; die Anfrage hat sie nie erreicht

Eine Schnittstelle, die nicht vorhanden ist, weil dieses Deployment sie nicht aktiviert hat, antwortet mit 501 samt Code und Begründung, nicht mit 404 — ein unerklärlicher 404 auf einer Route, die das UI gerade aufgerufen hat, wirkt wie ein fehlerhaftes Deployment.

Routen fügen darüber hinaus ihre eigenen spezifischeren Codes hinzu (EMAIL_EXISTS, TOKEN_EXPIRED, UNKNOWN_FILTER_OPERATOR, …), betrachten Sie die Liste der Codes daher als offen. Das typisierte SDK wandelt alle in einen einzigen RebaseApiError um, der status, code und details enthält — siehe Fehlerbehandlung.

Verwenden Sie searchString für die Volltextsuche über String-Felder:

GET /api/data/products?searchString=wireless%20keyboard

Eine Collection mit einer vector-Eigenschaft nimmt auf der Auflistung eine Nearest-Neighbour-Suche entgegen: ?vector_search=embedding&vector=[0.15,0.22,-0.05], mit optionalem vector_distance und vector_threshold. Die Parameter, Schwellenwerte und das Verhalten von Filtern auf dem Index stehen unter Vektorsuche.

POST /api/data/:slug/query (diese Auflistung) und POST /api/data/:slug/count nehmen die Query-Parameter in einem JSON-Body entgegen, für eine Abfrage, deren URL nicht passen würde — ein Embedding, eine lange in-Liste. Siehe Abfragen, die für eine URL zu lang sind.

Verwenden Sie den Parameter include, um verknüpfte Entitäten einzubinden:

# Include specific relations
GET /api/data/articles?include=author,categories
# Include all relations, one hop deep
GET /api/data/articles?include=*
# A relation of a relation — up to three hops
GET /api/data/articles?include=comments.author

Ein Name, der keine Relation der Collection ist, führt auf jeder Ebene zu einem 400 UNKNOWN_RELATION. Früher wurde dies ignoriert, was mit 200 antwortete, wobei das Feld einfach fehlte — nicht zu unterscheiden von einer Zeile, die tatsächlich keine verknüpfte Zeile hat, sodass ein Tippfehler exakt wie leere Daten aussah. Ein Pfad, der tiefer als drei Hops ist, führt zu INCLUDE_TOO_DEEP.

Die kommagetrennte Form bietet keine Möglichkeit, ein limit pro Relation anzugeben, daher akzeptiert include auch JSON — erkennbar an einer führenden geschweiften Klammer:

GET /api/data/posts?include={"comments":{"limit":5,"where":{"published":["==",true]},"orderBy":"createdAt:desc","fields":["id","body"],"include":{"author":true}}}
Schlüssel Bedeutung
limit Zeilen pro übergeordneter Zeile, nicht über die gesamte Seite. Wird von der Abfrage angewendet, sodass die Zeilen darüber hinaus nie aus der Datenbank gelesen werden
where Derselbe Filterdialekt, den auch das Top-Level-where verwendet
logical Eine or/and/not-Gruppe über die verknüpften Zeilen
orderBy Dieselbe Sortierschreibweise, einschließlich der NULL-Platzierung. Gleichstände sowie ein limit ohne orderBy richten sich nach dem Schlüssel der verknüpften Zeile, aufsteigend
fields Spalten der verknüpften Zeile; ihr Primärschlüssel bleibt immer erhalten
include Relationen der verknüpften Zeile wiederum
count true liefert zusätzlich die Gesamtzahl jedes Elternelements unter _count ("_count":{"comments":37}), gezählt von derselben Anweisung. Nur To-many-Relationen. Siehe Zählen, was ein Limit auslässt

true bedeutet „vollständig laden“, sodass {"author":true} und author dieselbe Anfrage sind. Beide Schreibweisen funktionieren auf der Listen-Route, der Get-by-ID-Route und den verschachtelten Subcollection-Routen.

Jeder Hop ist eine gebatchte Abfrage für die gesamte Seite, niemals eine pro Zeile.

Eingebundene Relationen werden direkt in die Antwort eingebettet:

{
"id": 1,
"title": "Getting Started",
"authorId": 42,
"author": {
"id": 42,
"name": "Jane Doe",
"email": "jane@example.com",
"companyId": 7
}
}

Eine eingebundene Zeile ist das, was die eigene Route des Ziels (GET /api/data/authors/42) liefert: dieselben Spalten und Kodierungen, Fremdschlüssel als Spalten, keine Referenzobjekte.

Idempotenzschlüssel, bedingte Schreibvorgänge (ETag / If-Match), Feldoperationen ($inc, $push, $pull, $merge), Upsert auf einem natürlichen Schlüssel, Prefer: return=minimal und der Collection-übergreifende Endpunkt POST /api/data/_batch befinden sich alle auf einer eigenen Seite: Schreiboperationen über REST.

Auf Postgres führt jede REST-Mutation (POST, PATCH, DELETE) die Callbacks ihrer Collection der Reihe nach aus, innerhalb der einen Transaktion, die den Schreibvorgang trägt:

Request ──► BEGIN ──► beforeSave/beforeDelete ──► DB operation ──► afterSave/afterDelete ──► COMMIT ──► Response

Jeder Hook wird abgewartet, und ein Fehler in einem davon macht den gesamten Schreibvorgang rückgängig:

  1. beforeSave, beforeDelete laufen vor dem Schreibvorgang. Sie können die eingehenden Werte ändern, validieren oder die Anfrage durch einen Fehler abweisen — der Aufrufer erhält 400 CALLBACK_REJECTED, und nichts wird geschrieben.
  2. afterSave, afterDelete laufen nach dem Schreibvorgang, aber vor dem Commit. Ein Fehler rollt die Zeile zurück und antwortet mit demselben 400, wobei details.stage den Hook benennt. Sie halten die Transaktion offen, solange sie laufen, und sind deshalb der falsche Ort für einen Webhook oder jeden anderen Netzwerkaufruf — Hooks beschreibt, wohin diese Arbeit gehört.
Methode Pfad Auth Beschreibung
GET /health und /api/health keine Liveness-/Readiness-Prüfung
GET /api/docs keine; schema:read, wenn enableSwagger false ist (der Standard der Runtime in der Produktion) Die OpenAPI 3.0-JSON-Spezifikation
GET /api/swagger keine Swagger UI. Wird nur außerhalb der Produktion bereitgestellt; REBASE_ENABLE_SWAGGER kann es in der Produktion nicht einschalten. Nicht bereitgestellt, wenn enableSwagger false ist, auch nicht in der Entwicklung
GET /api/meta/schema-version keine Der Schema-Hash, aus dem dieses Backend erstellt wurde, und die Runtime-Version — bewusst unauthentifiziert, und nichts, was das Schema beschreibt
GET /api/meta/contract schema:read: ein Admin, der Service-Key oder ein API-Key, der ihn hält Der vollständige Collection-Vertrag für rebase generate-sdk --from. Fail-closed: 404, wenn keine Authentifizierung konfiguriert ist
GET /metrics REBASE_METRICS_TOKEN, falls gesetzt Prometheus-Metriken, wenn REBASE_METRICS=true

Die OpenAPI-Spezifikation wird automatisch aus Ihren Collection-Definitionen generiert: Sie beschreibt die Listen-, Query-, Count-, Aggregate-, Lese-, Erstellungs-, Aktualisierungs-, Lösch- und Bulk-Endpunkte jeder vom Backend bereitgestellten Collection, deren verschachtelte Routen und _batch, mitsamt deren Query-Parametern und Response-Schemas. Sie ist kein vollständiges Abbild der gesamten HTTP-Oberfläche — die Routen für Auth, Storage, Functions und Cron sind ausschließlich auf dieser Website dokumentiert — und als excludeFromApi markierte Spalten werden ausgelassen.

Für eine softDelete-Collection gibt die Spezifikation an, dass ein Löschen, einzeln oder als Bulk, die Zeile in den Papierkorb verschiebt, und dokumentiert ?hard=true bei beiden Löschvorgängen sowie ?deleted=include|only bei der Liste, dem einzelnen Lesen, der Zählung und der Aggregation. Der API-Explorer von Studio sendet nur die Parameter, die die Spezifikation nennt; erst dadurch kann er eine Zeile endgültig löschen oder den Papierkorb auflisten.

Automatisierte Aufrufer authentifizieren sich mit einem Key mit beschränktem Gültigkeitsbereich (Scoped Key) anstelle einer Session: API-Keys.

Das vollständige Collection-Schema des Projekts — jede Collection, Eigenschaft und Relation — wird einem authentifizierten Admin bereitgestellt:

GET /api/meta/contract

Es braucht den Scope schema:read, den ein Admin hält, und wird auf einem Deployment ohne konfigurierte Authentifizierung überhaupt nicht bereitgestellt (404 CONTRACT_UNAVAILABLE), anstatt das Schema für jeden offenzulegen. Das Gegenstück dazu gibt einen Versions-String zurück, der das Schema repräsentiert, ohne es zu beschreiben, und bewusst ohne Anmeldedaten erreichbar ist — was von einem CI-Job abgefragt wird:

GET /api/meta/schema-version

Für die Struktur der Endpunkte statt des dahinterliegenden Schemas befindet sich das OpenAPI-Dokument unter GET /api/docs, mit der Swagger UI unter /api/swagger außerhalb der Produktion.