Guides
Rate limits
Each API key can make up to 600 requests per minute. Every authenticated response tells you how much of that budget is left.
How the limit works
Limits apply per API key and use a token bucket. Each key has a bucket of 600 requests that refills continuously at 10 requests per second. You can burst up to the full bucket, then continue at the refill rate.
- Separate integrations with separate keys don’t share a budget.
- Every authenticated request counts, including requests that fail (for example, with
404) and conditional requests answered with304. - Design for 600 requests per minute per key. Enforcement is approximate, so don’t depend on getting more.
Rate limit headers
Every authenticated response, successful or not, includes the headers below. Responses to requests that fail authentication (401, 403 account_inactive, or a 503 while the key is being checked) don’t include them, because no key’s budget was consulted.
| Header | Meaning |
|---|---|
RateLimit-Limit | Size of the bucket (600). |
RateLimit-Remaining | Requests you can make right now. |
RateLimit-Reset | Seconds until the bucket is completely full again. |
Retry-After | On 429 responses, the number of seconds to wait before retrying. 503 service_unavailable responses include it too. |
HTTP/1.1 200 OK
Content-Type: application/json
RateLimit-Limit: 600
RateLimit-Remaining: 587
RateLimit-Reset: 2When you exceed the limit
Requests over the limit fail with 429 Too Many Requests and code rate_limit_exceeded. Wait at least Retry-After seconds before trying again.
HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
RateLimit-Limit: 600
RateLimit-Remaining: 0
RateLimit-Reset: 60
Retry-After: 1
X-Request-Id: req_k3j2h1g0f9e8d7c6b5a4z3y2
{
"type": "https://weguard.ai/docs/errors#rate_limit_exceeded",
"title": "Rate limit exceeded",
"status": 429,
"code": "rate_limit_exceeded",
"detail": "Rate limit of 600 requests per minute exceeded for this API key.",
"instance": "/core/v1/devices",
"requestId": "req_k3j2h1g0f9e8d7c6b5a4z3y2"
}Retrying with backoff
Retry 429, 500 and 503 responses with exponential backoff and jitter, and honor Retry-After when it’s present. Jitter spreads out retries from parallel workers so they don’t all hit the API at once.
const RETRYABLE = new Set([429, 500, 503]);
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function getWithBackoff(url, maxAttempts = 5) {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const response = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.WEGUARD_API_KEY}` },
});
if (!RETRYABLE.has(response.status)) return response;
// Prefer the server's hint; otherwise exponential backoff with full jitter.
const retryAfter = Number(response.headers.get("Retry-After"));
const cap = Math.min(30_000, 1000 * 2 ** attempt);
await sleep(retryAfter > 0 ? retryAfter * 1000 : Math.random() * cap);
}
throw new Error("Gave up after repeated 429/500/503 responses");
}import os
import random
import time
import requests
RETRYABLE = {429, 500, 503}
def get_with_backoff(url, params=None, max_attempts=5):
headers = {"Authorization": f"Bearer {os.environ['WEGUARD_API_KEY']}"}
for attempt in range(max_attempts):
response = requests.get(url, params=params, headers=headers, timeout=30)
if response.status_code not in RETRYABLE:
return response
# Prefer the server's hint; otherwise exponential backoff with full jitter.
retry_after = response.headers.get("Retry-After")
cap = min(30, 2 ** attempt)
time.sleep(float(retry_after) if retry_after else random.uniform(0, cap))
raise RuntimeError("Gave up after repeated 429/500/503 responses")Staying under the limit
- Use
limit=100(or the endpoint’s maximum) to fetch more per request. See Pagination. - Prefer one paginated pass over the list endpoint to fetching devices one by one.
- Send
If-None-Matchwith theETagyou received to skip downloading unchanged data. See Conditional requests. - Spread scheduled jobs out instead of starting them all on the hour.
- Watch
RateLimit-Remainingand slow down before it reaches zero.