Limites de débit
Chaque clé d’API a un débit maximal, compté sur une fenêtre fixe d’une seconde. Le comptage est partagé entre toutes les instances du service : la limite est la même quel que soit le serveur qui répond.
Le débit appliqué est celui de la clé si elle en a un, sinon celui de l’offre de l’espace (Pro : 25 requêtes par seconde, Business : 50). GET /v1/workspace le donne dans limits.requests_per_second.
En-têtes#
Chaque réponse authentifiée porte l’état de la fenêtre en cours :
RateLimit-Limit: 25
RateLimit-Remaining: 19
RateLimit-Reset: 1
RateLimit-Policy: 25;w=1| En-tête | Sens |
|---|---|
RateLimit-Limit |
Requêtes permises par fenêtre. |
RateLimit-Remaining |
Requêtes restantes dans la fenêtre en cours. |
RateLimit-Reset |
Secondes avant la fenêtre suivante (toujours 1). |
RateLimit-Policy |
Politique appliquée : <limite>;w=<fenêtre en secondes>. |
Dépassement#
Au-delà de la limite, l’API répond 429 avec Retry-After: 1 :
{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded: 25 requests per second per API key.",
"details": { "limit": 25, "window_seconds": 1 }
}
}Attendez la durée de Retry-After, puis réessayez. Ne distinguez pas rate_limited (débit, quelques millisecondes à attendre) de daily_quota_exceeded (quota quotidien, jusqu’à minuit UTC) par le statut seul : lisez le code.
Relancer proprement#
async function call(url, init = {}, attempt = 0) {
const res = await fetch(url, {
...init,
headers: { Authorization: `Bearer ${process.env.AICSCAN_API_KEY}`, ...init.headers },
});
if (res.status === 429 && attempt < 5) {
const body = await res.json();
if (body.error.code === 'daily_quota_exceeded') throw new Error('daily quota exceeded');
const wait = Number(res.headers.get('Retry-After') ?? 1) * 1000;
await new Promise((r) => setTimeout(r, wait + Math.random() * 250));
return call(url, init, attempt + 1);
}
return res;
}import os, random, time
import requests
def call(method, url, attempt=0, **kwargs):
headers = {"Authorization": f"Bearer {os.environ['AICSCAN_API_KEY']}", **kwargs.pop("headers", {})}
resp = requests.request(method, url, headers=headers, timeout=10, **kwargs)
if resp.status_code == 429 and attempt < 5:
if resp.json()["error"]["code"] == "daily_quota_exceeded":
raise RuntimeError("daily quota exceeded")
time.sleep(int(resp.headers.get("Retry-After", "1")) + random.random() / 4)
return call(method, url, attempt + 1, headers=headers, **kwargs)
return respPour une écriture (POST), envoyez la même Idempotency-Key à chaque tentative : une relance ne crée jamais de doublon.
Conseils#
- Plutôt que d’interroger
GET /v1/matchesen boucle, recevez les alertes par webhook ou par le flux temps réel. - Pour parcourir un long historique, utilisez
limit=100: une page de 100 éléments compte pour un seul appel. - Répartissez les traitements de masse : quelques requêtes par seconde suffisent à parcourir des milliers d’éléments.