Rate limit by the key or user, and by IP only as the fallback
Limiting by IP throttles an office of a thousand people behind one NAT, and does nothing about a single attacker with a thousand addresses. Limit by whatever identifies the caller.
const key = apiKey ?? userId ?? ip;
await limiter.consume(key);Return 429 with a Retry-After header, so well-behaved clients back off instead of hammering you.
securityhttp