RATIOserver API – CRUD-Handbuch

Referenz für Aufruf und Auswertung der Standard-CRUD-Endpunkte (get / getFiltered / getById / getKey / insert / update / delete)

1. Grundprinzip

Alle CRUD-Endpunkte folgen demselben Muster, unabhängig von Modul oder Entität:

PunktRegel
HTTP-MethodeImmer POST – auch bei den get*-Endpunkten. Parameter stehen im Body, nicht im Query-String.
Content-TypeRequest und Response immer application/json
BodyImmer ein JSON-Objekt, auch wenn keine Parameter nötig sind: {}
AuthHeader Authorization: Bearer <jwttoken>, außer bei Routen mit Auth=false
URL-Schema{{baseURL}}/<modul>/<methode>, z. B. http://localhost/ibapi/dokumente/getvorlagen

<modul> ist der Controller-Bereich (z. B. dokumente, toupac, adressen, anmiet, fuhrpark, incoming), <methode> der Endpunktname klein geschrieben.

2. Die 7 Standard-Endpunkttypen

Pro Entität <Entity> (Tabellenname) existieren i. d. R. diese sieben Endpunkte:

EndpunktZweckBeispiel-Body
get<entity>Liste aller Datensätze{"fields":["nr","name"],"orderby":"name"}
get<entity>filteredListe, gefiltert nach optionalen Parametern (Abschnitt 4){"fields":"*","art":"BRIEF","orderby":"name"}
get<entity>byidEinzelner Datensatz über Primärschlüssel{"nr":1,"fields":"*"}
get<entity>keyNächsten freien Schlüsselwert (Generator) ermitteln{}
insert<entity>Neuen Datensatz anlegen{"name":"...","art":"BRIEF"}
update<entity>Bestehenden Datensatz ändern{"nr":1,"name":"Neuer Wert"}
delete<entity>Datensatz löschen{"nr":1}

Zusätzliche Steuerparameter (alle lesenden Endpunkte)

  • fields – siehe Abschnitt 3
  • orderby – Sortierfeld, muss ein erlaubtes Feld sein, sonst Sortierfeld "..." ist nicht erlaubt.
  • limit / offset – Pagination, nur bei get<entity> und ...filtered (Abschnitt 8). Ohne limit (bzw. limit:0) werden alle Datensätze geliefert.

3. Feldliste (fields)

Jeder lesende Endpunkt hat eine im Server-Code fest hinterlegte Whitelist erlaubter Felder (ersichtlich aus Postman-Collection bzw. Quellcode des Endpunkts). fields steuert, welche dieser Felder in der Antwort landen – nicht, wonach gefiltert wird.

Wert von fieldsBedeutung
"*"Alle für diesen Endpunkt erlaubten Felder
["feld1","feld2"]Nur die genannten Felder – jedes muss in der Whitelist stehen
Schlüssel fehlt ganzVerhalten wie "*"
[]Fehler Keine Felder angegeben.
Feldname nicht in WhitelistFehler Feld "<name>" ist nicht erlaubt.
weder "*" noch ArrayFehler "fields" muss "*" oder ein JSON-Array sein.

Alle Fehler dieser Kategorie kommen mit HTTP 200 – dazu mehr in Abschnitt 6.

4. Filterparameter bei get<entity>filtered

Liefert dieselbe Grundliste wie get<entity>, zusätzlich beliebig viele Filterbedingungen möglich. Auch hier gilt eine endpunktspezifische Whitelist möglicher Filterfelder.

  • Ein Filterfeld wirkt nur, wenn es im Body vorhanden und nicht null ist.
  • Fehlt ein Filterfeld, entfällt die Einschränkung ersatzlos – kein Fehler.
  • Mehrere Filter werden immer mit AND verknüpft, kein OR über den Standardmechanismus.
  • Kein Filter gesetzt ⇒ Verhalten identisch zu get<entity>.
  • Jeder Filter prüft auf Gleichheit (feld = wert), keine Bereichs-/Vergleichsoperatoren, sofern nicht anders dokumentiert.
  • Ein Feld im Body, das nicht zu den definierten Filterfeldern gehört, wird stillschweigend ignoriert.
