API · v1 · stabil
CODEMETA OSDeveloper Center
Konsole öffnen
Konzept

Pagination

Lesedauer · 7 Min.Aktualisiert · 2026-07-29

Jeder Listen-Endpoint (GET /api/v1/<resource>) liefert eine Seite, nie den kompletten Datenbestand. Die Seite steht in data, die Blätter-Information in meta.

{
  "data": [  ],
  "meta": {
    "nextCursor": "0192f2c0-8d1c-7a3b-9e4f-d3e7f1a2b5c8",
    "hasMore": true,
    "limit": 50
  }
}

Es gibt zwei Blätter-Verfahren: Offset (der Standardweg) und Cursor (für große Durchläufe). Beide steuern Sie über Query-Parameter.

Query-Parameter

Parameter Typ Default Bedeutung
limit number 50 Zeilen pro Seite. Hart auf 200 begrenzt (siehe Hinweis unten).
offset number 0 Zeilen überspringen. Offset-Paging.
cursor string UUID der zuletzt gelesenen Zeile. Cursor-Paging.
sort string Feld, nach dem sortiert wird.
order string asc asc oder desc. Nur wirksam zusammen mit sort.
withTotal boolean false Wenn true, enthält meta zusätzlich total (exakte Trefferzahl).

Offset-Paging

Der Standardweg. Sie erhöhen offset um limit, bis hasMore auf false steht.

curl "https://os.codemeta.de/api/v1/tickets?limit=100&offset=200" \
  -H "X-Tenant-Id: $TENANT" \
  -b cookies.txt

Offset-Paging funktioniert mit jeder Sortierung und ist die richtige Wahl für UI-Seiten, Sprünge zu einer bestimmten Seite und Durchläufe bis einige tausend Zeilen. Für sehr tiefe Durchläufe wird offset mit wachsender Tiefe langsamer — dort ist Cursor-Paging die bessere Wahl.

Cursor-Paging

Statt zu überspringen, setzen Sie beim zuletzt gelesenen Datensatz an. Der cursor ist die nextCursor-Angabe der vorherigen Antwort.

CURSOR="0192f2c0-8d1c-7a3b-9e4f-d3e7f1a2b5c8"   # meta.nextCursor der Vorseite

curl "https://os.codemeta.de/api/v1/tickets?limit=100&sort=_id&order=asc&cursor=$CURSOR" \
  -H "X-Tenant-Id: $TENANT" \
  -b cookies.txt

Ein ungültiger Cursor (kein UUID-Format) wird mit 400 abgelehnt, nicht still ignoriert.

Das meta-Objekt

Feld Typ Immer da? Bedeutung
nextCursor string | null ja ID der letzten Zeile dieser Seite, oder null bei der letzten Seite.
hasMore boolean ja Ob mindestens eine weitere Zeile existiert.
limit number ja Das tatsächlich angewandte Limit — nach der 200er-Kappung.
total number nein Exakte Trefferzahl. Nur bei ?withTotal=true.

hasMore wird über ein zusätzlich gelesenes Element ermittelt (limit + 1), nicht über einen Zähl-Query. Deshalb ist total standardmäßig nicht enthalten: ein exaktes countDocuments kostet auf großen Collections 50–200 ms pro Request, die die meisten Clients nie lesen. Wenn Sie eine Trefferzahl anzeigen („1.284 Ergebnisse“), fordern Sie sie mit ?withTotal=true an.

Sortierung und Stabilität

Ohne sort wird nach createdAt absteigend sortiert (neueste zuerst). Mit sort gilt order, das ohne Angabe asc ist.

Jede Sortierung bekommt serverseitig _id als Tiebreaker angehängt. Das ist für Offset-Paging zwingend: bei mehreren Zeilen mit identischem Sortierwert — etwa hunderten Datensätzen aus demselben Import mit gleichem createdAt — garantiert die Datenbank ohne eindeutigen Tiebreaker keine stabile Reihenfolge zwischen zwei getrennten Seiten-Requests. Zeilen fielen sonst zwischen die Seitengrenzen und tauchten nie auf.

Vollständiger Durchlauf

Alle Tickets eines Tenants einlesen, per Cursor:

NEXT=""
while : ; do
  RES=$(curl -s "https://os.codemeta.de/api/v1/tickets?limit=200&sort=_id&order=asc${NEXT:+&cursor=$NEXT}" \
    -H "X-Tenant-Id: $TENANT" -b cookies.txt)

  echo "$RES" | jq -c '.data[]' >> tickets.ndjson

  [ "$(echo "$RES" | jq -r '.meta.hasMore')" = "true" ] || break
  NEXT=$(echo "$RES" | jq -r '.meta.nextCursor')
done

Abweichende Antwortform bei einzelnen Endpoints

Die generischen Entity-Listen und die Timeline-Route antworten mit meta. Einige ältere, nicht-generische Endpoints — Benutzer, Einladungen, Tenants, Sitzungen, Benachrichtigungen sowie die Security- und Vault-Audit-Logs — liefern stattdessen:

{
  "data": [  ],
  "pagination": {
    "cursor": "0192f2c0-…",
    "hasMore": true
  }
}

Inhaltlich ist pagination.cursor dasselbe wie meta.nextCursor. Lesen Sie defensiv (meta ?? pagination, nextCursor ?? cursor), wenn Ihr Client beide Endpoint-Familien anspricht. Die Vereinheitlichung auf meta ist vorgesehen; ein Entfernen von pagination würde vorher über den Changelog angekündigt.

Grenzen

  • limit maximal 200 pro Seite.
  • Jede Listen-Abfrage hat ein serverseitiges Zeitlimit von 15 Sekunden. Ein Timeout ist praktisch immer ein Filter, der keinen Index trifft — grenzen Sie enger ein, statt das Limit zu erhöhen.
  • Für laufende Aktualisierung ist Polling auf Listen der falsche Weg. Nutzen Sie Webhooks und das Listing nur für die Erstbefüllung.

Verwandte Konzepte

Suche