Rate limits
Each API key has a per-minute request limit. Exceed it and you get
429 rate_limited; wait the number of seconds in Retry-After.
Limits
- The administrator picks each product's limit in the panel: 60, 300 (default) or 1000 requests per minute, per environment.
- Requests are counted per API key in fixed one-minute windows (aligned to the clock minute). All requests of a key share one counter, and each request is compared with the limit of the product it belongs to.
- Need more throughput for an initial load? Use the bulk import (up to 500 employees per request) or talk to support.
Headers
Authenticated requests that reach the rate-limit check include:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Requests allowed per minute. |
X-RateLimit-Remaining | Requests left in the current window. |
X-RateLimit-Reset | When the next window starts (Unix epoch, seconds). |
Retry-After | Only on 429: seconds to wait. |
HTTP/1.1 429 Too Many Requests
Retry-After: 17
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1772463660
X-Request-Id: req_0c1d2e3f4a5b6c7d
{"error": {"code": "rate_limited", "message": "Rate limit exceeded. Retry after the indicated number of seconds."}}
Retrying correctly
Python
import random
import time
def request_with_retries(session, method, url, max_attempts=5, **kwargs):
for attempt in range(1, max_attempts + 1):
response = session.request(method, url, timeout=30, **kwargs)
if response.status_code == 429:
delay = int(response.headers.get("Retry-After", "1"))
elif response.status_code in (500, 502, 503):
delay = min(2 ** attempt, 30) + random.random()
else:
return response
if attempt == max_attempts:
return response
time.sleep(delay)
JavaScript
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function fetchWithRetries(url, options = {}, maxAttempts = 5) {
for (let attempt = 1; ; attempt++) {
const response = await fetch(url, options);
let delay;
if (response.status === 429) delay = Number(response.headers.get("Retry-After") || 1) * 1000;
else if ([500, 502, 503].includes(response.status)) delay = Math.min(2 ** attempt, 30) * 1000 + Math.random() * 1000;
else return response;
if (attempt === maxAttempts) return response;
await sleep(delay);
}
}
C#
static async Task<HttpResponseMessage> SendWithRetriesAsync(
HttpClient http, Func<HttpRequestMessage> build, int maxAttempts = 5)
{
for (var attempt = 1; ; attempt++)
{
var response = await http.SendAsync(build());
TimeSpan delay;
if ((int)response.StatusCode == 429)
delay = response.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(1);
else if ((int)response.StatusCode is 500 or 502 or 503)
delay = TimeSpan.FromSeconds(Math.Min(Math.Pow(2, attempt), 30) + Random.Shared.NextDouble());
else
return response;
if (attempt == maxAttempts) return response;
await Task.Delay(delay);
}
}
With Polly (.NET) you can express the same with a retry policy that reads
Retry-After. Don't retry 4xx errors other than 429.
Besides the per-key limit, POST /messages and Verify have their own caps per
sender and phone (see Messages and
Verify); they also return 429 rate_limited with
Retry-After. If those caps cannot be checked, sends
(POST /messages, POST /verify/start) return
503 limits_unavailable; POST /verify/check is not blocked.