hcengineering/platform · warning · Error

rate-limit

Error message

rate-limit

What it means

checkRateLimits detects an HTTP 429 from the server, sleeps for the server-indicated wait time (Retry-After-ms, Retry-After, or X-RateLimit-Reset; default 1s), then throws an Error with the rateLimitError message ('rate-limit'). It signals the API quota was exhausted for this client.

Source

Thrown at foundations/core/packages/api-client/src/rest/rest.ts:196

  }

  private async checkRateLimits (response: Response): Promise<void> {
    if (response.status === 429) {
      // Extract rate limit information from headers
      const retryAfter = response.headers.get('Retry-After')
      const retryAfterMS = response.headers.get('Retry-After-ms')
      const rateLimitReset = response.headers.get('X-RateLimit-Reset')

      this.updateRateLimit(response)
      const waitTime =
        (retryAfterMS != null ? parseInt(retryAfterMS) : undefined) ??
        (retryAfter != null
          ? parseInt(retryAfter) * 1000
          : rateLimitReset != null
            ? new Date(parseInt(rateLimitReset)).getTime() - Date.now()
            : 1000) // Default to 1 seconds if no headers are provided
      await new Promise((resolve) => setTimeout(resolve, waitTime))
      throw new Error(rateLimitError)
    }
  }

  async getAccount (): Promise<Account> {
    const requestUrl = concatLink(this.endpoint, `/api/v1/account/${this.workspace}`)
    await this.checkRate()
    const result = await withRetry<Account & { error?: Status }>(async () => {
      const response = await fetch(requestUrl, this.requestInit())
      if (!response.ok) {
        await this.checkRateLimits(response)
        throw new PlatformError(unknownError(response.statusText))
      }
      this.updateRateLimit(response)
      return await extractJson<Account>(response)
    })
    if (result.error !== undefined) {
      throw new PlatformError(result.error)
    }

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Catch the error and retry after the delay indicated by Retry-After / X-RateLimit-Reset headers (the client already waited once — add your own backoff on top).
  2. Reduce request rate: batch requests, add throttling/debounce in loops.
  3. Respect the client's built-in slowdown (checkRate) rather than creating parallel clients bypassing it.
  4. Request a higher quota from the server admin or distribute work across tokens/workspaces.

Example fix

// before
for (const q of queries) await client.searchFulltext(q, {}) // slams the API
// after
for (const q of queries) {
  try { await client.searchFulltext(q, {}) }
  catch (e) { if (String(e.message).includes('rate-limit')) await sleep(5000) }
  await sleep(200)
}
Defensive patterns

Strategy: retry

Validate before calling

// Throttle proactively using the last observed rate-limit state
const remaining = Number(lastResponse?.headers?.get('X-RateLimit-Remaining') ?? '100')
if (remaining < 10) await sleep(5000) // back off before hitting 429

Type guard

function isRateLimitError(e: unknown): e is Error {
  return e instanceof Error && e.message.includes('rate-limit')
}

Try / catch

async function withRateLimitRetry<T>(fn: () => Promise<T>, attempts = 5): Promise<T> {
  for (let i = 0; ; i++) {
    try { return await fn() }
    catch (e) {
      if (isRateLimitError(e) && i < attempts - 1) { await sleep(2000 * 2 ** i); continue }
      throw e
    }
  }
}

Prevention

When it happens

Trigger: Any REST call (e.g. searchFulltext, domainRequest, getAccount) returning status 429 after exceeding the server's X-RateLimit quota; tight loops issuing many requests with one shared token.

Common situations: Bulk scripts iterating thousands of objects, background jobs hammering searchFulltext, multiple processes sharing one token, retry loops without backoff amplifying load.

Related errors


AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29). Data as JSON: /api/errors/2f11904a002bf160. Report an issue: GitHub.