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

# Rate Limits

> Understanding API rate limiting, quotas, and best practices for the Snipe-IT API

The Snipe-IT API implements rate limiting to ensure fair usage and maintain system performance. All API requests are subject to throttling based on the number of requests per minute.

## Rate Limit Configuration

### Default Limits

By default, the API allows **120 requests per minute** per authenticated user. This limit applies to all endpoints under the `/api/v1` prefix.

### Customizing Rate Limits

Administrators can adjust the rate limit by setting the `API_THROTTLE_PER_MINUTE` environment variable:

```bash .env theme={null}
API_THROTTLE_PER_MINUTE=120
```

<Note>
  After changing this value, restart your web server for the changes to take effect.
</Note>

## Rate Limit Headers

Every API response includes headers that inform you about your current rate limit status:

<ResponseField name="X-RateLimit-Limit" type="integer">
  The maximum number of requests allowed per minute (e.g., `120`)
</ResponseField>

<ResponseField name="X-RateLimit-Remaining" type="integer">
  The number of requests remaining in the current window (e.g., `115`)
</ResponseField>

<ResponseField name="X-RateLimit-Reset" type="integer">
  The number of seconds until the rate limit resets (e.g., `45`)
</ResponseField>

<ResponseField name="X-RateLimit-Reset-Timestamp" type="integer">
  The Unix timestamp when the rate limit will reset (e.g., `1710507600`)
</ResponseField>

<ResponseField name="Retry-After" type="integer">
  Only present when rate limited. Seconds to wait before retrying (e.g., `60`)
</ResponseField>

### Example Response Headers

```http theme={null}
HTTP/1.1 200 OK
Content-Type: application/json
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 115
X-RateLimit-Reset: 45
X-RateLimit-Reset-Timestamp: 1710507600
```

## Rate Limit Exceeded

When you exceed the rate limit, the API returns an HTTP `429 Too Many Requests` status code:

<CodeGroup>
  ```json Response (429) theme={null}
  {
    "message": "Too Many Requests"
  }
  ```

  ```http Headers theme={null}
  HTTP/1.1 429 Too Many Requests
  Content-Type: application/json
  X-RateLimit-Limit: 120
  X-RateLimit-Remaining: 0
  X-RateLimit-Reset: 60
  X-RateLimit-Reset-Timestamp: 1710507660
  Retry-After: 60
  ```
</CodeGroup>

### Handling Rate Limit Errors

When you receive a `429` response:

1. **Check the `Retry-After` header** to determine how long to wait
2. **Pause your requests** for the specified duration
3. **Implement exponential backoff** if you continue hitting limits
4. **Review your request patterns** to optimize API usage

## Implementation Details

Rate limiting in Snipe-IT uses Laravel's built-in throttling middleware with custom extensions:

<AccordionGroup>
  <Accordion title="Middleware Implementation">
    The API uses the `api-throttle` middleware defined in `/routes/api.php`:

    ```php theme={null}
    Route::group(['prefix' => 'v1', 'middleware' => ['api', 'api-throttle:api']], function () {
        // All API routes
    });
    ```

    This middleware extends Laravel's `ThrottleRequests` class with additional header support.
  </Accordion>

  <Accordion title="Rate Limit Calculation">
    * Limits are calculated **per authenticated user**
    * The counter resets every **60 seconds** (sliding window)
    * Each request increments the counter by 1
    * Limits are enforced before the request is processed
  </Accordion>

  <Accordion title="Storage Backend">
    Rate limit counters are stored using your configured cache driver (file, Redis, Memcached, etc.). Configure this in your `.env`:

    ```bash theme={null}
    CACHE_DRIVER=redis  # Recommended for production
    ```
  </Accordion>
</AccordionGroup>

## Best Practices

### 1. Monitor Rate Limit Headers

Always check the `X-RateLimit-Remaining` header to track your usage:

