diff --git a/src/_repobee/ext/github.py b/src/_repobee/ext/github.py
index 3bb248f..657a413 100644
--- a/src/_repobee/ext/github.py
+++ b/src/_repobee/ext/github.py
@@ -129,6 +129,17 @@ class GitHubAPI(plug.API):
         """
         if not user:
             raise TypeError("argument 'user' must not be empty")
+        if not (
+            base_url == "https://api.github.com"
+            or base_url.endswith("/api/v3")
+        ):
+            raise plug.PlugError(
+                "invalid base url, should either be https://api.github.com or "
+                "end with '/api/v3'. See the docs: "
+                "https://repobee.readthedocs.io/en/stable/"
+                "getting_started.html#configure-repobee-for-the-target"
+                "-organization-show-config-and-verify-settings"
+            )
         self._github = github.Github(login_or_token=token, base_url=base_url)
         self._org_name = org_name
         self._base_url = base_url

Add a check for GitHub base url
Check that the url is either `https://api.github.com`, or that it ends with `/api/v3`. There have been way too many user errors on incorrect urls, and the error messages can be cryptic.
diff --git a/tests/unit_tests/plugin_tests/test_github.py b/tests/unit_tests/plugin_tests/test_github.py
index 959aafc..84ead19 100644
--- a/tests/unit_tests/plugin_tests/test_github.py
+++ b/tests/unit_tests/plugin_tests/test_github.py
@@ -317,6 +317,32 @@ def api(happy_github, organization, no_teams):
     return _repobee.ext.github.GitHubAPI(BASE_URL, TOKEN, ORG_NAME, USER)
 
 
+class TestInit:
+    def test_raises_on_empty_user_arg(self):
+        with pytest.raises(TypeError) as exc_info:
+            _repobee.ext.github.GitHubAPI(BASE_URL, TOKEN, ORG_NAME, "")
+
+        assert "argument 'user' must not be empty" in str(exc_info.value)
+
+    @pytest.mark.parametrize("url", ["https://github.com", constants.HOST_URL])
+    def test_raises_when_url_is_bad(self, url):
+        with pytest.raises(plug.PlugError) as exc_info:
+            _repobee.ext.github.GitHubAPI(url, TOKEN, ORG_NAME, USER)
+
+        assert (
+            "invalid base url, should either be https://api.github.com or "
+            "end with '/api/v3'" in str(exc_info.value)
+        )
+
+    @pytest.mark.parametrize(
+        "url", ["https://api.github.com", constants.BASE_URL]
+    )
+    def test_accepts_valid_urls(self, url):
+        api = _repobee.ext.github.GitHubAPI(url, TOKEN, ORG_NAME, USER)
+
+        assert isinstance(api, plug.API)
+
+
 class TestEnsureTeamsAndMembers:
     @staticmethod
     def assert_teams_equal(actual_teams, expected_teams):

pip install -e .[TEST]
parse_log_pytest
requirements.txt
pytest>=4.0.0 pytest-cov>=2.6.1 pytest-mock codecov pytest
apt-get update apt-get install -y gcc
3.7
requirements/app.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
appdirs==1.4.4
certifi @ file:///croot/certifi_1671487769961/work/certifi
cffi==1.15.1
charset-normalizer==3.4.1
codecov==2.1.13
colored==1.4.4
coverage==7.2.7
cryptography==44.0.2
daiquiri==3.2.1
Deprecated==1.2.18
exceptiongroup==1.2.2
idna==3.10
importlib-metadata==6.7.0
iniconfig==2.0.0
packaging==24.0
pluggy==1.2.0
pycparser==2.21
PyGithub==2.3.0
PyJWT==2.8.0
PyNaCl==1.5.0
pytest==7.4.4
pytest-cov==4.1.0
pytest-mock==3.11.1
python-gitlab==1.9.0
python-json-logger==3.0.1
-e git+https://github.com/repobee/repobee.git@f5adada7aa91cfa67735957d606b34d7d14a4118#egg=repobee
repobee-plug==0.11.0
requests==2.31.0
six==1.17.0
tomli==2.0.1
typing_extensions==4.7.1
urllib3==2.0.7
wrapt==1.16.0
zipp==3.15.0

name: repobee
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=22.3.1=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - appdirs==1.4.4
      - cffi==1.15.1
      - charset-normalizer==3.4.1
      - codecov==2.1.13
      - colored==1.4.4
      - coverage==7.2.7
      - cryptography==44.0.2
      - daiquiri==3.2.1
      - deprecated==1.2.18
      - exceptiongroup==1.2.2
      - idna==3.10
      - importlib-metadata==6.7.0
      - iniconfig==2.0.0
      - packaging==24.0
      - pluggy==1.2.0
      - pycparser==2.21
      - pygithub==2.3.0
      - pyjwt==2.8.0
      - pynacl==1.5.0
      - pytest==7.4.4
      - pytest-cov==4.1.0
      - pytest-mock==3.11.1
      - python-gitlab==1.9.0
      - python-json-logger==3.0.1
      - repobee==2.2.0
      - repobee-plug==0.11.0
      - requests==2.31.0
      - six==1.17.0
      - tomli==2.0.1
      - typing-extensions==4.7.1
      - urllib3==2.0.7
      - wrapt==1.16.0
      - zipp==3.15.0
prefix: /opt/conda/envs/repobee

tests/unit_tests/plugin_tests/test_github.py::TestInit::test_raises_when_url_is_bad[https://github.com] tests/unit_tests/plugin_tests/test_github.py::TestInit::test_raises_when_url_is_bad[https://some_enterprise_host]
tests/unit_tests/plugin_tests/test_github.py::TestInit::test_raises_on_empty_user_arg tests/unit_tests/plugin_tests/test_github.py::TestInit::test_accepts_valid_urls[https://api.github.com] tests/unit_tests/plugin_tests/test_github.py::TestInit::test_accepts_valid_urls[https://some_enterprise_host/api/v3] tests/unit_tests/plugin_tests/test_github.py::TestEnsureTeamsAndMembers::test_no_previous_teams tests/unit_tests/plugin_tests/test_github.py::TestEnsureTeamsAndMembers::test_all_teams_exist_but_without_members tests/unit_tests/plugin_tests/test_github.py::TestEnsureTeamsAndMembers::test_raises_on_non_422_exception[unexpected_exc0] tests/unit_tests/plugin_tests/test_github.py::TestEnsureTeamsAndMembers::test_raises_on_non_422_exception[unexpected_exc1] tests/unit_tests/plugin_tests/test_github.py::TestEnsureTeamsAndMembers::test_raises_on_non_422_exception[unexpected_exc2] tests/unit_tests/plugin_tests/test_github.py::TestEnsureTeamsAndMembers::test_running_twice_has_no_ill_effects tests/unit_tests/plugin_tests/test_github.py::TestCreateRepos::test_creates_correct_repos tests/unit_tests/plugin_tests/test_github.py::TestCreateRepos::test_skips_existing_repos tests/unit_tests/plugin_tests/test_github.py::TestCreateRepos::test_raises_on_unexpected_error[unexpected_exception0] tests/unit_tests/plugin_tests/test_github.py::TestCreateRepos::test_raises_on_unexpected_error[unexpected_exception1] tests/unit_tests/plugin_tests/test_github.py::TestCreateRepos::test_raises_on_unexpected_error[unexpected_exception2] tests/unit_tests/plugin_tests/test_github.py::TestCreateRepos::test_returns_all_urls tests/unit_tests/plugin_tests/test_github.py::TestCreateRepos::test_create_repos_without_team_id tests/unit_tests/plugin_tests/test_github.py::TestGetRepoUrls::test_with_token_and_user tests/unit_tests/plugin_tests/test_github.py::TestGetRepoUrls::test_with_students tests/unit_tests/plugin_tests/test_github.py::TestOpenIssue::test_on_existing_repos tests/unit_tests/plugin_tests/test_github.py::TestOpenIssue::test_on_some_non_existing_repos tests/unit_tests/plugin_tests/test_github.py::TestOpenIssue::test_no_crash_when_no_repos_are_found tests/unit_tests/plugin_tests/test_github.py::TestCloseIssue::test_closes_correct_issues tests/unit_tests/plugin_tests/test_github.py::TestCloseIssue::test_no_crash_if_no_repos_found tests/unit_tests/plugin_tests/test_github.py::TestCloseIssue::test_no_crash_if_no_issues_found tests/unit_tests/plugin_tests/test_github.py::TestGetIssues::test_get_all_open_issues tests/unit_tests/plugin_tests/test_github.py::TestGetIssues::test_get_all_closed_issues tests/unit_tests/plugin_tests/test_github.py::TestGetIssues::test_get_issues_when_one_repo_doesnt_exist tests/unit_tests/plugin_tests/test_github.py::TestGetIssues::test_get_open_issues_by_regex tests/unit_tests/plugin_tests/test_github.py::TestAddReposToReviewTeams::test_with_default_issue tests/unit_tests/plugin_tests/test_github.py::TestAddReposToTeams::test_happy_path tests/unit_tests/plugin_tests/test_github.py::TestDeleteTeams::test_delete_non_existing_teams_does_not_crash tests/unit_tests/plugin_tests/test_github.py::TestDeleteTeams::test_delete_existing_teams tests/unit_tests/plugin_tests/test_github.py::TestVerifySettings::test_happy_path tests/unit_tests/plugin_tests/test_github.py::TestVerifySettings::test_empty_token_raises_bad_credentials tests/unit_tests/plugin_tests/test_github.py::TestVerifySettings::test_incorrect_info_raises_not_found_error[get_user] tests/unit_tests/plugin_tests/test_github.py::TestVerifySettings::test_incorrect_info_raises_not_found_error[get_organization] tests/unit_tests/plugin_tests/test_github.py::TestVerifySettings::test_bad_token_scope_raises tests/unit_tests/plugin_tests/test_github.py::TestVerifySettings::test_not_owner_raises tests/unit_tests/plugin_tests/test_github.py::TestVerifySettings::test_raises_unexpected_exception_on_unexpected_status tests/unit_tests/plugin_tests/test_github.py::TestVerifySettings::test_none_user_raises tests/unit_tests/plugin_tests/test_github.py::TestVerifySettings::test_mismatching_user_login_raises tests/unit_tests/plugin_tests/test_github.py::TestGetPeerReviewProgress::test_nothing_returns tests/unit_tests/plugin_tests/test_github.py::TestGetPeerReviewProgress::test_with_review_teams_but_no_repos
src/_repobee/ext/github.py
src/_repobee/ext/github.py