{"record":{"id":"4c603267330e16cb","repo":"SonarSource/sonarqube","slug":"unable-to-contact-bitbucket-cloud-servers-configu","errorCode":null,"errorMessage":"Unable to contact Bitbucket Cloud servers: Configure the OAuth client in the Bitbucket workspace to be a private client","messagePattern":"Unable to contact Bitbucket Cloud servers: Configure the OAuth client in the Bitbucket workspace to be a private client","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucket/bitbucketcloud/BitbucketCloudRestClient.java","lineNumber":138,"sourceCode":"      doGetWithApiToken(encodedApiTokenCredentials, buildUrl(\"/repositories/\" + workspace), r -> null);\n    } catch (NotFoundException | IllegalStateException e) {\n      throw new IllegalArgumentException(e.getMessage());\n    }\n  }\n\n  private Token validateAccessToken(String clientId, String clientSecret) {\n    Request request = createAccessTokenRequest(clientId, clientSecret);\n    try (Response response = client.newCall(request).execute()) {\n      if (response.isSuccessful()) {\n        return buildGson().fromJson(response.body().charStream(), Token.class);\n      }\n\n      ErrorDetails errorMsg = getTokenError(response.body(), response.message());\n      if (errorMsg.body != null) {\n        LOG.atInfo().log(() -> String.format(BBC_FAIL_WITH_RESPONSE, response.request().url(), response.code(), errorMsg.body));\n        switch (errorMsg.body) {\n          case \"invalid_grant\":\n            throw new IllegalArgumentException(UNABLE_TO_CONTACT_BBC_SERVERS + \": \" + OAUTH_CONSUMER_NOT_PRIVATE);\n          case \"unauthorized_client\":\n            throw new IllegalArgumentException(UNABLE_TO_CONTACT_BBC_SERVERS + \": \" + UNAUTHORIZED_CLIENT);\n          default:\n            if (errorMsg.parsedErrorMsg != null) {\n              throw new IllegalArgumentException(ERROR_BBC_SERVERS + \": \" + errorMsg.parsedErrorMsg);\n            } else {\n              throw new IllegalArgumentException(UNABLE_TO_CONTACT_BBC_SERVERS);\n            }\n        }\n      } else {\n        LOG.atInfo().log(() -> String.format(BBC_FAIL_WITH_RESPONSE, response.request().url(), response.code(), response.message()));\n      }\n      throw new IllegalArgumentException(UNABLE_TO_CONTACT_BBC_SERVERS);\n\n    } catch (IOException e) {\n      LOG.info(String.format(BBC_FAIL_WITH_ERROR, request.url(), e.getMessage()));\n      throw new IllegalArgumentException(UNABLE_TO_CONTACT_BBC_SERVERS, e);\n    }","sourceCodeStart":120,"sourceCodeEnd":156,"githubUrl":"https://github.com/SonarSource/sonarqube/blob/184c821202192afc1c599fc912d0889b69fffa53/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucket/bitbucketcloud/BitbucketCloudRestClient.java#L120-L156","documentation":"During validateAccessToken() the client exchanges a Bitbucket OAuth client id/secret for an access token at bitbucket.org/site/oauth2/access_token. Bitbucket answered with an error body whose 'error' field is 'invalid_grant', so the client throws IllegalArgumentException('Unable to contact Bitbucket Cloud servers: Configure the OAuth client in the Bitbucket workspace to be a private client'). Bitbucket returns invalid_grant for this grant_type when the OAuth consumer is a public (non-private) client, which does not permit the client_credentials grant used here.","triggerScenarios":"Calling BitbucketCloudRestClient.validate(clientId, clientSecret, workspace) (or any flow reaching validateAccessToken/token) where the OAuth consumer configured in the Bitbucket workspace is created as a public client: the POST to /site/oauth2/access_token with grant_type=client_credentials and Basic clientId:clientSecret returns a 400 body {\"error\":\"invalid_grant\", ...} and the switch in validateAccessToken maps it to this exact message.","commonSituations":"Admin follows older integration docs and creates the OAuth consumer without checking 'This is a private consumer'; consumer was later switched to public in Bitbucket workspace settings; wrong consumer selected (a public app-level consumer instead of the workspace private consumer); consumer lacking required scopes such as repository/pullrequest read.","solutions":["In Bitbucket: Workspace Settings > OAuth consumers, edit the consumer used by SonarQube and enable 'This is a private consumer' (callback URL not required for client_credentials)","Re-create the consumer as private if it cannot be edited, then copy the new Key/Secret into SonarQube's Bitbucket Cloud configuration","Ensure the consumer has the Account: Read, Repository: Read and Pull Request: Read scopes so the subsequent pullrequest scope check passes","Retry the SonarQube configuration validation ('Check configuration') after saving the consumer changes"],"exampleFix":"// before (public consumer -> invalid_grant)\nPOST https://bitbucket.org/site/oauth2/access_token\n  grant_type=client_credentials, Basic <clientId>:<clientSecret>  => 400 {\"error\":\"invalid_grant\"}\n// after (consumer marked private in Bitbucket workspace settings)\n// same request => 200 {\"access_token\":\"...\",\"scopes\":\"...pullrequest...\"}","handlingStrategy":"validation","validationCode":"// Pre-check before wiring SonarQube: confirm consumer is private by attempting the grant manually\n// curl -s -X POST -u <key>:<secret> -d grant_type=client_credentials \\\n//   https://bitbucket.org/site/oauth2/access_token\n// If body contains \"error\":\"invalid_grant\" -> consumer is public or misconfigured; fix in Bitbucket first.\nif (tokenResponse.contains(\"\\\"invalid_grant\\\"\")) {\n  throw new ConfigurationException(\"Mark the Bitbucket OAuth consumer as private (workspace settings) before validating\");\n}","typeGuard":null,"tryCatchPattern":"try {\n  client.validate(clientId, clientSecret, workspace);\n} catch (IllegalArgumentException e) {\n  if (e.getMessage().contains(\"private client\")) {\n    log.error(\"Bitbucket OAuth consumer is public; enable 'This is a private consumer' in workspace settings\");\n  }\n  throw e;\n}","preventionTips":["Always create Bitbucket OAuth consumers with 'This is a private consumer' checked — required for grant_type=client_credentials","Use a dedicated workspace-level consumer for SonarQube, not a personal/public one","Grant the consumer Account:Read, Repository:Read and Pull Request:Read scopes","Re-run 'Check configuration' after any consumer edit in Bitbucket"],"tags":["bitbucket","oauth","configuration","authentication"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"184c821202192afc1c599fc912d0889b69fffa53","analyzedAt":"2026-09-09T12:23:51.573Z","contentChangedAt":"2026-09-09T12:23:51.573Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}