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

# Rate Limits

> Rate limit rules for each Zenstep API endpoint — limits, headers, and how to handle 429 responses.

## Overview

Rate limits protect the Zenstep API from abuse and ensure fair resource allocation. Limits are applied per-organisation and per-endpoint bucket.

***

## Limits by endpoint

| Endpoint                                         | Bucket        | Limit        | Window     |
| ------------------------------------------------ | ------------- | ------------ | ---------- |
| `POST /api/v1/events`                            | `events`      | 300 requests | 60 seconds |
| `GET /api/v1/flows`                              | `flows`       | 60 requests  | 60 seconds |
| `GET /api/v1/flows/[id]`                         | `flows`       | 60 requests  | 60 seconds |
| `POST /api/v1/flows/[id]/complete`               | `flows`       | 60 requests  | 60 seconds |
| `GET/POST /api/v1/flows/[id]/checklist-progress` | `flows`       | 60 requests  | 60 seconds |
| `POST /api/v1/steps/[id]/fingerprint`            | `fingerprint` | 30 requests  | 60 seconds |
| `POST /api/v1/steps/[id]/health`                 | `health`      | 60 requests  | 60 seconds |
| `GET /api/v1/heartbeat`                          | `heartbeat`   | 10 requests  | 60 seconds |
| `GET /api/v1/snippet-status`                     | `status`      | 10 requests  | 60 seconds |

<Note>
  The `events` bucket allows batch requests of up to 50 events each — so the
  effective throughput is up to **15,000 events per minute** per organisation at
  the maximum batch size.
</Note>

***

## Rate limit headers

Every response includes rate limit headers:

```
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 247
X-RateLimit-Reset: 1718000060
```

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

***

## Handling 429 responses

When you exceed the rate limit, the API returns:

```http theme={null}
HTTP/1.1 429 Too Many Requests
Retry-After: 23
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1718000060
```

```json theme={null}
{
  "error": "Rate limit exceeded",
  "retryAfter": 23
}
```

Use the `Retry-After` header value (in seconds) to delay your next request:

```javascript theme={null}
async function fetchWithRetry(url, options, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const response = await fetch(url, options);

    if (response.status === 429) {
      const retryAfter = parseInt(
        response.headers.get("Retry-After") ?? "5",
        10,
      );
      await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
      continue;
    }

    return response;
  }
  throw new Error("Max retries exceeded");
}
```

***

## Best practices

**Batch events** — the `/api/v1/events` endpoint accepts up to 50 events per request. Always batch events from your snippet rather than sending one request per event.

**Cache flow data** — the `/api/v1/flows` response is stable between publishes. Cache it for the duration of a user session and only re-fetch on SPA navigation or after a configurable TTL (the Zenstep snippet caches for the page lifecycle by default).

**Backoff on 429** — implement exponential backoff for any integration that calls the API from a backend service.
