{"record":{"id":"21ab2cf20c23cda5","repo":"languagetool-org/languagetool","slug":"apikey-and-password-was-set-set-only-apikey","errorCode":null,"errorMessage":"apiKey AND password was set, set only apiKey","messagePattern":"apiKey AND password was set, set only apiKey","errorType":"validation","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"languagetool-server/src/main/java/org/languagetool/server/ServerTools.java","lineNumber":145,"sourceCode":"  static void setCommonHeaders(HttpExchange httpExchange, String contentType, String allowOriginUrl) {\n    httpExchange.getResponseHeaders().set(\"Content-Type\", contentType);\n    setAllowOrigin(httpExchange, allowOriginUrl);\n  }\n\n  static void setAllowOrigin(HttpExchange httpExchange, String allowOriginUrl) {\n    if (allowOriginUrl != null) {\n      httpExchange.getResponseHeaders().set(\"Access-Control-Allow-Origin\", allowOriginUrl);\n    }\n  }\n\n  static UserLimits getUserLimits(Map<String, String> params, HTTPServerConfig config) {\n    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) {","sourceCodeStart":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/languagetool-org/languagetool/blob/2e990059ce67d5e2a0f7f7ca5d31160c6709df4b/languagetool-server/src/main/java/org/languagetool/server/ServerTools.java#L127-L163","documentation":"ServerTools.getUserLimits validates authentication parameters for /v2/check. Passing both apiKey and password with a username is ambiguous (two credential schemes), so it throws BadRequestException (HTTP 400) telling you to send only apiKey.","triggerScenarios":"Calling /v2/check with params username=X, apiKey=Y, AND password=Z — any request containing all three parameters.","commonSituations":"Migrating from old username/password auth to API keys and leaving the old password param in the client config; template/query builders that always append both fields; copy-pasted example code including both.","solutions":["Remove the password parameter and keep username + apiKey","Remove apiKey and keep username + password if you intentionally use password auth (legacy)","Search your client code/config for where both params are injected and make them mutually exclusive"],"exampleFix":"// before\nparams.put(\"username\", user); params.put(\"apiKey\", key); params.put(\"password\", pwd);\n// after\nparams.put(\"username\", user); params.put(\"apiKey\", key); // drop password","handlingStrategy":"validation","validationCode":"function validateAuthParams(params) {\n  if (params.username && params.apiKey && params.password) {\n    throw new Error('Send only apiKey (or only password), not both');\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  return await lt.check(params);\n} catch (e) {\n  if (e.status === 400 && /apiKey AND password/.test(e.message)) {\n    delete params.password; return await lt.check(params);\n  }\n  throw e;\n}","preventionTips":["Make apiKey and password mutually exclusive in your client config layer","Remove legacy password params after migrating to API keys","Write a unit test asserting the request params contain exactly one credential scheme"],"tags":["http","bad-request","authentication","languagetool"],"backgroundTag":"mutually-exclusive-flags","analyzedSha":"2e990059ce67d5e2a0f7f7ca5d31160c6709df4b","analyzedAt":"2026-09-06T09:20:17.015Z","contentChangedAt":"2026-09-06T09:20:17.015Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}