{"record":{"id":"e328fe9ad711654e","repo":"languagetool-org/languagetool","slug":"with-username-set-you-also-need-to-specify-api","errorCode":null,"errorMessage":"With 'username' set, you also need to specify 'apiKey'","messagePattern":"With 'username' set, you also need to specify 'apiKey'","errorType":"validation","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"languagetool-server/src/main/java/org/languagetool/server/ServerTools.java","lineNumber":157,"sourceCode":"    return getUserLimits(params, config, null);\n  }\n\n  static UserLimits getUserLimits(Map<String, String> params, HTTPServerConfig config, String authHeader) {\n    if (params.get(\"username\") != null) {\n      if (params.get(\"apiKey\") != null && params.get(\"password\") != null) {\n        throw new BadRequestException(\"apiKey AND password was set, set only apiKey\");\n      }\n      if (params.get(\"apiKey\") != null) {\n        return UserLimits.getLimitsByApiKey(config, params.get(\"username\"), params.get(\"apiKey\"));\n      } else if (params.get(\"password\") != null) {\n        return UserLimits.getLimitsFromUserAccount(config, params.get(\"username\"), params.get(\"password\"));\n      } else if (params.get(\"tokenV2\") != null) {\n        if (authHeader != null) {\n          return UserLimits.getLimitsWithJwtToken(config, authHeader, params.get(\"username\"), params.get(\"tokenV2\"));\n        }\n        return UserLimits.getLimitsByAddonToken(config, params.get(\"username\"), params.get(\"tokenV2\"));\n      } else {\n        throw new BadRequestException(\"With 'username' set, you also need to specify 'apiKey'\");\n      }\n    } else {\n      if (params.get(\"apiKey\") != null) {\n        throw new BadRequestException(\"apiKey was set, but username was not: \" + params.get(\"apiKey\"));\n      }\n      if (params.get(\"password\") != null) {\n        throw new BadRequestException(\"password was set, but username was not\");\n      }\n      if (authHeader != null) {\n        return UserLimits.getLimitsWithJwtToken(config, authHeader, params.get(\"username\"), params.get(\"tokenV2\"));\n      }\n      return UserLimits.getDefaultLimits(config);\n    }\n  }\n\n  @NotNull\n  static Mode getMode(Map<String, String> params) {\n    Mode mode;","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/languagetool-org/languagetool/blob/2e990059ce67d5e2a0f7f7ca5d31160c6709df4b/languagetool-server/src/main/java/org/languagetool/server/ServerTools.java#L139-L175","documentation":"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'.","triggerScenarios":"Calling /v2/check with username=... but no apiKey, no password, and no tokenV2 parameter.","commonSituations":"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.","solutions":["Add the apiKey parameter alongside username","Alternatively provide password (legacy) or tokenV2 (addon token) with the username","If you do not need user limits, remove the username parameter to fall back to default/IP limits"],"exampleFix":"// before\nPOST /v2/check?username=alice&text=...\n// after\nPOST /v2/check?username=alice&apiKey=SECRET&text=...","handlingStrategy":"validation","validationCode":"function validateAuthParams(params) {\n  if (params.username && !params.apiKey && !params.password && !params.tokenV2) {\n    throw new Error(\"'username' requires 'apiKey' (or password/tokenV2)\");\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  return await lt.check(params);\n} catch (e) {\n  if (e.status === 400 && /also need to specify 'apiKey'/.test(e.message)) {\n    throw new ConfigError('LT_USERNAME set without LT_API_KEY');\n  }\n  throw e;\n}","preventionTips":["Always pair username with apiKey in your credential store","Fail fast at startup if only one of the pair is configured","Omit username entirely when using anonymous/IP-based limits"],"tags":["http","bad-request","authentication","missing-parameter","languagetool"],"backgroundTag":"missing-required-argument","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"}