Les endpoints qui renvoient des listes (missions, événements, livraisons webhook) paginent par curseur. Plutôt que des numéros de page, vous suivez un jeton opaque (next_cursor) d’une page à la suivante jusqu’à atteindre la fin. Ce modèle reste stable même quand des données sont créées pendant que vous itérez.
Toutes les requêtes ci-dessous utilisent la base URL https://api.myexpressdriver.com/v1 et l’en-tête d’authentification X-Api-Key: med_live_....

Principe

Une requête de liste accepte deux paramètres de requête : limit (taille de page) et cursor (position de départ). La réponse renvoie un tableau data et un next_cursor.
curl -s "https://api.myexpressdriver.com/v1/transports?limit=50" \
  -H "X-Api-Key: med_live_xxxxxxxxxxxxxxxxxxxx"
Réponse :
{
  "data": [
    { "id": "-O9xAbCdEf", "transport_id": "M-54321", "status": "scheduled" }
  ],
  "next_cursor": "WyIyMDI2LTA2LTE2VDEwOjAwOjAwWiIsIi1POXgiXQ=="
}
Pour obtenir la page suivante, renvoyez la valeur de next_cursor dans le paramètre cursor :
curl -s "https://api.myexpressdriver.com/v1/transports?limit=50&cursor=WyIyMDI2LTA2LTE2VDEwOjAwOjAwWiIsIi1POXgiXQ==" \
  -H "X-Api-Key: med_live_xxxxxxxxxxxxxxxxxxxx"
Quand next_cursor vaut null, vous avez atteint la dernière page : il n’y a plus rien à récupérer.
La condition d’arrêt d’une boucle de pagination est toujours next_cursor === null. Ne vous fiez pas à data.length < limit : une page peut être pleine et pourtant être la dernière.

Paramètres

limit
integer
défaut:"50"
Nombre maximal d’éléments par page. Le maximum dépend de l’endpoint (voir le tableau ci-dessous). Une valeur supérieure au maximum est plafonnée au maximum.
cursor
string
Jeton opaque renvoyé par le champ next_cursor de la page précédente. À omettre pour récupérer la première page.
Le curseur est opaque : c’est un base64 de [created_at, id]. Ne le construisez pas, ne le décodez pas et ne le modifiez pas vous-même — son format peut évoluer sans préavis. Transmettez-le tel quel.

Limites par endpoint

La taille de page par défaut et son maximum dépendent de l’endpoint.
Endpointlimit par défautlimit max
GET /transports50200
GET /events50200
GET /webhooks/deliveries50200
GET /transports/{id}/positions100500
GET /transports/{id}/waypoints100500
Les endpoints de tracking (/positions, /waypoints) renvoient un objet { "data": [...] } borné par limit, sans champ next_cursor. Pour /positions, paginez plutôt par fenêtre temporelle avec le paramètre since (voir l’onglet « Positions » plus bas). La pagination par curseur (next_cursor) concerne /transports, /events et /webhooks/deliveries.

Forme de la réponse

data
array
requis
Le tableau d’éléments de la page courante. Vide ([]) s’il n’y a aucun résultat.
next_cursor
string | null
requis
Le curseur à passer dans le paramètre cursor pour obtenir la page suivante. Vaut null sur la dernière page.

Boucle de pagination complète (Node.js)

Voici une fonction réutilisable qui suit next_cursor jusqu’à null et accumule tous les éléments. Elle préserve les filtres d’origine (status, type, since, etc.) à chaque tour de boucle.
// Node.js >= 18 (fetch global)
const BASE = "https://api.myexpressdriver.com/v1";
const API_KEY = process.env.MED_API_KEY; // med_live_...

/**
 * Récupère TOUS les éléments d'un endpoint paginé par curseur.
 * @param {string} path   Ex. "/transports"
 * @param {object} params Filtres et limit. Ex. { status: "in_transit", limit: 200 }
 * @returns {Promise<Array>} Tous les éléments, toutes pages confondues.
 */
async function fetchAll(path, params = {}) {
  const all = [];
  let cursor = null;

  do {
    const query = new URLSearchParams(params);
    if (cursor) query.set("cursor", cursor);

    const res = await fetch(`${BASE}${path}?${query}`, {
      headers: { "X-Api-Key": API_KEY },
    });

    if (res.status === 429) {
      // Respectez Retry-After puis reprenez SANS avancer le curseur.
      const wait = Number(res.headers.get("Retry-After") ?? 1);
      await new Promise((r) => setTimeout(r, wait * 1000));
      continue;
    }

    if (!res.ok) {
      // Erreur RFC 9457 (application/problem+json)
      const problem = await res.json();
      throw new Error(`${problem.status} ${problem.title}: ${problem.detail ?? ""}`);
    }

    const page = await res.json();
    all.push(...page.data);

    cursor = page.next_cursor; // null => dernière page
  } while (cursor !== null);

  return all;
}

// Exemple : toutes les missions en cours de transport (pages de 200).
const transports = await fetchAll("/transports", {
  status: "in_transit",
  limit: 200,
});
console.log(`${transports.length} missions récupérées`);
Demandez la limit maximale (200 pour les listes, 500 pour les positions) afin de réduire le nombre d’allers-retours. Combinez-la avec les filtres (status, type, since) pour ne rapatrier que ce dont vous avez besoin.

Variante curl (boucle shell)

Le même algorithme en Bash, avec jq pour lire next_cursor :
BASE="https://api.myexpressdriver.com/v1"
cursor=""

while :; do
  url="$BASE/transports?limit=200"
  [ -n "$cursor" ] && url="$url&cursor=$cursor"

  resp=$(curl -s -H "X-Api-Key: $MED_API_KEY" "$url")

  # … traiter le tableau .data ici …
  echo "$resp" | jq -r '.data[].transport_id'

  cursor=$(echo "$resp" | jq -r '.next_cursor // empty')
  [ -z "$cursor" ] && break    # next_cursor == null => fin
done

Cas particuliers selon l’endpoint

Curseur classique. Combinez avec le filtre status (statut public) pour ne lister qu’un sous-ensemble :
curl -s "https://api.myexpressdriver.com/v1/transports?status=completed&limit=200" \
  -H "X-Api-Key: med_live_xxxxxxxxxxxxxxxxxxxx"
Statuts publics acceptés : scheduled, assigned, in_transit, in_transit_return, awaiting_documents, under_review, completed, incident, cancelled.

Bonnes pratiques

Sur de gros volumes, n’attendez pas d’avoir tout en mémoire : traitez (ou persistez) chaque page dès sa réception, puis passez au curseur suivant. Cela borne votre empreinte mémoire.
Si une page renvoie 429 Too Many Requests, attendez la durée indiquée par l’en-tête Retry-After (en secondes) puis rejouez la même requête avec le même curseur — ne l’avancez pas tant que la page n’a pas été récupérée avec succès.
Le curseur sert à enchaîner les pages d’un même parcours. Pour reprendre une synchronisation ultérieurement, préférez le filtre since (sur /events et /positions) plutôt que de stocker un curseur, dont le format est opaque et susceptible d’évoluer.

Étapes suivantes

Démarrage rapide

Créez votre première mission et récupérez-la en quelques minutes.

Gestion des erreurs

Comprendre le format RFC 9457 et réagir aux codes 401, 422, 429.

Réconciliation des événements

Rejouez le journal /events pour rattraper les webhooks manqués.