Rate Limits
The SellerCockpit API uses rate limiting to ensure fair usage and protect service availability. All API requests are subject to both per-minute and daily limits.
Rate Limit Tiers
Rate limits vary based on your subscription plan:
| Plan | Requests/Minute | Requests/Day |
|---|---|---|
| Free | 30 | 500 |
| Starter | 60 | 1,000 |
| Professional | 120 | 5,000 |
| Enterprise | 300 | 20,000 |
Note: Rate limits are per API key, not per company. If you need higher limits, create multiple API keys or upgrade your plan.
Rate Limit Headers
Every API response includes rate limit information in the headers:
X-RateLimit-Limit: 60X-RateLimit-Remaining: 42X-RateLimit-Reset: 1704459600000Header Descriptions
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed in the current window |
X-RateLimit-Remaining | Requests remaining in the current window |
X-RateLimit-Reset | Unix timestamp (milliseconds) when the limit resets |
Example:
const response = await fetch(url, options);
console.log('Limit:', response.headers.get('X-RateLimit-Limit'));console.log('Remaining:', response.headers.get('X-RateLimit-Remaining'));console.log('Resets at:', new Date( parseInt(response.headers.get('X-RateLimit-Reset'))));Rate Limit Windows
Per-Minute Limit
- Window: 60 seconds (rolling window)
- Reset: Continuously rolling (not fixed intervals)
- Tracking: Based on the last 60 seconds of activity
Example: If you make 60 requests at 10:00:00, you can make another request at 10:01:01 (60 seconds after the first request).
Daily Limit
- Window: 24 hours (midnight to midnight UTC)
- Reset: Every day at 00:00 UTC
- Tracking: Cumulative count from midnight to midnight
Rate Limit Exceeded Responses
When you exceed a rate limit, the API returns a 429 Too Many Requests status with details:
Per-Minute Limit Exceeded
Request:
curl https://your-project.supabase.co/functions/v1/api-v1-contacts \ -H "Authorization: Bearer sellercockpit_live_..."Response (429):
{ "error": { "code": "RATE_LIMIT_EXCEEDED", "message": "Rate limit exceeded. Please wait before making more requests." }}Headers:
X-RateLimit-Limit: 60X-RateLimit-Remaining: 0X-RateLimit-Reset: 1704459660000Retry-After: 45Daily Limit Exceeded
Response (429):
{ "error": { "code": "DAILY_LIMIT_EXCEEDED", "message": "Daily API limit exceeded. Limit resets at midnight UTC." }}Headers:
X-RateLimit-Limit: 1000X-RateLimit-Remaining: 0X-RateLimit-Reset: 1704499200000Retry-After: 39600Retry-After Header
The Retry-After header indicates how many seconds to wait before retrying:
Retry-After: 45This value represents:
- Per-minute limit: Seconds until the rolling window allows another request
- Daily limit: Seconds until midnight UTC (when the daily counter resets)
Handling Rate Limits
Basic Retry Logic
Implement exponential backoff when you receive a 429 response:
JavaScript/TypeScript:
async function makeRequest(url: string, options: RequestInit) { const response = await fetch(url, options);
if (response.status === 429) { const retryAfter = parseInt(response.headers.get('Retry-After') || '60'); console.log(`Rate limited. Retrying after ${retryAfter} seconds...`);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000)); return makeRequest(url, options); // Retry }
return response;}Python:
import timeimport requests
def make_request(url, headers): response = requests.get(url, headers=headers)
if response.status_code == 429: retry_after = int(response.headers.get('Retry-After', 60)) print(f'Rate limited. Retrying after {retry_after} seconds...')
time.sleep(retry_after) return make_request(url, headers) # Retry
return responseAdvanced Retry with Exponential Backoff
For production systems, use exponential backoff with jitter:
JavaScript/TypeScript:
async function makeRequestWithBackoff( url: string, options: RequestInit, maxRetries = 3): Promise<Response> { for (let attempt = 0; attempt <= maxRetries; attempt++) { const response = await fetch(url, options);
if (response.status !== 429) { return response; }
if (attempt === maxRetries) { throw new Error('Max retries reached'); }
const retryAfter = parseInt(response.headers.get('Retry-After') || '60'); const jitter = Math.random() * 1000; // Add 0-1 second jitter const delay = (retryAfter * 1000) + jitter;
console.log(`Attempt ${attempt + 1}/${maxRetries}: Waiting ${delay}ms`); await new Promise(resolve => setTimeout(resolve, delay)); }
throw new Error('Unexpected error');}Respecting Rate Limit Headers
Proactively check remaining requests before making calls:
JavaScript/TypeScript:
class ApiClient { private rateLimitRemaining: number = Infinity; private rateLimitReset: number = 0;
async request(url: string, options: RequestInit): Promise<Response> { // Check if we're rate limited if (this.rateLimitRemaining === 0) { const now = Date.now(); const waitTime = Math.max(0, this.rateLimitReset - now);
if (waitTime > 0) { console.log(`Waiting ${waitTime}ms for rate limit to reset...`); await new Promise(resolve => setTimeout(resolve, waitTime)); } }
const response = await fetch(url, options);
// Update rate limit info this.rateLimitRemaining = parseInt( response.headers.get('X-RateLimit-Remaining') || '0' ); this.rateLimitReset = parseInt( response.headers.get('X-RateLimit-Reset') || '0' );
return response; }}Best Practices
1. Implement Caching
Cache API responses to reduce the number of requests:
const cache = new Map();const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
async function getCachedData(url: string): Promise<any> { const cached = cache.get(url);
if (cached && Date.now() - cached.timestamp < CACHE_TTL) { return cached.data; }
const response = await fetch(url, { headers }); const data = await response.json();
cache.set(url, { data, timestamp: Date.now() }); return data;}2. Batch Requests
When possible, use filter parameters instead of making multiple requests:
Instead of this:
// ❌ Multiple requestsconst contact1 = await fetch('/api-v1-contacts/id1');const contact2 = await fetch('/api-v1-contacts/id2');const contact3 = await fetch('/api-v1-contacts/id3');Do this:
// ✅ Single request with filterconst contacts = await fetch('/api-v1-contacts?id[in]=id1,id2,id3');3. Use Webhooks
For real-time updates, use webhooks instead of polling:
// ❌ Polling (uses many requests)setInterval(async () => { const contacts = await fetch('/api-v1-contacts'); checkForUpdates(contacts);}, 60000); // Every minute
// ✅ Webhooks (zero requests)// Configure a webhook in SellerCockpit settings// Your server receives updates automatically4. Optimize Pagination
Use appropriate page sizes to minimize requests:
// ❌ Small pages = more requestsconst page1 = await fetch('/api-v1-contacts?limit=10');const page2 = await fetch('/api-v1-contacts?cursor=xyz&limit=10');// ... many more requests
// ✅ Larger pages = fewer requestsconst page1 = await fetch('/api-v1-contacts?limit=100');// Fewer requests needed for the same data5. Monitor Usage
Track your API usage to stay within limits:
async function monitoredRequest(url: string, options: RequestInit) { const response = await fetch(url, options);
const remaining = parseInt( response.headers.get('X-RateLimit-Remaining') || '0' ); const limit = parseInt( response.headers.get('X-RateLimit-Limit') || '0' ); const usagePercent = ((limit - remaining) / limit) * 100;
if (usagePercent > 80) { console.warn(`Rate limit usage: ${usagePercent.toFixed(1)}%`); }
return response;}6. Use Multiple API Keys
For high-volume integrations, distribute requests across multiple API keys:
class LoadBalancer { private apiKeys: string[]; private currentIndex = 0;
constructor(apiKeys: string[]) { this.apiKeys = apiKeys; }
getNextKey(): string { const key = this.apiKeys[this.currentIndex]; this.currentIndex = (this.currentIndex + 1) % this.apiKeys.length; return key; }
async request(url: string, options: Omit<RequestInit, 'headers'>) { const apiKey = this.getNextKey(); return fetch(url, { ...options, headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } }); }}Rate Limit Debugging
Check Current Usage
View your API key’s current usage in the SellerCockpit dashboard:
- Go to Settings → API Keys
- Click on your API key
- View the Usage Statistics panel
Log Rate Limit Headers
Always log rate limit headers during development:
const response = await fetch(url, options);
console.log({ limit: response.headers.get('X-RateLimit-Limit'), remaining: response.headers.get('X-RateLimit-Remaining'), reset: new Date(parseInt(response.headers.get('X-RateLimit-Reset'))), status: response.status});Upgrading Your Plan
If you consistently hit rate limits, consider upgrading your plan:
- Analyze usage patterns - Identify peak times and bottlenecks
- Optimize first - Implement caching, batching, and webhooks
- Calculate needs - Estimate required requests per day
- Upgrade plan - Choose a plan that meets your needs with headroom
Contact sales@sellercockpit.com for custom enterprise limits.