Every key gets the same Card Data limit: 300 requests per minute, measured over a rolling 60-second window, independent of your credit balance. The limit controls how fast you may spend, credits control how much — throughput is never a plan differentiator.
The Gacha and Instant Pack APIs on the
same key have their own, lower ceiling — 240 requests/minute plus shared
concurrency caps that queue briefly and shed sustained overload with a 503 —
documented on the Gacha overview.
Every response carries the state of your window:
X-RateLimit-Limit: 300 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1785470340 Retry-After: 60
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Requests allowed in the window |
X-RateLimit-Remaining | Requests left before you're throttled |
X-RateLimit-Reset | When the window rolls over (Unix epoch seconds) |
Retry-After | Present only on a 429. Seconds to wait, and always better than your own guess. |
You get 429. Throttled requests are never billed — rate limiting
runs before metering — so a retry storm costs you latency but not credits. Back off
exponentially, and cap the number of attempts:
async function call(path, attempt = 0) {
const res = await fetch(BASE + path, { headers: { 'X-API-Key': KEY } });
if (res.status === 429 && attempt < 5) {
// Honour the server's own number first; only guess if it's absent.
const wait = Number(res.headers.get('Retry-After')) ||
Math.min(2 ** attempt, 30);
await new Promise((r) => setTimeout(r, wait * 1000));
return call(path, attempt + 1);
}
return res;
} Overload shedding. Separately from your per-key window, the API bounds
concurrent catalog reads globally. Under heavy load a request can get an immediate 503 with Retry-After: 1 — also never billed. Treat it exactly like a 429: wait and retry.