diff --git a/.env.dev b/.env.dev
index 75954b6bc21ed4d113b28ac7782229f8ed5e89ae..de58e27583f7d520e29d997ccb0bb02f55823133 100644
--- a/.env.dev
+++ b/.env.dev
@@ -1,3 +1,3 @@
-BACKEND_URL=http://localhost:6001
+BACKEND_URL=http://localhost:12081
 YOUTUBE_API_KEY=
 API_AUTHENTICATION_REQUIRED=False
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 4a10a92e0d091afc9038707ae248c6a134344767..fd73e4bc053ac7834042a924d85654d7185b0f3f 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,18 +1,26 @@
+variables:
+  IMAGE_NAME: funkwhale/funkwhale
+  IMAGE: $IMAGE_NAME:$CI_BUILD_REF
+  IMAGE_LATEST: $IMAGE_NAME:latest
+
+
+
 stages:
   - test
   - build
+  - deploy
 
 test_api:
   stage: test
+  image: funkwhale/funkwhale:base
   before_script:
-    - docker-compose -f api/test.yml build
+    - cd api
+    - pip install -r requirements/test.txt
   script:
-    - docker-compose -f api/test.yml run test
-  after_script:
-    - docker-compose -f api/test.yml run test rm -rf funkwhale_api/media/
+    - pytest
 
   tags:
-    - dind
+    - docker
 
 build_front:
   stage: build
@@ -53,6 +61,33 @@ pages:
     paths:
       - public
   only:
-    - master
+    - develop
   tags:
     - docker
+
+docker_develop:
+  stage: deploy
+  before_script:
+    - docker login -u $DOCKER_LOGIN -p $DOCKER_PASSWORD
+    - cd api
+  script:
+    - docker build -t $IMAGE .
+    - docker push $IMAGE
+  only:
+    - develop
+  tags:
+    - dind
+
+docker_release:
+  stage: deploy
+  before_script:
+    - docker login -u $DOCKER_LOGIN -p $DOCKER_PASSWORD
+    - cd api
+  script:
+    - docker build -t $IMAGE -t $IMAGE_LATEST .
+    - docker push $IMAGE
+    - docker push $IMAGE_LATEST
+  only:
+    - master
+  tags:
+    - dind
diff --git a/api/config/api_urls.py b/api/config/api_urls.py
index 4ed81d284f10c34426f50e778683c6fc0cdcddf5..b56944d4eed8b75faa0c51202c286ef4662d16ee 100644
--- a/api/config/api_urls.py
+++ b/api/config/api_urls.py
@@ -2,6 +2,8 @@ from rest_framework import routers
 from django.conf.urls import include, url
 from funkwhale_api.music import views
 from funkwhale_api.playlists import views as playlists_views
+from rest_framework_jwt import views as jwt_views
+
 
 router = routers.SimpleRouter()
 router.register(r'tags', views.TagViewSet, 'tags')
@@ -12,15 +14,19 @@ router.register(r'import-batches', views.ImportBatchViewSet, 'import-batches')
 router.register(r'submit', views.SubmitViewSet, 'submit')
 router.register(r'playlists', playlists_views.PlaylistViewSet, 'playlists')
 router.register(r'playlist-tracks', playlists_views.PlaylistTrackViewSet, 'playlist-tracks')
-urlpatterns = router.urls
+v1_patterns = router.urls
 
