{"record":{"id":"80a91b47870caa21","repo":"overleaf/overleaf","slug":"toomanyrequests","errorCode":"TooManyRequests","errorMessage":"rate-limit hit when joining project","messagePattern":"rate-limit hit when joining project","errorType":"error_code","errorClass":"CodedError","httpStatus":429,"severity":"warning","filePath":"services/real-time/app/js/WebApiManager.js","lineNumber":37,"sourceCode":"  const url = new URL(settings.apis.web.url)\n  url.pathname = Path.posix.join('project', projectId, 'join')\n  let data\n  try {\n    data = await fetchJson(url, {\n      method: 'POST',\n      basicAuth: {\n        user: settings.apis.web.user,\n        password: settings.apis.web.pass,\n      },\n      json: {\n        userId,\n        anonymousAccessToken: user.anonymousAccessToken,\n      },\n    })\n  } catch (error) {\n    if (error instanceof RequestFailedError) {\n      if (error.response.status === 429) {\n        throw new CodedError(\n          'rate-limit hit when joining project',\n          'TooManyRequests'\n        )\n      } else if (error.response.status === 403) {\n        throw new NotAuthorizedError()\n      } else if (error.response.status === 404) {\n        throw new CodedError('project not found', 'ProjectNotFound')\n      }\n      throw new WebApiRequestFailedError(error.response.status)\n    }\n    throw OError.tag(error, 'join project request failed')\n  }\n  if (!(data && data.project)) {\n    throw new CorruptedJoinProjectResponseError()\n  }\n  const userMetadata = {\n    isRestrictedUser: data.isRestrictedUser,\n    isTokenMember: data.isTokenMember,","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/overleaf/overleaf/blob/28ad3b03b71cb4311decdcb55c36b33ec10d72db/services/real-time/app/js/WebApiManager.js#L19-L55","documentation":"joinProject proxies a join-project request to the web API; when that upstream call answers HTTP 429 the manager rethrows it as a CodedError with code 'TooManyRequests'. It signals the server-side rate limiter rejected the join attempt, not a bug in the caller's payload. Retry only after honoring the backoff interval.","triggerScenarios":"Calling joinProject when the upstream web API returns status 429 — i.e., the user/project has exceeded the allowed rate of join-project requests (rapid re-joins, scripted loops, or many concurrent tab joins).","commonSituations":"Scripts or tests hammering joinProject in a loop; a user opening the same project in many tabs simultaneously; aggressive reconnection logic retrying immediately after failures without backoff.","solutions":["Wait and retry with exponential backoff, honoring any Retry-After header from the response","Cache the join result so repeated navigations/rejoins don't re-issue the request","Add jittered client-side throttling so concurrent tabs don't each call joinProject","Ask the admin whether the rate-limit threshold should be raised for your workload"],"exampleFix":"// before\nfor (const p of projects) await realTimeClient.joinProject(userId, p)\n// after\nfor (const p of projects) {\n  await withBackoff(() => realTimeClient.joinProject(userId, p)) // retries on TooManyRequests with delay\n}","handlingStrategy":"retry","validationCode":"// client-side token bucket before calling joinProject\nif (!limiter.tryAcquire('joinProject', { capacity: 5, refillPerMin: 10 })) {\n  await sleep(backoff())\n}","typeGuard":null,"tryCatchPattern":"try {\n  await manager.joinProject(user, projectId)\n} catch (err) {\n  if (err instanceof CodedError && err.code === 'TooManyRequests') {\n    await sleep(jitteredBackoff(attempt))\n    return retry()\n  }\n  throw err\n}","preventionTips":["Apply exponential backoff with jitter on all join attempts","Deduplicate concurrent joinProject calls per user+project","Cache joined state across tab reconnects","Monitor 429 rates and alert on sustained throttling"],"tags":["rate-limit","http-429","real-time","retry"],"backgroundTag":"rate-limit-exceeded","analyzedSha":"28ad3b03b71cb4311decdcb55c36b33ec10d72db","analyzedAt":"2026-09-03T02:10:22.807Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T07:17:11.731Z"}