API Rate Limiting & Optimization
StateSet implements rate limiting to ensure fair usage and maintain service reliability for all users. This guide covers understanding rate limits, implementing efficient request patterns, and optimizing your API usage.
Rate Limit Overview
StateSet uses a sliding window rate limiting algorithm that provides smooth, predictable limits across different time windows.
Per-Second Limits Prevents burst traffic spikes and ensures responsive service
Per-Minute Limits Controls sustained request volume over longer periods
Per-Hour Limits Manages overall API consumption and prevents abuse
Current Rate Limits
By Plan Type
Starter Plan Growth Plan Enterprise Plan Endpoint Category Per Second Per Minute Per Hour Orders API 10 300 5,000 Customers API 5 150 2,500 Returns API 5 150 2,500 Webhooks 20 600 10,000 Analytics 2 60 1,000
Endpoint Category Per Second Per Minute Per Hour Orders API 10 300 5,000 Customers API 5 150 2,500 Returns API 5 150 2,500 Webhooks 20 600 10,000 Analytics 2 60 1,000
Endpoint Category Per Second Per Minute Per Hour Orders API 50 1,500 25,000 Customers API 25 750 12,500 Returns API 25 750 12,500 Webhooks 100 3,000 50,000 Analytics 10 300 5,000
Endpoint Category Per Second Per Minute Per Hour Orders API 200 6,000 100,000 Customers API 100 3,000 50,000 Returns API 100 3,000 50,000 Webhooks 500 15,000 250,000 Analytics 50 1,500 25,000
Special Considerations
Batch endpoints have higher rate limits but count as multiple requests
Each item in a batch counts toward your rate limit
Maximum batch size: 100 items per request
List endpoints with large limit
parameters consume more quota
Requests with limit > 100
count as Math.ceil(limit/100)
requests
Use pagination instead of large limit values
Webhook deliveries do not count against your rate limits
Failed webhook retries use separate retry quotas
Webhook verification calls are not rate limited
Every API response includes rate limit information in the headers:
HTTP / 1.1 200 OK
X-RateLimit-Limit : 1500
X-RateLimit-Remaining : 1247
X-RateLimit-Reset : 1640995200
X-RateLimit-Window : 60
X-RateLimit-Category : orders-api
Retry-After : 45
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 when the window resets X-RateLimit-Window
Window duration in seconds (60 for per-minute limits) X-RateLimit-Category
API category for this endpoint Retry-After
Seconds to wait before retrying (present when rate limited)
Implementing Rate Limit Handling
Basic Rate Limit Detection
import { StateSetClient } from 'stateset-node' ;
import winston from 'winston' ;
const logger = winston . createLogger ({
level: 'info' ,
format: winston . format . json (),
transports: [ new winston . transports . Console ()]
});
class RateLimitHandler {
constructor ( client ) {
this . client = client ;
this . requestCounts = new Map ();
}
async makeRequest ( operation , ... args ) {
try {
const response = await operation ( ... args );
this . logRateLimitInfo ( response );
return response ;
} catch ( error ) {
if ( error . status === 429 ) {
return this . handleRateLimit ( error , operation , ... args );
}
throw error ;
}
}
logRateLimitInfo ( response ) {
const headers = response . headers || {};
logger . info ( 'Rate limit status' , {
remaining: headers [ 'x-ratelimit-remaining' ],
limit: headers [ 'x-ratelimit-limit' ],
resetTime: new Date ( headers [ 'x-ratelimit-reset' ] * 1000 ),
category: headers [ 'x-ratelimit-category' ]
});
}
async handleRateLimit ( error , operation , ... args ) {
const retryAfter = error . headers ?.[ 'retry-after' ] || 60 ;
logger . warn ( 'Rate limit exceeded, waiting before retry' , {
retryAfter ,
category: error . headers ?.[ 'x-ratelimit-category' ]
});
await this . sleep ( retryAfter * 1000 );
return this . makeRequest ( operation , ... args );
}
sleep ( ms ) {
return new Promise ( resolve => setTimeout ( resolve , ms ));
}
}
// Usage
const client = new StateSetClient ({ apiKey: process . env . STATESET_API_KEY });
const rateLimitHandler = new RateLimitHandler ( client );
const orders = await rateLimitHandler . makeRequest (
() => client . orders . list ({ limit: 50 })
);
Advanced Rate Limiting with Queue
import PQueue from 'p-queue' ;
class AdvancedRateLimitHandler {
constructor ( client , options = {}) {
this . client = client ;
this . queues = new Map ();
this . rateLimitInfo = new Map ();
// Default queue options
this . defaultQueueOptions = {
concurrency: 5 ,
interval: 1000 ,
intervalCap: 10 ,
... options . queueOptions
};
}
getQueue ( category = 'default' ) {
if ( ! this . queues . has ( category )) {
const queueOptions = this . getCategoryQueueOptions ( category );
this . queues . set ( category , new PQueue ( queueOptions ));
}
return this . queues . get ( category );
}
getCategoryQueueOptions ( category ) {
const categorySettings = {
'orders-api' : { concurrency: 10 , interval: 1000 , intervalCap: 10 },
'customers-api' : { concurrency: 5 , interval: 1000 , intervalCap: 5 },
'returns-api' : { concurrency: 5 , interval: 1000 , intervalCap: 5 },
'analytics' : { concurrency: 2 , interval: 1000 , intervalCap: 2 }
};
return {
... this . defaultQueueOptions ,
... categorySettings [ category ]
};
}
async makeRequest ( operation , category = 'default' , priority = 0 ) {
const queue = this . getQueue ( category );
return queue . add ( async () => {
try {
const response = await operation ();
this . updateRateLimitInfo ( category , response );
return response ;
} catch ( error ) {
if ( error . status === 429 ) {
await this . handleRateLimit ( error , category );
throw error ; // Re-queue will happen automatically
}
throw error ;
}
}, { priority });
}
updateRateLimitInfo ( category , response ) {
const headers = response . headers || {};
this . rateLimitInfo . set ( category , {
remaining: parseInt ( headers [ 'x-ratelimit-remaining' ]) || 0 ,
limit: parseInt ( headers [ 'x-ratelimit-limit' ]) || 1000 ,
resetTime: parseInt ( headers [ 'x-ratelimit-reset' ]) || 0 ,
lastUpdated: Date . now ()
});
}
async handleRateLimit ( error , category ) {
const retryAfter = parseInt ( error . headers ?.[ 'retry-after' ]) || 60 ;
const queue = this . getQueue ( category );
logger . warn ( 'Rate limit exceeded for category' , {
category ,
retryAfter ,
queueSize: queue . size ,
pending: queue . pending
});
// Pause the queue
queue . pause ();
await this . sleep ( retryAfter * 1000 );
// Resume the queue
queue . start ();
}
getRateLimitStatus ( category = 'default' ) {
return this . rateLimitInfo . get ( category ) || null ;
}
sleep ( ms ) {
return new Promise ( resolve => setTimeout ( resolve , ms ));
}
}
// Usage with priority queueing
const handler = new AdvancedRateLimitHandler ( client );
// High priority request (emergency order processing)
const criticalOrder = await handler . makeRequest (
() => client . orders . get ( orderId ),
'orders-api' ,
10
);
// Normal priority batch processing
const orders = await Promise . all ([
handler . makeRequest (() => client . orders . list ({ page: 1 }), 'orders-api' , 0 ),
handler . makeRequest (() => client . orders . list ({ page: 2 }), 'orders-api' , 0 ),
handler . makeRequest (() => client . orders . list ({ page: 3 }), 'orders-api' , 0 )
]);
Optimization Strategies
Instead of requesting large datasets at once:
// ❌ Inefficient - May trigger rate limits
const allOrders = await client . orders . list ({ limit: 1000 });
// ✅ Efficient - Paginated requests
async function getAllOrdersPaginated () {
const allOrders = [];
let hasMore = true ;
let cursor = null ;
while ( hasMore ) {
const response = await client . orders . list ({
limit: 100 ,
cursor: cursor
});
allOrders . push ( ... response . data );
cursor = response . next_cursor ;
hasMore = response . has_more ;
// Small delay to respect rate limits
await new Promise ( resolve => setTimeout ( resolve , 100 ));
}
return allOrders ;
}
2. Batch Operations
Use batch endpoints when available:
// ❌ Multiple individual requests
for ( const order of orders ) {
await client . orders . update ( order . id , { status: 'processed' });
}
// ✅ Single batch request
await client . orders . batchUpdate (
orders . map ( order => ({
id: order . id ,
status: 'processed'
}))
);
3. Webhook-First Architecture
Reduce polling by using webhooks:
// ❌ Constant polling
setInterval ( async () => {
const orders = await client . orders . list ({
status: 'pending' ,
updated_since: lastCheck
});
processNewOrders ( orders );
}, 30000 );
// ✅ Webhook-driven updates
app . post ( '/webhooks/stateset' , ( req , res ) => {
const event = req . body ;
if ( event . type === 'order.updated' ) {
processOrderUpdate ( event . data );
}
res . status ( 200 ). send ( 'OK' );
});
4. Intelligent Caching
Cache frequently requested data:
class StateSetCache {
constructor ( client , ttl = 300000 ) { // 5 minutes default TTL
this . client = client ;
this . cache = new Map ();
this . ttl = ttl ;
}
async get ( key , fetchFunction ) {
const cached = this . cache . get ( key );
const now = Date . now ();
if ( cached && ( now - cached . timestamp ) < this . ttl ) {
logger . debug ( 'Cache hit' , { key });
return cached . data ;
}
logger . debug ( 'Cache miss, fetching' , { key });
const data = await fetchFunction ();
this . cache . set ( key , {
data ,
timestamp: now
});
return data ;
}
clear ( key ) {
if ( key ) {
this . cache . delete ( key );
} else {
this . cache . clear ();
}
}
}
// Usage
const cache = new StateSetCache ( client );
const customer = await cache . get (
`customer: ${ customerId } ` ,
() => client . customers . get ( customerId )
);
Monitoring Rate Limits
Rate Limit Dashboard
Create a monitoring dashboard for your rate limit usage:
class RateLimitMonitor {
constructor () {
this . metrics = {
requests: new Map (),
rateLimits: new Map (),
errors: new Map ()
};
}
recordRequest ( category , success = true ) {
const key = ` ${ category } : ${ this . getTimeWindow () } ` ;
const current = this . metrics . requests . get ( key ) || { success: 0 , total: 0 };
current . total ++ ;
if ( success ) current . success ++ ;
this . metrics . requests . set ( key , current );
}
recordRateLimit ( category , remainingRequests , totalLimit ) {
const key = ` ${ category } : ${ this . getTimeWindow () } ` ;
this . metrics . rateLimits . set ( key , {
remaining: remainingRequests ,
total: totalLimit ,
utilizationPct: (( totalLimit - remainingRequests ) / totalLimit ) * 100 ,
timestamp: Date . now ()
});
}
getTimeWindow () {
return Math . floor ( Date . now () / 60000 ); // 1-minute windows
}
getUtilizationReport () {
const report = {};
for ( const [ key , data ] of this . metrics . rateLimits ) {
const [ category ] = key . split ( ':' );
if ( ! report [ category ]) report [ category ] = [];
report [ category ]. push ( data );
}
return report ;
}
// Alert when utilization is high
checkAlerts () {
const report = this . getUtilizationReport ();
for ( const [ category , dataPoints ] of Object . entries ( report )) {
const latest = dataPoints [ dataPoints . length - 1 ];
if ( latest . utilizationPct > 80 ) {
logger . warn ( 'High API utilization detected' , {
category ,
utilization: latest . utilizationPct ,
remaining: latest . remaining
});
}
}
}
}
// Usage with middleware
const monitor = new RateLimitMonitor ();
const monitoredClient = new Proxy ( client , {
get ( target , prop ) {
const original = target [ prop ];
if ( typeof original === 'object' && original !== null ) {
return new Proxy ( original , {
get ( apiTarget , apiProp ) {
const apiMethod = apiTarget [ apiProp ];
if ( typeof apiMethod === 'function' ) {
return async ( ... args ) => {
const category = ` ${ prop } -api` ;
try {
const result = await apiMethod . apply ( apiTarget , args );
monitor . recordRequest ( category , true );
if ( result . headers ) {
monitor . recordRateLimit (
category ,
parseInt ( result . headers [ 'x-ratelimit-remaining' ]),
parseInt ( result . headers [ 'x-ratelimit-limit' ])
);
}
return result ;
} catch ( error ) {
monitor . recordRequest ( category , false );
throw error ;
}
};
}
return apiMethod ;
}
});
}
return original ;
}
});
Best Practices Summary
Request Optimization
Use pagination instead of large limit values
Implement batch operations where possible
Cache frequently requested data
Use webhooks to reduce polling
Error Handling
Always check rate limit headers
Implement exponential backoff for retries
Use queues to manage request flow
Monitor and alert on high utilization
Architecture Patterns
Design webhook-first integrations
Implement circuit breakers for resilience
Use separate queues for different priorities
Cache aggressively with smart invalidation
Monitoring & Alerting
Track utilization across all categories
Set alerts at 80% utilization threshold
Monitor request success rates
Review patterns during peak usage
Getting Help
If you’re consistently hitting rate limits or need higher limits:
Review your usage patterns using the monitoring tools above
Optimize your integration following the strategies in this guide
Consider upgrading to a higher plan with increased limits
Contact support at support@stateset.com for custom rate limits