languagetool-org/languagetool · error · BadRequestException

With 'username' set, you also need to specify 'apiKey'

Error message

With 'username' set, you also need to specify 'apiKey'

What it means

When the 'username' parameter is present, getUserLimits requires one of apiKey, password, or tokenV2 to authenticate the user. If none is provided it throws BadRequestException (HTTP 400) asking for 'apiKey'.

Source

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

    return getUserLimits(params, config, null);
  }

  static UserLimits getUserLimits(Map<String, String> params, HTTPServerConfig config, String authHeader) {
    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;

View on GitHub (pinned to 2e990059ce)

Solutions

  1. Add the apiKey parameter alongside username
  2. Alternatively provide password (legacy) or tokenV2 (addon token) with the username
  3. If you do not need user limits, remove the username parameter to fall back to default/IP limits

Example fix

// before
POST /v2/check?username=alice&text=...
// after
POST /v2/check?username=alice&apiKey=SECRET&text=...
Defensive patterns

Strategy: validation

Validate before calling

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

Try / catch

try {
  return await lt.check(params);
} catch (e) {
  if (e.status === 400 && /also need to specify 'apiKey'/.test(e.message)) {
    throw new ConfigError('LT_USERNAME set without LT_API_KEY');
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling /v2/check with username=... but no apiKey, no password, and no tokenV2 parameter.

Common situations: Adding a username for limits but forgetting the credential; stripping credentials during config refactor; sending username in the URL and expecting the API key in a header the server does not read here.

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/e328fe9ad711654e. Report an issue: GitHub.