languagetool-org/languagetool · error · BadRequestException

apiKey was set, but username was not: {apiKey}

Error message

apiKey was set, but username was not: {apiKey}

What it means

Parameter validation in ServerTools.getUserLimits: an apiKey was supplied in the request parameters without a username, so the API-key-based user limits lookup cannot identify the user.

Source

Thrown at languagetool-server/src/main/java/org/languagetool/server/ServerTools.java:161

    if (params.get("username") != null) {
      if (params.get("apiKey") != null && params.get("password") != null) {
        throw new BadRequestException("apiKey AND password was set, set only apiKey");
      }
      if (params.get("apiKey") != null) {
        return UserLimits.getLimitsByApiKey(config, params.get("username"), params.get("apiKey"));
      } else if (params.get("password") != null) {
        return UserLimits.getLimitsFromUserAccount(config, params.get("username"), params.get("password"));
      } else if (params.get("tokenV2") != null) {
        if (authHeader != null) {
          return UserLimits.getLimitsWithJwtToken(config, authHeader, params.get("username"), params.get("tokenV2"));
        }
        return UserLimits.getLimitsByAddonToken(config, params.get("username"), params.get("tokenV2"));
      } else {
        throw new BadRequestException("With 'username' set, you also need to specify 'apiKey'");
      }
    } else {
      if (params.get("apiKey") != null) {
        throw new BadRequestException("apiKey was set, but username was not: " + params.get("apiKey"));
      }
      if (params.get("password") != null) {
        throw new BadRequestException("password was set, but username was not");
      }
      if (authHeader != null) {
        return UserLimits.getLimitsWithJwtToken(config, authHeader, params.get("username"), params.get("tokenV2"));
      }
      return UserLimits.getDefaultLimits(config);
    }
  }

  @NotNull
  static Mode getMode(Map<String, String> params) {
    Mode mode;
    if (params.get("mode") != null) {
      String modeParam = params.get("mode");
      if ("textLevelOnly".equals(modeParam)) {
        mode = Mode.TEXTLEVEL_ONLY;

View on GitHub (pinned to 2e990059ce)

Solutions

  1. Add the matching username parameter next to apiKey
  2. Verify the username is the account the API key was issued for
  3. If your tooling only supports an apiKey, enable it to also send the associated username (Lan­guageTool requires the pair)

Example fix

// before
params.put("apiKey", apiKey);
// after
params.put("apiKey", apiKey); params.put("username", username);
Defensive patterns

Strategy: validation

Validate before calling

function validateAuthParams(params) {
  if (params.apiKey && !params.username) {
    throw new Error("'apiKey' requires 'username'");
  }
}

Try / catch

try {
  return await lt.check(params);
} catch (e) {
  if (e.status === 400 && /apiKey was set, but username was not/.test(e.message)) {
    params.username = getUsernameFor(params.apiKey); return await lt.check(params);
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling /v2/check (or another endpoint that runs getUserLimits) with apiKey=... but no username parameter.

Common situations: Sending only the API key assuming it is self-contained (keys are checked with username in this server); losing the username during env-var/config propagation; client libraries that only expose an apiKey option.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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