diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 7a7e106e82ae453268abc914ec5f209e69042a3a..e10b8b981c0d1fc4354c19bfc97ad31af9ae67b9 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -136,6 +136,7 @@ test_api:
     - branches
   before_script:
     - apk add make git gcc python3-dev musl-dev
+    - apk add postgresql-dev py3-psycopg2 libldap libffi-dev make zlib-dev jpeg-dev openldap-dev
     - cd api
     - pip3 install -r requirements/base.txt
     - pip3 install -r requirements/local.txt
diff --git a/api/funkwhale_api/audio/filters.py b/api/funkwhale_api/audio/filters.py
index 9b7088c1dbad89a035b0fb48f16d648ce97cb7b0..76f712a7cb214d257970a3d28a362b996c532a1a 100644
--- a/api/funkwhale_api/audio/filters.py
+++ b/api/funkwhale_api/audio/filters.py
@@ -41,7 +41,7 @@ class ChannelFilter(moderation_filters.HiddenContentFilterSet):
 
     class Meta:
         model = models.Channel
-        fields = ["q", "scope", "tag", "subscribed", "ordering", "external"]
+        fields = []
         hidden_content_fields_mapping = moderation_filters.USER_FILTER_CONFIG["CHANNEL"]
 
     def filter_subscribed(self, queryset, name, value):
diff --git a/api/funkwhale_api/common/filters.py b/api/funkwhale_api/common/filters.py
index dec4a89ab22b8ba7bcd8336f2a1406f1e08e0a83..6b199d531b5985a2b2bd1172b0ef2c87d5caa968 100644
--- a/api/funkwhale_api/common/filters.py
+++ b/api/funkwhale_api/common/filters.py
@@ -119,7 +119,6 @@ class MultipleQueryFilter(filters.TypedMultipleChoiceFilter):
     def __init__(self, *args, **kwargs):
         kwargs["widget"] = QueryArrayWidget()
         super().__init__(*args, **kwargs)
-        self.lookup_expr = "in"
 
 
 def filter_target(value):
diff --git a/api/funkwhale_api/favorites/filters.py b/api/funkwhale_api/favorites/filters.py
index b4dad93ac1392243385808cbbb1978c0e98eee07..0b76a4dd04340da3ad62b2391a82dd9e9534dc9e 100644
--- a/api/funkwhale_api/favorites/filters.py
+++ b/api/funkwhale_api/favorites/filters.py
@@ -14,7 +14,7 @@ class TrackFavoriteFilter(moderation_filters.HiddenContentFilterSet):
     class Meta:
         model = models.TrackFavorite
         # XXX: 1.0 remove the user filter, we have scope=me now
-        fields = ["user", "q", "scope"]
+        fields = ["user"]
         hidden_content_fields_mapping = moderation_filters.USER_FILTER_CONFIG[
             "TRACK_FAVORITE"
         ]
diff --git a/api/funkwhale_api/federation/filters.py b/api/funkwhale_api/federation/filters.py
index bfc48bcfbfcc6f9c4d3b33794523a5e4b833abc4..a57bc40713a03c2c28274c8eaa225cd6a7414b3d 100644
--- a/api/funkwhale_api/federation/filters.py
+++ b/api/funkwhale_api/federation/filters.py
@@ -20,7 +20,7 @@ class FollowFilter(django_filters.FilterSet):
 
     class Meta:
         model = models.Follow
-        fields = ["approved", "pending", "q"]
+        fields = ["approved"]
 
     def filter_pending(self, queryset, field_name, value):
         if value.lower() in ["true", "1", "yes"]:
diff --git a/api/funkwhale_api/history/filters.py b/api/funkwhale_api/history/filters.py
index 16a03204fbad744ca1c50cea55d2aed92e427fb7..1e88f1b224d3201f65d5112f2a6e65fb05c38ed1 100644
--- a/api/funkwhale_api/history/filters.py
+++ b/api/funkwhale_api/history/filters.py
@@ -16,4 +16,4 @@ class ListeningFilter(moderation_filters.HiddenContentFilterSet):
         hidden_content_fields_mapping = moderation_filters.USER_FILTER_CONFIG[
             "LISTENING"
         ]
