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

# Rate Limiting

> Understand API rate limits and how to handle them in your application

# Rate Limiting

Ticksupply uses a token bucket algorithm to enforce fair usage limits. This guide explains the limits, how to monitor your usage, and best practices for staying within bounds.

## Rate limit overview

| Window     | Limit           | Scope       |
| ---------- | --------------- | ----------- |
| Per minute | 200 requests    | Per account |
| Per hour   | 10,000 requests | Per account |

<Note>
  Rate limits are shared across all API keys on the same account. If you have multiple applications using different keys, they share the same limits.
</Note>

## Rate limit headers

Every API response includes headers showing your current rate limit status:

| Header                         | Description                                                                                                           |
| ------------------------------ | --------------------------------------------------------------------------------------------------------------------- |
| `X-RateLimit-Limit-Minute`     | Maximum requests allowed per minute                                                                                   |
| `X-RateLimit-Remaining-Minute` | Requests remaining this minute                                                                                        |
| `X-RateLimit-Reset-Minute`     | Unix timestamp (seconds) when the minute window next has capacity. Included only when the minute bucket is exhausted. |
| `X-RateLimit-Limit-Hour`       | Maximum requests allowed per hour                                                                                     |
| `X-RateLimit-Remaining-Hour`   | Requests remaining this hour                                                                                          |
| `X-RateLimit-Reset-Hour`       | Unix timestamp (seconds) when the hour window next has capacity. Included only when the hour bucket is exhausted.     |

Example response headers on a normal call:

```
X-RateLimit-Limit-Minute: 200
X-RateLimit-Remaining-Minute: 195
X-RateLimit-Limit-Hour: 10000
X-RateLimit-Remaining-Hour: 9850
```

When a window is exhausted, the matching `X-RateLimit-Reset-*` header is added alongside the rest. Its value is an absolute Unix timestamp in seconds — not a delay:

```
X-RateLimit-Remaining-Minute: 0
X-RateLimit-Reset-Minute: 1776426360
```

Compute the wait time as `reset - now` (seconds), not by treating the value as a duration.

## When rate limits are exceeded

When you exceed rate limits, the API returns a `429 Too Many Requests` response:

```json theme={"system"}
{
  "error": {
    "code": "rate_limited",
    "message": "Rate limit exceeded. Retry after 30 seconds."
  }
}
```

The response includes a `Retry-After` header indicating how many seconds to wait:

```
HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/json
```

## Handling rate limits

### Basic retry with exponential backoff

<CodeGroup>
  ```python Python theme={"system"}
  import time
  import requests

  def make_request_with_retry(url, headers, max_retries=5):
      for attempt in range(max_retries):
          response = requests.get(url, headers=headers)
          
          if response.status_code == 429:
              retry_after = int(response.headers.get("Retry-After", 30))
              print(f"Rate limited. Waiting {retry_after} seconds...")
              time.sleep(retry_after)
              continue
          
          response.raise_for_status()
          return response.json()
      
      raise Exception("Max retries exceeded")

  # Usage
  data = make_request_with_retry(
      "https://api.ticksupply.com/v1/exchanges",
      {"X-Api-Key": API_KEY}
  )
  ```

  ```javascript JavaScript theme={"system"}
  async function makeRequestWithRetry(url, headers, maxRetries = 5) {
    for (let attempt = 0; attempt < maxRetries; attempt++) {
      const response = await fetch(url, { headers });
      
      if (response.status === 429) {
        const retryAfter = parseInt(response.headers.get("Retry-After") || "30");
        console.log(`Rate limited. Waiting ${retryAfter} seconds...`);
        await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
        continue;
      }
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      
      return response.json();
    }
    
    throw new Error("Max retries exceeded");
  }

  // Usage
  const data = await makeRequestWithRetry(
    "https://api.ticksupply.com/v1/exchanges",
    { "X-Api-Key": API_KEY }
  );
  ```
</CodeGroup>

### Proactive rate limiting

Monitor the rate limit headers to avoid hitting limits:

```python theme={"system"}
import time

class RateLimitedClient:
    def __init__(self, api_key):
        self.api_key = api_key
        self.remaining_minute = 200
        self.reset_minute = 0
    
    def request(self, method, url, **kwargs):
        # Check if we should wait
        if self.remaining_minute <= 5:
            wait_time = max(0, self.reset_minute - time.time()) + 1
            if wait_time > 0:
                print(f"Approaching limit, waiting {wait_time:.1f}s")
                time.sleep(wait_time)
        
        headers = kwargs.pop("headers", {})
        headers["X-Api-Key"] = self.api_key
        
        response = requests.request(method, url, headers=headers, **kwargs)
        
        # Update rate limit tracking
        self.remaining_minute = int(
            response.headers.get("X-RateLimit-Remaining-Minute", 200)
        )
        # Reset-Minute is an absolute Unix timestamp (seconds) and is
        # only present when the minute bucket is empty. When absent,
        # fall back to "roughly one minute from now".
        reset_header = response.headers.get("X-RateLimit-Reset-Minute")
        self.reset_minute = (
            int(reset_header) if reset_header else time.time() + 60
        )
        
        return response
```

