languagetool-org/languagetool · warning · TooManyRequestsException

Client request size limit of bytes per seconds exceeded in

Error message

Client request size limit of  bytes per  seconds exceeded in text-level checks

What it means

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.

Source

Thrown at languagetool-server/src/main/java/org/languagetool/server/RequestLimiter.java:229

          }
        }
        if (event.fingerprint.equals(fingerprint)) {
          requestsByFingerprint++;
          requestSizeByFingerprint += event.getSizeInBytes() * modeFactor;
        }
        if (ipFingerprintFactor > 0 && requestLimit > 0 && requestsByFingerprint > requestLimit) {
          String msg = "limit: " + requestLimit + " / " + requestLimitPeriodInSeconds + ", requests: "  + requestsByIp + ", ip: " + ipAddress + ", fingerprint: " + fingerprint;
          throw new TooManyRequestsException("Client request limit of " + requestLimit + " requests per " +
            requestLimitPeriodInSeconds + " seconds exceeded"); }
        if (requestLimit > 0 && requestsByIp > ipRequestLimit) {
          String msg = "limit: " + ipRequestLimit + " / " + requestLimitPeriodInSeconds + ", requests: "  + requestsByIp + ", ip: " + ipAddress + ", fingerprint: " + fingerprint;
          throw new TooManyRequestsException("IP request limit of " + ipRequestLimit + " requests per " +
            requestLimitPeriodInSeconds + " seconds exceeded");
        }
        if (event.mode == JLanguageTool.Mode.TEXTLEVEL_ONLY) {
          if (ipFingerprintFactor > 0 && requestLimitInBytes > 0 && requestSizeByFingerprint > requestLimitInBytes) {
            String msg = "limit in Mode.TEXTLEVEL_ONLY: " + requestLimitInBytes + " / " + requestLimitPeriodInSeconds + ", request size: "  + requestSizeByIp + ", ip: " + ipAddress + ", fingerprint: " + fingerprint;
            throw new TooManyRequestsException("Client request size limit of " + requestLimitInBytes + " bytes per " +
              requestLimitPeriodInSeconds + " seconds exceeded in text-level checks");
          }
          if (requestLimitInBytes > 0 && requestSizeByIp > ipRequestLimitInBytes) {
            String msg = "limit in Mode.TEXTLEVEL_ONLY: " + ipRequestLimitInBytes + " / " + requestLimitPeriodInSeconds + ", request size: "  + requestSizeByIp + ", ip: " + ipAddress + ", fingerprint: " + fingerprint;
            throw new TooManyRequestsException("IP request size limit of " + ipRequestLimitInBytes + " bytes per " +
              requestLimitPeriodInSeconds + " seconds exceeded in text-level checks");
          }
        } else {
          if (ipFingerprintFactor > 0 && requestLimitInBytes > 0 && requestSizeByFingerprint > requestLimitInBytes) {
            String msg = "limit: " + requestLimitInBytes + " / " + requestLimitPeriodInSeconds + ", request size: "  + requestSizeByIp + ", ip: " + ipAddress + ", fingerprint: " + fingerprint;
            throw new TooManyRequestsException("Client request size limit of " + requestLimitInBytes + " bytes per " +
              requestLimitPeriodInSeconds + " seconds exceeded");
          }
          if (requestLimitInBytes > 0 && requestSizeByIp > ipRequestLimitInBytes) {
            String msg = "limit: " + ipRequestLimitInBytes + " / " + requestLimitPeriodInSeconds + ", request size: "  + requestSizeByIp + ", ip: " + ipAddress + ", fingerprint: " + fingerprint;
            throw new TooManyRequestsException("IP request size limit of " + ipRequestLimitInBytes + " bytes per " +
              requestLimitPeriodInSeconds + " seconds exceeded");
          }

View on GitHub (pinned to 2e990059ce)

Solutions

  1. Wait until requestLimitPeriodInSeconds elapses so the size window resets, then retry with smaller payloads
  2. Reduce request size: split documents into smaller chunks and/or check less text per request
  3. Upgrade to a Premium API key (username/apiKey params) which carries higher size limits
  4. If you operate the server, raise requestLimitInBytes via the server config (e.g. --limitRequestSizeInBytes) or increase requestLimitPeriodInSeconds
  5. Check for a shared IP/fingerprint (proxy, VPN, corporate NAT) causing aggregation and use a different network or authenticated access

Example fix

// before: checking a huge document in one call
POST /v2/check?mode=textLevelOnly  body: text=<500KB>
// after: chunk the text and add backoff
if (clientBytesInWindow + chunk.length > requestLimitInBytes) await sleep(periodRemaining);
POST /v2/check?mode=textLevelOnly  body: text=<chunk of ~20KB>
Defensive patterns

Strategy: retry

Validate before calling

const bytes = new Blob([params.get('text')]).size;
if (bytes + bytesSentInWindow > REQUEST_LIMIT_BYTES) { await sleep(msUntilWindowReset); }

Try / catch

try {
  const res = await fetch(url, opts);
  if (res.status === 429) { const retryAfter = parseInt(res.headers.get('Retry-After') || '60', 10); await sleep(retryAfter * 1000); return retry(); }
} catch (e) { /* network error handling */ }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of languagetool-org/languagetool@2e990059ce (2026-09-06). Data as JSON: /api/errors/2d56cc7491f4f2d2. Report an issue: GitHub.