# Pagination, erreurs, 401 et 429

Un script qui marche sur la première page, avec un serveur de bonne humeur, c'est facile. Un script robuste doit aussi gérer la pagination, les erreurs et un serveur qui demande de ralentir.

### La pagination

Une API ne renvoie jamais une liste de 10 000 éléments d'un coup : elle découpe en **pages**. Sur API-ritif, une liste ressemble à ceci :

```json
{
  "items": [ {"id": 1, "name": "Hello World"}, ... ],
  "page": 1,
  "per_page": 5,
  "total": 13,
  "_links": {
    "self":  {"href": "/v1/cocktails?page=1&per_page=5"},
    "next":  {"href": "/v1/cocktails?page=2&per_page=5"},
    "last":  {"href": "/v1/cocktails?page=3&per_page=5"}
  }
}
```

La méthode la plus fiable : **suivre le lien `next`** jusqu'à ce qu'il n'y en ait plus. Pas besoin de calculer les numéros de page.

```python
import pprint
from urllib.parse import urljoin

def get_all(path, headers=None):
    items = []
    url = urljoin(BASE, path)
    while url:
        res = requests.get(url, headers=headers)
        res.raise_for_status()
        data = res.json()
        items.extend(data["items"])
        next_link = data["_links"].get("next")
        url = urljoin(BASE, next_link["href"]) if next_link else None
    return items

cocktails = get_all("/v1/cocktails")

pprint.pp(cocktails)
```

<p class="callout info">Les liens sont relatifs (`/v1/...`) : `urljoin()` les combine avec l'adresse du serveur.</p>

### Lire les erreurs

API-ritif renvoie ses erreurs au format `application/problem+json` (détaillé au chapitre 5). Le champ `detail` est fait pour être affiché :

```json
{
  "type": "https://api-ritif.example/problems/out-of-stock",
  "title": "Rupture de stock",
  "status": 409,
  "detail": "Le 404 Spritz est introuvable. Comme d'habitude."
}
```

```python
if not res.ok:
    problem = res.json()
    print(problem["status"], problem["title"], ":", problem["detail"])
```

### 401 : l'authentification a échoué

Un `401` signifie que le serveur ne vous reconnaît pas : clé absente, clé fausse, ou **token expiré**. Certains tokens ne vivent que quelques minutes (c'est le cas des JWT d'API-ritif, voir chapitre 8).

Réaction attendue d'un client :

- clé absente ou fausse : inutile d'insister, il faut corriger la configuration ;
- token expiré : en redemander un nouveau, puis **rejouer** la requête une fois.

### 429 : le serveur demande de ralentir

Pour se protéger, une API limite le nombre de requêtes par client (*rate limiting*). Au-delà, elle répond `429 Too Many Requests`, souvent avec un header `Retry-After` qui indique combien de secondes attendre.

```http
HTTP/1.1 429 Too Many Requests
Retry-After: 10
Content-Type: application/problem+json

{"title": "Too Many Requests", "status": 429, "detail": "Le barman est débordé, reviens dans 10 s"}
```

Un bon client **attend**, puis réessaie :

```python
import time

def polite_get(url, **kwargs):
    while True:
        res = requests.get(url, **kwargs)
        if res.status_code != 429:
            return res
        wait = int(res.headers.get("Retry-After", 1))
        print("429 reçu, pause de", wait, "s")
        time.sleep(wait)
```

<p class="callout success">Sans `Retry-After`, la bonne pratique est le **backoff exponentiel** : attendre 1 s, puis 2 s, puis 4 s, puis 8 s… Le serveur respire, et vous finissez par passer.</p>

<p class="callout warning">Réessayer immédiatement en boucle après un `429`, c'est comme klaxonner dans un embouteillage : ça n'avance pas plus vite, et tout le monde vous déteste.</p>