diff --git a/swh/core/api.py b/swh/core/api/__init__.py
similarity index 100%
rename from swh/core/api.py
rename to swh/core/api/__init__.py
diff --git a/swh/core/api_async.py b/swh/core/api/asynchronous.py
similarity index 100%
copy from swh/core/api_async.py
copy to swh/core/api/asynchronous.py
diff --git a/swh/core/serializers.py b/swh/core/api/serializers.py
similarity index 100%
rename from swh/core/serializers.py
rename to swh/core/api/serializers.py
diff --git a/swh/core/api_async.py b/swh/core/api_async.py
index eed981e..9502582 100644
--- a/swh/core/api_async.py
+++ b/swh/core/api_async.py
@@ -1,56 +1 @@
-import aiohttp.web
-import asyncio
-import json
-import logging
-import multidict
-import pickle
-import sys
-import traceback
-
-from .serializers import msgpack_dumps, msgpack_loads, SWHJSONDecoder
-
-
-def encode_data_server(data, **kwargs):
-    return aiohttp.web.Response(
-        body=msgpack_dumps(data),
-        headers=multidict.MultiDict({'Content-Type': 'application/x-msgpack'}),
-        **kwargs
-    )
-
-
-@asyncio.coroutine
-def decode_request(request):
-    content_type = request.headers.get('Content-Type')
-    data = yield from request.read()
-
-    if content_type == 'application/x-msgpack':
-        r = msgpack_loads(data)
-    elif content_type == 'application/json':
-        r = json.loads(data, cls=SWHJSONDecoder)
-    else:
-        raise ValueError('Wrong content type `%s` for API request'
-                         % content_type)
-    return r
-
-
-@asyncio.coroutine
-def error_middleware(app, handler):
-    @asyncio.coroutine
-    def middleware_handler(request):
-        try:
-            return (yield from handler(request))
-        except Exception as e:
-            if isinstance(e, aiohttp.web.HTTPException):
-                raise
-            logging.exception(e)
-            exception = traceback.format_exception(*sys.exc_info())
-            res = {'exception': exception,
-                   'exception_pickled': pickle.dumps(e)}
-            return encode_data_server(res, status=500)
-    return middleware_handler
-
-
-class SWHRemoteAPI(aiohttp.web.Application):
-    def __init__(self, *args, middlewares=(), **kwargs):
-        middlewares = (error_middleware,) + middlewares
-        super().__init__(*args, middlewares=middlewares, **kwargs)
+from swh.core.api.asynchronous import *  # noqa, for bw compat
diff --git a/swh/core/tests/test_serializers.py b/swh/core/tests/test_serializers.py
index f9e80e9..40aec9a 100644
--- a/swh/core/tests/test_serializers.py
+++ b/swh/core/tests/test_serializers.py
@@ -1,81 +1,81 @@
 # Copyright (C) 2015-2018  The Software Heritage developers
 # See the AUTHORS file at the top-level directory of this distribution
 # License: GNU General Public License version 3, or any later version
 # See top-level LICENSE file for more information
 
 import datetime
 import json
 import unittest
 from uuid import UUID
 
 import arrow
 
-from swh.core.serializers import (
+from swh.core.api.serializers import (
     SWHJSONDecoder,
     SWHJSONEncoder,
     msgpack_dumps,
     msgpack_loads
 )
 
 
 class Serializers(unittest.TestCase):
     def setUp(self):
         self.tz = datetime.timezone(datetime.timedelta(minutes=118))
 
         self.data = {
             'bytes': b'123456789\x99\xaf\xff\x00\x12',
             'datetime_naive': datetime.datetime(2015, 1, 1, 12, 4, 42, 231455),
             'datetime_tz': datetime.datetime(2015, 3, 4, 18, 25, 13, 1234,
                                              tzinfo=self.tz),
             'datetime_utc': datetime.datetime(2015, 3, 4, 18, 25, 13, 1234,
                                               tzinfo=datetime.timezone.utc),
             'datetime_delta': datetime.timedelta(64),
             'arrow_date': arrow.get('2018-04-25T16:17:53.533672+00:00'),
             'swhtype': 'fake',
             'swh_dict': {'swhtype': 42, 'd': 'test'},
             'random_dict': {'swhtype': 43},
             'uuid': UUID('cdd8f804-9db6-40c3-93ab-5955d3836234'),
         }
 
         self.encoded_data = {
             'bytes': {'swhtype': 'bytes', 'd': 'F)}kWH8wXmIhn8j01^'},
             'datetime_naive': {'swhtype': 'datetime',
                                'd': '2015-01-01T12:04:42.231455'},
             'datetime_tz': {'swhtype': 'datetime',
                             'd': '2015-03-04T18:25:13.001234+01:58'},
             'datetime_utc': {'swhtype': 'datetime',
                              'd': '2015-03-04T18:25:13.001234+00:00'},
             'datetime_delta': {'swhtype': 'timedelta',
                                'd': {'days': 64, 'seconds': 0,
                                      'microseconds': 0}},
             'arrow_date': {'swhtype': 'arrow',
                            'd': '2018-04-25T16:17:53.533672+00:00'},
             'swhtype': 'fake',
             'swh_dict': {'swhtype': 42, 'd': 'test'},
             'random_dict': {'swhtype': 43},
             'uuid': {'swhtype': 'uuid',
                      'd': 'cdd8f804-9db6-40c3-93ab-5955d3836234'},
         }
 
         self.generator = (i for i in range(5))
         self.gen_lst = list(range(5))
 
     def test_round_trip_json(self):
         data = json.dumps(self.data, cls=SWHJSONEncoder)
         self.assertEqual(self.data, json.loads(data, cls=SWHJSONDecoder))
 
     def test_encode_swh_json(self):
         data = json.dumps(self.data, cls=SWHJSONEncoder)
         self.assertEqual(self.encoded_data, json.loads(data))
 
     def test_round_trip_msgpack(self):
         data = msgpack_dumps(self.data)
         self.assertEqual(self.data, msgpack_loads(data))
 
     def test_generator_json(self):
         data = json.dumps(self.generator, cls=SWHJSONEncoder)
         self.assertEqual(self.gen_lst, json.loads(data, cls=SWHJSONDecoder))
 
     def test_generator_msgpack(self):
         data = msgpack_dumps(self.generator)
         self.assertEqual(self.gen_lst, msgpack_loads(data))