{"record":{"id":"81cdf1f8f27eb3a3","repo":"languagetool-org/languagetool","slug":"apikey-was-set-but-username-was-not-apikey","errorCode":null,"errorMessage":"apiKey was set, but username was not: {apiKey}","messagePattern":"apiKey was set, but username was not: (.+?)","errorType":"validation","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"languagetool-server/src/main/java/org/languagetool/server/ServerTools.java","lineNumber":161,"sourceCode":"    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;\n    if (params.get(\"mode\") != null) {\n      String modeParam = params.get(\"mode\");\n      if (\"textLevelOnly\".equals(modeParam)) {\n        mode = Mode.TEXTLEVEL_ONLY;","sourceCodeStart":143,"sourceCodeEnd":179,"githubUrl":"https://github.com/languagetool-org/languagetool/blob/2e990059ce67d5e2a0f7f7ca5d31160c6709df4b/languagetool-server/src/main/java/org/languagetool/server/ServerTools.java#L143-L179","documentation":"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.","triggerScenarios":"Calling /v2/check (or another endpoint that runs getUserLimits) with apiKey=... but no username parameter.","commonSituations":"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.","solutions":["Add the matching username parameter next to apiKey","Verify the username is the account the API key was issued for","If your tooling only supports an apiKey, enable it to also send the associated username (Lan­guageTool requires the pair)"],"exampleFix":"// before\nparams.put(\"apiKey\", apiKey);\n// after\nparams.put(\"apiKey\", apiKey); params.put(\"username\", username);","handlingStrategy":"validation","validationCode":"function validateAuthParams(params) {\n  if (params.apiKey && !params.username) {\n    throw new Error(\"'apiKey' requires 'username'\");\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  return await lt.check(params);\n} catch (e) {\n  if (e.status === 400 && /apiKey was set, but username was not/.test(e.message)) {\n    params.username = getUsernameFor(params.apiKey); return await lt.check(params);\n  }\n  throw e;\n}","preventionTips":["Store apiKey and its username together (keyed credential records)","Never log or embed the apiKey in errors — the server echoes it in the message","Validate the credential pair before constructing requests"],"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-14T00:17:10.932Z"}