-urlpatterns += [
+v1_patterns += [
     url(r'^providers/', include('funkwhale_api.providers.urls', namespace='providers')),
     url(r'^favorites/', include('funkwhale_api.favorites.urls', namespace='favorites')),
     url(r'^search$', views.Search.as_view(), name='search'),
     url(r'^radios/', include('funkwhale_api.radios.urls', namespace='radios')),
     url(r'^history/', include('funkwhale_api.history.urls', namespace='history')),
     url(r'^users/', include('funkwhale_api.users.api_urls', namespace='users')),
-    url(r'^token/', 'rest_framework_jwt.views.obtain_jwt_token'),
-    url(r'^token/refresh/', 'rest_framework_jwt.views.refresh_jwt_token'),
+    url(r'^token/', jwt_views.obtain_jwt_token),
+    url(r'^token/refresh/', jwt_views.refresh_jwt_token),
+]
+
+urlpatterns = [
+    url(r'^v1/', include(v1_patterns, namespace='v1'))
 ]
diff --git a/api/config/settings/common.py b/api/config/settings/common.py
index 5c55dfdc164fee32b469a1b3ce2c2ccb514979a0..93381c4f5399425e340961bbde35500884df88bc 100644
--- a/api/config/settings/common.py
+++ b/api/config/settings/common.py
@@ -199,7 +199,7 @@ CRISPY_TEMPLATE_PACK = 'bootstrap3'
 STATIC_ROOT = str(ROOT_DIR('staticfiles'))
 
 # See: https://docs.djangoproject.com/en/dev/ref/settings/#static-url
-STATIC_URL = env("STATIC_URL", default='/static/')
+STATIC_URL = env("STATIC_URL", default='/staticfiles/')
 
 # See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS
 STATICFILES_DIRS = (
diff --git a/api/funkwhale_api/favorites/tests/test_favorites.py b/api/funkwhale_api/favorites/tests/test_favorites.py
index a68659ba41ba28a1f4160b9cb36dbc0483ead8d6..230f030fe6b84f7f5dc3fd298e48cc8b0cd6ffb2 100644
--- a/api/funkwhale_api/favorites/tests/test_favorites.py
+++ b/api/funkwhale_api/favorites/tests/test_favorites.py
@@ -24,7 +24,7 @@ class TestFavorites(TestCase):
     def test_user_can_get_his_favorites(self):
         favorite = TrackFavorite.add(self.track, self.user)
 
-        url = reverse('api:favorites:tracks-list')
+        url = reverse('api:v1:favorites:tracks-list')
         self.client.login(username=self.user.username, password='test')
 
         response = self.client.get(url)
@@ -41,7 +41,7 @@ class TestFavorites(TestCase):
         self.assertEqual(expected, parsed_json['results'])
 
     def test_user_can_add_favorite_via_api(self):
-        url = reverse('api:favorites:tracks-list')
+        url = reverse('api:v1:favorites:tracks-list')
         self.client.login(username=self.user.username, password='test')
         response = self.client.post(url, {'track': self.track.pk})
 
@@ -60,7 +60,7 @@ class TestFavorites(TestCase):
     def test_user_can_remove_favorite_via_api(self):
         favorite = TrackFavorite.add(self.track, self.user)
 
-        url = reverse('api:favorites:tracks-detail', kwargs={'pk': favorite.pk})
+        url = reverse('api:v1:favorites:tracks-detail', kwargs={'pk': favorite.pk})
         self.client.login(username=self.user.username, password='test')
         response = self.client.delete(url, {'track': self.track.pk})
         self.assertEqual(response.status_code, 204)
@@ -69,7 +69,7 @@ class TestFavorites(TestCase):
     def test_user_can_remove_favorite_via_api_using_track_id(self):
         favorite = TrackFavorite.add(self.track, self.user)
 
-        url = reverse('api:favorites:tracks-remove')
+        url = reverse('api:v1:favorites:tracks-remove')
         self.client.login(username=self.user.username, password='test')
         response = self.client.delete(
             url, json.dumps({'track': self.track.pk}),
@@ -83,7 +83,7 @@ class TestFavorites(TestCase):
 
     def test_can_restrict_api_views_to_authenticated_users(self):
         urls = [
-            ('api:favorites:tracks-list', 'get'),
+            ('api:v1:favorites:tracks-list', 'get'),
         ]
 
         for route_name, method in urls:
@@ -103,7 +103,7 @@ class TestFavorites(TestCase):
     def test_can_filter_tracks_by_favorites(self):
         favorite = TrackFavorite.add(self.track, self.user)
 
-        url = reverse('api:tracks-list')
+        url = reverse('api:v1:tracks-list')
         self.client.login(username=self.user.username, password='test')
 
         response = self.client.get(url, data={'favorites': True})
diff --git a/api/funkwhale_api/history/tests/test_history.py b/api/funkwhale_api/history/tests/test_history.py
index 448c5b15e5c18fc0d42cd1c1bfcb044e3f94c739..61009615a607ac7c3f6a0dcd1876d2413cddb1e3 100644
--- a/api/funkwhale_api/history/tests/test_history.py
+++ b/api/funkwhale_api/history/tests/test_history.py
@@ -23,7 +23,7 @@ class TestHistory(TestCase):
 
     def test_anonymous_user_can_create_listening_via_api(self):
         track = mommy.make('music.Track')
-        url = self.reverse('api:history:listenings-list')
+        url = self.reverse('api:v1:history:listenings-list')
         response = self.client.post(url, {
             'track': track.pk,
         })
@@ -38,7 +38,7 @@ class TestHistory(TestCase):
 
         self.client.login(username=self.user.username, password='test')
 
-        url = self.reverse('api:history:listenings-list')
+        url = self.reverse('api:v1:history:listenings-list')
         response = self.client.post(url, {
             'track': track.pk,
         })
diff --git a/api/funkwhale_api/music/metadata.py b/api/funkwhale_api/music/metadata.py
index 3fe61e652b085eb404c9c6753475d9a079507aed..d1be9a4e1f879dd0b5328767c311f29141b85e4a 100644
--- a/api/funkwhale_api/music/metadata.py
+++ b/api/funkwhale_api/music/metadata.py
@@ -1,34 +1,130 @@
 import mutagen
+import arrow
 
 NODEFAULT = object()
 
-class Metadata(object):
-    ALIASES = {
-        'release': 'musicbrainz_albumid',
-        'artist': 'musicbrainz_artistid',
-        'recording': 'musicbrainz_trackid',
+
+class TagNotFound(KeyError):
+    pass
+
+
+def get_id3_tag(f, k):
+    # First we try to grab the standard key
+    try:
+        return f.tags[k].text[0]
+    except KeyError:
+        pass
+    # then we fallback on parsing non standard tags
+    all_tags = f.tags.getall('TXXX')
+    try:
+        matches = [
+            t
+            for t in all_tags
+            if t.desc.lower() == k.lower()
+        ]
+        return matches[0].text[0]
+    except (KeyError, IndexError):
+        raise TagNotFound(k)
+
+
+def get_mp3_recording_id(f, k):
+    try:
+        return [
+            t
+            for t in f.tags.getall('UFID')
+            if 'musicbrainz.org' in t.owner
+        ][0].data.decode('utf-8')
+    except IndexError:
+        raise TagNotFound(k)
+
+CONF = {
+    'OggVorbis': {
+        'getter': lambda f, k: f[k][0],
+        'fields': {
+            'track_number': {
+                'field': 'TRACKNUMBER',
+                'to_application': int
+            },
+            'title': {
+                'field': 'title'
+            },
+            'artist': {
+                'field': 'artist'
+            },
+            'album': {
+                'field': 'album'
+            },
+            'date': {
+                'field': 'date',
+                'to_application': lambda v: arrow.get(v).date()
+            },
+            'musicbrainz_albumid': {
+                'field': 'musicbrainz_albumid'
+            },
+            'musicbrainz_artistid': {
+                'field': 'musicbrainz_artistid'
+            },
+            'musicbrainz_recordingid': {
+                'field': 'musicbrainz_trackid'
+            },
+        }
+    },
+    'MP3': {
+        'getter': get_id3_tag,
+        'fields': {
+            'track_number': {
+                'field': 'TPOS',
+                'to_application': lambda v: int(v.split('/')[0])
+            },
+            'title': {
+                'field': 'TIT2'
+            },
+            'artist': {
+                'field': 'TPE1'
+            },
+            'album': {
+                'field': 'TALB'
+            },
+            'date': {
+                'field': 'TDRC',
+                'to_application': lambda v: arrow.get(str(v)).date()
+            },
+            'musicbrainz_albumid': {
+                'field': 'MusicBrainz Album Id'
+            },
+            'musicbrainz_artistid': {
+                'field': 'MusicBrainz Artist Id'
+            },
+            'musicbrainz_recordingid': {
+                'field': 'UFID',
+                'getter': get_mp3_recording_id,
+            },
+        }
     }
+}
+
+
+class Metadata(object):
 
     def __init__(self, path):
         self._file = mutagen.File(path)
+        self._conf = CONF[self.get_file_type(self._file)]
 
-    def get(self, key, default=NODEFAULT, single=True):
+    def get_file_type(self, f):
+        return f.__class__.__name__
+
+    def get(self, key, default=NODEFAULT):
+        field_conf = self._conf['fields'][key]
+        real_key = field_conf['field']
         try:
-            v = self._file[key]
+            getter = field_conf.get('getter', self._conf['getter'])
+            v = getter(self._file, real_key)
         except KeyError:
             if default == NODEFAULT:
-                raise
+                raise TagNotFound(real_key)
             return default
 
-        # Some tags are returned as lists of string
-        if single:
-            return v[0]
+        converter = field_conf.get('to_application')
+        if converter:
+            v = converter(v)
         return v
-
-    def __getattr__(self, key):
-        try:
-            alias = self.ALIASES[key]
-        except KeyError:
-            raise ValueError('Invalid alias {}'.format(key))
-
-        return self.get(alias, single=True)
diff --git a/api/funkwhale_api/music/models.py b/api/funkwhale_api/music/models.py
index 8d4e84a235aed713f0210e0e642724ec4f9210b3..70230847700b9b6b788b8d36ea22b9774a963947 100644
--- a/api/funkwhale_api/music/models.py
+++ b/api/funkwhale_api/music/models.py
@@ -314,7 +314,7 @@ class Track(APIModelMixin):
         return work
 
     def get_lyrics_url(self):
-        return reverse('api:tracks-lyrics', kwargs={'pk': self.pk})
+        return reverse('api:v1:tracks-lyrics', kwargs={'pk': self.pk})
 
     @property
     def full_name(self):
diff --git a/api/funkwhale_api/music/tests/test.mp3 b/api/funkwhale_api/music/tests/test.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..35a6e5fcef6fb272010c19534995393f840eafa7
Binary files /dev/null and b/api/funkwhale_api/music/tests/test.mp3 differ
diff --git a/api/funkwhale_api/music/tests/test_api.py b/api/funkwhale_api/music/tests/test_api.py
index ff3deedf37c1e6f903f3521fb12de5a401984746..d8f56eeb92604b9b9399c61c0124b9b9d8256fed 100644
--- a/api/funkwhale_api/music/tests/test_api.py
+++ b/api/funkwhale_api/music/tests/test_api.py
@@ -20,7 +20,7 @@ class TestAPI(TMPDirTestCaseMixin, TestCase):
     def test_can_submit_youtube_url_for_track_import(self, *mocks):
         mbid = '9968a9d6-8d92-4051-8f76-674e157b6eed'
         video_id = 'tPEE9ZwTmy0'
-        url = reverse('api:submit-single')
+        url = reverse('api:v1:submit-single')
         user = User.objects.create_superuser(username='test', email='test@test.com', password='test')
         self.client.login(username=user.username, password='test')
         response = self.client.post(url, {'import_url': 'https://www.youtube.com/watch?v={0}'.format(video_id), 'mbid': mbid})
@@ -33,7 +33,7 @@ class TestAPI(TMPDirTestCaseMixin, TestCase):
         user = User.objects.create_superuser(username='test', email='test@test.com', password='test')
         mbid = '9968a9d6-8d92-4051-8f76-674e157b6eed'
         video_id = 'tPEE9ZwTmy0'
-        url = reverse('api:submit-single')
+        url = reverse('api:v1:submit-single')
         self.client.login(username=user.username, password='test')
         with self.settings(CELERY_ALWAYS_EAGER=False):
             response = self.client.post(url, {'import_url': 'https://www.youtube.com/watch?v={0}'.format(video_id), 'mbid': mbid})
@@ -69,7 +69,7 @@ class TestAPI(TMPDirTestCaseMixin, TestCase):
                 },
             ]
         }
-        url = reverse('api:submit-album')
+        url = reverse('api:v1:submit-album')
         self.client.login(username=user.username, password='test')
         with self.settings(CELERY_ALWAYS_EAGER=False):
             response = self.client.post(url, json.dumps(payload), content_type="application/json")
@@ -123,7 +123,7 @@ class TestAPI(TMPDirTestCaseMixin, TestCase):
                 }
             ]
         }
-        url = reverse('api:submit-artist')
+        url = reverse('api:v1:submit-artist')
         self.client.login(username=user.username, password='test')
         with self.settings(CELERY_ALWAYS_EAGER=False):
             response = self.client.post(url, json.dumps(payload), content_type="application/json")
@@ -159,7 +159,7 @@ class TestAPI(TMPDirTestCaseMixin, TestCase):
         batch = models.ImportBatch.objects.create(submitted_by=user1)
         job = models.ImportJob.objects.create(batch=batch, mbid=mbid, source=source)
 
-        url = reverse('api:import-batches-list')
+        url = reverse('api:v1:import-batches-list')
 
         self.client.login(username=user2.username, password='test')
         response2 = self.client.get(url)
@@ -175,7 +175,7 @@ class TestAPI(TMPDirTestCaseMixin, TestCase):
         artist2 = models.Artist.objects.create(name='Test2')
         query = 'test1'
         expected = '[{0}]'.format(json.dumps(serializers.ArtistSerializerNested(artist1).data))
-        url = self.reverse('api:artists-search')
+        url = self.reverse('api:v1:artists-search')
         response = self.client.get(url + '?query={0}'.format(query))
 
         self.assertJSONEqual(expected, json.loads(response.content.decode('utf-8')))
@@ -187,17 +187,17 @@ class TestAPI(TMPDirTestCaseMixin, TestCase):
         track2 = models.Track.objects.create(artist=artist2, title="test_track2")
         query = 'test track 1'
         expected = '[{0}]'.format(json.dumps(serializers.TrackSerializerNested(track1).data))
