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 :
| Limite | Valeur | Où elle s'applique |
|---|---|---|
Reads (GET /...) | 600 req/min soutenu, burst 100 | Par clé API. Renvoie 429 avec code rate_limited au-delà. |
Writes (POST / PUT / PATCH / DELETE) | 120 req/min soutenu, burst 30 | Par 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 5 | Par clé API. Renvoie 429 avec code rate_limited. |
| Tentatives d'auth échouées | 5 / minute / IP | Renvoie 429 avec le code too_many_failures au-delà. Cooldown de 60 secondes. |
| Taille du body | 5 MB | Par requête, sur tous les endpoints. Au-delà, 413. Utilisez plutôt Imports. |
| Taille de batch | 500 items | Par appel POST /products/batch. |
| Imports, nombre de lignes | Pas de hard cap | Les 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 concurrents | Pas de hard cap | Restez 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| Header | Signification |
|---|---|
Retry-After | Secondes à attendre avant le prochain retry. Toujours présent sur 429. À utiliser comme plancher de votre backoff. |
X-RateLimit-Limit | Cap soutenu par minute pour ce groupe d'endpoints. |
X-RateLimit-Remaining | Requêtes restantes dans la fenêtre courante. Tombe à 0 quand le cap est atteint. |
X-RateLimit-Reset | Unix 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
{
"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
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
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 += 1Ruby
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
endBest 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/batchcoûte une requête pour jusqu'à 500 produits, contre 500POST /productsindividuels. 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-Keypour 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_idsur 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.
| Besoin | N | Utilisez |
|---|---|---|
| Push de produits | 1 | POST /products |
| Push de produits | 2–500 | POST /products/batch |
| Push de produits | 500+ | POST /imports (NDJSON) |
| Read d'un seul produit | 1 | GET /products/{id} |
| Read de produits par filtre | plusieurs | GET /products?... (filtrage côté serveur, pas un fan-out client) |
| Read de produits par external_id | plusieurs | GET /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_limitedettoo_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.