diff --git a/src/http/middleware/groups.js b/src/http/middleware/groups.js
new file mode 100644
index 0000000000000000000000000000000000000000..8fd1ec9040e2b5b0918c5c69a29f1595bdda37c9
--- /dev/null
+++ b/src/http/middleware/groups.js
@@ -0,0 +1,15 @@
+const context = require('http/middleware/ThreadContextWrapper')
+const errors = require('http/middleware/ErrorHandler')
+const includes = require('http/middleware/ParseIncludes')
+const profiling = require('http/middleware/Profiler')
+const loaders = require('http/middleware/MountLoaders')
+const device = require('http/middleware/DeviceProperties').extractDevice
+
+exports.apiMiddlewareGroup = [
+	context,
+	profiling,
+	errors,
+	includes,
+	loaders,
+	device,
+]
diff --git a/src/http/routers/routes_v2.js b/src/http/routers/routes_v2.js
new file mode 100644
index 0000000000000000000000000000000000000000..cc55705e627026a13dedaf911a9fd8ada83a0b41
--- /dev/null
+++ b/src/http/routers/routes_v2.js
@@ -0,0 +1,77 @@
+const Router = require('@koa/router')
+const { apiMiddlewareGroup } = require('../middleware/groups')
+const router = new Router()
+
+const controller = (path, handler) => require(`../controllers/${path}`)[handler]
+const param = name => require(`../params/${name}`)
+
+apiMiddlewareGroup.forEach(middleware => router.use(middleware))
+
+router.post('/auth/login')
+router.post('/auth/register')
+router.post('/auth/password-reset')
+
+router.get('/self')
+router.get('/self/bundles')
+router.get('/self/:property')
+
+router.get('/metrics')
+router.post('/metrics')
+
+router.get('/images')
+router.post('/images')
+router.post('/images/:imageId/share')
+
+router.post('/an/ev', controller('api/analytics', 'track'))
+
+api.post('/metrics', controller('api/content', 'postMetric'))
+api.get('/metrics', controller('api/content', 'getWithin'))
+
+api.get('/images', controller('api/storage', 'getFiles'))
+api.post(
+	'/images',
+	upload.single('featured_image'),
+	controller('api/storage', 'saveFile'),
+)
+api.post('/images/:imageId/feature', controller('api/storage', 'featureImage'))
+
+/** @deprecated */
+api.post(
+	'/feature',
+	upload.single('featured_image'),
+	controller('api/storage', 'saveFile'),
+)
+
+api.get('/feed', controller('api/storage', 'feed'))
+api.post('/feed/:fileId/like', controller('api/storage', 'like'))
+api.post('/feed/:fileId/unlike', controller('api/storage', 'unlike'))
+
+api.post('/register', controller('api/auth', 'register'))
+api.post('/login', controller('api/auth', 'login'))
+
+api.post('/auth/reset-token', controller('api/auth', 'triggerPasswordReset'))
+api.post('/auth/reset-password', controller('api/auth', 'handlePasswordReset'))
+
+api.param('oauthClientId', param('oauth_client'))
+
+api.get('/oauth/clients', controller('api/oauth', 'listClients'))
+api.post('/oauth/clients', controller('api/oauth', 'createClient'))
+api.post(
+	'/oauth/clients/:oauthClientId/redirects',
+	controller('api/oauth', 'addClientRedirect'),
+)
+api.delete(
+	'/oauth/clients/:oauthClientId/redirects',
+	controller('api/oauth', 'removeClientRedirect'),
+)
+
+api.get('/self', controller('api/user', 'self'))
+api.get('/self/bundles', controller('api/app', 'getBundles'))
+api.put('/self/:property', controller('api/user', 'updateOne'))
+
+api.post('/an/id', async ctx => {})
+api.post('/an/ev', controller('api/analytics', 'track'))
+
+api.post('/feedback', controller('api/feedback', 'send'))
+
+module.exports = router
diff --git a/src/vendor/sentry.js b/src/vendor/sentry.js
index 84f12e23fa758283590eeef046edc3ffeb3107fe..afd80edcf4b16229a47efe8fb127f4c71dc042c9 100644
--- a/src/vendor/sentry.js
+++ b/src/vendor/sentry.js
@@ -1,7 +1,7 @@
 const Sentry = require('@sentry/node')
 const Tracing = require('@sentry/tracing')
 
-const blockedPaths = new Set(['/api/.secure/jwks', '/api'])
+const blockedPaths = new Set(['/api/.secure/jwks', '/api', '/api/an/ev'])
 
 exports.configure = function () {
 	const pkg = require('../../package.json')