{"record":{"id":"2f11904a002bf160","repo":"hcengineering/platform","slug":"rate-limit","errorCode":null,"errorMessage":"rate-limit","messagePattern":"rate-limit","errorType":"http","errorClass":"Error","httpStatus":429,"severity":"warning","filePath":"foundations/core/packages/api-client/src/rest/rest.ts","lineNumber":196,"sourceCode":"  }\n\n  private async checkRateLimits (response: Response): Promise<void> {\n    if (response.status === 429) {\n      // Extract rate limit information from headers\n      const retryAfter = response.headers.get('Retry-After')\n      const retryAfterMS = response.headers.get('Retry-After-ms')\n      const rateLimitReset = response.headers.get('X-RateLimit-Reset')\n\n      this.updateRateLimit(response)\n      const waitTime =\n        (retryAfterMS != null ? parseInt(retryAfterMS) : undefined) ??\n        (retryAfter != null\n          ? parseInt(retryAfter) * 1000\n          : rateLimitReset != null\n            ? new Date(parseInt(rateLimitReset)).getTime() - Date.now()\n            : 1000) // Default to 1 seconds if no headers are provided\n      await new Promise((resolve) => setTimeout(resolve, waitTime))\n      throw new Error(rateLimitError)\n    }\n  }\n\n  async getAccount (): Promise<Account> {\n    const requestUrl = concatLink(this.endpoint, `/api/v1/account/${this.workspace}`)\n    await this.checkRate()\n    const result = await withRetry<Account & { error?: Status }>(async () => {\n      const response = await fetch(requestUrl, this.requestInit())\n      if (!response.ok) {\n        await this.checkRateLimits(response)\n        throw new PlatformError(unknownError(response.statusText))\n      }\n      this.updateRateLimit(response)\n      return await extractJson<Account>(response)\n    })\n    if (result.error !== undefined) {\n      throw new PlatformError(result.error)\n    }","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/foundations/core/packages/api-client/src/rest/rest.ts#L178-L214","documentation":"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.","triggerScenarios":"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.","commonSituations":"Bulk scripts iterating thousands of objects, background jobs hammering searchFulltext, multiple processes sharing one token, retry loops without backoff amplifying load.","solutions":["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).","Reduce request rate: batch requests, add throttling/debounce in loops.","Respect the client's built-in slowdown (checkRate) rather than creating parallel clients bypassing it.","Request a higher quota from the server admin or distribute work across tokens/workspaces."],"exampleFix":"// before\nfor (const q of queries) await client.searchFulltext(q, {}) // slams the API\n// after\nfor (const q of queries) {\n  try { await client.searchFulltext(q, {}) }\n  catch (e) { if (String(e.message).includes('rate-limit')) await sleep(5000) }\n  await sleep(200)\n}","handlingStrategy":"retry","validationCode":"// Throttle proactively using the last observed rate-limit state\nconst remaining = Number(lastResponse?.headers?.get('X-RateLimit-Remaining') ?? '100')\nif (remaining < 10) await sleep(5000) // back off before hitting 429","typeGuard":"function isRateLimitError(e: unknown): e is Error {\n  return e instanceof Error && e.message.includes('rate-limit')\n}","tryCatchPattern":"async function withRateLimitRetry<T>(fn: () => Promise<T>, attempts = 5): Promise<T> {\n  for (let i = 0; ; i++) {\n    try { return await fn() }\n    catch (e) {\n      if (isRateLimitError(e) && i < attempts - 1) { await sleep(2000 * 2 ** i); continue }\n      throw e\n    }\n  }\n}","preventionTips":["Serialize requests instead of firing them in parallel; add per-request delay in bulk jobs","Monitor X-RateLimit-Remaining and back off before exhausting the quota","Retry with exponential backoff and honor Retry-After headers","Distribute heavy batch workloads across time windows or multiple accounts"],"tags":["rate-limit","http","retry"],"backgroundTag":"http-429-rate-limit","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}