Skip to content

Rate limits

Combien de trafic l'API publique accepte aujourd'hui, comment les limites par clé API sont enforced, et comment concevoir un client qui backoff correctement sous throttling.

Cette page fait foi pour le comportement de throttle. L'envelope 429, les codes rate_limited et too_many_failures, et les headers Retry-After / X-RateLimit-* sont documentés de bout en bout ci-dessous.

Aujourd'hui

Deux plafonds distincts sont actifs :

LimiteValeurOù elle s'applique
Reads (GET /...)600 req/min soutenu, burst 100Par clé API. Renvoie 429 avec code rate_limited au-delà.
Writes (POST / PUT / PATCH / DELETE)120 req/min soutenu, burst 30Par clé API. Renvoie 429 avec code rate_limited.
Déclenchement d'imports (POST /imports, POST /imports/{id}/start, POST /imports/{id}/cancel)10 req/min soutenu, burst 5Par clé API. Renvoie 429 avec code rate_limited.
Tentatives d'auth échouées5 / minute / IPRenvoie 429 avec le code too_many_failures au-delà. Cooldown de 60 secondes.
Taille du body5 MBPar requête, sur tous les endpoints. Au-delà, 413. Utilisez plutôt Imports.
Taille de batch500 itemsPar appel POST /products/batch.
Imports, nombre de lignesPas de hard capLes fichiers NDJSON au-delà de ~1M de lignes mettent sensiblement plus de temps à être traités. Splittez en plusieurs imports si vous le pouvez.
Imports concurrentsPas de hard capRestez raisonnable, lancer des dizaines de gros imports en parallèle pour la même company est ok sur quelques bursts, pas en régime permanent.

Les buckets sont indépendants : saturer les reads ne throttle pas vos writes, et vice-versa.

Planifiez sur les valeurs documentées

Les limites documentées sont le contrat. Traitez le tableau ci-dessus comme le hard cap et ne construisez pas vos budgets de retry sur une marge supplémentaire.

Granularité : par clé API

Les limites s'appliquent par clé API, pas par company. Chaque clé tient ses propres compteurs, provisioner des clés supplémentaires élargit votre budget effectif et isole une intégration bruyante du reste de votre trafic. Si vous avez un cron de backfill qui burste de temps en temps et un storefront client-facing qui ne doit jamais voir de 429, créez une clé dédiée pour chacun.

Le trade-off : pas de cap global par company. On s'attend à ce que le budget par clé suffise pour n'importe quel sync réaliste ; si une seule intégration en a légitimement besoin de plus, créez des clés additionnelles plutôt que de nous demander de lever les limites.

La réponse 429

Quand une requête est throttlée, l'API répond 429 Too Many Requests avec l'envelope d'erreur standard. Le code d'erreur est rate_limited pour les buckets par clé API et too_many_failures pour le throttle de la couche d'auth (par IP). Voir Codes d'erreur Humind.

Headers de réponse

HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 12
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1714060800
HeaderSignification
Retry-AfterSecondes à attendre avant le prochain retry. Toujours présent sur 429. À utiliser comme plancher de votre backoff.
X-RateLimit-LimitCap soutenu par minute pour ce groupe d'endpoints.
X-RateLimit-RemainingRequêtes restantes dans la fenêtre courante. Tombe à 0 quand le cap est atteint.
X-RateLimit-ResetUnix epoch (secondes) du reset de la fenêtre, où le budget se recharge.

Les valeurs de burst ne sont pas dans les headers

X-RateLimit-Limit n'expose que la limite soutenue par minute. Le budget de burst (par exemple 30 burst sur 120 writes soutenus) n'est pas remonté dans les headers — traitez-le comme du headroom court terme additionnel, pas comme un signal runtime documenté.

Body de réponse

json
{
  "error": {
    "code": "rate_limited",
    "message": "Rate limit exceeded. Retry after 12 seconds.",
    "request_id": "req_8f3a1c2d4e5b6a7f",
    "details": {
      "retry_after": 12
    }
  }
}

Le header Retry-After et details.retry_after sont toujours d'accord. Lisez l'un ou l'autre, le header fait foi.

Implémenter le backoff

Lisez Retry-After sur chaque 429, attendez au moins ce délai, puis doublez l'attente à chaque échec suivant jusqu'à un cap de 60 secondes. Arrêtez après 5 retries ; si vous êtes encore throttlé, le problème n'est pas transient.

Node