-        fields = ["hidden", "scope"]
+        fields = []
diff --git a/api/funkwhale_api/manage/filters.py b/api/funkwhale_api/manage/filters.py
index 7279119956b0e10cf235b30ad9a76298dd9cf162..39ba56d9d5f663d9b91e71e1f18ed84fb6d3f9d5 100644
--- a/api/funkwhale_api/manage/filters.py
+++ b/api/funkwhale_api/manage/filters.py
@@ -60,7 +60,7 @@ class ManageChannelFilterSet(filters.FilterSet):
 
     class Meta:
         model = audio_models.Channel
-        fields = ["q"]
+        fields = []
 
 
 class ManageArtistFilterSet(filters.FilterSet):
@@ -89,7 +89,7 @@ class ManageArtistFilterSet(filters.FilterSet):
 
     class Meta:
         model = music_models.Artist
-        fields = ["q", "name", "mbid", "fid", "content_category"]
+        fields = ["name", "mbid", "fid", "content_category"]
 
 
 class ManageAlbumFilterSet(filters.FilterSet):
@@ -119,7 +119,7 @@ class ManageAlbumFilterSet(filters.FilterSet):
 
     class Meta:
         model = music_models.Album
-        fields = ["q", "title", "mbid", "fid", "artist"]
+        fields = ["title", "mbid", "fid", "artist"]
 
 
 class ManageTrackFilterSet(filters.FilterSet):
@@ -158,7 +158,7 @@ class ManageTrackFilterSet(filters.FilterSet):
 
     class Meta:
         model = music_models.Track
-        fields = ["q", "title", "mbid", "fid", "artist", "album", "license"]
+        fields = ["title", "mbid", "fid", "artist", "album", "license"]
 
 
 class ManageLibraryFilterSet(filters.FilterSet):
@@ -204,7 +204,7 @@ class ManageLibraryFilterSet(filters.FilterSet):
 
     class Meta:
         model = music_models.Library
-        fields = ["q", "name", "fid", "privacy_level", "domain"]
+        fields = ["name", "fid", "privacy_level"]
 
 
 class ManageUploadFilterSet(filters.FilterSet):
