# Paginierung

> Lange Listen seitenweise mit `limit` und einem undurchsichtigen `cursor` lesen.

Listen, die groß werden können, verwenden Keyset-Paginierung: Sie fordern mit `limit` eine Seite an, und die
Antwort sagt Ihnen, wo die nächste Seite beginnt.

## Welche Operationen blättern

| Operation                                                                                                    | Reihenfolge                |
| ------------------------------------------------------------------------------------------------------------ | -------------------------- |
| [`listBatteryModels`](https://passport.kabin.sh/docs/de/api-reference/catalog/list-battery-models)         | Nach Name, dann ID         |
| [`listBatteries`](https://passport.kabin.sh/docs/de/api-reference/catalog/list-batteries)                  | Nach Seriennummer, dann ID |
| [`listEvidence`](https://passport.kabin.sh/docs/de/api-reference/evidence/list-evidence)                   | Neueste zuerst             |
| [`listFacilities`](https://passport.kabin.sh/docs/de/api-reference/facilities/list-facilities)             | Nach Name, dann ID         |
| [`listLifecycleEvents`](https://passport.kabin.sh/docs/de/api-reference/lifecycle/list-lifecycle-events)   | Neueste zuerst             |
| [`listPassportVersions`](https://passport.kabin.sh/docs/de/api-reference/passports/list-passport-versions) | Neueste zuerst             |

Andere Listen, etwa Werte, Wertehistorien und Modellversionen, liefern alle Einträge in einer Antwort.

## Anfrage

- **limit** `integer` (query, Standard: 50): Anzahl der Einträge pro Seite, von 1 bis 200.
- **cursor** `string` (query): Der `next_cursor` der vorigen Seite. Für die erste Seite weglassen.

Senden Sie die Filter der Operation (zum Beispiel `battery_model_id` oder `passport_status` bei
`listBatteries`) mit jeder Seite unverändert mit.

## Antwort

- **items** `object[]` (erforderlich): Die Einträge dieser Seite, höchstens `limit`.
- **next_cursor** `string | null` (erforderlich): Der Cursor für die nächste Seite oder `null`, wenn dies die letzte Seite ist.

```json title="200"
{
  "items": [
    { "id": "0192f0a3-1b7c-7e2a-8f45-6a9d0c3e5b12", "serial_number": "SV2027-000001" },
    { "id": "0192f0a3-1b7c-7e2a-8f45-6a9d0c3e5b13", "serial_number": "SV2027-000002" }
  ],
  "next_cursor": "eyJzIjoiU1YyMDI3LTAwMDAwMiJ9"
}
```

Das Beispiel zeigt nur zwei Felder je Einheit; echte Einträge haben das vollständige Schema der Operation.

## Regeln für Cursor

- Ein Cursor ist **undurchsichtig**. Lesen, bauen oder ändern Sie ihn nicht; sein Inhalt kann sich jederzeit
  ändern.
- Ein Cursor gehört zu **der Abfrage, die ihn erzeugt hat**: dieselbe Operation, dieselben Pfadparameter,
  dieselben Filter. Verwenden Sie ihn gleich für die nächste Seite und speichern Sie Cursor nicht als
  dauerhafte Lesezeichen.
- `next_cursor: null` heißt, dass keine weiteren Einträge folgen.
- Ein unbrauchbarer Cursor ergibt `400 validation_error` auf dem Feld `cursor`; ein `limit` außerhalb von 1
  bis 200 ein `400` auf dem Feld `limit`.
- Seiten sind keine Momentaufnahme. Einträge, die während des Blätterns entstehen oder sich ändern, können
  erscheinen oder fehlen. Für eine konsistente Kopie blättern Sie durch und gleichen danach über die ID ab
  (siehe [Einheiten synchronisieren](https://passport.kabin.sh/docs/de/guides/sync-units)).

## Alle Seiten lesen

Die folgenden Schleifen lesen alle Einheiten mit veröffentlichtem Pass, 200 pro Seite.

```ts title="TypeScript"
type Battery = { id: string; serial_number: string };
type Page<T> = { items: T[]; next_cursor: string | null };

const base = `${process.env.BATTERYPASS_URL}/api/v1`;
const headers = { Authorization: `Bearer ${process.env.BATTERYPASS_API_KEY}` };

async function* listBatteries(filters: Record<string, string>): AsyncGenerator<Battery> {
  let cursor: string | null = null;
  do {
    const query = new URLSearchParams({ ...filters, limit: '200' });
    if (cursor) query.set('cursor', cursor);
    const res = await fetch(`${base}/batteries?${query}`, { headers });
    if (!res.ok) throw new Error(`listBatteries: ${res.status} ${await res.text()}`);
    const page: Page<Battery> = await res.json();
    yield* page.items;
    cursor = page.next_cursor;
  } while (cursor);
}

let count = 0;
for await (const unit of listBatteries({ passport_status: 'published' })) {
  console.log(unit.serial_number, unit.id);
  count++;
}
console.log(`${count} Einheiten`);
```

```python title="Python"
import os

import requests

base = f"{os.environ['BATTERYPASS_URL']}/api/v1"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['BATTERYPASS_API_KEY']}"


def list_batteries(**filters):
    params = {**filters, "limit": 200}
    while True:
        res = session.get(f"{base}/batteries", params=params, timeout=30)
        res.raise_for_status()
        page = res.json()
        yield from page["items"]
        if page["next_cursor"] is None:
            return
        params["cursor"] = page["next_cursor"]


count = 0
for unit in list_batteries(passport_status="published"):
    print(unit["serial_number"], unit["id"])
    count += 1
print(count, "Einheiten")
```

```go title="Go"
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"os"
	"time"
)

type battery struct {
	ID           string `json:"id"`
	SerialNumber string `json:"serial_number"`
}

type batteryPage struct {
	Items      []battery `json:"items"`
	NextCursor *string   `json:"next_cursor"`
}

func fetchPage(client *http.Client, query url.Values) (batteryPage, error) {
	var page batteryPage
	req, err := http.NewRequest(http.MethodGet, os.Getenv("BATTERYPASS_URL")+"/api/v1/batteries?"+query.Encode(), nil)
	if err != nil {
		return page, err
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("BATTERYPASS_API_KEY"))
	res, err := client.Do(req)
	if err != nil {
		return page, err
	}
	defer res.Body.Close()
	if res.StatusCode != http.StatusOK {
		return page, fmt.Errorf("listBatteries: %s", res.Status)
	}
	return page, json.NewDecoder(res.Body).Decode(&page)
}

func main() {
	client := &http.Client{Timeout: 30 * time.Second}
	query := url.Values{"limit": {"200"}, "passport_status": {"published"}}
	count := 0
	for {
		page, err := fetchPage(client, query)
		if err != nil {
			log.Fatal(err)
		}
		for _, unit := range page.Items {
			fmt.Println(unit.SerialNumber, unit.ID)
			count++
		}
		if page.NextCursor == nil {
			break
		}
		query.Set("cursor", *page.NextCursor)
	}
	fmt.Println(count, "Einheiten")
}
```

> [!TIP]
> Jede Seite ist eine Anfrage gegen das Limit Ihrer Organisation von 600 Anfragen pro Minute. Verwenden Sie
> `limit=200` für Massenabfragen und lesen Sie für lange Durchläufe [Ratenlimits](https://passport.kabin.sh/docs/de/rate-limits).
