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

# Rate limiting

> Understand request limits and how to stay within them.

The Propal API uses a **sliding window** rate limiter. Each organization has a maximum number of requests allowed per hour. The window slides continuously with no fixed reset time.

## Limits

|                     | Limit |
| ------------------- | ----- |
| **Requests / hour** | 1000  |

<Note>
  Rate limits apply per organization, not per API key. If you have multiple API keys for the same organization, they share the same limit.
</Note>

## Response headers

Every API response includes rate limit information in the headers:

```
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 997
X-RateLimit-Reset: 1712178000
```

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

## When you're rate limited

When you exceed your limit, the API returns a `429 Too Many Requests` response:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded."
  }
}
```

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

```
Retry-After: 3600
```

## Best practices

<Steps>
  <Step title="Monitor your usage">
    Check `X-RateLimit-Remaining` in response headers. If it's dropping fast, slow down your requests.
  </Step>

  <Step title="Use bulk operations">
    Fetch up to 100 items per request with `limit=100` instead of making many small requests.
  </Step>

  <Step title="Cache responses">
    Cache API responses locally when the data doesn't change frequently (e.g., templates, themes, organization details).
  </Step>

  <Step title="Implement backoff">
    When you receive a `429`, wait for the `Retry-After` duration before retrying. Don't retry immediately.
  </Step>

  <Step title="Spread requests over time">
    If you need to sync large amounts of data, spread your requests evenly over the hour rather than bursting them all at once.
  </Step>
</Steps>

## Example: rate-limit-aware client

```javascript theme={"theme":{"light":"github-light","dark":"github-dark"}}
class PropalClient {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.remaining = Infinity;
  }

  async request(path, options = {}) {
    // Pre-check: if we know we're close to the limit, slow down
    if (this.remaining <= 5) {
      await new Promise(r => setTimeout(r, 1000));
    }

    const response = await fetch(`https://api.propal.io/v1${path}`, {
      ...options,
      headers: {
        'Authorization': `Bearer ${this.apiKey}`,
        'Content-Type': 'application/json',
        ...options.headers,
      },
    });

    // Update our local tracking
    this.remaining = parseInt(
      response.headers.get('X-RateLimit-Remaining') ?? '1000'
    );

    if (response.status === 429) {
      const retryAfter = parseInt(
        response.headers.get('Retry-After') ?? '60'
      );
      console.log(`Rate limited. Waiting ${retryAfter}s...`);
      await new Promise(r => setTimeout(r, retryAfter * 1000));
      return this.request(path, options); // Retry
    }

    return response;
  }
}
```

## Need higher limits?

[Contact us](mailto:contact@propal.io) if you need higher rate limits for your organization.
