Paginierung, Fehler und Polling

Die Paginierungs-Umschläge, der einheitliche Fehlervertrag und wie Sie Änderungen ohne Webhooks per Polling erkennen

Diese Seite behandelt die übergreifenden Verträge, die alle Endpunkte teilen: Paginierung, die einheitliche Fehlerform und — da die API keine Webhooks bietet — das Polling nach Änderungen.

Offset-Paginierung

Fast jeder Listen-Endpunkt nutzt denselben Offset-Paginierungsvertrag mit zwei Query-Parametern:

ParameterTypStandardVerhalten
limitinteger50Seitengröße. Begrenzt auf den Bereich [1, 200] — ein Wert unter 1 wird zu 1, ein Wert über 200 wird zu 200.
offsetinteger0Anzahl der zu überspringenden Einträge. Mindestens 0 — ein negativer Wert wird zu 0.

Werte außerhalb des Bereichs werden stillschweigend begrenzt, nie abgelehnt. ?limit=10000 liefert keinen 400 — es wird als limit=200 behandelt. Die harte Obergrenze liegt immer bei 200 Einträgen pro Aufruf.

Eine paginierte Listenantwort sieht so aus:

{
  "data": [ ],
  "pagination": {
    "limit": 50,
    "offset": 0,
    "total": 342
  }
}
  • data — die Seite mit Einträgen.
  • pagination.limit / pagination.offset — spiegeln die effektiv angewendeten (begrenzten) Werte zurück.
  • pagination.total — die Gesamtzahl der auf die Abfrage passenden Einträge, über alle Seiten hinweg. Daran erkennen Sie, wann Sie mit dem Blättern aufhören können.

Um alle Seiten zu durchlaufen, erhöhen Sie offset bei jedem Aufruf um limit, bis offset >= total:

async function listAll(path, apiKey, limit = 200) {
  const items = [];
  let offset = 0;
  let total = Infinity;
 
  while (offset < total) {
    const url = new URL(`https://app.kabeen.io/public/v1${path}`);
    url.searchParams.set("limit", limit);
    url.searchParams.set("offset", offset);
 
    const res = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
    const body = await res.json();
 
    items.push(...body.data);
    total = body.pagination.total;
    offset += limit;
  }
 
  return items;
}

Ausnahme: Anwendungskatalog — Seitennummern-Paginierung

GET /application-catalog durchsucht den globalen, workspace-unabhängigen Referenzkatalog, der per Seitennummer statt offset/limit paginiert:

{
  "data": [ ],
  "total": 1204,
  "page": 1
}

Fordern Sie die nächste Seite mit ?page=2, ?page=3 usw. an (1-basiert, Standard 1). Für diesen Endpunkt gibt es keinen limit-Query-Parameter — die Seitengröße wird vom Katalogdienst festgelegt. Hören Sie auf, sobald data leer zurückkommt.

Ausnahme: Audit-Log — Cursor-Paginierung

GET /audit-log ist append-only und hochvolumig und nutzt daher Keyset-Paginierung (Cursor) statt Offset, neueste zuerst:

{
  "data": [ ],
  "total": 58213,
  "nextCursor": "2026-07-21T09:12:03.441Z,3f9c...",
  "hasMore": true
}
  • nextCursor ist ein opakes Token — behandeln Sie es als Blackbox und parsen oder konstruieren Sie es nie selbst.
  • Übergeben Sie es als ?cursor=..., um die nächste Seite zu erhalten.
  • hasMore sagt Ihnen, ob Sie fortfahren sollen; nextCursor ist nur aussagekräftig, solange hasMore true ist (es ist nullable und fehlt auf der letzten Seite).
  • total bleibt die Gesamtzahl der auf Ihre Filter passenden Einträge, aber die Schleife steuern Sie über hasMore/nextCursor, nicht über den Vergleich eines Offsets mit total.

Ausnahme: Unterressourcen-Listen — nackte Arrays

