Rate limits

| Limit | Value | |---|---| | Per key, per minute | 300 requests (5 per second) | | Per key, in one burst | 60 requests | | Per key, per day (UTC) | 20 000 requests | | Requests without a valid key, per address | 30 per minute |

Every response made with a key says how much is left, in the IETF RateLimit headers and the older X-RateLimit-* ones:

RateLimit-Policy: "token";q=300;w=60
RateLimit: "token";r=57;t=58
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 57
X-RateLimit-Reset: 58

r is what you can send right now and t the seconds until the window resets. The limit refills at 5 requests per second and never holds more than 60, so spread requests out instead of firing them all at once.

When you get a 429

Stop, wait the number of seconds in Retry-After, then try again. Retrying at once only makes it worse:

async function get(url: string, attempt = 0): Promise<Response> {
const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.TAVERN_KEY}` } });
if (res.status !== 429 || attempt === 3) return res;
const wait = Number(res.headers.get("Retry-After") ?? 1);
await new Promise((resolve) => setTimeout(resolve, wait * 1000));
return get(url, attempt + 1);
}

A key that keeps hammering the API after its limit (a hundred 429s within ten minutes) is suspended for an hour, and the API answers 403 banned until then.

Spending less

The game data only changes when the game is updated. Cache what you fetch, use ETags (see best practices), and ask for many items at once with /v1/items?ids=12,1522,1544 instead of one request each.