// Kein Filter -> alle Datensätze
{ "fields": "*" }

// Ein Filter -> art = "BRIEF"
{ "fields": "*", "art": "BRIEF" }

// Zwei Filter (UND) -> art = "BRIEF" UND bereich = "EINGANG"
{ "fields": "*", "art": "BRIEF", "bereich": "EINGANG" }

DoSelectFilteredDynamic-Endpunkte

Die meisten ...filtered-Endpunkte sind serverseitig über DoSelectFilteredDynamic implementiert. Filterfelder und SQL-Bedingung sind als zwei parallele Listen im Server-Code hinterlegt:

CONDITIONS:     array[0..1] of string = ('Field1 = :Field1', 'Field2 = :Field2');
FILTER_PARAMS:  array[0..1] of string = ('Field1', 'Field2');
  • Prüfreihenfolge: nach der im Code festgelegten Reihenfolge (FILTER_PARAMS[0], [1], ...), nicht nach der Reihenfolge im JSON-Body.
  • Verkettung: jeder vorhandene, nicht-null Parameter wird als (CONDITIONS[i]) angehängt, alle Bedingungen zusammen per AND: WHERE (Bedingung_A) AND (Bedingung_B) ...
  • Fehlender Parameter: Bedingung entfällt komplett aus der WHERE-Klausel (nicht IS NULL, taucht im generierten SQL gar nicht auf).
  • Kein Parameter gesetzt: WHERE-Klausel leer, Verhalten wie get<entity>.
Reihenfolge im Body ist irrelevant {"art":"BRIEF","bereich":"EINGANG"} und {"bereich":"EINGANG","art":"BRIEF"} erzeugen exakt dieselbe WHERE-Klausel (art = :art) AND (bereich = :bereich), da die Verkettungsreihenfolge durch FILTER_PARAMS/CONDITIONS im Server-Code vorgegeben ist – nicht durch den Request.

5. Antwort im Erfolgsfall

a) Leseoperationen ohne Pagination

get<entity>, ...filtered, ...byid, ...key – jeweils ohne limit. HTTP 200

{
  "header": {
    "nr": "ftinteger",
    "name": "ftstring 100",
    "erstelltam": "ftdate",
    "menge": "ftfloat"
  },
  "data": [
    { "nr": 1, "name": "Beispiel", "erstelltam": "2026-07-01", "menge": 3.5 },
    { "nr": 2, "name": "Zweiter Eintrag", "erstelltam": null, "menge": 0 }
  ]
}
  • header: Feldname → Datentyp (ftstring N, ftinteger, ftfloat, ftdate, ftdatetime, ftblob).
  • data: Array der Datensätze, Feldnamen klein geschrieben; NULL als JSON null.
  • Datumsfelder als "yyyy-mm-dd", Zeitstempel als "yyyy-mm-dd hh:nn:ss".
  • BLOB-Felder als Base64-String.
  • Kein Treffer (z. B. Filter passt auf nichts) ⇒ kein Fehler, sondern "data": [].

b) Leseoperationen mit Pagination

get<entity> / ...filtered mit limit > 0. HTTP 200

{
  "total": 137,
  "limit": 20,
  "offset": 0,
  "data": {
    "header": { "nr": "ftinteger", "name": "ftstring 100" },
    "data": [ { "nr": 1, "name": "Beispiel" } ]
  }
}
Verschachtelte Struktur header/data liegen hier unter data.header/data.data – nicht direkt auf oberster Ebene wie bei (a). total ist die Trefferzahl unter Berücksichtigung aktiver Filter, nicht die Gesamtzahl der Tabelle.

c) Schreiboperationen

insert<entity>, update<entity>, delete<entity>. HTTP 200

{ "status": "OK" }

Kein Datensatz, keine neue ID in der Antwort. Für neue Primärschlüssel vorher get<entity>key aufrufen.

6. Antwort im Fehlerfall

