{"record":{"id":"2d56cc7491f4f2d2","repo":"languagetool-org/languagetool","slug":"client-request-size-limit-of-bytes-per-seconds-e","errorCode":null,"errorMessage":"Client request size limit of  bytes per  seconds exceeded in text-level checks","messagePattern":"Client request size limit of  bytes per  seconds exceeded in text-level checks","errorType":"http","errorClass":"TooManyRequestsException","httpStatus":429,"severity":"warning","filePath":"languagetool-server/src/main/java/org/languagetool/server/RequestLimiter.java","lineNumber":229,"sourceCode":"          }\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\");\n          }\n          if (requestLimitInBytes > 0 && requestSizeByIp > ipRequestLimitInBytes) {\n            String msg = \"limit: \" + 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\");\n          }","sourceCodeStart":211,"sourceCodeEnd":247,"githubUrl":"https://github.com/languagetool-org/languagetool/blob/2e990059ce67d5e2a0f7f7ca5d31160c6709df4b/languagetool-server/src/main/java/org/languagetool/server/RequestLimiter.java#L211-L247","documentation":"LanguageTool's server-side RequestLimiter enforces a per-fingerprint request-size quota in Mode.TEXTLEVEL_ONLY. When the bytes sent by one client fingerprint exceed requestLimitInBytes within requestLimitPeriodInSeconds, checkLimit throws TooManyRequestsException (HTTP 429). It protects the text-level-only API from large-volume abuse.","triggerScenarios":"POSTing text to /v2/check with mode=textLevelOnly (or TEXTLEVEL_ONLY set via param/command line) when the cumulative byte size of requests sharing the same fingerprint (browser/parameter-based, weighted by ipFingerprintFactor) exceeds requestLimitInBytes within the configured requestLimitPeriodInSeconds window.","commonSituations":"Public API users hitting the free-tier size quota; batch-checking many long documents in a loop without backoff; server misconfigured with too-low 'limitRequestSizeInBytes' style config; shared IPs/fingerprints behind proxies aggregating into one bucket.","solutions":["Wait until requestLimitPeriodInSeconds elapses so the size window resets, then retry with smaller payloads","Reduce request size: split documents into smaller chunks and/or check less text per request","Upgrade to a Premium API key (username/apiKey params) which carries higher size limits","If you operate the server, raise requestLimitInBytes via the server config (e.g. --limitRequestSizeInBytes) or increase requestLimitPeriodInSeconds","Check for a shared IP/fingerprint (proxy, VPN, corporate NAT) causing aggregation and use a different network or authenticated access"],"exampleFix":"// before: checking a huge document in one call\nPOST /v2/check?mode=textLevelOnly  body: text=<500KB>\n// after: chunk the text and add backoff\nif (clientBytesInWindow + chunk.length > requestLimitInBytes) await sleep(periodRemaining);\nPOST /v2/check?mode=textLevelOnly  body: text=<chunk of ~20KB>","handlingStrategy":"retry","validationCode":"const bytes = new Blob([params.get('text')]).size;\nif (bytes + bytesSentInWindow > REQUEST_LIMIT_BYTES) { await sleep(msUntilWindowReset); }","typeGuard":null,"tryCatchPattern":"try {\n  const res = await fetch(url, opts);\n  if (res.status === 429) { const retryAfter = parseInt(res.headers.get('Retry-After') || '60', 10); await sleep(retryAfter * 1000); return retry(); }\n} catch (e) { /* network error handling */ }","preventionTips":["Track cumulative bytes sent per limit window and stop below the quota","Chunk large documents instead of sending them whole","Add exponential backoff on HTTP 429","Use authenticated (Premium) requests for higher limits"],"tags":["rate-limit","http","http-429","languagetool"],"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"}