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.
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.
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()));
}
}