# Limits and retries

> Rate limits, retry behaviour and pagination.

## Rate limits

| Limit | Threshold | When you reach it |
| --- | --- | --- |
| Requests per second | 5 | Queue and retry with backoff |
| Requests per minute | 100 | Queue and retry with backoff |
| Requests per hour | 2,000 | Queue and resume in the next hour |
| Concurrent connections | 3 | Queue additional requests |

## Retries

On `429` or any `5xx`, wait and retry with exponential backoff: start at 1 second, double each time, cap at 60 seconds. After 5 consecutive failures, stop and alert someone. Never retry a `400`.

```js
async function callWithRetry(apiCall, maxAttempts = 5) {
  let delay = 1000;
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    const response = await apiCall();
    if (response.status >= 200 && response.status < 300) return response;
    if (response.status !== 429 && response.status < 500) {
      throw new Error(`PayCaptain rejected the request: ${response.status}`); // do not retry
    }
    await new Promise((resolve) => setTimeout(resolve, delay));
    delay = Math.min(delay * 2, 60000);
  }
  throw new Error("PayCaptain unavailable after 5 attempts. Halt and alert.");
}
```

## Pagination

`GET /employees` and `GET /payslips` return up to 50 records per page. Request pages in order until you receive a `204`.

```js
async function fetchAll(path, params) {
  const records = [];
  for (let page = 1; ; page++) {
    const response = await callWithRetry(() => get(path, { ...params, page }));
    if (response.status === 204) return records;
    records.push(...(await response.json()));
  }
}
```