Einige Unterressourcen-Endpunkte liefern ein reines JSON-Array ganz ohne Umschlag — kein data-Wrapper, keine pagination. Sie stellen kleine, begrenzte Sammlungen dar, die an eine einzelne übergeordnete Ressource gebunden sind. Beispiele: GET /applications/{id}/owners, GET /applications/{id}/tags, GET /applications/{id}/teams, GET /applications/{id}/comments, GET /servers/{id}/owners, GET /servers/{id}/tags, GET /networks/{id}/routers, GET /routers/{id}/networks, GET /routers/{id}/tags. Ein Aufruf eines dieser Endpunkte liefert direkt [ ... ].

Fehlerbehandlung

Jede Fehlerantwort aus der eigenen Anfrageverarbeitung der API — jeder 400, 403, 404, 409, 422 und 5xx, auf jedem Endpunkt — hat dieselbe JSON-Form:

{
  "code": "not_found",
  "message": "server not found",
  "status": 404
}
FeldTypBeschreibung
codestringStabiler, maschinenlesbarer Fehlerbezeichner (z. B. not_found, insufficient_permission). Zum Verzweigen im Code geeignet.
messagestringMenschenlesbare Erklärung. Für Logs und Debugging gedacht, nicht für switch-Anweisungen.
statusintegerDer HTTP-Statuscode, im Body gespiegelt.

Statuscodes

StatusBedeutungWann er auftritt
400 Bad RequestFehlerhafte EingabeEin Pfad- oder Query-Wert kann nicht geparst werden — eine unparsebare UUID, ein unbekannter Enum-/Filterwert, ein ungültiges ISO-8601-Datum oder ein fehlendes/leeres Pflichtfeld.
401 UnauthorizedFehlende oder ungültige CredentialsKein Authorization-/X-Api-Key-Header, oder der Schlüssel ist unbekannt, widerrufen oder abgelaufen. Sonderfall: siehe unten.
403 ForbiddenNicht erlaubtDer Schlüssel trägt nicht den vom Endpunkt geforderten Berechtigungs-Scope (insufficient_permission), oder der Schlüssel ist an gar keinen Workspace gebunden (forbidden).
404 Not FoundRessource existiert in Ihrem Workspace nichtDie Id existiert nicht, oder sie gehört zu einem anderen Workspace. Die API unterscheidet die beiden Fälle nie — beide liefern denselben 404; ein Schlüssel kann Fehlermeldungen also nicht nutzen, um fremde Ressourcen aufzuzählen.
409 ConflictAnfrage steht im Konflikt mit dem aktuellen ZustandVom Fehlervertrag reserviert; derzeit von keinem Endpunkt erzeugt.
422 Unprocessable EntityWohlgeformt, aber semantisch ungültigDie Anfrage parst einwandfrei, verletzt aber eine Geschäftsregel — z. B. der Versuch, name/os/manufacturer auf einem agentengemeldeten (automatischen) Server zu ändern.
5xxUnerwarteter ServerfehlerNicht Teil des normalen Kontrollflusses — als transient behandeln und mit Backoff erneut versuchen.

Der 401-Sonderfall

Jeder Status oben kommt als einheitlicher JSON-Body zurück — außer 401. Die Authentifizierung wird durchgesetzt, bevor die Anfrage die eigene Fehlerbehandlung der API erreicht; ein echter 401 kommt daher zurück als:

  • Status 401, leerer Body (kein JSON — versuchen Sie nicht, code/message/status daraus zu parsen).
  • Ein WWW-Authenticate: Bearer realm="kbine", error="invalid_token"-Response-Header.

Erwartet Ihr Client immer einen JSON-Fehler-Body, behandeln Sie 401 als Sonderfall (oder verzweigen Sie schlicht allein über den Status).

400 und 422 überschneiden sich im Geist, nicht aber in der Absicht: 400 bedeutet, die API konnte Ihre Eingabe nicht einmal parsen; 422 bedeutet, sie wurde einwandfrei geparst, aber die Bedeutung ist angesichts des aktuellen Ressourcenzustands ungültig.

Bekannte code-Werte

In zukünftigen Releases können neue Codes hinzukommen — sehen Sie immer einen Default-Fall vor.