-        url = self.reverse('api:tracks-search')
+        url = self.reverse('api:v1:tracks-search')
         response = self.client.get(url + '?query={0}'.format(query))
 
         self.assertJSONEqual(expected, json.loads(response.content.decode('utf-8')))
 
     def test_can_restrict_api_views_to_authenticated_users(self):
         urls = [
-            ('api:tags-list', 'get'),
-            ('api:tracks-list', 'get'),
-            ('api:artists-list', 'get'),
-            ('api:albums-list', 'get'),
+            ('api:v1:tags-list', 'get'),
+            ('api:v1:tracks-list', 'get'),
+            ('api:v1:artists-list', 'get'),
+            ('api:v1:albums-list', 'get'),
         ]
 
         for route_name, method in urls:
diff --git a/api/funkwhale_api/music/tests/test_lyrics.py b/api/funkwhale_api/music/tests/test_lyrics.py
index f74ecb2ef6bd37e751667729b8ae56727d73a907..0ea22371bad091ed17796db24b58f414191979f4 100644
--- a/api/funkwhale_api/music/tests/test_lyrics.py
+++ b/api/funkwhale_api/music/tests/test_lyrics.py
@@ -59,7 +59,7 @@ Is it me you're looking for?"""
             work=None,
             mbid='07ca77cf-f513-4e9c-b190-d7e24bbad448')
 
-        url = reverse('api:tracks-lyrics', kwargs={'pk': track.pk})
+        url = reverse('api:v1:tracks-lyrics', kwargs={'pk': track.pk})
         user = User.objects.create_user(
             username='test', email='test@test.com', password='test')
         self.client.login(username=user.username, password='test')
diff --git a/api/funkwhale_api/music/tests/test_metadata.py b/api/funkwhale_api/music/tests/test_metadata.py
index 7832baedb0d9164000558a51e1a408097c8661d6..9b8c7665337597c04d5770efb4bdc829f18ba380 100644
--- a/api/funkwhale_api/music/tests/test_metadata.py
+++ b/api/funkwhale_api/music/tests/test_metadata.py
@@ -1,5 +1,6 @@
 import unittest
 import os
+import datetime
 from test_plus.test import TestCase
 from funkwhale_api.music import metadata
 
@@ -8,20 +9,72 @@ DATA_DIR = os.path.dirname(os.path.abspath(__file__))
 
 class TestMetadata(TestCase):
 
-    def test_can_get_metadata_from_file(self, *mocks):
+    def test_can_get_metadata_from_ogg_file(self, *mocks):
         path = os.path.join(DATA_DIR, 'test.ogg')
         data = metadata.Metadata(path)
 
+        self.assertEqual(
+            data.get('title'),
+            'Peer Gynt Suite no. 1, op. 46: I. Morning'
+        )
+        self.assertEqual(
+            data.get('artist'),
+            'Edvard Grieg'
+        )
+        self.assertEqual(
+            data.get('album'),
+            'Peer Gynt Suite no. 1, op. 46'
+        )
+        self.assertEqual(
+            data.get('date'),
+            datetime.date(2012, 8, 15),
+        )
+        self.assertEqual(
+            data.get('track_number'),
+            1
+        )
+
         self.assertEqual(
             data.get('musicbrainz_albumid'),
             'a766da8b-8336-47aa-a3ee-371cc41ccc75')
         self.assertEqual(
-            data.get('musicbrainz_trackid'),
+            data.get('musicbrainz_recordingid'),
             'bd21ac48-46d8-4e78-925f-d9cc2a294656')
         self.assertEqual(
             data.get('musicbrainz_artistid'),
             '013c8e5b-d72a-4cd3-8dee-6c64d6125823')
 
-        self.assertEqual(data.release, data.get('musicbrainz_albumid'))
-        self.assertEqual(data.artist, data.get('musicbrainz_artistid'))
-        self.assertEqual(data.recording, data.get('musicbrainz_trackid'))
+    def test_can_get_metadata_from_id3_mp3_file(self, *mocks):
+        path = os.path.join(DATA_DIR, 'test.mp3')
+        data = metadata.Metadata(path)
+
+        self.assertEqual(
+            data.get('title'),
+            'Bend'
+        )
+        self.assertEqual(
+            data.get('artist'),
+            'Binärpilot'
+        )
+        self.assertEqual(
+            data.get('album'),
+            'You Can\'t Stop Da Funk'
+        )
+        self.assertEqual(
+            data.get('date'),
+            datetime.date(2006, 2, 7),
+        )
+        self.assertEqual(
+            data.get('track_number'),
+            1
+        )
+
+        self.assertEqual(
+            data.get('musicbrainz_albumid'),
+            'ce40cdb1-a562-4fd8-a269-9269f98d4124')
+        self.assertEqual(
+            data.get('musicbrainz_recordingid'),
+            'f269d497-1cc0-4ae4-a0c4-157ec7d73fcb')
+        self.assertEqual(
+            data.get('musicbrainz_artistid'),
+            '9c6bddde-6228-4d9f-ad0d-03f6fcb19e13')
diff --git a/api/funkwhale_api/music/views.py b/api/funkwhale_api/music/views.py
index 4887ad1399eca48684e08177b83ce6475e6419bb..772f4173ea0a5de52ff3e5b75293c8d6affb0ecd 100644
--- a/api/funkwhale_api/music/views.py
+++ b/api/funkwhale_api/music/views.py
@@ -121,13 +121,14 @@ class TrackViewSet(TagViewSetMixin, SearchMixin, viewsets.ReadOnlyModelViewSet):
 
 
 class TagViewSet(viewsets.ReadOnlyModelViewSet):
-    queryset = Tag.objects.all()
+    queryset = Tag.objects.all().order_by('name')
     serializer_class = serializers.TagSerializer
     permission_classes = [ConditionalAuthentication]
 
 
 class Search(views.APIView):
     max_results = 3
+
     def get(self, request, *args, **kwargs):
         query = request.GET['query']
         results = {
diff --git a/api/funkwhale_api/musicbrainz/tests/test_api.py b/api/funkwhale_api/musicbrainz/tests/test_api.py
index 1734c943c117980a13c262f355ac546574f78656..f962e0f781e515716b808c7697fd0b63692cf174 100644
--- a/api/funkwhale_api/musicbrainz/tests/test_api.py
+++ b/api/funkwhale_api/musicbrainz/tests/test_api.py
@@ -13,7 +13,7 @@ class TestAPI(TestCase):
         return_value=api_data.recordings['search']['brontide matador'])
     def test_can_search_recording_in_musicbrainz_api(self, *mocks):
         query = 'brontide matador'
-        url = reverse('api:providers:musicbrainz:search-recordings')
+        url = reverse('api:v1:providers:musicbrainz:search-recordings')
         expected = api_data.recordings['search']['brontide matador']
         response = self.client.get(url, data={'query': query})
 
@@ -24,7 +24,7 @@ class TestAPI(TestCase):
         return_value=api_data.releases['search']['brontide matador'])
     def test_can_search_release_in_musicbrainz_api(self, *mocks):
         query = 'brontide matador'
-        url = reverse('api:providers:musicbrainz:search-releases')
+        url = reverse('api:v1:providers:musicbrainz:search-releases')
         expected = api_data.releases['search']['brontide matador']
         response = self.client.get(url, data={'query': query})
 
@@ -35,7 +35,7 @@ class TestAPI(TestCase):
         return_value=api_data.artists['search']['lost fingers'])
     def test_can_search_artists_in_musicbrainz_api(self, *mocks):
         query = 'lost fingers'
-        url = reverse('api:providers:musicbrainz:search-artists')
+        url = reverse('api:v1:providers:musicbrainz:search-artists')
         expected = api_data.artists['search']['lost fingers']
         response = self.client.get(url, data={'query': query})
 
@@ -46,7 +46,7 @@ class TestAPI(TestCase):
         return_value=api_data.artists['get']['lost fingers'])
     def test_can_get_artist_in_musicbrainz_api(self, *mocks):
         uuid = 'ac16bbc0-aded-4477-a3c3-1d81693d58c9'
