{"record":{"id":"eea94b7126eaf6da","repo":"languagetool-org/languagetool","slug":"ip-request-limit-of-requests-per-seconds-exceede","errorCode":null,"errorMessage":"IP request limit of  requests per  seconds exceeded","messagePattern":"IP request limit of  requests per  seconds exceeded","errorType":"http","errorClass":"TooManyRequestsException","httpStatus":429,"severity":"error","filePath":"languagetool-server/src/main/java/org/languagetool/server/RequestLimiter.java","lineNumber":223,"sourceCode":"          if (whitelistLimit <= 0 || requestsByIp < whitelistLimit) {\n            continue;\n          } else {\n            String msg = \"limit: \" + ipRequestLimit + \" / \" + requestLimitPeriodInSeconds + \", requests: \"  + requestsByIp + \", ip: \" + ipAddress + \", fingerprint: \" + fingerprint;\n            throw new TooManyRequestsException(\"Whitelist request limit of \" + whitelistLimit + \" requests per \" +\n              requestLimitPeriodInSeconds + \" seconds exceeded\");\n          }\n        }\n        if (event.fingerprint.equals(fingerprint)) {\n          requestsByFingerprint++;\n          requestSizeByFingerprint += event.getSizeInBytes() * modeFactor;\n        }\n        if (ipFingerprintFactor > 0 && requestLimit > 0 && requestsByFingerprint > requestLimit) {\n          String msg = \"limit: \" + requestLimit + \" / \" + requestLimitPeriodInSeconds + \", requests: \"  + requestsByIp + \", ip: \" + ipAddress + \", fingerprint: \" + fingerprint;\n          throw new TooManyRequestsException(\"Client request limit of \" + requestLimit + \" requests per \" +\n            requestLimitPeriodInSeconds + \" seconds exceeded\"); }\n        if (requestLimit > 0 && requestsByIp > ipRequestLimit) {\n          String msg = \"limit: \" + ipRequestLimit + \" / \" + requestLimitPeriodInSeconds + \", requests: \"  + requestsByIp + \", ip: \" + ipAddress + \", fingerprint: \" + fingerprint;\n          throw new TooManyRequestsException(\"IP request limit of \" + ipRequestLimit + \" requests per \" +\n            requestLimitPeriodInSeconds + \" seconds exceeded\");\n        }\n        if (event.mode == JLanguageTool.Mode.TEXTLEVEL_ONLY) {\n          if (ipFingerprintFactor > 0 && requestLimitInBytes > 0 && requestSizeByFingerprint > requestLimitInBytes) {\n            String msg = \"limit in Mode.TEXTLEVEL_ONLY: \" + requestLimitInBytes + \" / \" + requestLimitPeriodInSeconds + \", request size: \"  + requestSizeByIp + \", ip: \" + ipAddress + \", fingerprint: \" + fingerprint;\n            throw new TooManyRequestsException(\"Client request size limit of \" + requestLimitInBytes + \" bytes per \" +\n              requestLimitPeriodInSeconds + \" seconds exceeded in text-level checks\");\n          }\n          if (requestLimitInBytes > 0 && requestSizeByIp > ipRequestLimitInBytes) {\n            String msg = \"limit in Mode.TEXTLEVEL_ONLY: \" + ipRequestLimitInBytes + \" / \" + requestLimitPeriodInSeconds + \", request size: \"  + requestSizeByIp + \", ip: \" + ipAddress + \", fingerprint: \" + fingerprint;\n            throw new TooManyRequestsException(\"IP request size limit of \" + ipRequestLimitInBytes + \" bytes per \" +\n              requestLimitPeriodInSeconds + \" seconds exceeded in text-level checks\");\n          }\n        } else {\n          if (ipFingerprintFactor > 0 && requestLimitInBytes > 0 && requestSizeByFingerprint > requestLimitInBytes) {\n            String msg = \"limit: \" + requestLimitInBytes + \" / \" + requestLimitPeriodInSeconds + \", request size: \"  + requestSizeByIp + \", ip: \" + ipAddress + \", fingerprint: \" + fingerprint;\n            throw new TooManyRequestsException(\"Client request size limit of \" + requestLimitInBytes + \" bytes per \" +\n              requestLimitPeriodInSeconds + \" seconds exceeded\");","sourceCodeStart":205,"sourceCodeEnd":241,"githubUrl":"https://github.com/languagetool-org/languagetool/blob/2e990059ce67d5e2a0f7f7ca5d31160c6709df4b/languagetool-server/src/main/java/org/languagetool/server/RequestLimiter.java#L205-L241","documentation":"HTTP 429 error thrown when the total number of requests from one IP address exceeds ipRequestLimit within requestLimitPeriodInSeconds. This is the per-IP guard, regardless of fingerprint, protecting the server from high-volume sources.","triggerScenarios":"checkLimit detects requestsByIp > ipRequestLimit while requestLimit > 0 within the period.","commonSituations":"Many users behind one NAT/proxy IP collectively exceeding the limit, load tests run against production, or microservices sharing an egress IP hitting the API concurrently.","solutions":["Reduce request rate or batch text checks into fewer, larger requests","Raise ipRequestLimit in the server configuration if the source is trusted","Route legitimate high-volume clients through distinct IPs/credentials or wait out the rate-limit window"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"const MIN_INTERVAL_MS = (periodSeconds * 1000) / ipRequestLimit;\nlet last = 0;\nasync function throttle() {\n  const wait = last + MIN_INTERVAL_MS - Date.now();\n  if (wait > 0) await new Promise(r => setTimeout(r, wait));\n  last = Date.now();\n}","typeGuard":null,"tryCatchPattern":"try {\n  return await check(text);\n} catch (e) {\n  if (e.status === 429 && /IP request limit/.test(e.message)) {\n    await sleep(periodSeconds * 1000);\n    return check(text);\n  }\n  throw e;\n}","preventionTips":["Coordinate all services behind a shared egress IP with a global rate limiter","Batch multiple texts per request where possible","Run load tests against staging, not production servers"],"tags":["rate-limit","languagetool","http-429"],"backgroundTag":"rate-limit-exceeded","analyzedSha":"2e990059ce67d5e2a0f7f7ca5d31160c6709df4b","analyzedAt":"2026-09-06T09:20:17.015Z","contentChangedAt":"2026-09-06T09:20:17.015Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}