{"record":{"id":"1b9a7f26ce99f9c5","repo":"SonarSource/sonarqube","slug":"your-gitlab-token-has-insufficient-scope","errorCode":null,"errorMessage":"Your GitLab token has insufficient scope","messagePattern":"Your GitLab token has insufficient scope","errorType":"exception","errorClass":"GitlabServerException","httpStatus":null,"severity":"error","filePath":"server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabApplicationClient.java","lineNumber":216,"sourceCode":"    } catch (UnsupportedEncodingException ex) {\n      throw new IllegalStateException(ex.getCause());\n    }\n  }\n\n  protected static void checkResponseIsSuccessful(Response response) throws IOException {\n    checkResponseIsSuccessful(response, \"GitLab Merge Request did not happen, please check your configuration\");\n  }\n\n  protected static void checkResponseIsSuccessful(Response response, String errorMessage) throws IOException {\n    if (!response.isSuccessful()) {\n      String body = response.body().string();\n      LOG.error(\"Gitlab API call to [{}] failed with {} http code. gitlab response content : [{}]\", response.request().url(), response.code(), body);\n      if (isTokenRevoked(response, body)) {\n        throw new GitlabServerException(response.code(), \"Your GitLab token was revoked\");\n      } else if (isTokenExpired(response, body)) {\n        throw new GitlabServerException(response.code(), \"Your GitLab token is expired\");\n      } else if (isInsufficientScope(response, body)) {\n        throw new GitlabServerException(response.code(), \"Your GitLab token has insufficient scope\");\n      } else if (response.code() == HTTP_FORBIDDEN) {\n        throw new GitlabServerException(response.code(), \"Forbidden access to GitLab. Verify your token's permissions and IP restrictions.\");\n      } else if (response.code() == HTTP_TOO_MANY_REQUESTS) {\n        throw new GitlabServerException(response.code(), \"GitLab API rate limit exceeded. Try again later.\");\n      } else if (response.code() == HTTP_UNAUTHORIZED) {\n        throw new GitlabServerException(response.code(), \"Invalid personal access token\");\n      } else if (response.isRedirect()) {\n        throw new GitlabServerException(response.code(), \"Request was redirected, please provide the correct URL\");\n      } else {\n        throw new GitlabServerException(response.code(), errorMessage);\n      }\n    }\n  }\n\n  private static boolean isTokenRevoked(Response response, String body) {\n    if (response.code() == HTTP_UNAUTHORIZED) {\n      try {\n        Optional<GsonError> gitlabError = GsonError.parseOne(body);","sourceCodeStart":198,"sourceCodeEnd":234,"githubUrl":"https://github.com/SonarSource/sonarqube/blob/184c821202192afc1c599fc912d0889b69fffa53/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabApplicationClient.java#L198-L234","documentation":"checkResponseIsSuccessful detects GitLab's 403 'insufficient_scope' error response and throws GitlabServerException 'Your GitLab token has insufficient scope'. The token is valid and not expired, but it lacks the OAuth scopes (notably 'api') required for the operation SonarQube attempted.","triggerScenarios":"Any GitLab API call via checkResponseIsSuccessful (checkProjectAccess, checkToken, getPersonalAccessTokenInfo, checkWritePermission, createProjectAccessToken) receives 403 with an insufficient_scope body at GitlabApplicationClient.java:216.","commonSituations":"PAT created with only read_api or read_user but the integration needs api scope; a read-only token used for creating projects or project access tokens; organization policy restricting token scopes.","solutions":["Recreate the GitLab PAT with the 'api' scope (plus read_user) selected.","Update the new token in SonarQube's GitLab DevOps integration configuration.","Verify GitLab instance policy does not down-scope tokens (e.g. read_api-only enforcement).","For project-level bindings, ensure the token owner is at least Maintainer on the target project."],"exampleFix":"// before: token scopes = ['read_api']\n// after: recreate PAT with scopes\nscopes: [\"api\", \"read_user\"]","handlingStrategy":"validation","validationCode":"// Validate scopes before configuring the integration\nJsonObject info = getJson(gitlabUrl + \"/api/v4/personal_access_tokens/self\", token);\nJsonArray scopes = info.getAsJsonArray(\"scopes\");\nif (!scopes.toString().contains(\"api\")) {\n  throw new IllegalStateException(\"GitLab PAT lacks the 'api' scope; recreate it with api + read_user\");\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Document the required scopes (api, read_user) wherever PATs are provisioned.","Re-run SonarQube's configuration check after any token change.","Watch for GitLab instance policies that force read-only scopes."],"tags":["gitlab","authentication","token","permissions","http-403"],"backgroundTag":"insufficient-permissions","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"}