Overview
The Inbox API enforces rate limits to ensure fair usage and platform stability. This guide covers the limits, how to detect them, and strategies for handling them gracefully.
Current limits
Rate limits are global per team — all endpoints share the same limits, and all API tokens for a team share the same quota.
Window Limit Per minute 300 requests Per hour 10,000 requests Per day 100,000 requests
All three windows are enforced simultaneously using a sliding window. If any window is exceeded, requests are rejected until that window resets.
Limits are per team, not per token. Multiple API tokens for the same team share the same rate limit quota.
Every response includes rate limit information:
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 295
X-RateLimit-Reset: 1705312800
Header Description X-RateLimit-LimitMaximum requests allowed in the window X-RateLimit-RemainingRequests remaining in current window X-RateLimit-ResetUnix timestamp when the window resets
Rate limit responses
When you exceed the limit:
Status: 429 Too Many Requests
{
"error" : {
"code" : "RATE_LIMITED" ,
"message" : "Rate limit exceeded. Try again in 60 seconds." ,
"status" : 429
}
}
Headers:
Exponential backoff
The recommended pattern for handling rate limits:
import axios , { AxiosError } from 'axios' ;
async function withRetry < T >(
fn : () => Promise < T >,
options : {
maxRetries ?: number ;
baseDelay ?: number ;
maxDelay ?: number ;
} = {}
) : Promise < T > {
const {
maxRetries = 5 ,
baseDelay = 1000 ,
maxDelay = 60000
} = options ;
let attempt = 0 ;
while ( true ) {
try {
return await fn ();
} catch ( error ) {
if ( ! axios . isAxiosError ( error )) throw error ;
const status = error . response ?. status ;
// Only retry on rate limits and server errors
if ( status !== 429 && ( status < 500 || status >= 600 )) {
throw error ;
}
attempt ++ ;
if ( attempt >= maxRetries ) {
throw new Error ( `Max retries ( ${ maxRetries } ) exceeded` );
}
// Calculate delay
let delay : number ;
if ( status === 429 ) {
// Use Retry-After header if available
const retryAfter = error . response ?. headers [ 'retry-after' ];
delay = retryAfter ? parseInt ( retryAfter , 10 ) * 1000 : baseDelay * Math . pow ( 2 , attempt );
} else {
// Exponential backoff for server errors
delay = Math . min ( baseDelay * Math . pow ( 2 , attempt ), maxDelay );
}
console . log ( `Retry ${ attempt } / ${ maxRetries } after ${ delay } ms` );
await new Promise ( resolve => setTimeout ( resolve , delay ));
}
}
}
// Usage
const thread = await withRetry (() => client . get ( `/threads/ ${ threadId } ` ));
Bulk operations
For operations that process many items, implement rate-aware batching:
async function bulkSendMessages (
threads : Array <{ id : string ; message : string }>
) {
const results = [];
for ( const thread of threads ) {
try {
const { data } = await withRetry (() =>
client . post ( `/threads/ ${ thread . id } /messages` , {
content: thread . message
})
);
results . push ({
threadId: thread . id ,
success: true ,
messageId: data . id
});
} catch ( error ) {
results . push ({
threadId: thread . id ,
success: false ,
error: error . message
});
}
// Small delay between sends to stay well within 300/min
await new Promise ( resolve => setTimeout ( resolve , 500 ));
}
return results ;
}
Proactive rate limit tracking
Track your usage to avoid hitting limits:
class RateLimitTracker {
private remaining : number ;
private resetTime : number ;
constructor () {
this . remaining = 300 ;
this . resetTime = Date . now () + 60000 ;
}
update ( headers : Record < string , string >) {
this . remaining = parseInt ( headers [ 'x-ratelimit-remaining' ] || '300' , 10 );
this . resetTime = parseInt ( headers [ 'x-ratelimit-reset' ] || '0' , 10 ) * 1000 ;
}
async waitIfNeeded () {
if ( this . remaining <= 5 ) {
const waitTime = Math . max ( 0 , this . resetTime - Date . now ());
if ( waitTime > 0 ) {
console . log ( `Approaching rate limit. Waiting ${ waitTime } ms` );
await new Promise ( resolve => setTimeout ( resolve , waitTime ));
}
}
}
canMakeRequest () : boolean {
return this . remaining > 0 || Date . now () > this . resetTime ;
}
}
// Usage with axios interceptor
const tracker = new RateLimitTracker ();
client . interceptors . response . use (
response => {
tracker . update ( response . headers );
return response ;
},
error => {
if ( error . response ?. headers ) {
tracker . update ( error . response . headers );
}
return Promise . reject ( error );
}
);
// Before each request
async function makeRequest ( fn : () => Promise < any >) {
await tracker . waitIfNeeded ();
return fn ();
}
When paginating through large datasets:
async function getAllThreadsWithRateLimits () {
const allThreads = [];
let cursorId : string | undefined ;
let cursorTimestamp : string | undefined ;
let requestCount = 0 ;
do {
const { data , headers } = await client . get ( '/threads' , {
params: {
limit: 100 ,
... ( cursorId && { cursorId , cursorTimestamp })
}
});
allThreads . push ( ... data . threads );
cursorId = data . nextCursor ?. id ;
cursorTimestamp = data . nextCursor ?. timestamp ;
requestCount ++ ;
// Check remaining requests
const remaining = parseInt ( headers [ 'x-ratelimit-remaining' ] || '300' , 10 );
if ( remaining < 10 && cursorId ) {
const resetTime = parseInt ( headers [ 'x-ratelimit-reset' ] || '0' , 10 ) * 1000 ;
const waitTime = Math . max ( 0 , resetTime - Date . now ());
console . log ( `Rate limit approaching. Waiting ${ waitTime } ms` );
await new Promise ( resolve => setTimeout ( resolve , waitTime ));
}
} while ( cursorId );
return allThreads ;
}
Best practices
Cache read results to avoid repeated requests: const cache = new Map < string , { data : any ; expiry : number }>();
async function getCachedThread ( threadId : string ) {
const cached = cache . get ( threadId );
if ( cached && cached . expiry > Date . now ()) {
return cached . data ;
}
const { data : thread } = await client . get ( `/threads/ ${ threadId } ` );
cache . set ( threadId , {
data: thread ,
expiry: Date . now () + 60000 // 1 minute cache
});
return thread ;
}
Monitor rate limit headers
Implement circuit breakers
Stop making requests temporarily when consistently hitting limits.
Sending too many messages too quickly may trigger platform-level restrictions on your X account, separate from API rate limits. Space out messages appropriately, especially for outreach.