> ## Documentation Index
> Fetch the complete documentation index at: https://docs.inboxapp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Rate limits

> Understanding and handling Inbox API rate limits

## 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.

<Note>
  Limits are per team, not per token. Multiple API tokens for the same team share the same rate limit quota.
</Note>

## Rate limit headers

Every response includes rate limit information:

```
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 295
X-RateLimit-Reset: 1705312800
```

| Header                  | Description                            |
| ----------------------- | -------------------------------------- |
| `X-RateLimit-Limit`     | Maximum requests allowed in the window |
| `X-RateLimit-Remaining` | Requests remaining in current window   |
| `X-RateLimit-Reset`     | Unix timestamp when the window resets  |

## Rate limit responses

When you exceed the limit:

**Status:** 429 Too Many Requests

```json theme={null}
{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Rate limit exceeded. Try again in 60 seconds.",
    "status": 429
  }
}
```

**Headers:**

```
Retry-After: 60
```

## Exponential backoff

The recommended pattern for handling rate limits:

<CodeGroup>
  ```typescript backoff.ts theme={null}
  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}`));
  ```

  ```javascript backoff.js theme={null}
  async function withRetry(fn, options = {}) {
    const {
      maxRetries = 5,
      baseDelay = 1000,
      maxDelay = 60000
    } = options;

    let attempt = 0;

    while (true) {
      try {
        return await fn();
      } catch (error) {
        const status = error.response?.status;

        if (status !== 429 && (status < 500 || status >= 600)) {
          throw error;
        }

        attempt++;
        if (attempt >= maxRetries) {
          throw new Error(`Max retries (${maxRetries}) exceeded`);
        }

        let delay;
        if (status === 429) {
          const retryAfter = error.response?.headers['retry-after'];
          delay = retryAfter ? parseInt(retryAfter, 10) * 1000 : baseDelay * Math.pow(2, attempt);
        } else {
          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
  withRetry(() => client.get(`/threads/${threadId}`))
    .then(response => console.log(response.data));
  ```
</CodeGroup>

## Bulk operations

For operations that process many items, implement rate-aware batching:

```typescript theme={null}
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:

```typescript theme={null}
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();
}
```

## Pagination with rate limits

When paginating through large datasets:

```typescript theme={null}
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

<AccordionGroup>
  <Accordion title="Cache responses" icon="database">
    Cache read results to avoid repeated requests:

    ```typescript theme={null}
    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;
    }
    ```
  </Accordion>

  <Accordion title="Monitor rate limit headers" icon="gauge">
    Track `X-RateLimit-Remaining` and pause before hitting zero.
  </Accordion>

  <Accordion title="Implement circuit breakers" icon="power-off">
    Stop making requests temporarily when consistently hitting limits.
  </Accordion>
</AccordionGroup>

## Platform considerations

<Warning>
  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.
</Warning>

## Related

<CardGroup cols={2}>
  <Card title="Error codes" icon="shield-x" href="/reference/error-codes">
    All error types including rate limits
  </Card>

  <Card title="Pagination" icon="book-open" href="/reference/pagination">
    Efficient data fetching
  </Card>

  <Card title="Working with messages" icon="comment" href="/guides/messages">
    Send and receive messages
  </Card>

  <Card title="Troubleshooting" icon="wrench" href="/resources/troubleshooting">
    Common issues and fixes
  </Card>
</CardGroup>
