Rate limits
How many requests BatteryPass accepts, and how to back off after a 429.
BatteryPass limits requests to protect the platform and the other organisations on it. A request over a limit
is answered with 429 rate_limited and a Retry-After header.
Limits
| Limit | Counted for | Window | Applies to |
|---|---|---|---|
| 600 requests | All API keys of an organisation together | 1 minute | Every request with a valid key, whatever its result |
| 30 attempts | One client address | 15 minutes | Requests with an unknown or revoked key |
| 300 requests | One client address | 1 minute | Public passport reads, public documents and the GS1 resolver (/01/{gtin}/21/{serial}) |
The windows are fixed: a window starts, counts up and is reset when it ends. All keys of an organisation share the 600 requests; more keys do not raise the limit.
The 429 response
HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 17
X-Request-Id: 8b1e6f03c2d94a7e5f0a1b2c
{
"type": "about:blank",
"title": "Too Many Requests",
"status": 429,
"code": "rate_limited",
"detail": "Too many attempts. Try again later.",
"request_id": "8b1e6f03c2d94a7e5f0a1b2c"
}Retry-After is the number of seconds until the current window ends. A request answered with 429 was not
carried out, so you can repeat it after the wait, writes included.
Back off
Wait at least Retry-After seconds, add a little random jitter so that parallel workers do not all start at
the same moment, and give up after a few attempts:
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
// Repeats a request after 429 responses, waiting as Retry-After says.
async function fetchWithBackoff(url: string, init: RequestInit = {}, attempts = 5): Promise<Response> {
for (let attempt = 1; ; attempt++) {
const res = await fetch(url, init);
if (res.status !== 429 || attempt === attempts) return res;
const seconds = Number(res.headers.get('Retry-After')) || 2 ** attempt;
await sleep(seconds * 1000 + Math.random() * 1000);
}
}
const res = await fetchWithBackoff(`${process.env.BATTERYPASS_URL}/api/v1/battery-models`, {
headers: { Authorization: `Bearer ${process.env.BATTERYPASS_API_KEY}` },
});
console.log(res.status);Stay below the limits
- Share the budget. All systems of your organisation draw from the same 600 requests per minute. Give each job a share, for example by throttling a nightly sync to a few requests per second.
- Read in pages of 200. One page of
listBatteriesreplaces 200 single reads; see Pagination. - Use exports for bulk data.
exportUnitsreturns up to 10 000 units with their unit-level values as one spreadsheet (XLSX or CSV). - Do not retry
401. Fix the key instead; failed attempts count towards the 30 per 15 minutes. - Cache public reads. Public passports are cached for five minutes; reading the same passport more often gains nothing.
If your integration needs more than these limits allow, contact support.