@@ -249,10 +249,7 @@ class ManageUploadFilterSet(filters.FilterSet):
     class Meta:
         model = music_models.Upload
         fields = [
-            "q",
             "fid",
-            "privacy_level",
-            "domain",
             "mimetype",
             "import_reference",
             "import_status",
@@ -275,7 +272,7 @@ class ManageDomainFilterSet(filters.FilterSet):
 
     class Meta:
         model = federation_models.Domain
-        fields = ["name", "allowed"]
+        fields = ["name"]
 
 
 class ManageActorFilterSet(filters.FilterSet):
@@ -300,7 +297,7 @@ class ManageActorFilterSet(filters.FilterSet):
 
     class Meta:
         model = federation_models.Actor
-        fields = ["q", "domain", "type", "manually_approves_followers", "local"]
+        fields = ["domain", "type", "manually_approves_followers"]
 
     def filter_local(self, queryset, name, value):
         return queryset.local(value)
@@ -320,7 +317,6 @@ class ManageUserFilterSet(filters.FilterSet):
     class Meta:
         model = users_models.User
         fields = [
-            "q",
             "is_active",
             "privacy_level",
             "is_staff",
@@ -337,7 +333,7 @@ class ManageInvitationFilterSet(filters.FilterSet):
 
     class Meta:
         model = users_models.Invitation
-        fields = ["q", "is_open"]
+        fields = []
 
     def filter_is_open(self, queryset, field_name, value):
         if value is None:
@@ -362,14 +358,10 @@ class ManageInstancePolicyFilterSet(filters.FilterSet):
     class Meta:
         model = moderation_models.InstancePolicy
         fields = [
-            "q",
             "block_all",
             "silence_activity",
             "silence_notifications",
             "reject_media",
-            "target_domain",
-            "target_account_domain",
-            "target_account_username",
         ]
 
 
@@ -378,7 +370,7 @@ class ManageTagFilterSet(filters.FilterSet):
 
     class Meta:
         model = tags_models.Tag
-        fields = ["q"]
+        fields = []
 
 
 class ManageReportFilterSet(filters.FilterSet):
@@ -404,7 +396,7 @@ class ManageReportFilterSet(filters.FilterSet):
 
     class Meta:
         model = moderation_models.Report
-        fields = ["q", "is_handled", "type", "submitter_email"]
+        fields = ["is_handled", "type", "submitter_email"]
 
 
 class ManageNoteFilterSet(filters.FilterSet):
@@ -423,7 +415,7 @@ class ManageNoteFilterSet(filters.FilterSet):
 
     class Meta:
         model = moderation_models.Note
-        fields = ["q"]
+        fields = []
 
 
 class ManageUserRequestFilterSet(filters.FilterSet):
@@ -446,4 +438,4 @@ class ManageUserRequestFilterSet(filters.FilterSet):
 
     class Meta:
         model = moderation_models.UserRequest
-        fields = ["q", "status", "type"]
+        fields = ["status", "type"]
diff --git a/api/funkwhale_api/music/filters.py b/api/funkwhale_api/music/filters.py
index feebaa5421e98e60a8c95e6f451002dde197c1d9..64c0cd5a6a65c402efa2764c9c2d43e55e0102c9 100644
--- a/api/funkwhale_api/music/filters.py
+++ b/api/funkwhale_api/music/filters.py
@@ -120,8 +120,6 @@ class ArtistFilter(
         model = models.Artist
         fields = {
             "name": ["exact", "iexact", "startswith", "icontains"],
-            "playable": ["exact"],
-            "scope": ["exact"],
             "mbid": ["exact"],
         }
         hidden_content_fields_mapping = moderation_filters.USER_FILTER_CONFIG["ARTIST"]
@@ -174,11 +172,9 @@ class TrackFilter(
         model = models.Track
         fields = {
             "title": ["exact", "iexact", "startswith", "icontains"],
-            "playable": ["exact"],
             "id": ["exact"],
             "album": ["exact"],
             "license": ["exact"],
-            "scope": ["exact"],
             "mbid": ["exact"],
         }
         hidden_content_fields_mapping = moderation_filters.USER_FILTER_CONFIG["TRACK"]
@@ -225,16 +221,9 @@ class UploadFilter(audio_filters.IncludeChannelsFilterSet):
     class Meta:
         model = models.Upload
         fields = [
-            "playable",
             "import_status",
             "mimetype",
-            "track",
-            "track_artist",
-            "album_artist",
-            "library",
             "import_reference",
-            "scope",
-            "channel",
         ]
         include_channels_field = "track__artist__channel"
 
@@ -273,7 +262,7 @@ class AlbumFilter(
 
     class Meta:
         model = models.Album
-        fields = ["playable", "q", "artist", "scope", "mbid"]
+        fields = ["artist", "mbid"]
         hidden_content_fields_mapping = moderation_filters.USER_FILTER_CONFIG["ALBUM"]
         include_channels_field = "artist__channel"
         channel_filter_field = "track__album"
@@ -290,4 +279,4 @@ class LibraryFilter(filters.FilterSet):
 
     class Meta:
         model = models.Library
-        fields = ["privacy_level", "q", "scope"]
+        fields = ["privacy_level"]
diff --git a/api/funkwhale_api/playlists/filters.py b/api/funkwhale_api/playlists/filters.py
index f49e9bd1a149d925715cfff61624726f57cf26b6..e12813b38d17cbf4c46080ae111f619149f68916 100644
--- a/api/funkwhale_api/playlists/filters.py
+++ b/api/funkwhale_api/playlists/filters.py
@@ -33,9 +33,6 @@ class PlaylistFilter(filters.FilterSet):
         fields = {
             "user": ["exact"],
             "name": ["exact", "icontains"],
-            "q": "exact",
-            "playable": "exact",
-            "scope": "exact",
         }
 
     def filter_playable(self, queryset, name, value):
diff --git a/api/funkwhale_api/radios/filtersets.py b/api/funkwhale_api/radios/filtersets.py
index 6f548dbeaff579f6080ed2953788218fab705889..f570b2d547758c7e606d90774120aba4a3dfbc6f 100644
--- a/api/funkwhale_api/radios/filtersets.py
+++ b/api/funkwhale_api/radios/filtersets.py
@@ -11,5 +11,4 @@ class RadioFilter(django_filters.FilterSet):
         model = models.Radio
         fields = {
             "name": ["exact", "iexact", "startswith", "icontains"],
-            "scope": "exact",
         }
diff --git a/api/funkwhale_api/subsonic/filters.py b/api/funkwhale_api/subsonic/filters.py
index a3c251e6684b9df0dd20943c081474d115dc67ca..f3fc81111fd26b98acd7328e5f256ab285452be7 100644
--- a/api/funkwhale_api/subsonic/filters.py
+++ b/api/funkwhale_api/subsonic/filters.py
@@ -8,7 +8,7 @@ class AlbumList2FilterSet(filters.FilterSet):
 
     class Meta:
         model = music_models.Album
-        fields = ["type"]
+        fields = []
 
     def filter_type(self, queryset, name, value):
         ORDERING = {
diff --git a/api/funkwhale_api/tags/filters.py b/api/funkwhale_api/tags/filters.py
index c41ace91b385cab033a31a09269a649b93d3984c..7922e3e126648e85f3b632cecf59ce105a7418bb 100644
--- a/api/funkwhale_api/tags/filters.py
+++ b/api/funkwhale_api/tags/filters.py
@@ -20,7 +20,7 @@ class TagFilter(filters.FilterSet):
 
     class Meta:
         model = models.Tag
-        fields = {"q": ["exact"], "name": ["exact", "startswith"]}
+        fields = {"name": ["exact", "startswith"]}
 
 
 def get_by_similar_tags(qs, tags):
diff --git a/api/funkwhale_api/users/migrations/0019_auto_20200718_0741.py b/api/funkwhale_api/users/migrations/0019_auto_20200718_0741.py
new file mode 100644
index 0000000000000000000000000000000000000000..11c0e655e2a0db42fd3e163ae2eed369d9a39afb
--- /dev/null
+++ b/api/funkwhale_api/users/migrations/0019_auto_20200718_0741.py
@@ -0,0 +1,23 @@
+# Generated by Django 3.0.8 on 2020-07-18 07:41
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('users', '0018_auto_20200705_0829'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='grant',
+            name='code_challenge',
+            field=models.CharField(blank=True, default='', max_length=128),
+        ),
+        migrations.AddField(
+            model_name='grant',
+            name='code_challenge_method',
+            field=models.CharField(blank=True, choices=[('plain', 'plain'), ('S256', 'S256')], default='', max_length=10),
+        ),
+    ]
diff --git a/api/funkwhale_api/users/oauth/views.py b/api/funkwhale_api/users/oauth/views.py
index 90706e70899e7a8a6bbebbea065db1d6113900a4..8fd88d908224df2e8686176bdde9529c814f9153 100644
--- a/api/funkwhale_api/users/oauth/views.py
+++ b/api/funkwhale_api/users/oauth/views.py
@@ -155,20 +155,21 @@ class AuthorizeView(views.APIView, oauth_views.AuthorizationView):
 
     def form_valid(self, form):
         try:
-            response = super().form_valid(form)
+            return super().form_valid(form)
 
         except models.Application.DoesNotExist:
             return self.json_payload({"non_field_errors": ["Invalid application"]}, 400)
 
-        if self.request.is_ajax() and response.status_code == 302:
+    def redirect(self, redirect_to, application, token=None):
+        if self.request.is_ajax():
             # Web client need this to be able to redirect the user
-            query = urllib.parse.urlparse(response["Location"]).query
+            query = urllib.parse.urlparse(redirect_to).query
             code = urllib.parse.parse_qs(query)["code"][0]
             return self.json_payload(
-                {"redirect_uri": response["Location"], "code": code}, status_code=200
+                {"redirect_uri": redirect_to, "code": code}, status_code=200
             )
 
-        return response
+        return super().redirect(redirect_to, application, token)
 
     def error_response(self, error, application):
         if isinstance(error, oauth2_exceptions.FatalClientError):
diff --git a/api/requirements/base.txt b/api/requirements/base.txt
index 6e31f857e6059f5f61139fcfa80f652f8f602eac..74f33bde13b211b93950e423b28d578be830d508 100644
--- a/api/requirements/base.txt
+++ b/api/requirements/base.txt
@@ -1,82 +1,82 @@
 # Bleeding edge Django
-django>=3.0.5,<3.1; python_version > '3.5'
+django>=3.0.8,<3.1; python_version > '3.5'
 django>=2.2.12,<3; python_version < '3.6'
 setuptools>=36
 # Configuration
 django-environ>=0.4,<0.5
 
 # Images
-Pillow>=6.2,<7
+Pillow>=7,<8
 
 # For user registration, either via email or social
 # Well-built with regular release cycles!
-django-allauth>=0.41,<0.42
+django-allauth>=0.42,<0.43
 
 
 # Python-PostgreSQL Database Adapter
 psycopg2-binary>=2.8,<=2.9
 
 # Time zones support
-pytz==2019.3
+pytz==2020.1
 
 # Redis support
-django-redis>=4.11,<4.12
-redis>=3.4,<3.5
-kombu>=4.5,<4.6
+django-redis>=4.12.1,<4.13
+redis>=3.5.3,<3.6
+kombu>=4.6.11,<4.7
 
-celery>=4.3,<4.4
+celery>=4.4.6,<4.5
 
 
 # Your custom requirements go here
-django-cors-headers>=3.2,<3.3
-musicbrainzngs==0.6
+django-cors-headers>=3.4,<3.5
+musicbrainzngs>=0.7.1,<0.8
 djangorestframework>=3.11,<3.12
 djangorestframework-jwt>=1.11,<1.12
 arrow>=0.15.5,<0.16
 persisting-theory>=0.2,<0.3
 django-versatileimagefield>=2.0,<2.1
-django-filter>=2.1,<2.2
+django-filter>=2.3,<2.4
 django-rest-auth>=0.9,<0.10
 # XXX: remove when we drop support for python 3.5
 ipython>=7.10,<8; python_version > '3.5'
 ipython>=7,<7.10; python_version < '3.6'
-mutagen>=1.44,<1.45
+mutagen>=1.45,<1.46
 
 pymemoize==1.0.3
 
-django-dynamic-preferences>=1.8.1,<1.9
+django-dynamic-preferences>=1.10,<1.11
 raven>=6.10,<7
-python-magic==0.4.15
+python-magic==0.4.18
 channels>=2.4,<2.5
 # XXX: remove when we drop support for python 3.5
 channels_redis==2.2.1; python_version < '3.6'
-channels_redis>=2.3.2,<2.4; python_version > '3.5'
+channels_redis>=3,<3.1; python_version > '3.5'
 uvicorn==0.8.6; python_version < '3.6'
-uvicorn>=0.11.3,<0.12; python_version > '3.5'
+uvicorn>=0.11.5,<0.12; python_version > '3.5'
 gunicorn>=20.0.4,<20.1
 
 cryptography>=2.8,<3
 # requests-http-signature==0.0.3
 # clone until the branch is merged and released upstream
 git+https://github.com/EliotBerriot/requests-http-signature.git@signature-header-support
-django-cleanup>=4,<4.1
+django-cleanup>=5,<5.1
 requests>=2.22<2.23
 pyOpenSSL>=19<20
 
 # for LDAP authentication
-python-ldap>=3.2.0,<3.3
-django-auth-ldap>=2.1.0,<2.2
+python-ldap>=3.3.1,<3.4
+django-auth-ldap>=2.2.0,<2.3
 
-pydub>=0.23.1,<0.24
-pyld==1.0.4
+pydub>=0.24.1,<0.25
+pyld>=1,<2
 aiohttp>=3.6,<3.7
 autobahn>=19.3.3
 
-django-oauth-toolkit==1.2
+django-oauth-toolkit>=1.3.2,<1.4
 django-storages>=1.9.1,<1.10
 boto3<3
 unicode-slugify==0.1.3
-django-cacheops==4.2
+django-cacheops>=5,<5.1
 
 click>=7,<8
 service_identity==18.1.0