Rate limiting — Public API
Limites par défaut
Chaque clé API a deux compteurs indépendants :
| Fenêtre | Défaut | Surchargeable par clé |
|---|---|---|
| Par minute | 60 requêtes | Oui (rateLimitPerMinute) |
| Par jour | 10 000 requêtes | Oui (rateLimitPerDay) |
Les compteurs sont per-clé (pas per-agence ni per-IP). Reset rolling : la fenêtre minute se réinitialise 60s après le 1er hit, idem pour la fenêtre jour à 24h.
Source : server/publicApi/rateLimit.ts
Filet de sécurité global
En complément, un rate limit global Express (apiLimiter) protège tout /api/* à 200 req/min par IP (filtré par Traefik en prod via trust proxy). En pratique, vous toucherez votre limite per-clé bien avant.
Headers de réponse
Toutes les réponses (succès ou erreur) incluent :
| Header | Description |
|---|---|
X-RateLimit-Limit | Limite max par minute pour cette clé |
X-RateLimit-Remaining | Requêtes restantes dans la fenêtre minute courante |
X-RateLimit-Reset | Timestamp Unix (secondes) du prochain reset minute |
En cas de dépassement quotidien :
| Header | Description |
|---|---|
X-RateLimit-Limit-Day | Limite max par jour |
X-RateLimit-Remaining-Day | 0 (vous êtes à la limite) |
Retry-After | Secondes avant le prochain reset |
Réponse 429
HTTP/1.1 429 Too Many Requests
Retry-After: 47
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1730019660
Content-Type: application/json
{
"error": "Rate limit exceeded: 60 requests per minute",
"code": "RATE_LIMIT_EXCEEDED",
"retryAfter": 47
}Stratégie de retry recommandée
- Respecter
Retry-After: ne pas retenter avant ce délai - Backoff exponentiel sur les erreurs 5xx (pas sur 429 — le serveur dit déjà combien attendre)
- Jitter (±20%) pour éviter le thundering herd si plusieurs clients retentent en même temps
- Limite max de retries : 3-5 selon criticité
Pseudo-code :
async function callApi(url, options, attempt = 0) {
const res = await fetch(url, options);
if (res.status === 429) {
const retryAfter = parseInt(res.headers.get('Retry-After') || '60', 10);
await sleep(retryAfter * 1000 + Math.random() * 200);
return callApi(url, options, attempt + 1);
}
if (res.status >= 500 && attempt < 3) {
await sleep(Math.pow(2, attempt) * 1000);
return callApi(url, options, attempt + 1);
}
return res;
}Surveiller votre consommation
Endpoint /me (voir authentication.md) renvoie les compteurs en temps réel — utile en pré-vol avant un batch lourd :
{
"usage": {
"minuteCount": 58, // ← attention, presque à la limite
"minuteResetAt": "2026-04-27T10:01:00.000Z",
"dayCount": 9847,
"dayResetAt": "2026-04-28T00:00:00.000Z"
}
}Augmenter les limites
Les limites par-clé sont éditables côté admin (PATCH /api/public-api/admin/keys/:id). Pour des cas d'usage à fort volume, contactez support@stormeo.io avec :
- Volume estimé (req/min, req/jour)
- Cas d'usage (Zapier interne ? sync inverse ? script de migration ?)
- Pic de charge attendu
Note technique
- Le bucket est in-memory (Map) — reset au redémarrage du serveur. Acceptable car le filet
apiLimiterglobal protège quand même. - Les buckets abandonnés (>25h sans hit) sont nettoyés toutes les 10 min pour éviter les fuites mémoire.
- Pas de file d'attente côté serveur : si vous saturez, vous recevez 429 immédiatement (fail fast).