yikart/AiToEarn · warning · HttpException

TOO_MANY_REQUESTS

TOO_MANY_REQUESTS

Error message

Too many requests

What it means

The global rate-limit guard in aitoearn-server counts requests per key against a configured limit within a ttl window. When count exceeds limit it throws an HttpException with code TOO_MANY_REQUESTS and includes the window ttl in data; X-RateLimit-Remaining: 0 and X-RateLimit-Reset headers are set on the response.

Source

Thrown at project/aitoearn-backend/apps/aitoearn-server/src/common/guards/rate-limit.guard.ts:83

    // 生成限流键
    const key = keyGenerator
      ? keyGenerator(request)
      : this.getDefaultKey(request)

    try {
      const count = await this.redisService.incrementRateLimit(key, ttl)

      // 设置响应头
      const response = context.switchToHttp().getResponse()
      response.setHeader('X-RateLimit-Limit', limit.toString())
      response.setHeader('X-RateLimit-Reset', (Date.now() + ttl * 1000).toString())

      // 检查是否超过限制
      if (count > limit) {
        response.setHeader('X-RateLimit-Remaining', '0')
        this.logger.warn(`Rate limit exceeded for key: ${key}, count: ${count}, limit: ${limit}`)
        throw new HttpException(
          {
            code: HttpStatus.TOO_MANY_REQUESTS,
            message: 'Too many requests',
            data: { ttl },
          },
          HttpStatus.TOO_MANY_REQUESTS,
        )
      }

      response.setHeader('X-RateLimit-Remaining', (limit - count).toString())

      return true
    }
    catch (error) {
      if (error instanceof HttpException) {
        throw error
      }
      this.logger.fatal(error, `Rate limit check failed`)

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Wait ttl seconds (from data.ttl or the X-RateLimit-Reset header) before retrying.
  2. Implement exponential backoff with jitter on 429 responses.
  3. Cache responses / reduce call frequency or batch requests.
  4. If limits are genuinely too low for legitimate traffic, raise the limit configuration for the route or use a higher-tier key.

Example fix

// before
setInterval(callApi, 100) // hammers the limit
// after
await pRetry(callApi, { retries: 5, minTimeout: 1000, factor: 2, onFailedAttempt: e => { if (e.statusCode !== 429) throw e } })
Defensive patterns

Strategy: retry

Validate before calling

// Check remaining quota from the previous response before calling again
const remaining = Number(prevResponse.headers['x-ratelimit-remaining'])
const resetAt = Number(prevResponse.headers['x-ratelimit-reset'])
if (remaining <= 0 && Date.now() < resetAt) await sleep(resetAt - Date.now())

Type guard

function isRateLimitError(e: unknown): e is { status: 429; response: { data: { ttl: number } } } {
  return typeof e === 'object' && e !== null && 'status' in e && (e as any).status === 429
}

Try / catch

try {
  res = await api.get(url)
} catch (e) {
  if (e.status === 429) {
    const waitMs = (e.response?.data?.ttl ?? 1) * 1000
    await sleep(waitMs)
    res = await api.get(url) // single retry after window; otherwise use exponential backoff
  } else throw e
}

Prevention

When it happens

Trigger: Any HTTP endpoint called more often than the configured limit within the ttl window for the same key — polling loops, retries without backoff, or many clients sharing one IP/API key and exhausting the bucket.

Common situations: Load tests or scripts hammering the API; multiple users behind one NAT/proxy sharing the rate-limit key; frontend retry loops after failures; misconfigured (too low) per-route limits.

Understand the failure class

Related errors


AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31). Data as JSON: /api/errors/300c5bfc582ede97. Report an issue: GitHub.