How it works
Rate limits
Requests and query time per minute, what happens when you go over, the headers that count them and why webhooks beat polling in a loop.
Each client has two limits per minute. Both exist so that an integration that queries too much doesn’t take capacity away from anyone’s alerts, yours included.
The two limits
| Limit | Default |
|---|---|
| Requests per minute | 600 |
| Query time per minute | 30 seconds |
The second is less usual. Each request spends the time it takes to resolve, so a long list counts for more than a single alert: asking for 500 alerts from a month uses more of that budget than asking for one by its id. If your integration often asks for large lists, paginate with lower limit values or, better, stop polling and use webhooks.
Both can be raised by contract.
The token endpoint (POST /v1/oauth/token) also has its own limit, per IP. Request a token once an hour and reuse it (see Renewing it).
Headers
Responses carry how much headroom you have left:
| Header | What it says |
|---|---|
X-RateLimit-Limit | Your limit. |
X-RateLimit-Remaining | What you have left in the current window. |
When you go over
The API responds 429 in application/problem+json, with the Retry-After header: the seconds you have to wait before trying again.
async function request(url, options = {}, attempts = 3) {
const res = await fetch(url, options);
if (res.status === 429 && attempts > 0) {
const wait = Number(res.headers.get('Retry-After') ?? 1);
await new Promise((r) => setTimeout(r, wait * 1000));
return request(url, options, attempts - 1);
}
return res;
}import time
import requests
def request(method: str, url: str, attempts: int = 3, **kwargs) -> requests.Response:
res = requests.request(method, url, timeout=10, **kwargs)
if res.status_code == 429 and attempts > 0:
time.sleep(float(res.headers.get("Retry-After", "1")))
return request(method, url, attempts - 1, **kwargs)
return resasync Task<HttpResponseMessage> SendAsync(Func<Task<HttpResponseMessage>> send, int attempts = 3)
{
var res = await send();
if ((int)res.StatusCode == 429 && attempts > 0)
{
var wait = res.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(1);
await Task.Delay(wait);
return await SendAsync(send, attempts - 1);
}
return res;
}Always respect Retry-After. Retrying earlier only lengthens the wait.
Webhooks instead of polling in a loop
The most common cause of a 429 is asking every few seconds “are there any new alerts?”. It’s what webhooks do best: every alert reaches you in a second or two, without using a single request.
- To find out what’s happening: webhooks.
- To catch up after an outage:
GET /v1/events. - To load the state at start-up or look for something specific: the lists, paginated.