Limites de débit (Rate Limits)
Pour garantir une qualité de service optimale à tous les utilisateurs, l'API Infoparcelle applique des limites de débit sur les requêtes.
Limites globales
Limite actuelle
- 1200 requêtes par minute (soit 20 requêtes par seconde)
- Le compteur se réinitialise automatiquement chaque minute
- S'applique à toutes les requêtes authentifiées avec la même clé API
Quotas mensuels
Les quotas dépendent de votre plan d'abonnement :
| Plan | Crédits/mois | Appels standards | Prix |
|---|---|---|---|
| 🌱 Starter | 5 000 | ~500 | 25€/mois |
| 🚀 Business | 25 000 | ~2 500 | 100€/mois |
| 💼 Pro | 50 000 | ~5 000 | 200€/mois |
| 🏢 Enterprise | 250 000 | ~25 000 | 750€/mois |
| 🌟 Scale | 500 000 | ~50 000 | 1 250€/mois |
| 🚀 Enterprise+ | 1 000 000+ | ~100 000+ | Sur mesure |
Dépassement de quota
Lorsque vous atteignez votre quota mensuel, vous pouvez soit :
- Activer les dépassements : paiement à l'usage au-delà du quota
- Passer à un plan supérieur : augmenter votre quota mensuel
Erreur 429 - Trop de requêtes
Quand se produit cette erreur ?
Vous recevez une erreur 429 Too Many Requests lorsque vous dépassez la limite de 1200 requêtes par minute (soit 20 requêtes par seconde).
Réponse type
HTTP/1.1 429 Too Many Requests
Retry-After: 30
{
"error": "rate_limit_exceeded",
"message": "Limite de débit dépassée. Maximum 20 requêtes par seconde.",
"retry_after": 30
}
Le header Retry-After indique le nombre de secondes à attendre avant de réessayer.
Bonnes pratiques
1. Implémenter un système de retry
- JavaScript
- Python
- PHP
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url, {
...options,
headers: {
'Authorization': `Bearer ${apiKey}`,
...options.headers,
},
});
// Si on atteint la limite
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After') || 60;
console.log(`Rate limit atteinte. Attente de ${retryAfter}s...`);
// Attendre avant de réessayer
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
continue;
}
// Si la requête réussit
if (response.ok) {
return await response.json();
}
// Autres erreurs
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
} catch (error) {
if (i === maxRetries - 1) throw error;
// Backoff exponentiel
await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
}
}
}
// Utilisation
const data = await fetchWithRetry(
'https://app.infoparcelle.fr/api/v1/geocoder/search?recherche=1+Rue+de+Rivoli'
);
import time
import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
def create_session_with_retry():
"""Créer une session avec retry automatique"""
session = requests.Session()
# Configuration du retry
retry = Retry(
total=3,
backoff_factor=1,
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["GET", "POST", "PUT", "DELETE"]
)
adapter = HTTPAdapter(max_retries=retry)
session.mount("http://", adapter)
session.mount("https://", adapter)
return session
def fetch_with_retry(url, max_retries=3):
session = create_session_with_retry()
for attempt in range(max_retries):
try:
response = session.get(
url,
headers={'Authorization': f'Bearer {api_key}'}
)
# Si on atteint la limite
if response.status_code == 429:
retry_after = int(response.headers.get('Retry-After', 60))
print(f'Rate limit atteinte. Attente de {retry_after}s...')
time.sleep(retry_after)
continue
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
if attempt == max_retries - 1:
raise
# Backoff exponentiel
time.sleep(2 ** attempt)
# Utilisation
data = fetch_with_retry(
'https://app.infoparcelle.fr/api/v1/geocoder/search?recherche=1+Rue+de+Rivoli'
)
<?php
function fetchWithRetry($url, $maxRetries = 3) {
$apiKey = getenv('INFOPARCELLE_API_KEY');
for ($attempt = 0; $attempt < $maxRetries; $attempt++) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
],
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// Si on atteint la limite
if ($httpCode === 429) {
$data = json_decode($response, true);
$retryAfter = $data['retry_after'] ?? 60;
echo "Rate limit atteinte. Attente de {$retryAfter}s...\n";
sleep($retryAfter);
continue;
}
// Si la requête réussit
if ($httpCode === 200) {
return json_decode($response, true);
}
// Autres erreurs
if ($attempt === $maxRetries - 1) {
throw new Exception("HTTP $httpCode: $response");
}
// Backoff exponentiel
sleep(pow(2, $attempt));
}
}
// Utilisation
$data = fetchWithRetry(
'https://app.infoparcelle.fr/api/v1/geocoder/search?recherche=1+Rue+de+Rivoli'
);
2. Espacer vos requêtes
Pour rester sous la limite de 20 requêtes/seconde, espacez vos requêtes :
- JavaScript
- Python
class RateLimiter {
constructor(maxRequests = 20, interval = 1000) {
this.maxRequests = maxRequests;
this.interval = interval;
this.queue = [];
this.processing = false;
}
async execute(fn) {
return new Promise((resolve, reject) => {
this.queue.push({ fn, resolve, reject });
this.process();
});
}
async process() {
if (this.processing || this.queue.length === 0) return;
this.processing = true;
while (this.queue.length > 0) {
const batch = this.queue.splice(0, this.maxRequests);
await Promise.all(
batch.map(async ({ fn, resolve, reject }) => {
try {
const result = await fn();
resolve(result);
} catch (error) {
reject(error);
}
})
);
if (this.queue.length > 0) {
await new Promise(resolve => setTimeout(resolve, this.interval));
}
}
this.processing = false;
}
}
// Utilisation
const limiter = new RateLimiter(20, 1000); // 20 requêtes par seconde
const adresses = ['1+Rue+de+Rivoli', 'Place+des', '5+Avenue+de+la', /* ... */];
const results = await Promise.all(
adresses.map(adresse =>
limiter.execute(() =>
fetch(`https://app.infoparcelle.fr/api/v1/geocoder/search?recherche=${adresse}`, {
headers: { 'Authorization': `Bearer ${apiKey}` }
}).then(r => r.json())
)
)
);
import asyncio
import aiohttp
from asyncio import Semaphore
class RateLimiter:
def __init__(self, max_requests=20, interval=1.0):
self.semaphore = Semaphore(max_requests)
self.interval = interval
self.last_call = 0
async def __aenter__(self):
await self.semaphore.acquire()
# Attendre si nécessaire
now = asyncio.get_event_loop().time()
time_since_last = now - self.last_call
if time_since_last < self.interval:
await asyncio.sleep(self.interval - time_since_last)
self.last_call = asyncio.get_event_loop().time()
async def __aexit__(self, exc_type, exc, tb):
self.semaphore.release()
async def fetch_with_limit(session, url, limiter):
async with limiter:
async with session.get(
url,
headers={'Authorization': f'Bearer {api_key}'}
) as response:
return await response.json()
async def main():
limiter = RateLimiter(max_requests=20, interval=1.0)
adresses = ['1+Rue+de+Rivoli', 'Place+des', '5+Avenue+de+la'] # ...
async with aiohttp.ClientSession() as session:
tasks = [
fetch_with_limit(
session,
f'https://app.infoparcelle.fr/api/v1/geocoder/search?recherche={adresse}',
limiter
)
for adresse in adresses
]
results = await asyncio.gather(*tasks)
return results
# Utilisation
results = asyncio.run(main())
3. Mettre en cache les données
Cachez les données qui changent rarement :
- JavaScript
- PHP
class ApiCache {
constructor(ttl = 3600000) { // 1 heure par défaut
this.cache = new Map();
this.ttl = ttl;
}
get(key) {
const item = this.cache.get(key);
if (!item) return null;
if (Date.now() > item.expiry) {
this.cache.delete(key);
return null;
}
return item.value;
}
set(key, value) {
this.cache.set(key, {
value,
expiry: Date.now() + this.ttl,
});
}
}
// Utilisation
const cache = new ApiCache(3600000); // 1 heure
async function fetchWithCache(url) {
const cached = cache.get(url);
if (cached) {
console.log('Résultat depuis le cache');
return cached;
}
const response = await fetch(url, {
headers: { 'Authorization': `Bearer ${apiKey}` },
});
const data = await response.json();
cache.set(url, data);
return data;
}
<?php
class ApiCache {
private $cacheDir;
private $ttl;
public function __construct($cacheDir = '/tmp/api-cache', $ttl = 3600) {
$this->cacheDir = $cacheDir;
$this->ttl = $ttl;
if (!is_dir($cacheDir)) {
mkdir($cacheDir, 0755, true);
}
}
public function get($key) {
$file = $this->getCacheFile($key);
if (!file_exists($file)) {
return null;
}
$data = json_decode(file_get_contents($file), true);
if (time() > $data['expiry']) {
unlink($file);
return null;
}
return $data['value'];
}
public function set($key, $value) {
$file = $this->getCacheFile($key);
$data = [
'value' => $value,
'expiry' => time() + $this->ttl,
];
file_put_contents($file, json_encode($data));
}
private function getCacheFile($key) {
return $this->cacheDir . '/' . md5($key) . '.json';
}
}
// Utilisation
$cache = new ApiCache('/tmp/api-cache', 3600);
function fetchWithCache($url, $cache) {
$cached = $cache->get($url);
if ($cached !== null) {
echo "Résultat depuis le cache\n";
return $cached;
}
// Fetch depuis l'API
$ch = curl_init($url);
// ... configuration curl
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
$cache->set($url, $data);
return $data;
}
4. Optimiser les requêtes
Utilisez le paramètre champs
Ne récupérez que les données dont vous avez besoin :
# ❌ Récupère tous les champs (plus lourd)
curl "https://app.infoparcelle.fr/api/v1/geocoder/search?recherche=1+Rue+de+Rivoli"
# ✅ Récupère uniquement les champs nécessaires
curl "https://app.infoparcelle.fr/api/v1/geocoder/search?recherche=1+Rue+de+Rivoli&champs=identifiant_ban,adresse_complete,centre"
Limitez le nombre de résultats
# ❌ Récupère 10 résultats par défaut
curl "https://app.infoparcelle.fr/api/v1/geocoder/search?recherche=1+Rue+de+Rivoli"
# ✅ Récupère uniquement le meilleur résultat
curl "https://app.infoparcelle.fr/api/v1/geocoder/search?recherche=1+Rue+de+Rivoli&limite=1"
Privilégiez les géométries légères
# ❌ Contour complet (lourd)
curl "https://app.infoparcelle.fr/api/v1/parcelles/12345?geometrie=contour"
# ✅ Point central (léger)
curl "https://app.infoparcelle.fr/api/v1/parcelles/12345?geometrie=centre"
5. Surveiller votre utilisation
Consultez régulièrement votre tableau de bord API pour :
- Voir votre consommation quotidienne/mensuelle
- Identifier les pics d'utilisation
- Anticiper les dépassements de quota
Codes d'erreur liés aux limites
| Code | Message | Solution |
|---|---|---|
429 | Trop de requêtes | Attendez la période indiquée dans Retry-After |
434 | Quota dépassé | Activez les dépassements ou passez à un plan supérieur |
435 | Mise à niveau requise | Passez à un plan supérieur pour accéder à cette fonctionnalité |
Prochaines étapes
- Gestion des erreurs - Gérer tous les codes d'erreur
- Référence API - Explorer les endpoints