diff --git a/repobee/apimeta.py b/repobee/apimeta.py
index e88584a..274f896 100644
--- a/repobee/apimeta.py
+++ b/repobee/apimeta.py
@@ -21,6 +21,7 @@ NotImplementedError) for any unimplemented API methods.
 import inspect
 import enum
 import collections
+import itertools
 from typing import List, Iterable, Optional, Generator, Tuple, Mapping
 
 import daiquiri
@@ -406,7 +407,7 @@ def methods(attrdict):
         name: method
         for name, method in attrdict.items()
         if callable(method)
-        and not (name.startswith("_") or name == "__init__")
+        and (not name.startswith("_") or name == "__init__")
     }
 
 
@@ -418,13 +419,31 @@ def parameters(function):
     ]
 
 
+def check_init_params(reference_params, compare_params):
+    """Check that the compare __init__'s parameters are a subset of the
+    reference class's version.
+    """
+    extra = set(compare_params) - set(reference_params)
+    if extra:
+        raise exception.APIImplementationError(
+            "unexpected arguments to __init__: {}".format(extra)
+        )
+
+
 def check_parameters(reference, compare):
     """Check if the parameters match, one by one. Stop at the first diff and
     raise an exception for that parameter.
+
+    An exception is made for __init__, for which the compare may be a subset of
+    the reference in no particular order.
     """
     reference_params = parameters(reference)
     compare_params = parameters(compare)
-    for ref, cmp in zip(reference_params, compare_params):
+    if reference.__name__ == "__init__":
+        check_init_params(reference_params, compare_params)
+        return
+
+    for ref, cmp in itertools.zip_longest(reference_params, compare_params):
         if ref != cmp:
             raise exception.APIImplementationError(
                 "{}: expected parameter '{}', found '{}'".format(

API meta class should only check that __init__ args are a subset
As GitLab does not need the `user` argument, the API meta class should only check that the arguments to `__init__` in an implementing class are a subset of the ones in the APISpec. This makes the whole thing more flexible for plugins.
diff --git a/tests/unit_tests/test_apimeta.py b/tests/unit_tests/test_apimeta.py
index 097d811..62b55c2 100644
--- a/tests/unit_tests/test_apimeta.py
+++ b/tests/unit_tests/test_apimeta.py
@@ -45,6 +45,31 @@ class TestAPI:
                 def get_teams(a):
                     pass
 
+    def test_accepts_init_with_strict_subset_of_args(self):
+        """Test that ``__init__`` can be defined with a strict subset of the
+        args in APISpec.__init__.
+        """
+
+        class API(apimeta.API):
+            def __init__(self, base_url):
+                pass
+
+        api = API("some-url")
+        assert isinstance(api, apimeta.API)
+
+    def test_raises_when_init_has_superset_of_args(self):
+        """Test that ``__init__`` cannot be defined with a superset of the args
+        in APISpec.__init__.
+        """
+
+        with pytest.raises(exception.APIImplementationError) as exc_info:
+
+            class API(apimeta.API):
+                def __init__(self, base_url, token, org_name, user, other):
+                    pass
+
+        assert "other" in str(exc_info.value)
+
     def test_accepts_correctly_defined_method(self):
         """API should accept a correctly defined method, and not alter it in any
         way.

pip install -e .
parse_log_pytest
requirements.txt
pytest>=4.0.0 pytest-cov>=2.6.1 pytest-mock codecov pytest
3.7
requirements.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.8.0
python-json-logger==3.0.1
-e git+https://github.com/repobee/repobee.git@fd8101a176a3d8e12ff1885ee8e1a51cb2911edd#egg=repobee
repobee-plug==0.5.1
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.8.0
      - python-json-logger==3.0.1
      - repobee==1.6.0
      - repobee-plug==0.5.1
      - 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/test_apimeta.py::TestAPI::test_raises_when_unimplemented_method_called[__init__] tests/unit_tests/test_apimeta.py::TestAPI::test_raises_when_init_has_superset_of_args
tests/unit_tests/test_apimeta.py::TestAPI::test_raises_when_unimplemented_method_called[ensure_teams_and_members] tests/unit_tests/test_apimeta.py::TestAPI::test_raises_when_unimplemented_method_called[get_teams] tests/unit_tests/test_apimeta.py::TestAPI::test_raises_when_unimplemented_method_called[create_repos] tests/unit_tests/test_apimeta.py::TestAPI::test_raises_when_unimplemented_method_called[get_repo_urls] tests/unit_tests/test_apimeta.py::TestAPI::test_raises_when_unimplemented_method_called[get_issues] tests/unit_tests/test_apimeta.py::TestAPI::test_raises_when_unimplemented_method_called[open_issue] tests/unit_tests/test_apimeta.py::TestAPI::test_raises_when_unimplemented_method_called[close_issue] tests/unit_tests/test_apimeta.py::TestAPI::test_raises_when_unimplemented_method_called[add_repos_to_review_teams] tests/unit_tests/test_apimeta.py::TestAPI::test_raises_when_unimplemented_method_called[get_review_progress] tests/unit_tests/test_apimeta.py::TestAPI::test_raises_when_unimplemented_method_called[delete_teams] tests/unit_tests/test_apimeta.py::TestAPI::test_raises_when_method_incorrectly_declared tests/unit_tests/test_apimeta.py::TestAPI::test_accepts_init_with_strict_subset_of_args tests/unit_tests/test_apimeta.py::TestAPI::test_accepts_correctly_defined_method tests/unit_tests/test_apimeta.py::TestAPI::test_raises_when_method_has_incorrect_default_arg tests/unit_tests/test_apimeta.py::TestAPI::test_accepts_correct_default_arg tests/unit_tests/test_apimeta.py::TestAPIObject::test_raises_when_accessing_none_implementation tests/unit_tests/test_apimeta.py::TestAPIObject::test_does_not_raise_when_accessing_initialized_implementation
repobee/apimeta.py
repobee/apimeta.py