diff --git a/.gitignore b/.gitignore
index 548cfd7b3753f07e48f7004df68f9138fa1e5976..25b088739964de23eb6ab5916132062e65d931fb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -89,3 +89,4 @@ data/
 .env
 
 po/*.po
+docs/swagger
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 6a0f4b9d89ef23ee59872c3f0ea7d19225e16182..5f65e60daa665d061cc7589f91ee3ab2755f056c 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -92,12 +92,14 @@ build_front:
 
 pages:
   stage: test
-  image: python:3.6-alpine
+  image: python:3.6
+  variables:
+    BUILD_PATH: "../public"
   before_script:
     - cd docs
   script:
     - pip install sphinx
-    - python -m sphinx . ../public
+    - ./build_docs.sh
   artifacts:
     paths:
       - public
diff --git a/api/config/settings/common.py b/api/config/settings/common.py
index de1d653cb91fa3518273b8b103ed74fdee4b9259..f1a383c587b123c39a6cbf3dcd5ad0de445a30cf 100644
--- a/api/config/settings/common.py
+++ b/api/config/settings/common.py
@@ -377,6 +377,7 @@ REST_FRAMEWORK = {
     ),
     'DEFAULT_AUTHENTICATION_CLASSES': (
         'funkwhale_api.common.authentication.JSONWebTokenAuthenticationQS',
+        'funkwhale_api.common.authentication.BearerTokenHeaderAuth',
         'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
         'rest_framework.authentication.SessionAuthentication',
         'rest_framework.authentication.BasicAuthentication',
diff --git a/api/funkwhale_api/common/auth.py b/api/funkwhale_api/common/auth.py
index 75839b93662bfb0eb19a78d3b108c5c3690e5e5d..faf13571d6cd73208530a479ed92364989e304ad 100644
--- a/api/funkwhale_api/common/auth.py
+++ b/api/funkwhale_api/common/auth.py
@@ -29,9 +29,6 @@ class TokenHeaderAuth(BaseJSONWebTokenAuthentication):
 
 
 class TokenAuthMiddleware:
-    """
-    Custom middleware (insecure) that takes user IDs from the query string.
-    """
 
     def __init__(self, inner):
         # Store the ASGI application we were passed
diff --git a/api/funkwhale_api/common/authentication.py b/api/funkwhale_api/common/authentication.py
index b75f3b516d5d2505f40900f7314f1f70e557ecb2..c7566eac8bd0f3d5c115a3a5d6b16dd3aec5dd9e 100644
--- a/api/funkwhale_api/common/authentication.py
+++ b/api/funkwhale_api/common/authentication.py
@@ -1,3 +1,6 @@
+from django.utils.encoding import smart_text
+from django.utils.translation import ugettext as _
+
 from rest_framework import exceptions
 from rest_framework_jwt import authentication
 from rest_framework_jwt.settings import api_settings
@@ -18,3 +21,37 @@ class JSONWebTokenAuthenticationQS(
     def authenticate_header(self, request):
         return '{0} realm="{1}"'.format(
             api_settings.JWT_AUTH_HEADER_PREFIX, self.www_authenticate_realm)
+
+
+class BearerTokenHeaderAuth(
+        authentication.BaseJSONWebTokenAuthentication):
+    """
+    For backward compatibility purpose, we used Authorization: JWT <token>
+    but Authorization: Bearer <token> is probably better.
+    """
+    www_authenticate_realm = 'api'
+
+    def get_jwt_value(self, request):
+        auth = authentication.get_authorization_header(request).split()
+        auth_header_prefix = 'bearer'
+
+        if not auth:
+            if api_settings.JWT_AUTH_COOKIE:
+                return request.COOKIES.get(api_settings.JWT_AUTH_COOKIE)
+            return None
+
+        if smart_text(auth[0].lower()) != auth_header_prefix:
+            return None
+
+        if len(auth) == 1:
+            msg = _('Invalid Authorization header. No credentials provided.')
+            raise exceptions.AuthenticationFailed(msg)
+        elif len(auth) > 2:
+            msg = _('Invalid Authorization header. Credentials string '
+                    'should not contain spaces.')
+            raise exceptions.AuthenticationFailed(msg)
+
+        return auth[1]
+
+    def authenticate_header(self, request):
+        return '{0} realm="{1}"'.format('Bearer', self.www_authenticate_realm)
diff --git a/api/funkwhale_api/music/filters.py b/api/funkwhale_api/music/filters.py
index 752422e75e64aae20f19bc46ab00cd4678c220d6..6da9cca63636fd51c562f066c790c473093cffe3 100644
--- a/api/funkwhale_api/music/filters.py
+++ b/api/funkwhale_api/music/filters.py
@@ -20,6 +20,9 @@ class ListenableMixin(filters.FilterSet):
 
 
 class ArtistFilter(ListenableMixin):
+    q = fields.SearchFilter(search_fields=[
+        'name',
+    ])
 
     class Meta:
         model = models.Artist
diff --git a/changes/changelog.d/178.doc b/changes/changelog.d/178.doc
new file mode 100644
index 0000000000000000000000000000000000000000..419e6984b80460904ec4859d8072554c133ca264
--- /dev/null
+++ b/changes/changelog.d/178.doc
@@ -0,0 +1 @@
+Foundations for API documentation with Swagger (#178)
diff --git a/dev.yml b/dev.yml
index 264fc953483d1dec5b19b30d3ade754efb47691d..534d8f5b5d8bbde692acde76ba1a2f1b7cb9dfbc 100644
--- a/dev.yml
+++ b/dev.yml
@@ -123,6 +123,15 @@ services:
       - '35730:35730'
       - '8001:8001'
 
+  api-docs:
+    image: swaggerapi/swagger-ui
+    environment:
+      - "API_URL=/swagger.yml"
+    ports:
+      - '8002:8080'
+    volumes:
+      - "./api/docs/swagger.yml:/usr/share/nginx/html/swagger.yml"
+
 networks:
   internal:
   federation:
diff --git a/docs/api.rst b/docs/api.rst
new file mode 100644
index 0000000000000000000000000000000000000000..650b3885ea73c7192cc17edb5cb9169b5b06ca2f
--- /dev/null
+++ b/docs/api.rst
@@ -0,0 +1,6 @@
+Funkwhale API
+=============
+
+Funkwhale API is still a work in progress and should not be considered as
+stable. We offer an `interactive documentation using swagger </swagger/>`_
+were you can browse available endpoints and try the API.
diff --git a/docs/build_docs.sh b/docs/build_docs.sh
new file mode 100755
index 0000000000000000000000000000000000000000..fbf2036af5599f845fb769c32555eeba9e77d948
--- /dev/null
+++ b/docs/build_docs.sh
@@ -0,0 +1,5 @@
+#!/bin/bash -eux
+# Building sphinx and swagger docs
+
+python -m sphinx . $BUILD_PATH
+TARGET_PATH="$BUILD_PATH/swagger" ./build_swagger.sh
diff --git a/docs/build_swagger.sh b/docs/build_swagger.sh
new file mode 100755
index 0000000000000000000000000000000000000000..13ae21b065856e864f3798f9498afd897e3ef265
--- /dev/null
+++ b/docs/build_swagger.sh
@@ -0,0 +1,9 @@
+#!/bin/bash -eux
+
+SWAGGER_VERSION="3.13.6"
+TARGET_PATH=${TARGET_PATH-"swagger"}
+rm -rf $TARGET_PATH /tmp/swagger-ui
+git clone --branch="v$SWAGGER_VERSION" --depth=1 "https://github.com/swagger-api/swagger-ui.git" /tmp/swagger-ui
+mv /tmp/swagger-ui/dist $TARGET_PATH
+cp swagger.yml $TARGET_PATH
+sed -i "s,http://petstore.swagger.io/v2/swagger.json,swagger.yml,g" $TARGET_PATH/index.html
diff --git a/docs/index.rst b/docs/index.rst
index a48b8353c77d88c414ed87e8a7f4f777cc109c24..e4f0e4422e90ce27adfb13044ec6ba936b413788 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -16,6 +16,7 @@ Funkwhale is a self-hosted, modern free and open-source music server, heavily in
    configuration
    importing-music
    federation
+   api
    upgrading
    third-party
    changelog
diff --git a/docs/swagger.yml b/docs/swagger.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7735a8f20ab18053e771951846e9f16911b2d408
--- /dev/null
+++ b/docs/swagger.yml
@@ -0,0 +1,186 @@
+openapi: "3.0"
+info:
+  description: "Documentation for [Funkwhale](https://funkwhale.audio) API. The API is **not** stable yet."
+  version: "1.0.0"
+  title: "Funkwhale API"
+
+servers:
+  - url: https://demo.funkwhale.audio/api/v1
+    description: Demo server
+  - url: https://node1.funkwhale.test/api/v1
+    description: Node 1 (local)
+
+components:
+  securitySchemes:
+    jwt:
+      type: http
+      scheme: bearer
+      bearerFormat: JWT
+      description: "You can get a token by using the /token endpoint"
+
+security:
+  - jwt: []
+
+paths:
+  /token/:
+    post:
+      tags:
+        - "auth"
+      description:
+        Obtain a JWT token you can use for authenticating your next requests.
+      security: []
+      responses:
+        '200':
+          description: Successfull auth
+        '400':
+          description: Invalid credentials
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              type: "object"
+              properties:
+                username:
+                  type: "string"
+                  example: "demo"
+                password:
+                  type: "string"
+                  example: "demo"
+
+  /artists/:
+    get:
+      tags:
+        - "artists"
+      parameters:
+        - name: "q"
+          in: "query"
+          description: "Search query used to filter artists"
+          schema:
+            required: false
+            type: "string"
+            example: "carpenter"
+        - name: "listenable"
+          in: "query"
+          description: "Filter/exclude artists with listenable tracks"
+          schema:
+            required: false
+            type: "boolean"
+      responses:
+        200:
+          content:
+            application/json:
+              schema:
+                type: "object"
+                properties:
+                  count:
+                    $ref: "#/properties/resultsCount"
+                  results:
+                    type: "array"
+                    items:
+                      $ref: "#/definitions/ArtistNested"
+
+properties:
+  resultsCount:
+    type: "integer"
+    format: "int64"
+    description: "The total number of resources matching the request"
+  mbid:
+    type: "string"
+    formats: "uuid"
+    description: "A musicbrainz ID"
+definitions:
+  Artist:
+    type: "object"
+    properties:
+      mbid:
+        required: false
+        $ref: "#/properties/mbid"
+      id:
+        type: "integer"
+        format: "int64"
+        example: 42
+      name:
+        type: "string"
+        example: "System of a Down"
+      creation_date:
+        type: "string"
+        format: "date-time"
+  ArtistNested:
+    type: "object"
+    allOf:
+      - $ref: "#/definitions/Artist"
+      - type: "object"
+        properties:
+          albums:
+            type: "array"
+            items:
+              $ref: "#/definitions/AlbumNested"
+
+  Album:
+    type: "object"
+    properties:
+      mbid:
+        required: false
+        $ref: "#/properties/mbid"
+      id:
+        type: "integer"
+        format: "int64"
+        example: 16
+      artist:
+        type: "integer"
+        format: "int64"
+        example: 42
+      title:
+        type: "string"
+        example: "Toxicity"
+      creation_date:
+        type: "string"
+        format: "date-time"
+      release_date:
+        type: "string"
+        required: false
+        format: "date"
+        example: "2001-01-01"
+
+  AlbumNested:
+    type: "object"
+    allOf:
+      - $ref: "#/definitions/Album"
+      - type: "object"
+        properties:
+          tracks:
+            type: "array"
+            items:
+              $ref: "#/definitions/Track"
+
+  Track:
+    type: "object"
+    properties:
+      mbid:
+        required: false
+        $ref: "#/properties/mbid"
+      id:
+        type: "integer"
+        format: "int64"
+        example: 66
+      artist:
+        type: "integer"
+        format: "int64"
+        example: 42
+      album:
+        type: "integer"
+        format: "int64"
+        example: 16
+      title:
+        type: "string"
+        example: "Chop Suey!"
+      position:
+        required: false
+        description: "Position of the track in the album"
+        type: "number"
+        minimum: 1
+        example: 1
+      creation_date:
+        type: "string"
+        format: "date-time"