Repository: jupyterhub/jupyterhub

Issue:
New user token returns `200` instead of `201`
### Bug description
The api docs for stable & latest list the following response status:
```
201 Created
The newly created token
```
But currently returns a status of `200`

#### Expected behaviour
Should return a response status of `201`
#### Actual behaviour
Currently returns a response status of `200`

### How to reproduce
Make a request to `POST /users/{name}/tokens`
See (https://jupyterhub.readthedocs.io/en/latest/_static/rest-api/index.html#path--users--name--tokens)



Discussion:
Thank you for opening your first issue in this project! Engagement like this is essential for open source projects! :hugs:
<br>If you haven't done so already, check out [Jupyter's Code of Conduct](https://github.com/jupyter/governance/blob/master/conduct/code_of_conduct.md). Also, please try to follow the issue template as it helps other other community members to contribute more effectively.
![welcome](https://raw.githubusercontent.com/jupyterhub/.github/master/images/welcome.jpg)
You can meet the other [Jovyans](https://jupyter.readthedocs.io/en/latest/community/content-community.html?highlight=jovyan#what-is-a-jovyan) by joining our [Discourse forum](http://discourse.jupyter.org/). There is also an intro thread there where you can stop by and say Hi! :wave:
<br>Welcome to the Jupyter community! :tada:


Resolving patch:
diff --git a/jupyterhub/apihandlers/users.py b/jupyterhub/apihandlers/users.py
--- a/jupyterhub/apihandlers/users.py
+++ b/jupyterhub/apihandlers/users.py
@@ -421,6 +421,7 @@ async def post(self, user_name):
         token_model = self.token_model(orm.APIToken.find(self.db, api_token))
         token_model['token'] = api_token
         self.write(json.dumps(token_model))
+        self.set_status(201)
 
 
 class UserTokenAPIHandler(APIHandler):


Test patch:
diff --git a/jupyterhub/tests/test_api.py b/jupyterhub/tests/test_api.py
--- a/jupyterhub/tests/test_api.py
+++ b/jupyterhub/tests/test_api.py
@@ -1366,8 +1366,8 @@ async def test_get_new_token_deprecated(app, headers, status):
 @mark.parametrize(
     "headers, status, note, expires_in",
     [
-        ({}, 200, 'test note', None),
-        ({}, 200, '', 100),
+        ({}, 201, 'test note', None),
+        ({}, 201, '', 100),
         ({'Authorization': 'token bad'}, 403, '', None),
     ],
 )
@@ -1386,7 +1386,7 @@ async def test_get_new_token(app, headers, status, note, expires_in):
         app, 'users/admin/tokens', method='post', headers=headers, data=body
     )
     assert r.status_code == status
-    if status != 200:
+    if status != 201:
         return
     # check the new-token reply
     reply = r.json()
@@ -1424,10 +1424,10 @@ async def test_get_new_token(app, headers, status, note, expires_in):
 @mark.parametrize(
     "as_user, for_user, status",
     [
-        ('admin', 'other', 200),
+        ('admin', 'other', 201),
         ('admin', 'missing', 403),
         ('user', 'other', 403),
-        ('user', 'user', 200),
+        ('user', 'user', 201),
     ],
 )
 async def test_token_for_user(app, as_user, for_user, status):
@@ -1448,7 +1448,7 @@ async def test_token_for_user(app, as_user, for_user, status):
     )
     assert r.status_code == status
     reply = r.json()
-    if status != 200:
+    if status != 201:
         return
     assert 'token' in reply
 
@@ -1486,7 +1486,7 @@ async def test_token_authenticator_noauth(app):
         data=json.dumps(data) if data else None,
         noauth=True,
     )
-    assert r.status_code == 200
+    assert r.status_code == 201
     reply = r.json()
     assert 'token' in reply
     r = await api_request(app, 'authorizations', 'token', reply['token'])
@@ -1509,7 +1509,7 @@ async def test_token_authenticator_dict_noauth(app):
         data=json.dumps(data) if data else None,
         noauth=True,
     )
-    assert r.status_code == 200
+    assert r.status_code == 201
     reply = r.json()
     assert 'token' in reply
     r = await api_request(app, 'authorizations', 'token', reply['token'])
diff --git a/jupyterhub/tests/test_roles.py b/jupyterhub/tests/test_roles.py
--- a/jupyterhub/tests/test_roles.py
+++ b/jupyterhub/tests/test_roles.py
@@ -661,11 +661,11 @@ async def test_load_roles_user_tokens(tmpdir, request):
     "headers, rolename, scopes, status",
     [
         # no role requested - gets default 'token' role
-        ({}, None, None, 200),
+        ({}, None, None, 201),
         # role scopes within the user's default 'user' role
-        ({}, 'self-reader', ['read:users'], 200),
+        ({}, 'self-reader', ['read:users'], 201),
         # role scopes outside of the user's role but within the group's role scopes of which the user is a member
-        ({}, 'groups-reader', ['read:groups'], 200),
+        ({}, 'groups-reader', ['read:groups'], 201),
         # non-existing role request
         ({}, 'non-existing', [], 404),
         # role scopes outside of both user's role and group's role scopes