Daten lesen und schreiben
Die Aufrufe der Datenschnittstelle: abfragen, blättern, schreiben, auswerten, exportieren.
Ein Werkzeug spricht mit der Instanz über HTTP und JSON, mit relativen Pfaden unter /api/. Meist ruft es dafür flow.js auf (Aufbau eines Werkzeugs). Diese Seite beschreibt die Aufrufe darunter. Für eine bestimmte Collection nennt describe_api sie mit den echten Feldnamen.
Zwei Regeln für jeden Aufruf
- Angemeldet ist, wer die Seite sieht. Die Sitzung ist ein Cookie.
fetchschickt es mitcredentials: "same-origin"mit. Ein Werkzeug kennt kein Token. - Jede Anfrage mit Rumpf trägt
Content-Type: application/json. Ohne ihn antwortet die Instanz mit 403 oder 415.
Abfragen
POST /api/collections/<collection>/query. Lesen ist ein POST, damit kein Filterwert in einer Adresse steht.
{
"filter": {"and": [
{"field": "status", "op": "in", "values": ["offen", "in arbeit"]},
{"field": "faellig", "op": "lte", "value": "2026-12-31"}
]},
"sort": {"field": "faellig", "direction": "asc"},
"limit": 50
}
Die Antwort:
{
"records": [
{"id": "bd4c5a07-…", "created_by": "4b40b106-…", "created_at": "2026-10-08T14:17:06.747188Z",
"updated_at": "2026-10-08T14:17:06.747188Z", "version": 1,
"titel": "Wartungsfenster ankuendigen", "status": "offen", "faellig": "2026-10-05"}
],
"hasMore": true,
"cursor": "…"
}
Eine Zeile trägt die technischen Spalten und daneben flach ihre Felder.
Filter. Ein Filter ist eine Bedingung {"field", "op", "value"} oder eine Gruppe {"and": [...]} beziehungsweise {"or": [...]}. in nimmt eine Liste unter values, is_null nimmt keinen Wert und trifft leere Felder. Ohne filter kommen alle Zeilen, die du lesen darfst.
| Typ | Operatoren |
|---|---|
text | eq, ne, in, contains, starts_with, is_null |
integer, decimal, date, datetime | eq, ne, in, lt, lte, gt, gte, is_null |
choice, reference, person, context | eq, ne, in, is_null |
boolean | eq, is_null |
longtext | is_null |
Sortieren. Nach genau einem Feld, asc oder desc. Nach longtext, reference, person und context lässt sich nicht sortieren.
Blättern. limit ist höchstens 200, die Vorgabe ist 50. Steht hasMore auf true, schickst du für die nächste Seite denselben Rumpf mit dem cursor aus der Antwort. Baue nie selbst einen Cursor und verwende keinen weiter, wenn sich Filter oder Sortierung ändern.
Grenzen. Höchstens 20 Bedingungen, drei Ebenen Verschachtelung, 100 Werte in einem in. Eine Überschreitung ist ein 422.
Verweisen folgen
include holt Felder der Zeile, auf die ein Verweisfeld zeigt, im selben Aufruf:
{"include": [{"through": "kunde", "fields": ["name", "ort"]}]}
Die Werte stehen flach in jeder Zeile, als kunde.name und kunde.ort. Das Verweisfeld selbst bleibt die Kennung. Du brauchst Leserecht auf die andere Collection. Nur query nimmt include.
Für eine Auswahlliste liefert GET /api/collections/<collection>/fields/<feld>/options die möglichen Ziele als {"options": [{"id", "label"}], "hasMore"}.
Anlegen, ändern, löschen
| Aufruf | Rumpf | Antwort |
|---|---|---|
POST /api/collections/<collection> | die Felder, flach | 201 mit der gespeicherten Zeile |
GET /api/collections/<collection>/<id> | die Zeile | |
PUT /api/collections/<collection>/<id> | version und die geänderten Felder | die Zeile mit neuer Version |
DELETE /api/collections/<collection>/<id> | {"version": 3} | {"status": "deleted"} |
{"version": 3, "status": "erledigt", "faellig": null}
Jedes Ändern und Löschen nennt die version, die du gelesen hast. Stimmt sie nicht mehr, hat jemand die Zeile inzwischen geändert: Die Instanz antwortet mit 409 und schreibt nichts. Lade die Zeile neu und zeige den aktuellen Stand, statt ihn zu überschreiben.
null leert ein Feld. Ein Pflichtfeld lässt sich nicht leeren.
Zählen und auswerten
Rechne auf dem Server, nicht im Browser. Alle drei Aufrufe nehmen denselben filter wie query.
| Aufruf | Rumpf | Antwort |
|---|---|---|
POST …/count | {"filter"} | {"count": 23} |
POST …/stats | {"field", "filter"} | rows, values, min, max, bei Zahlen auch sum und avg |
POST …/aggregate | {"groupBy", "aggregate", "filter", "limit", "sort"} | {"groups": [...], "more": false} |
{"groupBy": ["status"], "aggregate": [{"function": "sum", "field": "aufwand"}]}
{"groups": [{"status": "offen", "sum_aufwand": 2.7500}], "more": false}
Die Funktionen sind count, sum, avg, min und max. Ohne aggregate zählt der Aufruf. Er liefert höchstens limit Gruppen (Vorgabe 100), sortiert nach der ersten Kennzahl ("sort": "measure") oder nach dem Wert der Gruppe ("sort": "value"). count und stats lehnen sort, cursor und limit ab.
CSV exportieren und importieren
POST …/export nimmt filter und sort und antwortet mit allen passenden Zeilen als CSV-Download. Im Werkzeug: fetch, response.blob(), dann ein Link mit download. Die Instanz protokolliert jeden Export.
POST …/import nimmt die CSV-Bytes im Rumpf und den Dateinamen im Kopf X-File-Name, nicht als Multipart. Die erste Zeile nennt die Feldnamen. Die technischen Spalten eines Exports überspringt der Import. Er legt Zeilen an und ändert nie bestehende. Er gilt ganz oder gar nicht: Bei einer fehlerhaften Zeile schreibt er nichts und nennt die Zeilennummer. Die Antwort ist {"created": 12}.
Fehler
Eine Fehlerantwort ist application/problem+json:
{
"title": "Invalid request",
"status": 400,
"code": "UNKNOWN_FIELD",
"detail": "There is no such field in collection aufgaben",
"correlationId": "cc366e9e-…",
"errors": [
{"code": "UNKNOWN_FIELD", "field": "nix",
"allowedValues": ["aufwand", "faellig", "status", "titel"],
"message": "There is no such field in collection aufgaben"}
]
}
Zeige message beim Feld field. Die correlationId hilft der Administration, den Vorgang zu finden.
| Status | Bedeutung |
|---|---|
| 400 | ungültige Anfrage, mit Feldbezug in errors |
| 401 | die Sitzung ist zu Ende. Schicke die Person auf / |
| 403 | kein Recht. Neu anmelden hilft nicht |
| 404 | unbekannt, oder nicht für dich sichtbar |
| 409 | die Version stimmt nicht mehr |
| 413 | der Rumpf ist zu gross |
| 415 | falscher Inhaltstyp |
| 422 | eine Grenze der Anfrage ist überschritten |