overleaf/overleaf · warning · CodedError
TooManyRequests
TooManyRequests
Error message
rate-limit hit when joining project
What it means
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.
Source
Thrown at services/real-time/app/js/WebApiManager.js:37
const url = new URL(settings.apis.web.url)
url.pathname = Path.posix.join('project', projectId, 'join')
let data
try {
data = await fetchJson(url, {
method: 'POST',
basicAuth: {
user: settings.apis.web.user,
password: settings.apis.web.pass,
},
json: {
userId,
anonymousAccessToken: user.anonymousAccessToken,
},
})
} catch (error) {
if (error instanceof RequestFailedError) {
if (error.response.status === 429) {
throw new CodedError(
'rate-limit hit when joining project',
'TooManyRequests'
)
} else if (error.response.status === 403) {
throw new NotAuthorizedError()
} else if (error.response.status === 404) {
throw new CodedError('project not found', 'ProjectNotFound')
}
throw new WebApiRequestFailedError(error.response.status)
}
throw OError.tag(error, 'join project request failed')
}
if (!(data && data.project)) {
throw new CorruptedJoinProjectResponseError()
}
const userMetadata = {
isRestrictedUser: data.isRestrictedUser,
isTokenMember: data.isTokenMember,View on GitHub (pinned to 28ad3b03b7)
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
Example fix
// before
for (const p of projects) await realTimeClient.joinProject(userId, p)
// after
for (const p of projects) {
await withBackoff(() => realTimeClient.joinProject(userId, p)) // retries on TooManyRequests with delay
} Defensive patterns
Strategy: retry
Validate before calling
// client-side token bucket before calling joinProject
if (!limiter.tryAcquire('joinProject', { capacity: 5, refillPerMin: 10 })) {
await sleep(backoff())
} Try / catch
try {
await manager.joinProject(user, projectId)
} catch (err) {
if (err instanceof CodedError && err.code === 'TooManyRequests') {
await sleep(jitteredBackoff(attempt))
return retry()
}
throw err
} Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- Rate-limit exceeded. Please wait a while and try again. If
- ${this.featureName} rate limit exceeded
- ${this.featureName} rate limit exceeded
- too-many-login-requests-2-mins
- Unrecognised value for retryOptions.idempotencyStrategy
AI-assisted analysis of overleaf/overleaf@28ad3b03b7 (2026-09-03).
Data as JSON: /api/errors/80a91b47870caa21.
Report an issue: GitHub.