{"record":{"id":"6bf2d5391d4bbfd5","repo":"languagetool-org/languagetool","slug":"this-is-the-endpoint-for-the-basic-version-of-lang","errorCode":null,"errorMessage":"This is the endpoint for the basic version of LanguageTool. When using 'username' and 'apiKey' to access the premium version, use api.languagetoolplus.com instead.","messagePattern":"This is the endpoint for the basic version of LanguageTool\\. When using 'username' and 'apiKey' to access the premium version, use api\\.languagetoolplus\\.com instead\\.","errorType":"http","errorClass":"AuthException","httpStatus":403,"severity":"error","filePath":"languagetool-server/src/main/java/org/languagetool/server/DatabaseAccessOpenSource.java","lineNumber":212,"sourceCode":"        Date now = new Date();\n        map.put(\"created_at\", now);\n        map.put(\"updated_at\", now);\n        int affectedRows = session.insert(\"org.languagetool.server.UserDictMapper.addWord\", map);\n        logger.info(\"Added '\" + word + \"' for user \" + userId + \" to list of ignored words, affectedRows: \" + affectedRows);\n        return affectedRows == 1;\n      }\n    }\n  }\n\n  Long getUserId(String username, String apiKey) {\n    if (username == null || username.trim().isEmpty()) {\n      throw new IllegalArgumentException(\"username must be set\");\n    }\n    if (apiKey == null || apiKey.trim().isEmpty()) {\n      throw new IllegalArgumentException(\"apiKey must be set\");\n    }\n    if (sqlSessionFactory == null) {\n      throw new AuthException(\"This is the endpoint for the basic version of LanguageTool. \" +\n        \"When using 'username' and 'apiKey' to access the premium version, use api.languagetoolplus.com instead.\");\n    }\n    try {\n      Long value = dbLoggingCache.get(String.format(\"user_%s_%s\", username, apiKey), () -> {\n        try (SqlSession session = sqlSessionFactory.openSession()) {\n          Map<Object, Object> map = new HashMap<>();\n          map.put(\"username\", username);\n          map.put(\"apiKey\", apiKey);\n          Long id = session.selectOne(\"org.languagetool.server.UserDictMapper.getUserIdByApiKey\", map);\n          if (id == null) {\n            return -1L;\n          }\n          return id;\n        }\n      });\n      if (value == -1) {\n        throw new IllegalArgumentException(\"No user found for given username '\" + username + \"' and given api key\");\n      } else {","sourceCodeStart":194,"sourceCodeEnd":230,"githubUrl":"https://github.com/languagetool-org/languagetool/blob/2e990059ce67d5e2a0f7f7ca5d31160c6709df4b/languagetool-server/src/main/java/org/languagetool/server/DatabaseAccessOpenSource.java#L194-L230","documentation":"When sqlSessionFactory is null, the open-source implementation has no database configured, so getUserId throws AuthException telling the caller that this is the basic LanguageTool endpoint and username/apiKey (premium) auth belongs on api.languagetoolplus.com. It is a routing/configuration error: credentials were provided to a server that cannot process them.","triggerScenarios":"Sending username + apiKey authentication to an open-source languagetool-server that was started WITHOUT database configuration (no MyBatis/sqlSessionFactory), i.e. any request hitting getUserId on a basic self-hosted instance.","commonSituations":"Self-hosting LanguageTool and passing premium credentials (username/apiKey) that only work on the paid service; a server started without --database* config options receiving premium-style auth.","solutions":["If you have a premium subscription, change the client endpoint to https://api.languagetoolplus.com.","If self-hosting with your own users, start the server with database configuration so sqlSessionFactory is initialized (premium setup required for the bundled DB support).","Remove username/apiKey from the request when using the basic open-source server (use anonymous or OSS-supported auth)."],"exampleFix":"// before\nPOST http://localhost:8081/v2/check  { \"username\":\"bob\", \"apiKey\":\"k\" } // AuthException\n// after\nPOST https://api.languagetoolplus.com/v2/check  { \"username\":\"bob\", \"apiKey\":\"k\" }\n// or, on the basic server, omit username/apiKey entirely","handlingStrategy":"validation","validationCode":"if (usesUsernameApiKeyAuth && !isPremiumEndpoint(baseUrl)) {\n  throw new IllegalArgumentException(\"username/apiKey only valid on api.languagetoolplus.com or a DB-configured server\");\n}","typeGuard":"boolean isPremiumEndpoint(String url) { return url != null && url.contains(\"languagetoolplus.com\"); }","tryCatchPattern":"try {\n  response = api.check(text, username, apiKey);\n} catch (AuthException e) {\n  // retry without credentials or redirect to premium endpoint\n}","preventionTips":["Match auth scheme to server edition: credentials only for premium or DB-configured servers.","Read the server startup config to know whether database auth is enabled.","Route premium credentials to api.languagetoolplus.com in client configuration."],"tags":["java","authentication","misconfiguration","premium-only"],"backgroundTag":"unsupported-operation","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"}