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:
| Parameter | Typ | Standard | Verhalten |
|---|---|---|---|
limit | integer | 50 | Seitengröße. Begrenzt auf den Bereich [1, 200] — ein Wert unter 1 wird zu 1, ein Wert über 200 wird zu 200. |
offset | integer | 0 | Anzahl 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
}nextCursorist 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. hasMoresagt Ihnen, ob Sie fortfahren sollen;nextCursorist nur aussagekräftig, solangehasMoretrueist (es ist nullable und fehlt auf der letzten Seite).totalbleibt die Gesamtzahl der auf Ihre Filter passenden Einträge, aber die Schleife steuern Sie überhasMore/nextCursor, nicht über den Vergleich eines Offsets mittotal.
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
}| Feld | Typ | Beschreibung |
|---|---|---|
code | string | Stabiler, maschinenlesbarer Fehlerbezeichner (z. B. not_found, insufficient_permission). Zum Verzweigen im Code geeignet. |
message | string | Menschenlesbare Erklärung. Für Logs und Debugging gedacht, nicht für switch-Anweisungen. |
status | integer | Der HTTP-Statuscode, im Body gespiegelt. |
Statuscodes
| Status | Bedeutung | Wann er auftritt |
|---|---|---|
400 Bad Request | Fehlerhafte Eingabe | Ein 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 Unauthorized | Fehlende oder ungültige Credentials | Kein Authorization-/X-Api-Key-Header, oder der Schlüssel ist unbekannt, widerrufen oder abgelaufen. Sonderfall: siehe unten. |
403 Forbidden | Nicht erlaubt | Der 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 Found | Ressource existiert in Ihrem Workspace nicht | Die 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 Conflict | Anfrage steht im Konflikt mit dem aktuellen Zustand | Vom Fehlervertrag reserviert; derzeit von keinem Endpunkt erzeugt. |
422 Unprocessable Entity | Wohlgeformt, aber semantisch ungültig | Die Anfrage parst einwandfrei, verletzt aber eine Geschäftsregel — z. B. der Versuch, name/os/manufacturer auf einem agentengemeldeten (automatischen) Server zu ändern. |
5xx | Unerwarteter Serverfehler | Nicht 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/statusdaraus 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.
code | Typischer status | Hinweise |
|---|---|---|
bad_request | 400 | Generischer Standard für fehlerhafte Eingaben. |
invalid_uuid | 400 | Ein Pfad- oder Query-Wert, der eine UUID sein sollte, konnte nicht geparst werden. |
invalid_datetime | 400 | Ein Datums-/Zeitwert ist kein gültiges ISO-8601. |
unauthorized | 401 | Vom Vertrag definiert, in der Praxis aber nicht erreichbar — echte Authentifizierungsfehler tragen keinen JSON-Body. |
forbidden | 403 | Der Schlüssel ist an keinen Workspace gebunden (oder ein anderer generischer Autorisierungsfehler). |
insufficient_permission | 403 | Der Schlüssel ist an einen Workspace gebunden, hat aber nicht den erforderlichen Scope. |
not_found | 404 | Die Ressource existiert im Workspace des Schlüssels nicht. |
conflict | 409 | Reserviert — derzeit von keinem Endpunkt ausgegeben. |
unprocessable_entity | 422 | Die 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:
| Ressource | Feld |
|---|---|
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.