<CodeGroup>
  ```python Python Example theme={null}
  import requests

  headers = {
      'Authorization': 'Bearer YOUR_TOKEN',
      'Accept': 'application/json',
  }

  response = requests.get(
      'https://your-snipe-it-instance.com/api/v1/hardware',
      headers=headers
  )

  print(f"Remaining requests: {response.headers.get('X-RateLimit-Remaining')}")
  print(f"Limit resets in: {response.headers.get('X-RateLimit-Reset')} seconds")

  if int(response.headers.get('X-RateLimit-Remaining', 0)) < 10:
      print("Warning: Approaching rate limit!")
  ```

  ```javascript JavaScript Example theme={null}
  const response = await fetch(
    'https://your-snipe-it-instance.com/api/v1/hardware',
    {
      headers: {
        'Authorization': 'Bearer YOUR_TOKEN',
        'Accept': 'application/json',
      },
    }
  );

  const remaining = response.headers.get('X-RateLimit-Remaining');
  const reset = response.headers.get('X-RateLimit-Reset');

  console.log(`Remaining requests: ${remaining}`);
  console.log(`Limit resets in: ${reset} seconds`);

  if (parseInt(remaining) < 10) {
    console.warn('Warning: Approaching rate limit!');
  }
  ```
</CodeGroup>

### 2. Implement Backoff Strategies

Handle rate limit errors gracefully with exponential backoff:

<CodeGroup>
  ```python Python with Backoff theme={null}
  import requests
  import time
  from requests.exceptions import HTTPError

  def make_api_request(url, max_retries=3):
      headers = {
          'Authorization': 'Bearer YOUR_TOKEN',
          'Accept': 'application/json',
      }
      
      for attempt in range(max_retries):
          try:
              response = requests.get(url, headers=headers)
              response.raise_for_status()
              return response.json()
          
          except HTTPError as e:
              if e.response.status_code == 429:
                  # Get retry-after header or use exponential backoff
                  retry_after = int(e.response.headers.get('Retry-After', 2 ** attempt))
                  print(f"Rate limited. Retrying in {retry_after} seconds...")
                  time.sleep(retry_after)
              else:
                  raise
      
      raise Exception("Max retries exceeded")

  # Usage
  data = make_api_request('https://your-snipe-it-instance.com/api/v1/hardware')
  ```

  ```javascript JavaScript with Backoff theme={null}
  async function makeApiRequest(url, maxRetries = 3) {
    const headers = {
      'Authorization': 'Bearer YOUR_TOKEN',
      'Accept': 'application/json',
    };

    for (let attempt = 0; attempt < maxRetries; attempt++) {
      try {
        const response = await fetch(url, { headers });
        
        if (response.status === 429) {
          const retryAfter = parseInt(
            response.headers.get('Retry-After') || Math.pow(2, attempt)
          );
          console.log(`Rate limited. Retrying in ${retryAfter} seconds...`);
          await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
          continue;
        }
        
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        
        return await response.json();
      } catch (error) {
        if (attempt === maxRetries - 1) throw error;
      }
    }
    
    throw new Error('Max retries exceeded');
  }

  // Usage
  const data = await makeApiRequest('https://your-snipe-it-instance.com/api/v1/hardware');
  ```
</CodeGroup>

### 3. Batch Requests Efficiently

Use pagination and filters to reduce the number of requests:

<CodeGroup>
  ```bash Paginated Request theme={null}
  curl "https://your-snipe-it-instance.com/api/v1/hardware?limit=100&offset=0" \
    -H "Authorization: Bearer YOUR_TOKEN" \
    -H "Accept: application/json"
  ```

  ```bash Filtered Request theme={null}
  curl "https://your-snipe-it-instance.com/api/v1/hardware?status_id=2" \
    -H "Authorization: Bearer YOUR_TOKEN" \
    -H "Accept: application/json"
  ```
</CodeGroup>

<Note>
  The maximum results per request is controlled by the `MAX_RESULTS` setting (default: 500).
</Note>

### 4. Optimize Request Frequency

<CardGroup cols={2}>
  <Card title="Use Webhooks" icon="webhook">
    Consider using webhooks for event-driven updates instead of polling
  </Card>

  <Card title="Cache Responses" icon="database">
    Cache API responses locally when data doesn't change frequently
  </Card>

  <Card title="Schedule Bulk Operations" icon="clock">
    Run large batch operations during off-peak hours
  </Card>

  <Card title="Parallel Tokens" icon="users">
    Use multiple API tokens for different services if needed
  </Card>
</CardGroup>

### 5. Handle Edge Cases

