How can I allow users to restart their own server?

We’re migrating from version 1.3.0 to 4.0.2, and it seems some permissions have changed. We have a call to the hub api for DELETE /hub/api/users/<user>/server, and on the hub it shows that the delete:servers doesn’t seem to be derived from any existing permissions

Action is not authorized with current scopes; requires any of [delete:servers]

I’ve tried modifying the user role as such -

c.JupyterHub.load_roles = [{
    "name": "user",
    "scopes": ["self", "delete:servers"]

And other variations (inherit, delete:servers!user, etc), but either my changes have no effect, or they remove all the user’s permissions. Maybe I’m bad at reading the hub documentation for this but the syntax is very unclear to me. Is there something obvious I’m missing?

Are you making ther API call using the token from the singleuser environment? That’s been tightened up significantly to only contain the mininmal permissions needed for web access to the singleuser server.

If you create a new API token it should have all the expected permissions. Alternatively you can modify the server (not the user) role:

@manics, we were looking at this portion of the docs where it specifies overriding the default roles:
Roles — JupyterHub documentation,
So is it only possible to override the ‘server’ default role permissions?

I gave this a shot:

  "name": "server",
  "scopes": "inherit"

But it doesn’t seem to take, or have any effect. If this inherits from user though, wouldn’t that mean that delete:server has to exist somewhere in user?

We’re trying
c.JupyterHub.load_roles = [{
“name”: “tester”,
“scopes”: [“delete:servers!user”],
“users”: [“”]
and can see on the admin page that the user has the tester role, however when calling DELETE hub/api/users//server we get a 403 error.

Wondering if the one_or_none() query code in validate_scopes could be problematic

@manics question of where is the API call is originating is key.

The important piece of information that appears to be missing is that requests are made with tokens, and tokens have a subset of a user’s permissions. The token is missing the required scopes, but we still don’t know what token you are using, which will tell us what token needs expanded permissions.

The user definitely does already have permission to start and stop their own servers (this is included in self). However, the token likely does not.

In a typical single-user session, there are always two tokens:

  1. $JUPYTERHUB_API_TOKEN, which is the “server token”. This is governed by the server role (or more simply c.Spawner.server_token_scopes in 4.0), and is owned by the server’s owner
  2. the browser token, which is used to authenticate all requests from the browser. This is governed by c.Spawner.oauth_client_allowed_scopes, and owned by the logged-in user, which may or may not be the server’s owner.

Assuming you are using the server token, you can override the server role, e.g.:

c.JupyterHub.load_roles = [
   'name': 'server',
   'scopes': [ 'users:activity!user',

(or use ["inherit"] to get the pre-2.0 behavior.) Note: it must be ["inherit"], not "inherit". Passing a string should result in:

    KeyError: "Scope 'i' for role server does not exist"

since it will result in interpreting the string as a collection of single-character scopes.


Thank you this is working for us now