Skip to content

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:

PlanRequests/MinuteRequests/Day
Free30500
Starter601,000
Professional1205,000
Enterprise30020,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: 60
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1704459600000

Header Descriptions

HeaderDescription
X-RateLimit-LimitMaximum requests allowed in the current window
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix 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:

Terminal window
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: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1704459660000
Retry-After: 45

Daily Limit Exceeded

Response (429):

{
"error": {
"code": "DAILY_LIMIT_EXCEEDED",
"message": "Daily API limit exceeded. Limit resets at midnight UTC."
}
}

Headers:

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1704499200000
Retry-After: 39600

Retry-After Header

The Retry-After header indicates how many seconds to wait before retrying:

Retry-After: 45

This 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 time
import 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 response

Advanced 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 requests
const 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 filter
const 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 automatically

4. Optimize Pagination

Use appropriate page sizes to minimize requests:

// ❌ Small pages = more requests
const 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 requests
const page1 = await fetch('/api-v1-contacts?limit=100');
// Fewer requests needed for the same data

5. 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:

  1. Go to Settings → API Keys
  2. Click on your API key
  3. 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:

  1. Analyze usage patterns - Identify peak times and bottlenecks
  2. Optimize first - Implement caching, batching, and webhooks
  3. Calculate needs - Estimate required requests per day
  4. Upgrade plan - Choose a plan that meets your needs with headroom

Contact sales@sellercockpit.com for custom enterprise limits.

Next Steps