js
async function withBackoff(fetchOnce, { maxRetries = 5, cap = 60_000 } = {}) {
  let attempt = 0
  while (true) {
    const res = await fetchOnce()
    if (res.status !== 429) return res
    if (attempt >= maxRetries) return res
    const retryAfter = Number(res.headers.get('retry-after')) || 1
    const wait = Math.min(retryAfter * 1000 * 2 ** attempt, cap)
    await new Promise(r => setTimeout(r, wait))
    attempt++
  }
}

Python

python
import time, requests

def with_backoff(call, max_retries=5, cap=60):
    attempt = 0
    while True:
        res = call()
        if res.status_code != 429:
            return res
        if attempt >= max_retries:
            return res
        retry_after = int(res.headers.get('Retry-After', '1'))
        wait = min(retry_after * (2 ** attempt), cap)
        time.sleep(wait)
        attempt += 1

Ruby

ruby
def with_backoff(max_retries: 5, cap: 60)
  attempt = 0
  loop do
    res = yield
    return res if res.code.to_i != 429
    return res if attempt >= max_retries
    retry_after = res['Retry-After'].to_i
    retry_after = 1 if retry_after.zero?
    wait = [retry_after * (2 ** attempt), cap].min
    sleep wait
    attempt += 1
  end
end

Best practices

  • Respectez Retry-After. Traitez-le comme le plancher de votre backoff, pas un indice. Réessayer avant ne fait que ramasser un autre 429 et brûle du budget.
  • Backoff exponentiel avec cap. Commencez à Retry-After, doublez à chaque retry, cap à 60s. Cinq retries max, au-delà le problème n'est pas transient.
  • Préférez batch aux boucles. POST /products/batch coûte une requête pour jusqu'à 500 produits, contre 500 POST /products individuels. La boucle vide votre budget write en quelques secondes.
  • Utilisez Imports pour les gros pushes. 50 000 produits = un appel POST /imports, pas 100 batchs ni 50 000 calls individuels. Imports contourne aussi les limites de body-size et per-call.
  • Cachez les reads côté merchant. Si votre code lit le même produit plusieurs fois dans une fenêtre courte, cachez le résultat. L'API produit n'est pas une base de données, la round-tripper à chaque render brûle latence et quota.
  • Ne retentez pas les writes aveuglément. Associez chaque retry à un Idempotency-Key pour qu'un second call ne puisse pas double-créer. Voir Idempotency.
  • Étalez le trafic bulk. Si vous devez faire des milliers d'écritures, dripez-les à une ou deux par seconde plutôt que les balancer en un seul burst, les bursts se font clipper, les drips non.
  • Loggez request_id sur chaque 429. L'inclure dans les tickets de support rend le lookup logs immédiat.

Éviter les rate limits avec batches et imports

Le moyen le plus fiable de ne pas se prendre une rate limit est d'utiliser le bon endpoint pour le volume que vous déplacez.

BesoinNUtilisez
Push de produits1POST /products
Push de produits2–500POST /products/batch
Push de produits500+POST /imports (NDJSON)
Read d'un seul produit1GET /products/{id}
Read de produits par filtreplusieursGET /products?... (filtrage côté serveur, pas un fan-out client)
Read de produits par external_idplusieursGET /products/api:<external_id> par ID, cachez agressivement. Pour un backfill complet, utilisez Imports.

Le pattern : tout ce qui scale avec N doit être un seul call, pas N calls.

Connexions concurrentes

Pas de limite explicite sur les connexions concurrentes par clé API aujourd'hui. Une concurrence très haute se manifestera plutôt en latence qu'en throttling, donc gardez un parallélisme modéré même sur les endpoints où aucun 429 n'est appliqué.

Quelques règles :

  • Du trafic soutenu au-delà de ~50 req/seconde sur la même company commence à faire monter la P95. Gardez la concurrence modérée (quelques requêtes en vol à la fois, pas des centaines).
  • Réutilisez les connexions HTTP. Ouvrez un client keep-alive au démarrage et réutilisez-le pour chaque call. Faire un nouveau handshake TCP/TLS par requête est gaspilleur et lent.
  • Le connection pooling côté merchant aide plus que le parallélisme. Un pool de 4–8 connexions qui drippent du trafic régulier surclasse un flood de 100 requêtes concurrentes, autant en throughput qu'en compatibilité avec les limites documentées.

Pour aller plus loin

  • Errors : format d'erreur, liste complète des codes HTTP et codes d'erreur Humind, dont rate_limited et too_many_failures.
  • Conventions : format de requête, taille de body, idempotency.
  • Imports : le bon outil pour déplacer de gros catalogues sans cramer le budget write.

Released under the proprietary Humind license.