Apariencia
Rate Limits y Throttling
Resumen rápido
Amazon SP-API limita el número de peticiones que puedes hacer usando el algoritmo token bucket. Cada API tiene dos parámetros: rate (peticiones por segundo que se reponen) y burst (máximo acumulable). Si superas el burst, recibirás un error 429 QuotaExceeded. La clave es respetar los límites para evitar baneos y diseñar el código para funcionar dentro de ellos en lugar de ignorarlos.
Conceptos importantes
- Rate limit: El número de tokens (peticiones) por segundo que se recargan en el bucket. Si haces peticiones a esta tasa exacta, nunca serás bloqueado.
- Burst: El tamaño máximo del bucket. Puedes acumular hasta este número de peticiones y lanzarlas rápidamente al inicio.
- Token bucket algorithm: Algoritmo estándar de control de flujo. El bucket se llena a la tasa
ratehasta un máximo deburst. Cada petición consume un token. QuotaExceeded: El error429que recibes cuando el bucket está vacío.x-amz-rate-limit-limit: Header HTTP que Amazon devuelve en cada respuesta con tu límite real actual. Más fiable que los valores documentados porque refleja si Amazon te ha subido el límite dinámicamente.- Dynamic usage plans: Amazon puede subir o bajar tus límites automáticamente según el histórico de uso de tu app. Si haces muchas peticiones de forma consistente, Amazon puede triplicar tu rate.
- NextToken: Token de paginación que devuelven las APIs cuando hay más de 100 resultados. Debes hacer una petición por página, lo que consume tokens del bucket.
Cómo funciona
El algoritmo token bucket explicado
Bucket capacidad = burst (ej. 20 tokens)
Tasa de recarga = rate (ej. 0.0167 tokens/seg = 1 token/60 seg)
Estado inicial: bucket lleno (20 tokens)
→ Hago 20 peticiones rápidas → bucket vacío
→ Siguiente petición → ERROR 429 (QuotaExceeded)
→ Espero 60 segundos → el bucket tiene 1 token
→ Puedo hacer 1 petición más
→ Espero 60 segundos más → 1 token más
...Rate limits de las APIs principales
| API | Operación | Rate (req/s) | Burst |
|---|---|---|---|
| Orders | GetOrders | 0.0167 (1/min) | 20 |
| Orders | GetOrder | 0.5 | 30 |
| Orders | GetOrderItems | 0.5 | 30 |
| Catalog Items | SearchCatalogItems | 2 | 5 |
| Catalog Items | GetCatalogItem | 2 | 2 |
| Reports | CreateReport | 0.0167 | 15 |
| Reports | GetReport | 2 | 15 |
| Listings | patchListingsItem | 5 | 10 |
| Notifications | createDestination | 1 | 5 |
Estos valores son los predeterminados. El header
x-amz-rate-limit-limitte da tu valor real.
Pasos prácticos
Leer el rate limit real desde el header de respuesta
python
import requests
def make_api_request(access_token: str, url: str, params: dict) -> dict:
response = requests.get(
url,
headers={"x-amz-access-token": access_token},
params=params,
)
# Leer el rate limit real desde el header
rate_limit = response.headers.get("x-amz-rate-limit-limit")
if rate_limit:
print(f"Tu rate limit actual: {rate_limit} req/s")
if response.status_code == 429:
print("Rate limit excedido (QuotaExceeded). Esperando...")
# El header Retry-After puede indicar cuántos segundos esperar
retry_after = response.headers.get("Retry-After", "60")
return {"error": "throttled", "retry_after": int(retry_after)}
response.raise_for_status()
return response.json()Rate limiter básico en Python (siguiendo el modelo de Amazon)
python
import threading
import time
class RateLimiter:
"""
Implementa el token bucket algorithm de Amazon SP-API.
tokens_per_second: el valor del campo 'rate' de la documentación
capacity: el valor del campo 'burst' de la documentación
"""
def __init__(self, tokens_per_second: float, capacity: int):
self.tokens_per_second = tokens_per_second
self.capacity = capacity
self.tokens = float(capacity)
self.last_refill = time.time()
self.lock = threading.Lock()
def _refill(self):
now = time.time()
elapsed = now - self.last_refill
tokens_to_add = elapsed * self.tokens_per_second
self.tokens = min(self.capacity, self.tokens + tokens_to_add)
self.last_refill = now
def acquire(self):
"""Espera hasta que haya un token disponible."""
while True:
with self.lock:
self._refill()
if self.tokens >= 1:
self.tokens -= 1
return
wait_time = 1.0 / self.tokens_per_second
time.sleep(wait_time)
# Ejemplo de uso: Orders API (rate=0.0167, burst=20)
orders_limiter = RateLimiter(tokens_per_second=0.0167, capacity=20)
def get_orders_with_rate_limit(access_token: str, endpoint: str, marketplace_id: str):
all_orders = []
next_token = None
page = 0
while True:
orders_limiter.acquire() # Esperar hasta tener un token disponible
params = {"MarketplaceIds": marketplace_id}
if next_token:
params["NextToken"] = next_token
else:
params["CreatedAfter"] = "2024-01-01T00:00:00Z"
response = requests.get(
f"{endpoint}/orders/v0/orders",
headers={"x-amz-access-token": access_token},
params=params,
)
response.raise_for_status()
data = response.json().get("payload", {})
orders = data.get("Orders", [])
all_orders.extend(orders)
page += 1
print(f"Página {page}: {len(orders)} pedidos obtenidos")
next_token = data.get("NextToken")
if not next_token:
break
return all_ordersRetry con backoff exponencial
python
import time
import random
def request_with_retry(func, max_retries: int = 3, *args, **kwargs):
"""Reintenta en caso de throttling con backoff exponencial."""
for attempt in range(max_retries):
try:
return func(*args, **kwargs)
except requests.exceptions.HTTPError as e:
if e.response.status_code == 429:
wait = (2 ** attempt) + random.uniform(0, 1)
print(f"Throttled. Reintentando en {wait:.1f}s (intento {attempt + 1})")
time.sleep(wait)
else:
raise
raise Exception(f"Máximo de reintentos alcanzado ({max_retries})")Estrategias de optimización
| Estrategia | Reducción de llamadas |
|---|---|
| Usar Notifications API en lugar de polling | Hasta 100x menos |
| Usar Reports API en lugar de Get repetitivo | Hasta 50x menos |
SearchCatalogItems (20 items/llamada) en lugar de GetCatalogItem | 20x menos |
Usar ORDER_CHANGE en lugar de sondear Orders API | Miles de llamadas menos |
| Programar llamadas por la noche (menos tráfico) | Mejor respuesta |
Errores comunes
- Hardcodear los rate limits: Los valores de la documentación son los mínimos garantizados. Tu app puede tener valores más altos (o más bajos). Lee siempre el header
x-amz-rate-limit-limit. - No manejar el
429: Si no capturas este error, tu app fallará silenciosamente o lanzará excepciones no controladas. - Hacer polling agresivo: Bucles que consultan la API cada pocos segundos consumen el burst rápidamente y terminan bloqueados.
- Abusar demasiado: Amazon puede penalizarte o incluso banearte si haces demasiadas peticiones que superan límites de forma continuada.
- Confundir rate y burst:
ratees la velocidad de recarga.burstes cuánto puedes hacer de golpe.
Qué debo saber antes de programarlo
- Para obtener órdenes de historial (cientos de páginas con
NextToken), el rate deGetOrders(1 req/min) es muy lento. Es mejor usar Reports API (GET_FLAT_FILE_ALL_ORDERS_DATA_BY_ORDER_DATE_GENERAL) que te da todo en un solo archivo. - Si tu app tiene muchos clientes (SaaS), cada cliente tiene sus propias credenciales y sus propios rate limits. No compartes quota entre clientes.
- Un patrón de producción sólido: cola de trabajos + worker que respeta el rate limiter. Nunca llames a la API directo desde una petición HTTP al usuario final.
Pendiente de revisar
- ¿Amazon penaliza o banea por exceder el throttle repetidamente, o solo devuelve 429?
- ¿El dynamic usage plan se aplica por aplicación o por cuenta de vendedor?
- ¿Cómo funcionan exactamente los rate limits en apps con múltiples cuentas de vendedor (SaaS)?