codeTypischer statusHinweise
bad_request400Generischer Standard für fehlerhafte Eingaben.
invalid_uuid400Ein Pfad- oder Query-Wert, der eine UUID sein sollte, konnte nicht geparst werden.
invalid_datetime400Ein Datums-/Zeitwert ist kein gültiges ISO-8601.
unauthorized401Vom Vertrag definiert, in der Praxis aber nicht erreichbar — echte Authentifizierungsfehler tragen keinen JSON-Body.
forbidden403Der Schlüssel ist an keinen Workspace gebunden (oder ein anderer generischer Autorisierungsfehler).
insufficient_permission403Der Schlüssel ist an einen Workspace gebunden, hat aber nicht den erforderlichen Scope.
not_found404Die Ressource existiert im Workspace des Schlüssels nicht.
conflict409Reserviert — derzeit von keinem Endpunkt ausgegeben.
unprocessable_entity422Die Anfrage ist wohlgeformt, aber für die Zielressource semantisch ungültig.

Best Practices: Prüfen Sie zuerst den HTTP-Status, verzweigen Sie über code (nie über message), loggen Sie message zur Diagnose und fallen Sie auf status zurück bei Codes, die Sie nicht kennen.

Keine Webhooks — stattdessen Polling

Die API bietet in dieser Version keine Webhooks, Event-Abonnements oder sonstige Push-/Callback-Mechanismen. Sie ist reines Request/Response (Pull). Zwei komplementäre Ansätze decken fast jeden „Änderung erkennen"-Anwendungsfall ab:

1. Das Audit-Log pollen (am nächsten an einem Änderungs-Feed)

GET /public/v1/audit-log (Scope audit_log:read) ist die beste Annäherung an einen Event-Feed, die die API heute hat. Es liefert Workspace-Audit-Ereignisse, neueste zuerst, und unterstützt Datumsbereichsfilter (from/to), Akteursfilter (actorId), Kategorie-/Aktionsfilter (category, wiederholbares actions), Ressourcenfilter (resourceType, resourceId), Freitextsuche (query) und Cursor-Paginierung (limit max. 200 + cursor).

Pollen Sie es in einem Intervall, laufen Sie mit nextCursor vorwärts, solange hasMore true ist, und persistieren Sie den letzten Cursor (oder den timestamp des zuletzt verarbeiteten Ereignisses) zwischen den Läufen:

cursor = load_last_cursor()
loop:
  page = GET /audit-log?limit=200&cursor={cursor}
  for event in page.data:
    handle(event)
  if page.nextCursor:
    cursor = page.nextCursor
    save_cursor(cursor)
  if not page.hasMore:
    sleep(poll_interval)

2. Zeitstempel auf Ressourcen vergleichen

Die meisten Ressourcen tragen einen Zeitstempel, den Sie mit einem früheren Poll vergleichen können:

RessourceFeld
Anwendungen (GET /applications/{id})updatedAt
Datenobjekte (GET /data, GET /data/{id})updatedAt
Verträge (GET /contracts)updatedAt
Teams (GET /teams, GET /teams/{id})updatedAt
Ankündigungen (GET /announcements)updatedAt
Entdeckte Anwendungen (GET /discovered-applications)lastUpdate
Server (GET /servers, GET /servers/{id})lastCheckTime

Das ist gröber als das Audit-Log — es sagt Ihnen, dass sich etwas geändert hat, nicht was oder wer — aber es ist einfach und funktioniert auch für Ressourcen ohne eigene Audit-Kategorie.

Effizientes Polling

  • Respektieren Sie die Seitengrößen-Obergrenze (200) und fordern Sie die größte Seite an, die Ihr Intervall bequem verarbeiten kann.
  • Wählen Sie ein vernünftiges Intervall. Für die meisten Integrationen sind 1–5 Minuten ein sinnvoller Standard.
  • Persistieren Sie Ihren Cursor/Zeitstempel immer, bevor Sie auf die Seite reagieren, damit ein Absturz oder Neustart fortsetzt statt doppelt zu verarbeiten oder zu überspringen.
  • Bevorzugen Sie das Audit-Log, wenn Sie „was hat sich geändert" brauchen — es kennt Akteur, Aktion und Ressource.