-        url = reverse('api:providers:musicbrainz:artist-detail', kwargs={
+        url = reverse('api:v1:providers:musicbrainz:artist-detail', kwargs={
             'uuid': uuid,
         })
         response = self.client.get(url)
@@ -60,7 +60,7 @@ class TestAPI(TestCase):
     def test_can_broswe_release_group_using_musicbrainz_api(self, *mocks):
         uuid = 'ac16bbc0-aded-4477-a3c3-1d81693d58c9'
         url = reverse(
-            'api:providers:musicbrainz:release-group-browse',
+            'api:v1:providers:musicbrainz:release-group-browse',
             kwargs={
                 'artist_uuid': uuid,
             }
@@ -76,7 +76,7 @@ class TestAPI(TestCase):
     def test_can_broswe_releases_using_musicbrainz_api(self, *mocks):
         uuid = 'f04ed607-11b7-3843-957e-503ecdd485d1'
         url = reverse(
-            'api:providers:musicbrainz:release-browse',
+            'api:v1:providers:musicbrainz:release-browse',
             kwargs={
                 'release_group_uuid': uuid,
             }
diff --git a/api/funkwhale_api/playlists/tests/test_playlists.py b/api/funkwhale_api/playlists/tests/test_playlists.py
index 056ca06e6e9325eb35ee009291a294a38e2ba47b..49025cd11b078e072e196325ce32506b448e142f 100644
--- a/api/funkwhale_api/playlists/tests/test_playlists.py
+++ b/api/funkwhale_api/playlists/tests/test_playlists.py
@@ -38,7 +38,7 @@ class TestPlayLists(TestCase):
 
     def test_can_create_playlist_via_api(self):
         self.client.login(username=self.user.username, password='test')
-        url = reverse('api:playlists-list')
+        url = reverse('api:v1:playlists-list')
         data = {
             'name': 'test',
         }
@@ -54,7 +54,7 @@ class TestPlayLists(TestCase):
 
         self.client.login(username=self.user.username, password='test')
 
-        url = reverse('api:playlist-tracks-list')
+        url = reverse('api:v1:playlist-tracks-list')
         data = {
             'playlist': playlist.pk,
             'track': tracks[0].pk
diff --git a/api/funkwhale_api/providers/audiofile/importer.py b/api/funkwhale_api/providers/audiofile/importer.py
index d95c120e1f7e8eec9faacc14e4b010491d321c1c..9e1b0fb3f8bec4f2f74a2d1a4e643c70d73eb8c2 100644
--- a/api/funkwhale_api/providers/audiofile/importer.py
+++ b/api/funkwhale_api/providers/audiofile/importer.py
@@ -9,41 +9,34 @@ from funkwhale_api.music import models, metadata
 @celery.app.task(name='audiofile.from_path')
 def from_path(path):
     data = metadata.Metadata(path)
-
     artist = models.Artist.objects.get_or_create(
         name__iexact=data.get('artist'),
-        defaults={'name': data.get('artist')},
-    )[0]
+        defaults={
+            'name': data.get('artist'),
+            'mbid': data.get('musicbrainz_artistid', None),
 
-    release_date = None
-    try:
-        year, month, day = data.get('date', None).split('-')
-        release_date = datetime.date(
-            int(year), int(month), int(day)
-        )
-    except (ValueError, TypeError):
-        pass
+        },
+    )[0]
 
+    release_date = data.get('date', default=None)
     album = models.Album.objects.get_or_create(
         title__iexact=data.get('album'),
         artist=artist,
         defaults={
             'title': data.get('album'),
             'release_date': release_date,
+            'mbid': data.get('musicbrainz_albumid', None),
         },
     )[0]
 
-    position = None
-    try:
-        position = int(data.get('tracknumber', None))
-    except ValueError:
-        pass
+    position = data.get('track_number', default=None)
     track = models.Track.objects.get_or_create(
         title__iexact=data.get('title'),
         album=album,
         defaults={
             'title': data.get('title'),
             'position': position,
+            'mbid': data.get('musicbrainz_recordingid', None),
         },
     )[0]
 
diff --git a/api/funkwhale_api/providers/audiofile/tests/test_disk_import.py b/api/funkwhale_api/providers/audiofile/tests/test_disk_import.py
index 26532a8c93aba087d0dcd07fedaedfe9b47e23c1..4a91a36ebfc6d76fb1cdd7887d6d24b59485c991 100644
--- a/api/funkwhale_api/providers/audiofile/tests/test_disk_import.py
+++ b/api/funkwhale_api/providers/audiofile/tests/test_disk_import.py
@@ -14,21 +14,37 @@ class TestAudioFile(TestCase):
             'artist': ['Test artist'],
             'album': ['Test album'],
             'title': ['Test track'],
-            'tracknumber': ['4'],
-            'date': ['2012-08-15']
+            'TRACKNUMBER': ['4'],
+            'date': ['2012-08-15'],
+            'musicbrainz_albumid': ['a766da8b-8336-47aa-a3ee-371cc41ccc75'],
+            'musicbrainz_trackid': ['bd21ac48-46d8-4e78-925f-d9cc2a294656'],
+            'musicbrainz_artistid': ['013c8e5b-d72a-4cd3-8dee-6c64d6125823'],
         }
 
-        with unittest.mock.patch('mutagen.File', return_value=metadata):
+        m1 = unittest.mock.patch('mutagen.File', return_value=metadata)
+        m2 = unittest.mock.patch(
+            'funkwhale_api.music.metadata.Metadata.get_file_type',
+            return_value='OggVorbis',
+        )
+        with m1, m2:
             track_file = importer.from_path(
                 os.path.join(DATA_DIR, 'dummy_file.ogg'))
 
         self.assertEqual(
             track_file.track.title, metadata['title'][0])
+        self.assertEqual(
+            track_file.track.mbid, metadata['musicbrainz_trackid'][0])
         self.assertEqual(
             track_file.track.position, 4)
         self.assertEqual(
             track_file.track.album.title, metadata['album'][0])
+        self.assertEqual(
+            track_file.track.album.mbid,
+            metadata['musicbrainz_albumid'][0])
         self.assertEqual(
             track_file.track.album.release_date, datetime.date(2012, 8, 15))
         self.assertEqual(
             track_file.track.artist.name, metadata['artist'][0])
+        self.assertEqual(
+            track_file.track.artist.mbid,
+            metadata['musicbrainz_artistid'][0])
diff --git a/api/funkwhale_api/providers/youtube/tests/test_youtube.py b/api/funkwhale_api/providers/youtube/tests/test_youtube.py
index 56b87a35400351b3452b93b8ddb8b4bbd4151ec5..ca0a956285c845854267f146834825e7097f5dd3 100644
--- a/api/funkwhale_api/providers/youtube/tests/test_youtube.py
+++ b/api/funkwhale_api/providers/youtube/tests/test_youtube.py
@@ -26,7 +26,7 @@ class TestAPI(TestCase):
     def test_can_get_search_results_from_funkwhale(self, *mocks):
         query = '8 bit adventure'
         expected = json.dumps(client.search(query))
-        url = self.reverse('api:providers:youtube:search')
+        url = self.reverse('api:v1:providers:youtube:search')
         response = self.client.get(url + '?query={0}'.format(query))
 
         self.assertJSONEqual(expected, json.loads(response.content.decode('utf-8')))
@@ -67,7 +67,7 @@ class TestAPI(TestCase):
         }
 
         expected = json.dumps(client.search_multiple(queries))
-        url = self.reverse('api:providers:youtube:searchs')
+        url = self.reverse('api:v1:providers:youtube:searchs')
         response = self.client.post(
             url, json.dumps(queries), content_type='application/json')
 
diff --git a/api/funkwhale_api/radios/tests/test_radios.py b/api/funkwhale_api/radios/tests/test_radios.py
index 7d069be9c602cce65624de1ec756a82177013442..5729b4412104d906e208674177b893414ea9a44b 100644
--- a/api/funkwhale_api/radios/tests/test_radios.py
+++ b/api/funkwhale_api/radios/tests/test_radios.py
@@ -94,7 +94,7 @@ class TestRadios(TestCase):
         self.assertEqual(radio.session, restarted_radio.session)
 
     def test_can_get_start_radio_from_api(self):
-        url = reverse('api:radios:sessions-list')
+        url = reverse('api:v1:radios:sessions-list')
         response = self.client.post(url, {'radio_type': 'random'})
         session = models.RadioSession.objects.latest('id')
         self.assertEqual(session.radio_type, 'random')
@@ -107,7 +107,7 @@ class TestRadios(TestCase):
         self.assertEqual(session.user, self.user)
 
     def test_can_start_radio_for_anonymous_user(self):
-        url = reverse('api:radios:sessions-list')
+        url = reverse('api:v1:radios:sessions-list')
         response = self.client.post(url, {'radio_type': 'random'})
         session = models.RadioSession.objects.latest('id')
 
@@ -118,11 +118,11 @@ class TestRadios(TestCase):
         tracks = mommy.make('music.Track', _quantity=1)
 
         self.client.login(username=self.user.username, password='test')
-        url = reverse('api:radios:sessions-list')
+        url = reverse('api:v1:radios:sessions-list')
         response = self.client.post(url, {'radio_type': 'random'})
         session = models.RadioSession.objects.latest('id')
 
-        url = reverse('api:radios:tracks-list')
+        url = reverse('api:v1:radios:tracks-list')
         response = self.client.post(url, {'session': session.pk})
         data = json.loads(response.content.decode('utf-8'))
 
@@ -173,7 +173,7 @@ class TestRadios(TestCase):
 
     def test_can_start_artist_radio_from_api(self):
         artist = mommy.make('music.Artist')
-        url = reverse('api:radios:sessions-list')
+        url = reverse('api:v1:radios:sessions-list')
 
         response = self.client.post(url, {'radio_type': 'artist', 'related_object_id': artist.id})
         session = models.RadioSession.objects.latest('id')
diff --git a/api/funkwhale_api/templates/account/base.html b/api/funkwhale_api/templates/account/base.html
deleted file mode 100644
index c64b47a4a9499be12deee48f0efc6ccb4afabf10..0000000000000000000000000000000000000000
--- a/api/funkwhale_api/templates/account/base.html
+++ /dev/null
@@ -1,2 +0,0 @@
-{% extends "base.html" %}
-{% block title %}{% block head_title %}{% endblock head_title %}{% endblock title %}
diff --git a/api/funkwhale_api/templates/account/email.html b/api/funkwhale_api/templates/account/email.html
deleted file mode 100644
index 7c52a4a27718d87aa48111d6b96b0ec90c43421e..0000000000000000000000000000000000000000
--- a/api/funkwhale_api/templates/account/email.html
+++ /dev/null
@@ -1,80 +0,0 @@
-{% extends "account/base.html" %}
-
-{% load i18n %}
-{% load crispy_forms_tags %}
-
-{% block head_title %}{% trans "Account" %}{% endblock %}
-
-{% block content %}
-<div class="container">
-  <div class="row">
-    <div class="col-md-5">
-      <h2>{% trans "E-mail Addresses" %}</h2>
-      {% if user.emailaddress_set.all %}
-      <p>{% trans 'The following e-mail addresses are associated with your account:' %}</p>
-
-      <form action="{% url 'account_email' %}" class="email_list" method="post">
-      {% csrf_token %}
-      <fieldset class="blockLabels">
-
-        {% for emailaddress in user.emailaddress_set.all %}
-      <div class="ctrlHolder">
-            <label for="email_radio_{{ forloop.counter }}" class="{% if emailaddress.primary %}primary_email{% endif %}">
-
-            <input id="email_radio_{{ forloop.counter }}" type="radio" name="email" {% if emailaddress.primary %}checked="checked"{% endif %} value="{{ emailaddress.email }}"/>
-
-      {{ emailaddress.email }}
-          {% if emailaddress.verified %}
-          <span class="verified">{% trans "Verified" %}</span>
-          {% else %}
-          <span class="unverified">{% trans "Unverified" %}</span>
-          {% endif %}
-            {% if emailaddress.primary %}<span class="primary">{% trans "Primary" %}</span>{% endif %}
-      </label>
-      </div>
-        {% endfor %}
-
-      <div class="buttonHolder">
-            <button class="secondaryAction" type="submit" name="action_primary" >{% trans 'Make Primary' %}</button>
-            <button class="secondaryAction" type="submit" name="action_send" >{% trans 'Re-send Verification' %}</button>
-            <button class="primaryAction" type="submit" name="action_remove" >{% trans 'Remove' %}</button>
-      </div>
-
-      </fieldset>
-      </form>
-
-      {% else %}
-      <p><strong>{% trans 'Warning:'%}</strong> {% trans "You currently do not have any e-mail address set up. You should really add an e-mail address so you can receive notifications, reset your password, etc." %}</p>
-
-      {% endif %}
-
-
-          <h2>{% trans "Add E-mail Address" %}</h2>
-
-          <form method="post" action="." class="add_email">
-              {% csrf_token %}
-              {{ form|crispy }}
-              <button class="btn" name="action_add" type="submit">{% trans "Add E-mail" %}</button>
-          </form>
-    </div>
-  </div>
-</div>
-{% endblock %}
-
-
-{% block extra_body %}
-<script type="text/javascript">
-(function() {
-  var message = "{% trans 'Do you really want to remove the selected e-mail address?' %}";
-  var actions = document.getElementsByName('action_remove');
-  if (actions.length) {
-    actions[0].addEventListener("click", function(e) {
-      if (! confirm(message)) {
-        e.preventDefault();
-      }
-    });
-  }
-})();
-</script>
-{% endblock %}
-
diff --git a/api/funkwhale_api/templates/account/email_confirm.html b/api/funkwhale_api/templates/account/email_confirm.html
deleted file mode 100644
index 4395d46bfbc724cc30eebc54191d5be505603a7d..0000000000000000000000000000000000000000
--- a/api/funkwhale_api/templates/account/email_confirm.html
+++ /dev/null
@@ -1,37 +0,0 @@
-{% extends "account/base.html" %}
-
-{% load i18n %}
-{% load account %}
-
-{% block head_title %}{% trans "Confirm E-mail Address" %}{% endblock %}
-
-
-{% block content %}
-<div class="container">
-  <div class="row">
-    <div class="col-xs-12">
-        <h2>{% trans "Confirm E-mail Address" %}</h2>
-
-        {% if confirmation %}
-
-        {% user_display confirmation.email_address.user as user_display %}
-
-        <p>{% blocktrans with confirmation.email_address.email as email %}Please confirm that <a href="mailto:{{ email }}">{{ email }}</a> is an e-mail address for user {{ user_display }}.{% endblocktrans %}</p>
-
-        <form method="post" action="{% url 'account_confirm_email' confirmation.key %}">
-        {% csrf_token %}
-            <button id="confirm-button" class="submit" type="submit">{% trans 'Confirm' %}</button>
-        </form>
-
-        {% else %}
-
-        {% url 'account_email' as email_url %}
-
-        <p>{% blocktrans %}This e-mail confirmation link expired or is invalid. Please <a href="{{ email_url }}">issue a new e-mail confirmation request</a>.{% endblocktrans %}</p>
-
-        {% endif %}
-    </div>
-  </div>
-</div>
-{% endblock %}
-
diff --git a/api/funkwhale_api/templates/account/email_confirmed.html b/api/funkwhale_api/templates/account/email_confirmed.html
deleted file mode 100644
index 7b2edf08b8d1169305311911e73fb7c0b48b2a53..0000000000000000000000000000000000000000
--- a/api/funkwhale_api/templates/account/email_confirmed.html
+++ /dev/null
@@ -1,21 +0,0 @@
-{% extends "account/base.html" %}
-
-{% load i18n %}
-{% load account %}
-
-{% block head_title %}{% trans "Confirm E-mail Address" %}{% endblock %}
-
-
-{% block content %}
-<div class="container">
-  <div class="row">
-    <div class="col-xs-12">
-        <h2>{% trans "Confirm E-mail Address" %}</h2>
-
-        {% user_display email_address.user as user_display %}
-                
-        <p>{% blocktrans with email_address.email as email %}You have confirmed that <a href="mailto:{{ email }}">{{ email }}</a> is an e-mail address for user {{ user_display }}.{% endblocktrans %}</p>
-    </div>
-  </div>
-</div>
-{% endblock %}
diff --git a/api/funkwhale_api/templates/account/login.html b/api/funkwhale_api/templates/account/login.html
deleted file mode 100644
index 4a05dc772876f6f8dbc45de6909f004f98cb2ba0..0000000000000000000000000000000000000000
--- a/api/funkwhale_api/templates/account/login.html
+++ /dev/null
@@ -1,48 +0,0 @@
-{% extends "account/base.html" %}
-
-{% load i18n %}
-{% load account %}
-{% load socialaccount %}
-{% load crispy_forms_tags %}
-
-{% block head_title %}{% trans "Sign In" %}{% endblock %}
-
-{% block content %}
-<div class="container">
-  <div class="row">
-    <div class="col-md-5">
-      <h2>{% trans "Sign In" %}</h2>
-      {% get_providers as socialaccount_providers %}
-      {% if socialaccount_providers %}
-      <p>{% blocktrans with site.name as site_name %}Please sign in with one
-      of your existing third party accounts. Or, <a href="{{ signup_url }}">sign up</a>
-      for a {{ site_name }} account and sign in below:{% endblocktrans %}</p>
-
-      <div class="socialaccount_ballot">
-
-        <ul class="socialaccount_providers">
-          {% include "socialaccount/snippets/provider_list.html" with process="login" %}
-        </ul>
-
-        <div class="login-or">{% trans 'or' %}</div>
-
-      </div>
-
-      {% include "socialaccount/snippets/login_extra.html" %}
-
-      {% endif %}
-
-      <form class="login" method="POST" action="{% url 'account_login' %}">
-        {% csrf_token %}
-        {{ form|crispy }}
-        {% if redirect_field_value %}
-        <input type="hidden" name="{{ redirect_field_name }}" value="{{ redirect_field_value }}" />
-        {% endif %}
-        <button id="sign-in-button" class="btn btn-primary" type="submit">{% trans "Sign In" %}</button>
-        <a class="button secondaryAction" href="{% url 'account_reset_password' %}">{% trans "Forgot Password?" %}</a>
-      </form>
-    </div>
-  </div>
-</div>
-{% endblock %}
-
diff --git a/api/funkwhale_api/templates/account/logout.html b/api/funkwhale_api/templates/account/logout.html
deleted file mode 100644
index 4f1f8f3e6697c6c15d39501b2e847f0a75b6cb1e..0000000000000000000000000000000000000000
--- a/api/funkwhale_api/templates/account/logout.html
+++ /dev/null
@@ -1,28 +0,0 @@
-{% extends "account/base.html" %}
-
-{% load i18n %}
-
-{% block head_title %}{% trans "Sign Out" %}{% endblock %}
-
-{% block content %}
-<div class="container">
-  <div class="row">
-    <div class="col-md-5">
-
-        <h2>{% trans "Sign Out" %}</h2>
-
-        <p>{% trans 'Are you sure you want to sign out?' %}</p>
-
-        <form method="post" action="{% url 'account_logout' %}">
-          {% csrf_token %}
-          {% if redirect_field_value %}
-          <input type="hidden" name="{{ redirect_field_name }}" value="{{ redirect_field_value }}"/>
-          {% endif %}
-          <button class="btn btn-danger" type="submit">{% trans 'Sign Out' %}</button>
-        </form>
-    </div>
-  </div>
-</div>
-
-{% endblock %}
-
diff --git a/api/funkwhale_api/templates/account/password_change.html b/api/funkwhale_api/templates/account/password_change.html
deleted file mode 100644
index e282b14ad96ce6785d499b9dd15b460ad70afceb..0000000000000000000000000000000000000000
--- a/api/funkwhale_api/templates/account/password_change.html
+++ /dev/null
@@ -1,22 +0,0 @@
-{% extends "account/base.html" %}
-
-{% load i18n %}
-{% load crispy_forms_tags %}
-{% block head_title %}{% trans "Change Password" %}{% endblock %}
-
-{% block content %}
-<div class="container">
-  <div class="row">
-    <div class="col-md-5">
-        <h2>{% trans "Change Password" %}</h2>
-
-        <form method="POST" action="./" class="password_change">
-            {% csrf_token %}
-            {{ form|crispy }}
-            <button class="btn" type="submit" name="action">{% trans "Change Password" %}</button>
-        </form>
-    </div>
-  </div>
-</div>
-{% endblock %}
-
diff --git a/api/funkwhale_api/templates/account/password_reset.html b/api/funkwhale_api/templates/account/password_reset.html
deleted file mode 100644
index efdfbd05ca98700231af26ddf2f51cac7f240f16..0000000000000000000000000000000000000000
--- a/api/funkwhale_api/templates/account/password_reset.html
+++ /dev/null
@@ -1,39 +0,0 @@
-{% extends "account/base.html" %}
-
-{% load i18n %}
-{% load account %}
-{% load crispy_forms_tags %}
-
-{% block head_title %}{% trans "Password Reset" %}{% endblock %}
-
-{% block content %}
-<div class="container">
-  <div class="row">
-    <div class="col-md-5">
-
-        <h2>{% trans "Password Reset" %}</h2>
-        {% if user.is_authenticated %}
-        {% include "account/snippets/already_logged_in.html" %}
-        {% endif %}
-
-        <p>{% trans "Forgotten your password? Enter your e-mail address below, and we'll send you an e-mail allowing you to reset it." %}</p>
-
-        <form method="POST" action="./" class="password_reset">
-            {% csrf_token %}
-            {{ form|crispy }}
-            <button class="btn" type="submit">{% trans "Reset My Password" %}</button>
-        </form>
-
-        <p>{% blocktrans %}Please contact us if you have any trouble resetting your password.{% endblocktrans %}</p>
-    </div>
-  </div>
-</div>
-{% endblock %}
-
-{% block javascript %}
-    {{ block.super }}
-    <script>
-        $("#id_email").focus();
-    </script>
-{% endblock javascript %}
-
diff --git a/api/funkwhale_api/templates/account/password_reset_done.html b/api/funkwhale_api/templates/account/password_reset_done.html
deleted file mode 100644
index 865ecaa0a5a6d21cb21e4c7632993bf37db0759a..0000000000000000000000000000000000000000
--- a/api/funkwhale_api/templates/account/password_reset_done.html
+++ /dev/null
@@ -1,22 +0,0 @@
-{% extends "account/base.html" %}
-
-{% load i18n %}
-{% load account %}
-
-{% block head_title %}{% trans "Password Reset" %}{% endblock %}
-
-{% block content %}
-<div class="container">
-  <div class="row">
-    <div class="col-xs-12">
-        <h2>{% trans "Password Reset" %}</h2>
-        
-        {% if user.is_authenticated %}
-        {% include "account/snippets/already_logged_in.html" %}
-        {% endif %}
-        
-        <p>{% blocktrans %}We have sent you an e-mail. Please contact us if you do not receive it within a few minutes.{% endblocktrans %}</p>
-    </div>
-  </div>    
-</div>
-{% endblock %}
diff --git a/api/funkwhale_api/templates/account/password_reset_from_key.html b/api/funkwhale_api/templates/account/password_reset_from_key.html
deleted file mode 100644
index 41b50843f776021b404a03358e19c040fdccc861..0000000000000000000000000000000000000000
--- a/api/funkwhale_api/templates/account/password_reset_from_key.html
+++ /dev/null
@@ -1,32 +0,0 @@
-{% extends "account/base.html" %}
-
-{% load i18n %}
-{% load crispy_forms_tags %}
-
-{% block head_title %}{% trans "Change Password" %}{% endblock %}
-
-{% block content %}
-<div class="container">
-  <div class="row">
-    <div class="col-xs-12">
-    <h2>{% if token_fail %}{% trans "Bad Token" %}{% else %}{% trans "Change Password" %}{% endif %}</h2>
-
-    {% if token_fail %}
-        {% url 'account_reset_password' as passwd_reset_url %}
-        <p>{% blocktrans %}The password reset link was invalid, possibly because it has already been used.  Please request a <a href="{{ passwd_reset_url }}">new password reset</a>.{% endblocktrans %}</p>
-    {% else %}
-        {% if form %}
-            <form method="POST" action="./">
-                {% csrf_token %}
-                {{ form|crispy }}
-                <button type="submit" name="action">{% trans "change password" %}</button>
-            </form>
-        {% else %}
-            <p>{% trans 'Your password is now changed.' %}</p>
-        {% endif %}
-    {% endif %}
-    </div>
-  </div>
-</div>
-{% endblock %}
-
diff --git a/api/funkwhale_api/templates/account/password_reset_from_key_done.html b/api/funkwhale_api/templates/account/password_reset_from_key_done.html
deleted file mode 100644
index e940eb83df2d5ef7676314d2d5ac8da7a70396d9..0000000000000000000000000000000000000000
--- a/api/funkwhale_api/templates/account/password_reset_from_key_done.html
+++ /dev/null
@@ -1,16 +0,0 @@
-{% extends "account/base.html" %}
-
-{% load i18n %}
-{% block head_title %}{% trans "Change Password" %}{% endblock %}
-
-{% block content %}
-<div class="container">
-  <div class="row">
-    <div class="col-md-5">
-        <h2>{% trans "Change Password" %}</h2>
-        <p>{% trans 'Your password is now changed.' %}</p>
-    </div>
-  </div>
-</div>
-{% endblock %}
-
diff --git a/api/funkwhale_api/templates/account/password_set.html b/api/funkwhale_api/templates/account/password_set.html
deleted file mode 100644
index 03e2fcd2d90b0cb98dbce5e542d79465c658d832..0000000000000000000000000000000000000000
--- a/api/funkwhale_api/templates/account/password_set.html
+++ /dev/null
@@ -1,23 +0,0 @@
-
-{% extends "account/base.html" %}
-
-{% load i18n crispy_forms_tags %}
-
-{% block head_title %}{% trans "Set Password" %}{% endblock %}
-
-{% block content %}
-<div class="container">
-  <div class="row">
-    <div class="col-md-5">
-        <h2>{% trans "Set Password" %}</h2>
-
-        <form method="POST" action="./" class="password_set">
-            {% csrf_token %}
-            {{ form|crispy }}
-            <input type="submit" name="action" value="{% trans "Set Password" %}"/>
-        </form>
-    </div>
-  </div>
-</div>
-{% endblock %}
-
diff --git a/api/funkwhale_api/templates/account/signup.html b/api/funkwhale_api/templates/account/signup.html
deleted file mode 100644
index 5db1cac7774dfb35f53db204359da722cb0d9c09..0000000000000000000000000000000000000000
--- a/api/funkwhale_api/templates/account/signup.html
+++ /dev/null
@@ -1,33 +0,0 @@
-{% extends "account/base.html" %}
-
-{% load i18n %}
-{% load crispy_forms_tags %}
-
-{% block title %}{% trans "Signup" %}{% endblock title %}
-
-{% block content %}
-
-<div class="container">
-  <div class="row">
-    <div class="col-md-5">
-      <h1>{% trans "Sign Up" %}</h1>
-
-      <p>{% blocktrans %}Already have an account? Then please <a href="{{ login_url }}">sign in</a>.{% endblocktrans %}</p>
-
-      <form class="signup" id="signup_form" method="post" action="{% url 'account_signup' %}">
-        {% csrf_token %}
-        {{ form|crispy }}
-        {% if redirect_field_value %}
-        <input type="hidden" name="{{ redirect_field_name }}" value="{{ redirect_field_value }}" />
-        {% endif %}
-        <button id="sign-up-button" class="btn btn-primary" type="submit">{% trans "Sign Up" %} &raquo;</button>
-      </form>
-    </div>
-  </div>
-</div>
-
-
-{% endblock content %}
-
-
-
diff --git a/api/funkwhale_api/templates/account/signup_closed.html b/api/funkwhale_api/templates/account/signup_closed.html
deleted file mode 100644
index 24e93d7bc801316cf9c773f76f83fde0f886bd27..0000000000000000000000000000000000000000
--- a/api/funkwhale_api/templates/account/signup_closed.html
+++ /dev/null
@@ -1,18 +0,0 @@
-{% extends "account/base.html" %}
-
-{% load i18n %}
-
-{% block head_title %}{% trans "Sign Up Closed" %}{% endblock %}
-
-{% block content %}
-<div class="container">
-  <div class="row">
-    <div class="col-md-5">
-        <h2>{% trans "Sign Up Closed" %}</h2>
-
-        <p>{% trans "We are sorry, but the sign up is currently closed." %}</p>
-    </div>
-  </div>
-</div>
-{% endblock %}
-
diff --git a/api/funkwhale_api/templates/account/verification_sent.html b/api/funkwhale_api/templates/account/verification_sent.html
deleted file mode 100644
index f994b46e0fee1d4a5d8e71b3bb9b387000f91e85..0000000000000000000000000000000000000000
--- a/api/funkwhale_api/templates/account/verification_sent.html
+++ /dev/null
@@ -1,18 +0,0 @@
-{% extends "account/base.html" %}
-
-{% load i18n %}
-
-{% block head_title %}{% trans "Verify Your E-mail Address" %}{% endblock %}
-
-{% block content %}
-<div class="container">
-  <div class="row">
-    <div class="col-md-5">
-        <h2>{% trans "Verify Your E-mail Address" %}</h2>
-        
-        <p>{% blocktrans %}We have sent an e-mail to <a href="mailto:{{ email }}">{{ email }}</a> for verification. Follow the link provided to finalize the signup process. Please contact us if you do not receive it within a few minutes.{% endblocktrans %}</p>
-    </div>    
-  </div>
-</div>
-
-{% endblock %}
diff --git a/api/funkwhale_api/templates/account/verified_email_required.html b/api/funkwhale_api/templates/account/verified_email_required.html
deleted file mode 100644
index bf4887a4aeca5037394a596765a27ba69519938b..0000000000000000000000000000000000000000
--- a/api/funkwhale_api/templates/account/verified_email_required.html
+++ /dev/null
@@ -1,28 +0,0 @@
-{% extends "account/base.html" %}
-
-{% load i18n %}
-
-{% block head_title %}{% trans "Verify Your E-mail Address" %}{% endblock %}
-
-{% block content %}
-<div class="container">
-  <div class="row">
-    <div class="col-md-5">
-        <h2>{% trans "Verify Your E-mail Address" %}</h2>
-
-        {% url 'account_email' as email_url %}
-
-        <p>{% blocktrans %}This part of the site requires us to verify that
-        you are who you claim to be. For this purpose, we require that you
-        verify ownership of your e-mail address. {% endblocktrans %}</p>
-
-        <p>{% blocktrans %}We have sent an e-mail to you for
-        verification. Please click on the link inside this e-mail. Please
-        contact us if you do not receive it within a few minutes.{% endblocktrans %}</p>
-
-        <p>{% blocktrans %}<strong>Note:</strong> you can still <a href="{{ email_url }}">change your e-mail address</a>.{% endblocktrans %}</p>
-    </div>
-  </div>
-</div>
-{% endblock %}
-
diff --git a/api/funkwhale_api/templates/users/user_detail.html b/api/funkwhale_api/templates/users/user_detail.html
deleted file mode 100644
index f8deb6b726cde67e8d93209f96d0bfe7357d1f15..0000000000000000000000000000000000000000
--- a/api/funkwhale_api/templates/users/user_detail.html
+++ /dev/null
@@ -1,36 +0,0 @@
-{% extends "base.html" %}
-{% load static %}
-
-{% block title %}User: {{ object.username }}{% endblock %}
-
-{% block content %}
-<div class="container">
-
-  <div class="row">
-    <div class="col-sm-12">
-
-      <h2>{{ object.username }}</h2>
-      {% if object.name %}
-        <p>{{ object.name }}</p>
-      {% endif %}
-    </div>
-  </div>
-
-{% if object == request.user %}
-<!-- Action buttons -->
-<div class="row">
-
-  <div class="col-sm-12 ">
-    <a class="btn btn-primary" href="{% url 'users:update' %}">My Info</a>
-    <a class="btn btn-primary" href="{% url 'account_email' %}">E-Mail</a>
-    <!-- Your Stuff: Custom user template urls -->
-  </div>
-
-</div>
-<!-- End Action buttons -->
-{% endif %}
-
-
-</div>
-{% endblock content %}
-
diff --git a/api/funkwhale_api/templates/users/user_form.html b/api/funkwhale_api/templates/users/user_form.html
deleted file mode 100644
index c07b8f2fce122a35af4d504b80a1326212cc7d58..0000000000000000000000000000000000000000
--- a/api/funkwhale_api/templates/users/user_form.html
+++ /dev/null
@@ -1,17 +0,0 @@
-{% extends "base.html" %}
-{% load crispy_forms_tags %}
-
-{% block title %}{{ user.username }}{% endblock %}
-
-{% block content %}
-    <h1>{{ user.username }}</h1>
-    <form class="form-horizontal" method="post" action="{% url 'users:update' %}">
-        {% csrf_token %}
-        {{ form|crispy }}
-        <div class="control-group">
-          <div class="controls">
-            <button type="submit" class="btn">Update</button>
-          </div>
-        </div>
-    </form>
-{% endblock %}
diff --git a/api/funkwhale_api/templates/users/user_list.html b/api/funkwhale_api/templates/users/user_list.html
deleted file mode 100644
index 7fbcc52d4af5c3c51d23f8ae3fb98da2d6875c4f..0000000000000000000000000000000000000000
--- a/api/funkwhale_api/templates/users/user_list.html
+++ /dev/null
@@ -1,20 +0,0 @@
-{% extends "base.html" %}
-{% load static %}{% load i18n %}
-{% block title %}Members{% endblock %}
-
-{% block content %}
-
-<div class="container">
-
-    <h2>Users</h2>
-
-    <div class="list-group">
-        {% for user in user_list %}
-          <a href="{% url 'users:detail' user.username %}" class="list-group-item">
-            <h4 class="list-group-item-heading">{{ user.username }}</h4>
-          </a>
-        {% endfor %}
-
-    </div>
-
-{% endblock content %}
diff --git a/api/funkwhale_api/users/tests/test_views.py b/api/funkwhale_api/users/tests/test_views.py
index 5f8233bc6c74ecd3a128b7a3d498f864652b4738..6250a7ca7873a51b7508dc548c8fe88373d25c24 100644
--- a/api/funkwhale_api/users/tests/test_views.py
+++ b/api/funkwhale_api/users/tests/test_views.py
@@ -42,7 +42,7 @@ class UserTestCase(TestCase):
         self.assertEqual(response.status_code, 403)
 
     def test_can_fetch_data_from_api(self):
-        url = self.reverse('api:users:users-me')
+        url = self.reverse('api:v1:users:users-me')
         response = self.client.get(url)
         # login required
         self.assertEqual(response.status_code, 401)
diff --git a/api/funkwhale_api/users/urls.py b/api/funkwhale_api/users/urls.py
deleted file mode 100644
index 94b18b2644efcb5737cefaf656d6eb07653ea5a7..0000000000000000000000000000000000000000
--- a/api/funkwhale_api/users/urls.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import absolute_import, unicode_literals
-
-from django.conf.urls import url
-
-from . import views
-
-urlpatterns = [
-    
-]
diff --git a/api/pytest.ini b/api/pytest.ini
new file mode 100644
index 0000000000000000000000000000000000000000..4ab907403097c796663f02d6a878ccd197970a23
--- /dev/null
+++ b/api/pytest.ini
@@ -0,0 +1,5 @@
+[pytest]
+DJANGO_SETTINGS_MODULE=config.settings.test
+
+# -- recommended but optional:
+python_files = tests.py test_*.py *_tests.py
diff --git a/api/requirements/base.txt b/api/requirements/base.txt
index 6265255727f8c7c295eb8d6a768ad7e59252dc4b..ae851962a47279cf40070fe5e053a77ab8fb5620 100644
--- a/api/requirements/base.txt
+++ b/api/requirements/base.txt
@@ -1,17 +1,11 @@
 # Bleeding edge Django
-django==1.8.7
+django==1.11
 
 # Configuration
 django-environ==0.4.0
 django-secure==1.0.1
 whitenoise==2.0.6
 
-
-# Forms
-django-braces==1.8.1
-# django-crispy-forms==1.5.2
-# django-floppyforms==1.5.2
-
 # Models
 django-model-utils==2.3.1
 
@@ -26,10 +20,6 @@ django-allauth==0.24.1
 # Python-PostgreSQL Database Adapter
 psycopg2==2.6.1
 
-# Unicode slugification
-unicode-slugify==0.1.3
-django-autoslug==1.9.3
-
 # Time zones support
 pytz==2015.7
 
@@ -42,21 +32,21 @@ celery==3.1.19
 
 
 # Your custom requirements go here
-django-cors-headers
-musicbrainzngs
+django-cors-headers==2.1.0
+musicbrainzngs==0.6
 youtube_dl>=2015.12.21
-djangorestframework
-djangorestframework-jwt
-django-celery
-django-mptt
-google-api-python-client
-arrow
-django-taggit
-persisting_theory
-django-versatileimagefield
-django-cachalot
-django-rest-auth
-beautifulsoup4
-markdown
-ipython
-mutagen
+djangorestframework==3.6.3
+djangorestframework-jwt==1.11.0
+django-celery==3.2.1
+django-mptt==0.8.7
+google-api-python-client==1.6.2
+arrow==0.10.0
+django-taggit==0.22.1
+persisting-theory==0.2.1
+django-versatileimagefield==1.7.1
+django-cachalot==1.5.0
+django-rest-auth==0.9.1
+beautifulsoup4==4.6.0
+Markdown==2.6.8
+ipython==6.1.0
+mutagen==1.38
diff --git a/api/requirements/local.txt b/api/requirements/local.txt
index ff8f35c7420546d2612e3f648fe87d363d9d959e..3f681b26d59d131468b54bda896477a3f6191624 100644
--- a/api/requirements/local.txt
+++ b/api/requirements/local.txt
@@ -2,11 +2,11 @@
 -r base.txt
 coverage==4.0.3
 django_coverage_plugin==1.1
-Sphinx
+Sphinx==1.6.2
 django-extensions==1.5.9
 Werkzeug==0.11.2
 django-test-plus==1.0.11
-factory_boy==2.6.0
+factory_boy>=2.8.1
 
 # django-debug-toolbar that works with Django 1.5+
 django-debug-toolbar>=1.5,<1.6
diff --git a/api/requirements/test.txt b/api/requirements/test.txt
index bcb6ef060cc73db6cf7f55e531c57a8177e79492..a26cf5bdbb1a8b60209fb7632c9acf3b3f9c0b08 100644
--- a/api/requirements/test.txt
+++ b/api/requirements/test.txt
@@ -1,12 +1,11 @@
 # Test dependencies go here.
--r base.txt
+-r local.txt
 
 
-
-coverage==4.0.3
-django_coverage_plugin==1.1
 flake8==2.5.0
-django-test-plus==1.0.11
-factory_boy>=2.8.1
-model_mommy
-tox
+model-mommy==1.3.2
+tox==2.7.0
+pytest
+pytest-django
+pytest-sugar
+pytest-xdist
diff --git a/api/runtests b/api/runtests
index bd8db7a8465f10f906fd006eb5399c73c6166758..48e7b82679e6cab413f4d435cfc0b8f794019bb0 100755
--- a/api/runtests
+++ b/api/runtests
@@ -2,4 +2,4 @@
 
 DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
 
-docker-compose -f $DIR/test.yml run test python manage.py test "$@"
+docker-compose -f $DIR/test.yml run test pytest "$@"
diff --git a/api/test.yml b/api/test.yml
index dc50a9b545d75382531f2684de6cb7ed3925be78..6215e27de0a6e237d119f6a0a3fa7eff4ae0dd7e 100644
--- a/api/test.yml
+++ b/api/test.yml
@@ -1,8 +1,6 @@
 test:
   dockerfile: docker/Dockerfile.test
   build: .
-  command: python manage.py test
+  command: pytest
   volumes:
     - .:/app
-  environment:
-    - DJANGO_SETTINGS_MODULE=config.settings.test
diff --git a/deploy/env.prod.sample b/deploy/env.prod.sample
index 38d355cb19d91868ed472fb9333e7e73359db66d..fd3b4327f7415ce2f18fa2a21fde4e30de73ca54 100644
--- a/deploy/env.prod.sample
+++ b/deploy/env.prod.sample
@@ -30,7 +30,7 @@ DJANGO_SETTINGS_MODULE=config.settings.production
 DJANGO_SECRET_KEY=
 
 # You don't have to edit this
-DJANGO_ADMIN_URL=^admin/
+DJANGO_ADMIN_URL=^api/admin/
 
 # Update it to match the domain that will be used to reach your funkwhale
 # instance
diff --git a/deploy/nginx.conf b/deploy/nginx.conf
index 0b2a534dff2400761e1a670444ff6933517c3596..7395e37d90a4fa5b1ce800ce5c4afdfff2dfb5a4 100644
--- a/deploy/nginx.conf
+++ b/deploy/nginx.conf
@@ -40,7 +40,7 @@ server {
         proxy_set_header Host $host;
         proxy_set_header X-Real-IP $remote_addr;
         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-        proxy_set_header X-Forwarded-Proto https;
+        proxy_set_header X-Forwarded-Proto $scheme;
         proxy_redirect off;
         proxy_pass   http://funkwhale-api/api/;
     }
@@ -48,6 +48,6 @@ server {
         alias /srv/funkwhale/data/media/;
     }
     location /staticfiles/ {
-        alias /srv/funkwhale/data/staticfiles/;
+        alias /srv/funkwhale/data/static/;
     }
 }
diff --git a/dev.yml b/dev.yml
index 526ce1ba870f89bb8c6d2864b7db3bf026cc0b86..21b0912e397d554793b387892fa9a7c816f5f090 100644
--- a/dev.yml
+++ b/dev.yml
@@ -36,17 +36,18 @@ services:
         - C_FORCE_ROOT=true
     volumes:
       - ./api:/app
-
+      - ./data/music:/music
   api:
     env_file: .env.dev
     build:
       context: ./api
-      dockerfile: docker/Dockerfile.local
+      dockerfile: docker/Dockerfile.test
     command: python /app/manage.py runserver 0.0.0.0:12081
     volumes:
       - ./api:/app
+      - ./data/music:/music
     ports:
-      - "12081"
+      - "12081:12081"
     links:
       - postgres
       - redis
diff --git a/docs/changelog.rst b/docs/changelog.rst
new file mode 100644
index 0000000000000000000000000000000000000000..c4092dc8b549d8157b77500da46731d7b2cbb103
--- /dev/null
+++ b/docs/changelog.rst
@@ -0,0 +1,10 @@
+Changelog
+=========
+
+
+0.1
+-------
+
+2017-06-26
+
+Initial release
diff --git a/docs/importing-music.rst b/docs/importing-music.rst
index 29f33ffc72c56574bf43102a9f4df222305c0a25..5fdff7e931c571b07632dfe2a659f239c928757f 100644
--- a/docs/importing-music.rst
+++ b/docs/importing-music.rst
@@ -14,14 +14,18 @@ least an ``artist``, ``album`` and ``title`` tag, you can import those tracks as
 
     docker-compose run --rm api python manage.py import_files "/music/**/*.ogg" --recursive --noinput
 
+For the best results, we recommand tagging your music collection through
+`Picard <http://picard.musicbrainz.org/>`_ in order to have the best quality metadata.
+
 .. note::
 
     This command is idempotent, meaning you can run it multiple times on the same
     files and already imported files will simply be skipped.
 
-.. warning::
+.. note::
+
+    At the moment, only OGG/Vorbis and MP3 files with ID3 tags are supported
 
-    At the moment, only ogg files are supported. MP3 support will be implemented soon.
 
 Getting demo tracks
 ^^^^^^^^^^^^^^^^^^^
diff --git a/docs/index.rst b/docs/index.rst
index b4c4119bf6253a67a9a78310f06967854e2f0dc2..ca6504b52ba6293f7a88947c8d7073740d3d590f 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -14,7 +14,7 @@ Funkwhale is a self-hosted, modern free and open-source music server, heavily in
    features
    installation/index
    importing-music
-
+   changelog
 
 Indices and tables
 ==================
diff --git a/front/src/config.js b/front/src/config.js
index 03e7a18211f01fe001f4fc791ec3a889f506ac37..5bc5c160e5109e3aa4478c4e40c7485d3ccd9911 100644
--- a/front/src/config.js
+++ b/front/src/config.js
@@ -4,7 +4,7 @@ class Config {
     if (!this.BACKEND_URL.endsWith('/')) {
       this.BACKEND_URL += '/'
     }
-    this.API_URL = this.BACKEND_URL + 'api/'
+    this.API_URL = this.BACKEND_URL + 'api/v1/'
   }
 }