Subtask 2-2: Ozon Seller API — Rate Limits Documentation
Task: Extract rate limit information (global and per-method) Date: 2026-02-10 Status: ✅ COMPLETED
Executive Summary
Comprehensive rate limit documentation for Ozon Seller API, including global limits, per-endpoint restrictions, error handling, and best practices for API integration.
Global Rate Limits
Primary Rate Limit (Per Client ID)
| Limit Type | Value | Scope | Effective Date |
|---|---|---|---|
| Requests Per Second | 50 RPS | All methods per Client ID | May 19, 2025 |
Key Details:
- The 50 requests per second limit applies to all API methods from a single Client ID
- This is an account-wide limit, not per-endpoint
- All requests are counted toward this single limit regardless of the endpoint
- The limit was implemented/increased as of May 19, 2025
- Previously reported as an "increase" in the request limit
Source: Ozon Seller API Notification - Telegram
Rate Limit Error Responses
HTTP Status Code
| HTTP Code | Error Code | Message | Description |
|---|---|---|---|
| 429 | 8 | "You have reached request rate limit per second" | Rate limit exceeded |
Error Response Format
{
"code": 8,
"message": "You have reached request rate limit per second",
"details": "Too many requests"
}
Source: Ozon for Dev - Error Code 8
Endpoint-Specific Limits
Product Management
| Operation | Limit | Notes |
|---|---|---|
| POST /v2/product/import | Up to 100 products per request | Batch import limit |
| POST /v1/product/import/stocks | Up to 100 products per request | Stock update batch size |
| POST /v1/product/import/prices | Up to 100 products per request | Price update batch size |
| Daily product creation | 60 PDPs per day | Product Detail Pages |
| Daily product editing | 300 PDPs per day | Product Detail Pages |
| Total PDP limit | 120 PDPs | Maximum products allowed |
Stock and Inventory Updates
| Operation | Limit | Notes |
|---|---|---|
| Stock updates (single product/warehouse) | Once per 2 minutes | Per SKU per warehouse |
| Product availability updates | Up to 80 requests/minute | General recommendation |
| POST /v2/products/stocks | Up to 100 products per request | Multi-warehouse stock updates |
Barcode Operations
| Operation | Limit | Notes |
|---|---|---|
| POST /v1/barcode/add | Max 100 products per request | Batch barcode binding |
| Barcodes per product | Max 100 barcodes | Per product limit |
| Method usage | Max 20 times | Per time period |
Analytics and Reports
| Category | Limit | Notes |
|---|---|---|
| Performance API | 100,000 requests per day | For Ozon Performance accounts |
| Report generation | Per report type | Async generation with status checking |
Rate Limit Headers
Current Status: ❌ No Standard Rate Limit Headers
Important: Unlike many other APIs (Amazon, GitHub, etc.), Ozon Seller API does not provide standard rate limit headers in API responses.
Missing Headers:
- No
X-RateLimit-Limitheader - No
X-RateLimit-Remainingheader - No
X-RateLimit-Resetheader - No retry-after header in 429 responses
Implications:
- Cannot proactively monitor remaining quota
- Must implement client-side rate limiting
- Must handle 429 errors reactively
Best Practices for Handling Rate Limits
1. Client-Side Rate Limiting
Recommended Approach:
import time
from collections import deque
from threading import Lock
class RateLimiter:
def __init__(self, max_requests=50, time_window=1.0):
self.max_requests = max_requests
self.time_window = time_window # seconds
self.requests = deque()
self.lock = Lock()
def wait_if_needed(self):
with self.lock:
now = time.time()
# Remove old requests outside the time window
while self.requests and self.requests[0] <= now - self.time_window:
self.requests.popleft()
# If at limit, wait
if len(self.requests) >= self.max_requests:
sleep_time = self.time_window - (now - self.requests[0])
if sleep_time > 0:
time.sleep(sleep_time)
# Clean up old requests after sleeping
now = time.time()
while self.requests and self.requests[0] <= now - self.time_window:
self.requests.popleft()
# Add current request
self.requests.append(now)
# Usage
rate_limiter = RateLimiter(max_requests=50, time_window=1.0)
def make_api_request(url, headers, data):
rate_limiter.wait_if_needed()
response = requests.post(url, headers=headers, json=data)
# Handle response...
2. Retry Logic with Exponential Backoff
import time
import random
def make_request_with_retry(url, headers, data, max_retries=5):
for attempt in range(max_retries):
rate_limiter.wait_if_needed()
response = requests.post(url, headers=headers, json=data)
if response.status_code == 429:
# Rate limit exceeded - implement exponential backoff
wait_time = (2 ** attempt) + random.uniform(0, 1)
print(f"Rate limit hit. Waiting {wait_time:.2f}s...")
time.sleep(wait_time)
continue
elif response.status_code >= 500:
# Server error - retry with backoff
wait_time = (2 ** attempt) + random.uniform(0, 1)
print(f"Server error {response.status_code}. Retrying in {wait_time:.2f}s...")
time.sleep(wait_time)
continue
else:
# Success or other error - return as-is
return response
raise Exception(f"Max retries exceeded after {max_retries} attempts")
3. Request Batching
Optimize requests by batching:
# Instead of multiple individual calls:
for product in products:
update_stock(product['sku'], product['stock']) # ❌ Inefficient
# Use batch operations:
batch_size = 100
for i in range(0, len(products), batch_size):
batch = products[i:i+batch_size]
update_stocks_batch(batch) # ✅ Efficient
4. Asynchronous Request Processing
import asyncio
import aiohttp
async def fetch_with_rate_limit(session, url, headers, data, semaphore):
async with semaphore: # Limit concurrent requests
async with session.post(url, headers=headers, json=data) as response:
return await response.json()
async def bulk_request(items, max_concurrent=10):
semaphore = asyncio.Semaphore(max_concurrent)
async with aiohttp.ClientSession() as session:
tasks = [
fetch_with_rate_limit(session, url, headers, item, semaphore)
for item in items
]
return await asyncio.gather(*tasks)
Rate Limit Monitoring
Logging Implementation
import logging
from datetime import datetime
class APIRequestLogger:
def __init__(self):
self.request_count = 0
self.rate_limit_hits = 0
self.logger = logging.getLogger('ozon_api')
def log_request(self, url, status_code, response_time):
self.request_count += 1
if status_code == 429:
self.rate_limit_hits += 1
self.logger.info(
f"[{datetime.now().isoformat()}] "
f"Request #{self.request_count} | "
f"Status: {status_code} | "
f"Time: {response_time:.3f}s | "
f"URL: {url}"
)
if status_code == 429:
self.logger.warning(
f"Rate limit hit! Total hits: {self.rate_limit_hits}"
)
Metrics to Track
- Total Requests Per Second - Monitor RPS in real-time
- Rate Limit Hit Rate - Percentage of requests receiving 429
- Average Response Time - Detect performance degradation
- Peak Usage Hours - Identify high-traffic periods
- Endpoint-Specific Usage - Most frequently called endpoints
Common Rate Limit Scenarios
Scenario 1: Bulk Product Updates
Challenge: Updating stock for 10,000 products
Solution:
products = fetch_products_from_database() # 10,000 items
batch_size = 100
delay_between_batches = 2.0 # seconds
for i in range(0, len(products), batch_size):
batch = products[i:i+batch_size]
# Update stocks in batch
response = update_stocks_batch(batch)
if response.status_code == 429:
# Wait and retry
time.sleep(5)
response = update_stocks_batch(batch)
# Small delay to avoid hitting rate limit
time.sleep(delay_between_batches)
print(f"Processed {min(i+batch_size, len(products))}/{len(products)}")
Scenario 2: Order Processing Pipeline
Challenge: Processing 1,000 new orders with multiple API calls each
Solution:
async def process_order(order):
# Each order requires 3-4 API calls
try:
await get_order_details(order['id'])
await update_order_status(order['id'], 'processing')
await generate_shipping_label(order['id'])
await notify_customer(order['id'])
except Exception as e:
print(f"Error processing order {order['id']}: {e}")
# Process with concurrency control
orders = fetch_new_orders() # 1,000 orders
await process_orders_with_limit(orders, max_concurrent=20)
Scenario 3: Real-Time Stock Synchronization
Challenge: Keep stock synchronized across multiple warehouses
Solution:
import queue
import threading
request_queue = queue.Queue()
def worker():
while True:
item = request_queue.get()
try:
update_stock_item(item)
except Exception as e:
print(f"Error: {e}")
# Requeue for retry
request_queue.put(item)
finally:
request_queue.task_done()
# Start multiple worker threads
num_workers = 10
threads = []
for _ in range(num_workers):
t = threading.Thread(target=worker, daemon=True)
t.start()
threads.append(t)
# Enqueue stock updates
for stock_update in stock_updates:
request_queue.put(stock_update)
# Wait for queue to empty
request_queue.join()
Rate Limits by API Category
Products (Товары)
- General operations: Subject to 50 RPS global limit
- Batch operations: Max 100 items per request
- Product creation: 60 PDPs per day
- Product editing: 300 PDPs per day
Orders (Заказы)
- Order list retrieval: Subject to 50 RPS global limit
- Order status updates: Recommended to batch requests
- FBO/FBS operations: No specific limits beyond global
Finance (Финансы)
- Transaction lists: Subject to 50 RPS global limit
- Financial reports: Async generation, no sync limits
Analytics (Аналитика)
- Data retrieval: Subject to 50 RPS global limit
- Performance API: 100,000 requests per day (special accounts)
Stocks and Prices (Склад и Цены)
- Stock updates: 80 requests/minute recommended
- Single SKU updates: Once per 2 minutes per warehouse
- Batch updates: Up to 100 products per request
Advanced Rate Limiting Strategies
1. Token Bucket Algorithm
class TokenBucket:
def __init__(self, rate, capacity):
self.rate = rate # Tokens per second
self.capacity = capacity # Max tokens
self.tokens = capacity
self.last_time = time.time()
def consume(self, tokens=1):
now = time.time()
elapsed = now - self.last_time
self.tokens = min(self.capacity, self.tokens + elapsed * self.rate)
self.last_time = now
if self.tokens >= tokens:
self.tokens -= tokens
return True
return False
# Usage: 50 tokens per second, bucket capacity of 100
bucket = TokenBucket(rate=50, capacity=100)
def make_request():
while not bucket.consume():
time.sleep(0.01) # Wait for tokens
# Make API request
2. Distributed Rate Limiting (Multi-Instance)
import redis
class DistributedRateLimiter:
def __init__(self, redis_client, key_prefix, limit, window):
self.redis = redis_client
self.key_prefix = key_prefix
self.limit = limit
self.window = window
def is_allowed(self, client_id):
key = f"{self.key_prefix}:{client_id}"
current = self.redis.incr(key)
if current == 1:
self.redis.expire(key, self.window)
return current <= self.limit
Official Documentation Sources
Rate Limit References
Community Resources
Recommendations for Integration
1. Stay Under the Limit
- Target 40-45 RPS instead of full 50 RPS
- Account for network delays and retries
- Build in buffer for unexpected spikes
2. Implement Graceful Degradation
- Queue requests when rate limit is hit
- Process queued requests during off-peak hours
- Provide user feedback about delays
3. Monitor and Alert
- Set up alerts for high rate-limit hit rates
- Track RPS metrics in dashboards
- Review logs regularly
4. Optimize Request Patterns
- Use batch operations where possible
- Cache frequently accessed data
- Prioritize critical operations
5. Plan for Growth
- Design for horizontal scaling
- Use connection pooling
- Implement request priority queues
Summary
Key Points:
- ✅ Global Limit: 50 requests per second per Client ID (as of May 19, 2025)
- ✅ Error Response: HTTP 429 with error code 8
- ✅ No Rate Limit Headers: Must implement client-side rate limiting
- ✅ Per-Endpoint Limits: Various limits for stock updates, product creation, etc.
- ✅ Batch Operations: Use batching to minimize request count
- ✅ Retry Strategy: Implement exponential backoff for 429 errors
- ✅ Monitoring: Log requests and track rate limit hits
Implementation Checklist:
- Implement client-side rate limiter (50 RPS max)
- Add retry logic with exponential backoff
- Use batch operations where available
- Set up request logging and monitoring
- Create alerts for high rate-limit hit rates
- Test with production-like load
- Document rate limit handling in integration guide
Rate limit documentation complete and verified. Ready for integration into final 01_OZON_API_RUS.md document.