{ "status": "error", "message": "<Fehlertext>" }
SituationHTTP-StatusBeispiel message
Kein/leeres Token401„Keine Anmeldedaten verfügbar. Bitte neu anmelden.“
Ungültiges/abgelaufenes Token401„Anmeldung ungültig oder abgelaufen.“
Route ist LocalOnly, Zugriff von außerhalb403„Zugriff nur vom lokalen Server erlaubt.“
Unbekannte Route404„Dieser Pfad (/xyz) wurde nicht gefunden.“
Datenbankfehler (Unique-/FK-Verletzung, ungültiges SQL)400Firebird/InterBase-Fehlermeldung
Server-/Konfigurationsfehler400„Der Parameter [database] fehlt“
Fachlicher Validierungsfehler (nicht erlaubtes Feld, fehlender Pflichtparameter, ungültiges JSON, nicht erlaubtes Sortierfeld, ...)200„Feld "x" ist nicht erlaubt.“, „"nr" fehlt im Request-Body.“
Wichtig für Client-Entwickler Fachliche Validierungsfehler liefern HTTP 200, nicht 4xx – nur harte Datenbank-/Serverfehler liefern 400. Der HTTP-Statuscode allein ist kein verlässliches Erfolgskriterium; die Response muss immer zusätzlich auf status:"error" geprüft werden.

Prüfreihenfolge im Client

  1. Antwort als JSON parsen.
  2. status vorhanden und == "error"? → Fehlerfall, message anzeigen/loggen.
  3. Sonst → Erfolgsfall: Schreiboperationen anhand status == "OK", Leseoperationen anhand vorhandenem header/data (ggf. verschachtelt, siehe 5b).
  4. 401/403/404 zusätzlich als eindeutige, nicht-fachliche Fehler auswertbar, z. B. für automatischen Re-Login.

7. Beispiele (curl)

Liste ohne Filter

curl -X POST "http://localhost/ibapi/dokumente/getvorlagenfiltered" \
  -H "Authorization: Bearer <jwttoken>" \
  -H "Content-Type: application/json" \
  -d '{"fields":"*"}'

# -> 200, liefert ALLE Vorlagen (kein Filter gesetzt)

Liste mit einem Filter

curl -X POST "http://localhost/ibapi/dokumente/getvorlagenfiltered" \
  -H "Authorization: Bearer <jwttoken>" \
  -H "Content-Type: application/json" \
  -d '{"fields":"*","art":"BRIEF","orderby":"name"}'

# -> 200
# {"header":{"nr":"ftinteger","name":"ftstring 100","art":"ftstring 20"},
#  "data":[{"nr":1,"name":"Standardbrief","art":"BRIEF"}]}

Pflichtfeld fehlt fachlicher Fehler → trotzdem 200!

curl -X POST "http://localhost/ibapi/dokumente/getvorlagenbyid" \
  -H "Authorization: Bearer <jwttoken>" \
  -H "Content-Type: application/json" \
  -d '{"fields":"*"}'

# -> 200 (!)
# {"status":"error","message":"\"nr\" fehlt im Request-Body."}

Nicht erlaubtes Feld in fields

curl -X POST "http://localhost/ibapi/dokumente/getvorlagen" \
  -H "Authorization: Bearer <jwttoken>" \
  -H "Content-Type: application/json" \
  -d '{"fields":["nr","geheimesfeld"]}'

# -> 200 (!)
# {"status":"error","message":"Feld \"geheimesfeld\" ist nicht erlaubt."}

Kein/ungültiges Token

curl -X POST "http://localhost/ibapi/dokumente/getvorlagen" \
  -H "Content-Type: application/json" -d '{}'

# -> 401
# {"status":"error","message":"Keine Anmeldedaten verfügbar. Bitte neu anmelden."}

8. Pagination – Zusammenfassung

Body-ParameterWirkung
kein limit bzw. limit:0Alle Datensätze, Antwort wie in 5a (kein total-Umschlag)
limit:N (N > 0), optional offset:MAntwort wie in 5b, mit total/limit/offset und verschachteltem data.header/data.data