# Pagination

https://gorally.games/developpeurs/pagination

Trois listes se parcourent page par page : les ligues, les rencontres et les événements. Le même mécanisme sert aux trois.

## Lire une page

Deux paramètres de requête règlent la page.

- `limit` : le nombre d’éléments. Il vaut 25 par défaut, 100 au plus.
- `after` : l’`id` du dernier élément de la page précédente. Sans lui, tu lis depuis le début.

Un `limit` au-dessus de 100 est refusé, pas rogné. Un `limit` qui n’est pas un entier l’est aussi. L’API répond `INVALID_INPUT`.

**curl**

```bash
curl https://gorally.games/v1/matches \
  -H "Authorization: Bearer $RALLY_API_KEY"
```

**JavaScript**

```js
const response = await fetch('https://gorally.games/v1/matches', {
  headers: {
    Authorization: `Bearer ${process.env.RALLY_API_KEY}`,
  },
});
const data = await response.json();
```

**Python**

```python
import os

import requests

response = requests.get(
    "https://gorally.games/v1/matches",
    headers={"Authorization": f"Bearer {os.environ['RALLY_API_KEY']}"},
    timeout=10,
)
data = response.json()
```

## L’enveloppe

Toute liste paginée rend le même objet.

```json
{
  "object": "list",
  "data": [
    {
      "id": "f4e3d2c1-b0a9-4f8e-8d7c-6b5a4f3e2d1c",
      "categoryId": "e2b4d6f8-1a3c-4e5b-9d7f-0c2e4a6b8d1f",
      "status": "SCHEDULED",
      "teamAId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
      "teamBId": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
      "scoreA": null,
      "scoreB": null,
      "sets": null,
      "startsAt": "2027-01-13T00:15:00.000Z",
      "revision": 3
    }
  ],
  "has_more": true
}
```

- `object` vaut toujours `list`.
- `data` porte les éléments de la page.
- `has_more` vaut `true` quand il reste des éléments après cette page.

## Parcourir toute la liste

1. Lis la première page, sans `after`.
2. Traite `data`.
3. Si `has_more` vaut `true`, relis avec `after` égal à l’`id` du dernier élément de `data`. Si `data` est vide, garde le même `after`, patiente un instant et relis.
4. Arrête-toi quand `has_more` vaut `false`.

```js
let after;
let hasMore = true;
while (hasMore) {
  const url = new URL('https://gorally.games/v1/leagues');
  url.searchParams.set('limit', '100');
  if (after) url.searchParams.set('after', after);
  const page = await (await fetch(url, {
    headers: { Authorization: `Bearer ${process.env.RALLY_API_KEY}` },
  })).json();
  for (const league of page.data) console.log(league.name);
  hasMore = page.has_more;
  if (page.data.length > 0) after = page.data.at(-1).id;
  else if (hasMore) await new Promise((resolve) => setTimeout(resolve, 1000));
}
```

Un curseur inconnu est refusé avec `INVALID_INPUT`. Rally ne repart pas du début : tu recevrais une page en double. Ne fabrique pas de curseur, recopie un `id` reçu.

## Ce qui reste stable

Le parcours suit un numéro d’insertion interne, que rien ne déplace. Renommer une ligue ou corriger une rencontre entre deux pages ne la fait ni disparaître ni revenir.

Une écriture dont la transaction est encore en vol est retardée, jamais sautée. Elle apparaît à sa place quand elle est validée, une seule fois. Une page peut donc être plus courte que `limit` alors que `has_more` vaut `true` : continue. Quand cette écriture est juste après ton curseur, `data` est même vide avec `has_more` à `true`. Rien n’est perdu : relis un peu plus tard avec le même `after`.

## Les listes complètes

Les catégories d’une saison et le classement d’une catégorie rendent tous leurs éléments d’un coup. Ils ne prennent ni `limit` ni `after`, et leur enveloppe n’a pas de `has_more`.

## Et ensuite

- Lis le guide « Limites » : une boucle de pages compte dans ton quota.
