L’API Transport applique des limites de débit (rate limiting) pour garantir la stabilité du service et une qualité égale pour tous les groupes clients. Quand vous dépassez une limite, l’API renvoie un statut 429 Too Many Requests au lieu de traiter la requête.
Les limites de débit ne sont pas un mode de facturation. Elles protègent l’infrastructure contre les pics de trafic et les boucles d’appels accidentelles. Un client bien conçu (backoff + webhooks) ne devrait jamais les atteindre en fonctionnement normal.

Les trois dimensions de limitation

Le débit est contrôlé sur trois axes complémentaires. Une requête doit passer les trois pour être traitée ; le premier seuil atteint déclenche le 429.

Par IP

Protège contre les bursts provenant d’une même adresse source, avant même la résolution de la clé API. C’est la première barrière, utile contre les boucles de retry trop agressives.

Par clé

Chaque clé API (med_live_… / med_test_…) dispose de son propre budget. Une clé saturée n’impacte pas les autres clés de votre groupe.

Globale

Une limite de plateforme, partagée, qui protège le service amont (relai de création / tarification) contre une surcharge agrégée.
Le detail du corps d’erreur précise l’axe atteint, par exemple Rate limit (key) exceeded. ou Rate limit (ip) exceeded.. Comme toujours, branchez votre logique sur le statut (429) et non sur le texte de detail, qui peut évoluer.

La réponse 429

Quand une limite est franchie, l’API répond 429 au format RFC 9457 (application/problem+json) et joint un en-tête Retry-After indiquant le délai (en secondes) à attendre avant de réessayer.
Retry-After
integer
Nombre de secondes à patienter avant de soumettre une nouvelle requête. Respectez-le impérativement : réessayer plus tôt sera de nouveau refusé et peut prolonger la fenêtre de blocage.
Exemple de réponse complète :
HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 30

{
  "type": "about:blank",
  "title": "Too Many Requests",
  "status": 429,
  "detail": "Rate limit (key) exceeded."
}
L’absence de traitement du 429 dégrade votre intégration : sans backoff, vos retries entretiennent la saturation. Traitez le 429 comme un signal de ralentissement, pas comme une erreur fatale.

Bonnes pratiques

1

Respecter Retry-After + backoff exponentiel

En réponse à un 429, attendez au minimum la valeur de Retry-After. En son absence, appliquez un backoff exponentiel avec jitter (aléa) pour éviter que tous vos clients ne réessaient en même temps.
2

Regrouper les requêtes

Préférez une requête paginée à des centaines d’appels unitaires. Pour parcourir vos missions, utilisez la pagination par curseur (?limit=200&cursor=…) plutôt que d’interroger chaque mission une par une.
3

Préférer les webhooks au polling

Ne sondez pas (poll) en boucle GET /transports/{id} pour détecter un changement de statut. Abonnez-vous aux webhooks : MED vous notifie en push à chaque transition, sans consommer votre budget de requêtes.
4

Réconcilier par lot, pas en continu

Pour rattraper d’éventuels événements manqués, appelez périodiquement GET /events?since=<dernier_traité> (toutes les quelques minutes), au lieu d’un polling serré.

Polling vs webhooks

Interroger le détail d’une mission en boucle consomme votre budget et atteint vite la limite par clé, pour une information que vous auriez reçue gratuitement par webhook.
# Anti-pattern : sonder le statut toutes les secondes.
while true; do
  curl -s -H "X-Api-Key: $MED_API_KEY" \
    "$BASE/transports/$TID" | jq -r '.status'
  sleep 1   # ➜ saturation rapide de la limite "par clé"
done

Exemple : retry avec backoff exponentiel (Node.js)

Ce client fetch (Node.js ≥ 18) réessaie automatiquement sur 429 (et sur les erreurs amont 5xx/502, sûres à rejouer grâce à l’idempotence). Il respecte Retry-After quand il est présent, sinon applique un backoff exponentiel borné avec jitter.
// med-client.js — Node.js >= 18
const BASE = "https://api.myexpressdriver.com/v1";
const API_KEY = process.env.MED_API_KEY; // med_live_...

const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

/**
 * Appel API avec retry sur 429 et 5xx.
 * Respecte Retry-After ; sinon backoff exponentiel + jitter.
 */
async function medFetch(path, init = {}, { maxRetries = 5 } = {}) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const res = await fetch(`${BASE}${path}`, {
      ...init,
      headers: {
        "X-Api-Key": API_KEY,
        "Content-Type": "application/json",
        ...init.headers,
      },
    });

    // Succès ou erreur non rejouable (4xx hors 429) : on rend la réponse telle quelle.
    if (res.status !== 429 && res.status < 500) {
      return res;
    }

    // Plus de tentatives : on rend la dernière réponse (l'appelant décide).
    if (attempt === maxRetries) {
      return res;
    }

    // 1. Priorité à Retry-After (en secondes) si fourni par le serveur.
    const retryAfter = Number(res.headers.get("retry-after"));
    let delayMs;
    if (Number.isFinite(retryAfter) && retryAfter >= 0) {
      delayMs = retryAfter * 1000;
    } else {
      // 2. Sinon : backoff exponentiel borné (1s, 2s, 4s, … max 30s) + jitter.
      const base = Math.min(1000 * 2 ** attempt, 30_000);
      const jitter = Math.random() * 250;
      delayMs = base + jitter;
    }

    await sleep(delayMs);
  }
}

// Exemple d'utilisation : lister les missions en cours.
const res = await medFetch("/transports?status=in_transit&limit=50");
if (!res.ok) {
  const problem = await res.json(); // { type, title, status, detail }
  throw new Error(`${problem.status} ${problem.title}: ${problem.detail ?? ""}`);
}
const { data, next_cursor } = await res.json();
console.log(`${data.length} missions`, next_cursor);
Mutualisez ce wrapper : toutes vos requêtes passant par medFetch héritent du retry. Combiné à l’en-tête Idempotency-Key sur les POST mutants, un rejeu après 429 (ou 5xx) ne crée jamais de doublon.

Récapitulatif

429 (limite de débit) et les erreurs amont 502/5xx (relai de création ou de tarification). Les 4xx autres que 429 (par ex. 422 validation, 404 introuvable) ne doivent pas être réessayés tels quels : corrigez d’abord la requête.
Trois leviers : pagination (regrouper en pages de 200), webhooks au lieu du polling, et réconciliation espacée via GET /events?since=…. La grande majorité des 429 proviennent d’un polling trop fréquent évitable.
Non. Une requête rejetée en 429 n’a pas été traitée : rejouer la même Idempotency-Key exécutera l’opération une fois la fenêtre passée, sans conflit.

Étapes suivantes

Gérer les erreurs

Le format RFC 9457, les codes HTTP courants et la bonne façon de brancher votre logique sur le status.

Idempotence

Rejouer une requête en toute sécurité après un 429 ou une erreur réseau, sans créer de doublon.

Webhooks

Recevoir les changements en push plutôt que de sonder l’API — la meilleure façon de rester sous les limites.