I quite like the way JupyterHub deals with scopes¹ via roles²:
You create a role called "cleanup user servers", which requires a scope of "read:users" and "admin:server"³ (i.e. a role is bundle of scopes), and then define the services that role has, such as "garbage-collect".
You then generate a new service that names "garbage-collect", and associate an API key with it, and boom, that API key has the permissions to read user metrics, and administer the servers and is tied to a specific service.
Want to add another service to that role (e.g. "berate-users")? Just add it as a service in that role definition, and you can use the exact same API key.
You create a role called "cleanup user servers", which requires a scope of "read:users" and "admin:server"³ (i.e. a role is bundle of scopes), and then define the services that role has, such as "garbage-collect".
You then generate a new service that names "garbage-collect", and associate an API key with it, and boom, that API key has the permissions to read user metrics, and administer the servers and is tied to a specific service.
Want to add another service to that role (e.g. "berate-users")? Just add it as a service in that role definition, and you can use the exact same API key.
1: https://jupyterhub.readthedocs.io/en/stable/rbac/scopes.html
2: https://jupyterhub.readthedocs.io/en/stable/rbac/roles.html
3: or you can just shoot for the moon with the "admin" scope which encompasses everything.