## Best practices

### Batch requests when possible

Instead of making many individual requests, use endpoints that return multiple items:

```python theme={"system"}
# ❌ Avoid: Many individual requests
for subscription_id in subscription_ids:
    response = requests.get(
        f"https://api.ticksupply.com/v1/subscriptions/{subscription_id}",
        headers=headers
    )

# ✅ Better: Single paginated request
response = requests.get(
    "https://api.ticksupply.com/v1/subscriptions",
    headers=headers,
    params={"limit": 100}
)
```

### Use idempotency keys for retries

When retrying failed requests, use idempotency keys to prevent duplicate operations:

```bash theme={"system"}
curl -X POST -H "X-Api-Key: YOUR_API_KEY" \
  -H "Idempotency-Key: unique-request-id-123" \
  -H "Content-Type: application/json" \
  -d '{"datastream_id": 123}' \
  https://api.ticksupply.com/v1/subscriptions
```

### Implement request queuing

For high-volume applications, queue requests and process them at a sustainable rate:

```python theme={"system"}
import queue
import threading
import time

class RequestQueue:
    def __init__(self, api_key, requests_per_second=3):
        self.api_key = api_key
        self.delay = 1.0 / requests_per_second
        self.queue = queue.Queue()
        self.running = True
        self.worker = threading.Thread(target=self._process_queue)
        self.worker.start()
    
    def _process_queue(self):
        while self.running:
            try:
                request_info, result_queue = self.queue.get(timeout=1)
                response = requests.request(**request_info)
                result_queue.put(response)
                time.sleep(self.delay)
            except queue.Empty:
                continue
    
    def submit(self, method, url, **kwargs):
        kwargs["headers"] = kwargs.get("headers", {})
        kwargs["headers"]["X-Api-Key"] = self.api_key
        
        result_queue = queue.Queue()
        self.queue.put(({"method": method, "url": url, **kwargs}, result_queue))
        return result_queue.get()
```

### Cache responses

Cache catalog data that doesn't change frequently:

```python theme={"system"}
from functools import lru_cache
import time

@lru_cache(maxsize=1)
def get_exchanges():
    response = requests.get(
        "https://api.ticksupply.com/v1/exchanges",
        headers={"X-Api-Key": API_KEY}
    )
    return response.json()

# Cache is reused for subsequent calls
exchanges = get_exchanges()
```

<Tip>
  Exchange and stream type data changes infrequently. Cache these responses for at least 1 hour to reduce API calls.
</Tip>

## Other limits

Rate limits control request throughput, but two separate kinds of limits can deny specific calls without consuming rate-limit budget:

* **Billing and plan limits** — creating a new subscription, resuming a paused subscription, or starting an export beyond your plan returns `402 payment_required`. See the [Error Handling guide](/guides/error-handling#billing-errors-402) for the full list of causes.
* **Trial caps** — on the trial plan, the number of active subscriptions and the cumulative successful-export size are both capped. Further `POST /v1/subscriptions` or `POST /v1/exports` calls return `402 payment_required` once a cap is reached.

Neither is affected by `Retry-After` — retrying identical calls will keep failing until the underlying billing state changes. Upgrade your plan, update your payment method, or wait for your trial to convert in [billing settings](https://app.ticksupply.com/settings/billing).

To check programmatically where you stand — current `access_status`, plan code, period end, and usage totals — call [`GET /v1/billing/summary`](/api-reference/billing/get-billing-summary).

## Rate limit increases

If you need higher rate limits for your use case, contact our support team:

* Email: [support@ticksupply.com](mailto:support@ticksupply.com)
* Include your account ID and expected request volumes

<Note>
  Rate limit increases are evaluated on a case-by-case basis and may require an upgraded plan.
</Note>

## Next steps

<CardGroup cols={2}>
  <Card title="Error Handling" icon="circle-exclamation" href="/guides/error-handling">
    Handle API errors gracefully in your application
  </Card>

  <Card title="Idempotency" icon="rotate" href="/guides/idempotency">
    Safely retry requests using idempotency keys
  </Card>

  <Card title="Pagination" icon="list" href="/guides/pagination">
    Navigate large result sets efficiently
  </Card>

  <Card title="API Reference" icon="code" href="/api-reference/overview">
    Explore the complete API documentation
  </Card>
</CardGroup>