```python Python Complete Example theme={null}
import requests
import time
from typing import Optional, Dict, Any

class SnipeITClient:
    def __init__(self, base_url: str, token: str):
        self.base_url = base_url
        self.headers = {
            'Authorization': f'Bearer {token}',
            'Accept': 'application/json',
        }
        self.rate_limit_remaining = None
        self.rate_limit_reset = None
    
    def _make_request(self, method: str, endpoint: str, **kwargs) -> Dict[Any, Any]:
        url = f"{self.base_url}/api/v1/{endpoint}"
        
        # Wait if we're close to the limit
        if self.rate_limit_remaining and self.rate_limit_remaining < 5:
            wait_time = self.rate_limit_reset or 60
            print(f"Approaching rate limit. Waiting {wait_time} seconds...")
            time.sleep(wait_time)
        
        response = requests.request(method, url, headers=self.headers, **kwargs)
        
        # Update rate limit tracking
        self.rate_limit_remaining = int(
            response.headers.get('X-RateLimit-Remaining', 120)
        )
        self.rate_limit_reset = int(
            response.headers.get('X-RateLimit-Reset', 60)
        )
        
        # Handle rate limiting
        if response.status_code == 429:
            retry_after = int(response.headers.get('Retry-After', 60))
            print(f"Rate limited! Waiting {retry_after} seconds...")
            time.sleep(retry_after)
            return self._make_request(method, endpoint, **kwargs)  # Retry
        
        response.raise_for_status()
        return response.json()
    
    def get(self, endpoint: str, **kwargs) -> Dict[Any, Any]:
        return self._make_request('GET', endpoint, **kwargs)
    
    def post(self, endpoint: str, **kwargs) -> Dict[Any, Any]:
        return self._make_request('POST', endpoint, **kwargs)

# Usage
client = SnipeITClient('https://your-snipe-it-instance.com', 'YOUR_TOKEN')
assets = client.get('hardware')
print(f"Remaining API calls: {client.rate_limit_remaining}")
```

## Troubleshooting

<AccordionGroup>
  <Accordion title="Consistently hitting rate limits">
    * **Increase the limit**: Adjust `API_THROTTLE_PER_MINUTE` in your `.env` file
    * **Optimize queries**: Use filters and pagination to reduce request volume
    * **Implement caching**: Cache frequently accessed data
    * **Review architecture**: Consider if your integration pattern is optimal
  </Accordion>

  <Accordion title="Rate limit headers missing">
    * Ensure you're using `/api/v1` endpoints (rate limiting is only applied to API routes)
    * Check that the `api-throttle` middleware is active in `routes/api.php`
    * Verify your cache driver is working correctly
  </Accordion>

  <Accordion title="Different users, same limit">
    Rate limits are per-user, based on the authenticated user's API token. If you're seeing shared limits:

    * Verify you're using different tokens for different users
    * Check if requests are being proxied through a single account
  </Accordion>

  <Accordion title="Rate limit resets too slowly">
    Rate limits use a sliding 60-second window. If you:

    * Make 120 requests at 0:00
    * Wait until 0:30
    * Make another request

    You'll still be rate limited because the window hasn't fully elapsed since your first request.
  </Accordion>
</AccordionGroup>

## Rate Limit Monitoring

For production integrations, implement monitoring to track rate limit usage:

```python Monitoring Example theme={null}
import logging
from prometheus_client import Counter, Gauge

# Prometheus metrics
api_requests = Counter('snipeit_api_requests_total', 'Total API requests')
rate_limit_remaining = Gauge('snipeit_rate_limit_remaining', 'Remaining API requests')
rate_limit_exceeded = Counter('snipeit_rate_limit_exceeded_total', 'Rate limit exceeded count')

def track_api_request(response):
    api_requests.inc()
    
    remaining = int(response.headers.get('X-RateLimit-Remaining', 0))
    rate_limit_remaining.set(remaining)
    
    if response.status_code == 429:
        rate_limit_exceeded.inc()
        logging.warning(f"Rate limit exceeded! Retry after {response.headers.get('Retry-After')}s")
    
    if remaining < 10:
        logging.warning(f"Low rate limit: {remaining} requests remaining")
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Authentication" icon="key" href="/api/authentication">
    Learn about API token management
  </Card>

  <Card title="Pagination" icon="list" href="/api/assets">
    Efficiently handle large datasets
  </Card>
</CardGroup>
