diff --git a/src/components/follow_request_card/follow_request_card.js b/src/components/follow_request_card/follow_request_card.js
index 1a00a1c1..a8931787 100644
--- a/src/components/follow_request_card/follow_request_card.js
+++ b/src/components/follow_request_card/follow_request_card.js
@@ -7,11 +7,11 @@ const FollowRequestCard = {
   },
   methods: {
     approveUser () {
-      this.$store.state.api.backendInteractor.approveUser(this.user.id)
+      this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
       this.$store.dispatch('removeFollowRequest', this.user)
     },
     denyUser () {
-      this.$store.state.api.backendInteractor.denyUser(this.user.id)
+      this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
       this.$store.dispatch('removeFollowRequest', this.user)
     }
   }
diff --git a/src/components/moderation_tools/moderation_tools.js b/src/components/moderation_tools/moderation_tools.js
index 02b92fef..757166ed 100644
--- a/src/components/moderation_tools/moderation_tools.js
+++ b/src/components/moderation_tools/moderation_tools.js
@@ -45,12 +45,12 @@ const ModerationTools = {
     toggleTag (tag) {
       const store = this.$store
       if (this.tagsSet.has(tag)) {
-        store.state.api.backendInteractor.untagUser(this.user, tag).then(response => {
+        store.state.api.backendInteractor.untagUser({ user: this.user, tag }).then(response => {
           if (!response.ok) { return }
           store.commit('untagUser', { user: this.user, tag })
         })
       } else {
-        store.state.api.backendInteractor.tagUser(this.user, tag).then(response => {
+        store.state.api.backendInteractor.tagUser({ user: this.user, tag }).then(response => {
           if (!response.ok) { return }
           store.commit('tagUser', { user: this.user, tag })
         })
@@ -59,19 +59,19 @@ const ModerationTools = {
     toggleRight (right) {
       const store = this.$store
       if (this.user.rights[right]) {
-        store.state.api.backendInteractor.deleteRight(this.user, right).then(response => {
+        store.state.api.backendInteractor.deleteRight({ user: this.user, right }).then(response => {
           if (!response.ok) { return }
-          store.commit('updateRight', { user: this.user, right: right, value: false })
+          store.commit('updateRight', { user: this.user, right, value: false })
         })
       } else {
-        store.state.api.backendInteractor.addRight(this.user, right).then(response => {
+        store.state.api.backendInteractor.addRight({ user: this.user, right }).then(response => {
           if (!response.ok) { return }
-          store.commit('updateRight', { user: this.user, right: right, value: true })
+          store.commit('updateRight', { user: this.user, right, value: true })
         })
       }
     },
     toggleActivationStatus () {
-      this.$store.dispatch('toggleActivationStatus', this.user)
+      this.$store.dispatch('toggleActivationStatus', { user: this.user })
     },
     deleteUserDialog (show) {
       this.showDeleteUserDialog = show
@@ -80,7 +80,7 @@ const ModerationTools = {
       const store = this.$store
       const user = this.user
       const { id, name } = user
-      store.state.api.backendInteractor.deleteUser(user)
+      store.state.api.backendInteractor.deleteUser({ user })
         .then(e => {
           this.$store.dispatch('markStatusesAsDeleted', status => user.id === status.user.id)
           const isProfile = this.$route.name === 'external-user-profile' || this.$route.name === 'user-profile'
diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js
index 6c4054fd..a89c0cdc 100644
--- a/src/components/notifications/notifications.js
+++ b/src/components/notifications/notifications.js
@@ -47,6 +47,11 @@ const Notifications = {
   components: {
     Notification
   },
+  created () {
+    const { dispatch } = this.$store
+
+    dispatch('fetchAndUpdateNotifications')
+  },
   watch: {
     unseenCount (count) {
       if (count > 0) {
diff --git a/src/components/public_and_external_timeline/public_and_external_timeline.js b/src/components/public_and_external_timeline/public_and_external_timeline.js
index f614c13b..cbd4491b 100644
--- a/src/components/public_and_external_timeline/public_and_external_timeline.js
+++ b/src/components/public_and_external_timeline/public_and_external_timeline.js
@@ -10,7 +10,7 @@ const PublicAndExternalTimeline = {
     this.$store.dispatch('startFetchingTimeline', { timeline: 'publicAndExternal' })
   },
   destroyed () {
-    this.$store.dispatch('stopFetching', 'publicAndExternal')
+    this.$store.dispatch('stopFetchingTimeline', 'publicAndExternal')
   }
 }
 
diff --git a/src/components/public_timeline/public_timeline.js b/src/components/public_timeline/public_timeline.js
index 8976a99c..66c40d3a 100644
--- a/src/components/public_timeline/public_timeline.js
+++ b/src/components/public_timeline/public_timeline.js
@@ -10,7 +10,7 @@ const PublicTimeline = {
     this.$store.dispatch('startFetchingTimeline', { timeline: 'public' })
   },
   destroyed () {
-    this.$store.dispatch('stopFetching', 'public')
+    this.$store.dispatch('stopFetchingTimeline', 'public')
   }
 
 }
diff --git a/src/components/settings/settings.js b/src/components/settings/settings.js
index c49083f9..31a9e9be 100644
--- a/src/components/settings/settings.js
+++ b/src/components/settings/settings.js
@@ -84,7 +84,7 @@ const settings = {
         }
       }])
       .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
-    // Special cases (need to transform values)
+    // Special cases (need to transform values or perform actions first)
     muteWordsString: {
       get () { return this.$store.getters.mergedConfig.muteWords.join('\n') },
       set (value) {
@@ -93,6 +93,22 @@ const settings = {
           value: filter(value.split('\n'), (word) => trim(word).length > 0)
         })
       }
+    },
+    useStreamingApi: {
+      get () { return this.$store.getters.mergedConfig.useStreamingApi },
+      set (value) {
+        const promise = value
+          ? this.$store.dispatch('enableMastoSockets')
+          : this.$store.dispatch('disableMastoSockets')
+
+        promise.then(() => {
+          this.$store.dispatch('setOption', { name: 'useStreamingApi', value })
+        }).catch((e) => {
+          console.error('Failed starting MastoAPI Streaming socket', e)
+          this.$store.dispatch('disableMastoSockets')
+          this.$store.dispatch('setOption', { name: 'useStreamingApi', value: false })
+        })
+      }
     }
   },
   // Updating nested properties
diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue
index c4021137..b40c85dd 100644
--- a/src/components/settings/settings.vue
+++ b/src/components/settings/settings.vue
@@ -73,6 +73,15 @@
                     </li>
                   </ul>
                 </li>
+                <li>
+                  <Checkbox v-model="useStreamingApi">
+                    {{ $t('settings.useStreamingApi') }}
+                    <br/>
+                    <small>
+                    {{ $t('settings.useStreamingApiWarning') }}
+                    </small>
+                  </Checkbox>
+                </li>
                 <li>
                   <Checkbox v-model="autoLoad">
                     {{ $t('settings.autoload') }}
diff --git a/src/components/tag_timeline/tag_timeline.js b/src/components/tag_timeline/tag_timeline.js
index 458eb1c5..400c6a4b 100644
--- a/src/components/tag_timeline/tag_timeline.js
+++ b/src/components/tag_timeline/tag_timeline.js
@@ -19,7 +19,7 @@ const TagTimeline = {
     }
   },
   destroyed () {
-    this.$store.dispatch('stopFetching', 'tag')
+    this.$store.dispatch('stopFetchingTimeline', 'tag')
   }
 }
 
diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js
index 00055707..9558a0bd 100644
--- a/src/components/user_profile/user_profile.js
+++ b/src/components/user_profile/user_profile.js
@@ -112,9 +112,9 @@ const UserProfile = {
       }
     },
     stopFetching () {
-      this.$store.dispatch('stopFetching', 'user')
-      this.$store.dispatch('stopFetching', 'favorites')
-      this.$store.dispatch('stopFetching', 'media')
+      this.$store.dispatch('stopFetchingTimeline', 'user')
+      this.$store.dispatch('stopFetchingTimeline', 'favorites')
+      this.$store.dispatch('stopFetchingTimeline', 'media')
     },
     switchUser (userNameOrId) {
       this.stopFetching()
diff --git a/src/components/user_reporting_modal/user_reporting_modal.js b/src/components/user_reporting_modal/user_reporting_modal.js
index 833fa98a..38cf117b 100644
--- a/src/components/user_reporting_modal/user_reporting_modal.js
+++ b/src/components/user_reporting_modal/user_reporting_modal.js
@@ -64,7 +64,7 @@ const UserReportingModal = {
         forward: this.forward,
         statusIds: this.statusIdsToReport
       }
-      this.$store.state.api.backendInteractor.reportUser(params)
+      this.$store.state.api.backendInteractor.reportUser({ ...params })
         .then(() => {
           this.processing = false
           this.resetState()
diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index 3fdc5340..d5d671e4 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -242,7 +242,7 @@ const UserSettings = {
       })
     },
     importFollows (file) {
-      return this.$store.state.api.backendInteractor.importFollows(file)
+      return this.$store.state.api.backendInteractor.importFollows({ file })
         .then((status) => {
           if (!status) {
             throw new Error('failed')
@@ -250,7 +250,7 @@ const UserSettings = {
         })
     },
     importBlocks (file) {
-      return this.$store.state.api.backendInteractor.importBlocks(file)
+      return this.$store.state.api.backendInteractor.importBlocks({ file })
         .then((status) => {
           if (!status) {
             throw new Error('failed')
@@ -297,7 +297,7 @@ const UserSettings = {
         newPassword: this.changePasswordInputs[1],
         newPasswordConfirmation: this.changePasswordInputs[2]
       }
-      this.$store.state.api.backendInteractor.changePassword(params)
+      this.$store.state.api.backendInteractor.changePassword({ params })
         .then((res) => {
           if (res.status === 'success') {
             this.changedPassword = true
@@ -314,7 +314,7 @@ const UserSettings = {
         email: this.newEmail,
         password: this.changeEmailPassword
       }
-      this.$store.state.api.backendInteractor.changeEmail(params)
+      this.$store.state.api.backendInteractor.changeEmail({ params })
         .then((res) => {
           if (res.status === 'success') {
             this.changedEmail = true
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 85146ef5..60fc792f 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -358,6 +358,8 @@
     "post_status_content_type": "Post status content type",
     "stop_gifs": "Play-on-hover GIFs",
     "streaming": "Enable automatic streaming of new posts when scrolled to the top",
+    "useStreamingApi": "Receive posts and notifications real-time",
+    "useStreamingApiWarning": "(Not recommended, experimental, known to skip posts)",
     "text": "Text",
     "theme": "Theme",
     "theme_help": "Use hex color codes (#rrggbb) to customize your color theme.",
diff --git a/src/i18n/ru.json b/src/i18n/ru.json
index 19e10f1e..4cb2d497 100644
--- a/src/i18n/ru.json
+++ b/src/i18n/ru.json
@@ -219,6 +219,8 @@
     "subject_input_always_show": "Всегда показывать поле ввода темы",
     "stop_gifs": "Проигрывать GIF анимации только при наведении",
     "streaming": "Включить автоматическую загрузку новых сообщений при прокрутке вверх",
+    "useStreamingApi": "Получать сообщения и уведомления в реальном времени",
+    "useStreamingApiWarning": "(Не рекомендуется, экспериментально, сообщения могут пропадать)",
     "text": "Текст",
     "theme": "Тема",
     "theme_help": "Используйте шестнадцатеричные коды цветов (#rrggbb) для настройки темы.",
diff --git a/src/modules/api.js b/src/modules/api.js
index 1293e3c8..9c296275 100644
--- a/src/modules/api.js
+++ b/src/modules/api.js
@@ -6,6 +6,7 @@ const api = {
     backendInteractor: backendInteractorService(),
     fetchers: {},
     socket: null,
+    mastoUserSocket: null,
     followRequests: []
   },
   mutations: {
@@ -15,7 +16,8 @@ const api = {
     addFetcher (state, { fetcherName, fetcher }) {
       state.fetchers[fetcherName] = fetcher
     },
-    removeFetcher (state, { fetcherName }) {
+    removeFetcher (state, { fetcherName, fetcher }) {
+      window.clearInterval(fetcher)
       delete state.fetchers[fetcherName]
     },
     setWsToken (state, token) {
@@ -29,32 +31,134 @@ const api = {
     }
   },
   actions: {
-    startFetchingTimeline (store, { timeline = 'friends', tag = false, userId = false }) {
-      // Don't start fetching if we already are.
+    // Global MastoAPI socket control, in future should disable ALL sockets/(re)start relevant sockets
+    enableMastoSockets (store) {
+      const { state, dispatch } = store
+      if (state.mastoUserSocket) return
+      return dispatch('startMastoUserSocket')
+    },
+    disableMastoSockets (store) {
+      const { state, dispatch } = store
+      if (!state.mastoUserSocket) return
+      return dispatch('stopMastoUserSocket')
+    },
+
+    // MastoAPI 'User' sockets
+    startMastoUserSocket (store) {
+      return new Promise((resolve, reject) => {
+        try {
+          const { state, dispatch, rootState } = store
+          const timelineData = rootState.statuses.timelines.friends
+          state.mastoUserSocket = state.backendInteractor.startUserSocket({ store })
+          state.mastoUserSocket.addEventListener(
+            'message',
+            ({ detail: message }) => {
+              if (!message) return // pings
+              if (message.event === 'notification') {
+                dispatch('addNewNotifications', {
+                  notifications: [message.notification],
+                  older: false
+                })
+              } else if (message.event === 'update') {
+                dispatch('addNewStatuses', {
+                  statuses: [message.status],
+                  userId: false,
+                  showImmediately: timelineData.visibleStatuses.length === 0,
+                  timeline: 'friends'
+                })
+              }
+            }
+          )
+          state.mastoUserSocket.addEventListener('error', ({ detail: error }) => {
+            console.error('Error in MastoAPI websocket:', error)
+          })
+          state.mastoUserSocket.addEventListener('close', ({ detail: closeEvent }) => {
+            const ignoreCodes = new Set([
+              1000, // Normal (intended) closure
+              1001 // Going away
+            ])
+            const { code } = closeEvent
+            if (ignoreCodes.has(code)) {
+              console.debug(`Not restarting socket becasue of closure code ${code} is in ignore list`)
+            } else {
+              console.warn(`MastoAPI websocket disconnected, restarting. CloseEvent code: ${code}`)
+              dispatch('startFetchingTimeline', { timeline: 'friends' })
+              dispatch('startFetchingNotifications')
+              dispatch('restartMastoUserSocket')
+            }
+          })
+          resolve()
+        } catch (e) {
+          reject(e)
+        }
+      })
+    },
+    restartMastoUserSocket ({ dispatch }) {
+      // This basically starts MastoAPI user socket and stops conventional
+      // fetchers when connection reestablished
+      return dispatch('startMastoUserSocket').then(() => {
+        dispatch('stopFetchingTimeline', { timeline: 'friends' })
+        dispatch('stopFetchingNotifications')
+      })
+    },
+    stopMastoUserSocket ({ state, dispatch }) {
+      dispatch('startFetchingTimeline', { timeline: 'friends' })
+      dispatch('startFetchingNotifications')
+      console.log(state.mastoUserSocket)
+      state.mastoUserSocket.close()
+    },
+
+    // Timelines
+    startFetchingTimeline (store, {
+      timeline = 'friends',
+      tag = false,
+      userId = false
+    }) {
       if (store.state.fetchers[timeline]) return
 
-      const fetcher = store.state.backendInteractor.startFetchingTimeline({ timeline, store, userId, tag })
+      const fetcher = store.state.backendInteractor.startFetchingTimeline({
+        timeline, store, userId, tag
+      })
       store.commit('addFetcher', { fetcherName: timeline, fetcher })
     },
-    startFetchingNotifications (store) {
-      // Don't start fetching if we already are.
-      if (store.state.fetchers['notifications']) return
+    stopFetchingTimeline (store, timeline) {
+      const fetcher = store.state.fetchers[timeline]
+      if (!fetcher) return
+      store.commit('removeFetcher', { fetcherName: timeline, fetcher })
+    },
 
+    // Notifications
+    startFetchingNotifications (store) {
+      if (store.state.fetchers.notifications) return
       const fetcher = store.state.backendInteractor.startFetchingNotifications({ store })
       store.commit('addFetcher', { fetcherName: 'notifications', fetcher })
     },
-    startFetchingFollowRequest (store) {
-      // Don't start fetching if we already are.
-      if (store.state.fetchers['followRequest']) return
+    stopFetchingNotifications (store) {
+      const fetcher = store.state.fetchers.notifications
+      if (!fetcher) return
+      store.commit('removeFetcher', { fetcherName: 'notifications', fetcher })
+    },
+    fetchAndUpdateNotifications (store) {
+      store.state.backendInteractor.fetchAndUpdateNotifications({ store })
+    },
 
-      const fetcher = store.state.backendInteractor.startFetchingFollowRequest({ store })
-      store.commit('addFetcher', { fetcherName: 'followRequest', fetcher })
+    // Follow requests
+    startFetchingFollowRequests (store) {
+      if (store.state.fetchers['followRequests']) return
+      const fetcher = store.state.backendInteractor.startFetchingFollowRequests({ store })
+      store.commit('addFetcher', { fetcherName: 'followRequests', fetcher })
     },
-    stopFetching (store, fetcherName) {
-      const fetcher = store.state.fetchers[fetcherName]
-      window.clearInterval(fetcher)
-      store.commit('removeFetcher', { fetcherName })
+    stopFetchingFollowRequests (store) {
+      const fetcher = store.state.fetchers.followRequests
+      if (!fetcher) return
+      store.commit('removeFetcher', { fetcherName: 'followRequests', fetcher })
     },
+    removeFollowRequest (store, request) {
+      let requests = store.state.followRequests.filter((it) => it !== request)
+      store.commit('setFollowRequests', requests)
+    },
+
+    // Pleroma websocket
     setWsToken (store, token) {
       store.commit('setWsToken', token)
     },
@@ -72,10 +176,6 @@ const api = {
     disconnectFromSocket ({ commit, state }) {
       state.socket && state.socket.disconnect()
       commit('setSocket', null)
-    },
-    removeFollowRequest (store, request) {
-      let requests = store.state.followRequests.filter((it) => it !== request)
-      store.commit('setFollowRequests', requests)
     }
   }
 }
diff --git a/src/modules/config.js b/src/modules/config.js
index 329b4091..74025db1 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -35,6 +35,7 @@ export const defaultState = {
   highlight: {},
   interfaceLanguage: browserLocale,
   hideScopeNotice: false,
+  useStreamingApi: false,
   scopeCopy: undefined, // instance default
   subjectLineBehavior: undefined, // instance default
   alwaysShowSubjectInput: undefined, // instance default
diff --git a/src/modules/oauth_tokens.js b/src/modules/oauth_tokens.js
index 0159a3f1..907cae4a 100644
--- a/src/modules/oauth_tokens.js
+++ b/src/modules/oauth_tokens.js
@@ -9,7 +9,7 @@ const oauthTokens = {
       })
     },
     revokeToken ({ rootState, commit, state }, id) {
-      rootState.api.backendInteractor.revokeOAuthToken(id).then((response) => {
+      rootState.api.backendInteractor.revokeOAuthToken({ id }).then((response) => {
         if (response.status === 201) {
           commit('swapTokens', state.tokens.filter(token => token.id !== id))
         }
diff --git a/src/modules/polls.js b/src/modules/polls.js
index e6158b63..92b89a06 100644
--- a/src/modules/polls.js
+++ b/src/modules/polls.js
@@ -40,7 +40,7 @@ const polls = {
       commit('mergeOrAddPoll', poll)
     },
     updateTrackedPoll ({ rootState, dispatch, commit }, pollId) {
-      rootState.api.backendInteractor.fetchPoll(pollId).then(poll => {
+      rootState.api.backendInteractor.fetchPoll({ pollId }).then(poll => {
         setTimeout(() => {
           if (rootState.polls.trackedPolls[pollId]) {
             dispatch('updateTrackedPoll', pollId)
@@ -59,7 +59,7 @@ const polls = {
       commit('untrackPoll', pollId)
     },
     votePoll ({ rootState, commit }, { id, pollId, choices }) {
-      return rootState.api.backendInteractor.vote(pollId, choices).then(poll => {
+      return rootState.api.backendInteractor.vote({ pollId, choices }).then(poll => {
         commit('mergeOrAddPoll', poll)
         return poll
       })
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index e3a1f293..7d88761c 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -558,45 +558,45 @@ const statuses = {
     favorite ({ rootState, commit }, status) {
       // Optimistic favoriting...
       commit('setFavorited', { status, value: true })
-      rootState.api.backendInteractor.favorite(status.id)
+      rootState.api.backendInteractor.favorite({ id: status.id })
         .then(status => commit('setFavoritedConfirm', { status, user: rootState.users.currentUser }))
     },
     unfavorite ({ rootState, commit }, status) {
       // Optimistic unfavoriting...
       commit('setFavorited', { status, value: false })
-      rootState.api.backendInteractor.unfavorite(status.id)
+      rootState.api.backendInteractor.unfavorite({ id: status.id })
         .then(status => commit('setFavoritedConfirm', { status, user: rootState.users.currentUser }))
     },
     fetchPinnedStatuses ({ rootState, dispatch }, userId) {
-      rootState.api.backendInteractor.fetchPinnedStatuses(userId)
+      rootState.api.backendInteractor.fetchPinnedStatuses({ id: userId })
         .then(statuses => dispatch('addNewStatuses', { statuses, timeline: 'user', userId, showImmediately: true, noIdUpdate: true }))
     },
     pinStatus ({ rootState, dispatch }, statusId) {
-      return rootState.api.backendInteractor.pinOwnStatus(statusId)
+      return rootState.api.backendInteractor.pinOwnStatus({ id: statusId })
         .then((status) => dispatch('addNewStatuses', { statuses: [status] }))
     },
     unpinStatus ({ rootState, dispatch }, statusId) {
-      rootState.api.backendInteractor.unpinOwnStatus(statusId)
+      rootState.api.backendInteractor.unpinOwnStatus({ id: statusId })
         .then((status) => dispatch('addNewStatuses', { statuses: [status] }))
     },
     muteConversation ({ rootState, commit }, statusId) {
-      return rootState.api.backendInteractor.muteConversation(statusId)
+      return rootState.api.backendInteractor.muteConversation({ id: statusId })
         .then((status) => commit('setMutedStatus', status))
     },
     unmuteConversation ({ rootState, commit }, statusId) {
-      return rootState.api.backendInteractor.unmuteConversation(statusId)
+      return rootState.api.backendInteractor.unmuteConversation({ id: statusId })
         .then((status) => commit('setMutedStatus', status))
     },
     retweet ({ rootState, commit }, status) {
       // Optimistic retweeting...
       commit('setRetweeted', { status, value: true })
-      rootState.api.backendInteractor.retweet(status.id)
+      rootState.api.backendInteractor.retweet({ id: status.id })
         .then(status => commit('setRetweetedConfirm', { status: status.retweeted_status, user: rootState.users.currentUser }))
     },
     unretweet ({ rootState, commit }, status) {
       // Optimistic unretweeting...
       commit('setRetweeted', { status, value: false })
-      rootState.api.backendInteractor.unretweet(status.id)
+      rootState.api.backendInteractor.unretweet({ id: status.id })
         .then(status => commit('setRetweetedConfirm', { status, user: rootState.users.currentUser }))
     },
     queueFlush ({ rootState, commit }, { timeline, id }) {
@@ -611,19 +611,19 @@ const statuses = {
     },
     fetchFavsAndRepeats ({ rootState, commit }, id) {
       Promise.all([
-        rootState.api.backendInteractor.fetchFavoritedByUsers(id),
-        rootState.api.backendInteractor.fetchRebloggedByUsers(id)
+        rootState.api.backendInteractor.fetchFavoritedByUsers({ id }),
+        rootState.api.backendInteractor.fetchRebloggedByUsers({ id })
       ]).then(([favoritedByUsers, rebloggedByUsers]) => {
         commit('addFavs', { id, favoritedByUsers, currentUser: rootState.users.currentUser })
         commit('addRepeats', { id, rebloggedByUsers, currentUser: rootState.users.currentUser })
       })
     },
     fetchFavs ({ rootState, commit }, id) {
-      rootState.api.backendInteractor.fetchFavoritedByUsers(id)
+      rootState.api.backendInteractor.fetchFavoritedByUsers({ id })
         .then(favoritedByUsers => commit('addFavs', { id, favoritedByUsers, currentUser: rootState.users.currentUser }))
     },
     fetchRepeats ({ rootState, commit }, id) {
-      rootState.api.backendInteractor.fetchRebloggedByUsers(id)
+      rootState.api.backendInteractor.fetchRebloggedByUsers({ id })
         .then(rebloggedByUsers => commit('addRepeats', { id, rebloggedByUsers, currentUser: rootState.users.currentUser }))
     },
     search (store, { q, resolve, limit, offset, following }) {
diff --git a/src/modules/users.js b/src/modules/users.js
index 82d3c4e8..e54588df 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -32,7 +32,7 @@ const getNotificationPermission = () => {
 }
 
 const blockUser = (store, id) => {
-  return store.rootState.api.backendInteractor.blockUser(id)
+  return store.rootState.api.backendInteractor.blockUser({ id })
     .then((relationship) => {
       store.commit('updateUserRelationship', [relationship])
       store.commit('addBlockId', id)
@@ -43,12 +43,12 @@ const blockUser = (store, id) => {
 }
 
 const unblockUser = (store, id) => {
-  return store.rootState.api.backendInteractor.unblockUser(id)
+  return store.rootState.api.backendInteractor.unblockUser({ id })
     .then((relationship) => store.commit('updateUserRelationship', [relationship]))
 }
 
 const muteUser = (store, id) => {
-  return store.rootState.api.backendInteractor.muteUser(id)
+  return store.rootState.api.backendInteractor.muteUser({ id })
     .then((relationship) => {
       store.commit('updateUserRelationship', [relationship])
       store.commit('addMuteId', id)
@@ -56,7 +56,7 @@ const muteUser = (store, id) => {
 }
 
 const unmuteUser = (store, id) => {
-  return store.rootState.api.backendInteractor.unmuteUser(id)
+  return store.rootState.api.backendInteractor.unmuteUser({ id })
     .then((relationship) => store.commit('updateUserRelationship', [relationship]))
 }
 
@@ -324,11 +324,11 @@ const users = {
       commit('clearFollowers', userId)
     },
     subscribeUser ({ rootState, commit }, id) {
-      return rootState.api.backendInteractor.subscribeUser(id)
+      return rootState.api.backendInteractor.subscribeUser({ id })
         .then((relationship) => commit('updateUserRelationship', [relationship]))
     },
     unsubscribeUser ({ rootState, commit }, id) {
-      return rootState.api.backendInteractor.unsubscribeUser(id)
+      return rootState.api.backendInteractor.unsubscribeUser({ id })
         .then((relationship) => commit('updateUserRelationship', [relationship]))
     },
     toggleActivationStatus ({ rootState, commit }, user) {
@@ -387,7 +387,7 @@ const users = {
       })
     },
     searchUsers (store, query) {
-      return store.rootState.api.backendInteractor.searchUsers(query)
+      return store.rootState.api.backendInteractor.searchUsers({ query })
         .then((users) => {
           store.commit('addNewUsers', users)
           return users
@@ -399,7 +399,7 @@ const users = {
       let rootState = store.rootState
 
       try {
-        let data = await rootState.api.backendInteractor.register(userInfo)
+        let data = await rootState.api.backendInteractor.register({ ...userInfo })
         store.commit('signUpSuccess')
         store.commit('setToken', data.access_token)
         store.dispatch('loginUser', data.access_token)
@@ -436,10 +436,10 @@ const users = {
           store.commit('clearCurrentUser')
           store.dispatch('disconnectFromSocket')
           store.commit('clearToken')
-          store.dispatch('stopFetching', 'friends')
+          store.dispatch('stopFetchingTimeline', 'friends')
           store.commit('setBackendInteractor', backendInteractorService(store.getters.getToken()))
-          store.dispatch('stopFetching', 'notifications')
-          store.dispatch('stopFetching', 'followRequest')
+          store.dispatch('stopFetchingNotifications')
+          store.dispatch('stopFetchingFollowRequests')
           store.commit('clearNotifications')
           store.commit('resetStatuses')
         })
@@ -474,11 +474,24 @@ const users = {
                 store.dispatch('initializeSocket')
               }
 
-              // Start getting fresh posts.
-              store.dispatch('startFetchingTimeline', { timeline: 'friends' })
+              const startPolling = () => {
+                // Start getting fresh posts.
+                store.dispatch('startFetchingTimeline', { timeline: 'friends' })
 
-              // Start fetching notifications
-              store.dispatch('startFetchingNotifications')
+                // Start fetching notifications
+                store.dispatch('startFetchingNotifications')
+              }
+
+              if (store.getters.mergedConfig.useStreamingApi) {
+                store.dispatch('enableMastoSockets').catch((error) => {
+                  console.error('Failed initializing MastoAPI Streaming socket', error)
+                  startPolling()
+                }).then(() => {
+                  setTimeout(() => store.dispatch('setNotificationsSilence', false), 10000)
+                })
+              } else {
+                startPolling()
+              }
 
               // Get user mutes
               store.dispatch('fetchMutes')
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index a69fa53c..ef0267aa 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -72,6 +72,7 @@ const MASTODON_MUTE_CONVERSATION = id => `/api/v1/statuses/${id}/mute`
 const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute`
 const MASTODON_SEARCH_2 = `/api/v2/search`
 const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search'
+const MASTODON_STREAMING = '/api/v1/streaming'
 
 const oldfetch = window.fetch
 
@@ -451,7 +452,7 @@ const deleteRight = ({ right, credentials, ...user }) => {
   })
 }
 
-const activateUser = ({ credentials, screen_name: nickname }) => {
+const activateUser = ({ credentials, user: { screen_name: nickname } }) => {
   return promisedRequest({
     url: ACTIVATE_USER_URL,
     method: 'PATCH',
@@ -462,7 +463,7 @@ const activateUser = ({ credentials, screen_name: nickname }) => {
   }).then(response => get(response, 'users.0'))
 }
 
-const deactivateUser = ({ credentials, screen_name: nickname }) => {
+const deactivateUser = ({ credentials, user: { screen_name: nickname } }) => {
   return promisedRequest({
     url: DEACTIVATE_USER_URL,
     method: 'PATCH',
@@ -947,6 +948,99 @@ const search2 = ({ credentials, q, resolve, limit, offset, following }) => {
     })
 }
 
+export const getMastodonSocketURI = ({ credentials, stream, args = {} }) => {
+  return Object.entries({
+    ...(credentials
+      ? { access_token: credentials }
+      : {}
+    ),
+    stream,
+    ...args
+  }).reduce((acc, [key, val]) => {
+    return acc + `${key}=${val}&`
+  }, MASTODON_STREAMING + '?')
+}
+
+const MASTODON_STREAMING_EVENTS = new Set([
+  'update',
+  'notification',
+  'delete',
+  'filters_changed'
+])
+
+// A thin wrapper around WebSocket API that allows adding a pre-processor to it
+// Uses EventTarget and a CustomEvent to proxy events
+export const ProcessedWS = ({
+  url,
+  preprocessor = handleMastoWS,
+  id = 'Unknown'
+}) => {
+  const eventTarget = new EventTarget()
+  const socket = new WebSocket(url)
+  if (!socket) throw new Error(`Failed to create socket ${id}`)
+  const proxy = (original, eventName, processor = a => a) => {
+    original.addEventListener(eventName, (eventData) => {
+      eventTarget.dispatchEvent(new CustomEvent(
+        eventName,
+        { detail: processor(eventData) }
+      ))
+    })
+  }
+  socket.addEventListener('open', (wsEvent) => {
+    console.debug(`[WS][${id}] Socket connected`, wsEvent)
+  })
+  socket.addEventListener('error', (wsEvent) => {
+    console.debug(`[WS][${id}] Socket errored`, wsEvent)
+  })
+  socket.addEventListener('close', (wsEvent) => {
+    console.debug(
+      `[WS][${id}] Socket disconnected with code ${wsEvent.code}`,
+      wsEvent
+    )
+  })
+  // Commented code reason: very spammy, uncomment to enable message debug logging
+  /*
+  socket.addEventListener('message', (wsEvent) => {
+    console.debug(
+      `[WS][${id}] Message received`,
+      wsEvent
+    )
+  })
+  /**/
+
+  proxy(socket, 'open')
+  proxy(socket, 'close')
+  proxy(socket, 'message', preprocessor)
+  proxy(socket, 'error')
+
+  // 1000 = Normal Closure
+  eventTarget.close = () => { socket.close(1000, 'Shutting down socket') }
+
+  return eventTarget
+}
+
+export const handleMastoWS = (wsEvent) => {
+  const { data } = wsEvent
+  if (!data) return
+  const parsedEvent = JSON.parse(data)
+  const { event, payload } = parsedEvent
+  if (MASTODON_STREAMING_EVENTS.has(event)) {
+    // MastoBE and PleromaBE both send payload for delete as a PLAIN string
+    if (event === 'delete') {
+      return { event, id: payload }
+    }
+    const data = payload ? JSON.parse(payload) : null
+    if (event === 'update') {
+      return { event, status: parseStatus(data) }
+    } else if (event === 'notification') {
+      return { event, notification: parseNotification(data) }
+    }
+  } else {
+    console.warn('Unknown event', wsEvent)
+    return null
+  }
+}
+
 const apiService = {
   verifyCredentials,
   fetchTimeline,
diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js
index 3d4ec6ac..b7372ed0 100644
--- a/src/services/backend_interactor_service/backend_interactor_service.js
+++ b/src/services/backend_interactor_service/backend_interactor_service.js
@@ -1,236 +1,39 @@
-import apiService from '../api/api.service.js'
+import apiService, { getMastodonSocketURI, ProcessedWS } from '../api/api.service.js'
 import timelineFetcherService from '../timeline_fetcher/timeline_fetcher.service.js'
 import notificationsFetcher from '../notifications_fetcher/notifications_fetcher.service.js'
 import followRequestFetcher from '../../services/follow_request_fetcher/follow_request_fetcher.service'
 
-const backendInteractorService = credentials => {
-  const fetchStatus = ({ id }) => {
-    return apiService.fetchStatus({ id, credentials })
-  }
-
-  const fetchConversation = ({ id }) => {
-    return apiService.fetchConversation({ id, credentials })
-  }
-
-  const fetchFriends = ({ id, maxId, sinceId, limit }) => {
-    return apiService.fetchFriends({ id, maxId, sinceId, limit, credentials })
-  }
-
-  const exportFriends = ({ id }) => {
-    return apiService.exportFriends({ id, credentials })
-  }
-
-  const fetchFollowers = ({ id, maxId, sinceId, limit }) => {
-    return apiService.fetchFollowers({ id, maxId, sinceId, limit, credentials })
-  }
-
-  const fetchUser = ({ id }) => {
-    return apiService.fetchUser({ id, credentials })
-  }
-
-  const fetchUserRelationship = ({ id }) => {
-    return apiService.fetchUserRelationship({ id, credentials })
-  }
-
-  const followUser = ({ id, reblogs }) => {
-    return apiService.followUser({ credentials, id, reblogs })
-  }
-
-  const unfollowUser = (id) => {
-    return apiService.unfollowUser({ credentials, id })
-  }
-
-  const blockUser = (id) => {
-    return apiService.blockUser({ credentials, id })
-  }
-
-  const unblockUser = (id) => {
-    return apiService.unblockUser({ credentials, id })
-  }
-
-  const approveUser = (id) => {
-    return apiService.approveUser({ credentials, id })
-  }
-
-  const denyUser = (id) => {
-    return apiService.denyUser({ credentials, id })
-  }
-
-  const startFetchingTimeline = ({ timeline, store, userId = false, tag }) => {
+const backendInteractorService = credentials => ({
+  startFetchingTimeline ({ timeline, store, userId = false, tag }) {
     return timelineFetcherService.startFetching({ timeline, store, credentials, userId, tag })
-  }
+  },
 
-  const startFetchingNotifications = ({ store }) => {
+  startFetchingNotifications ({ store }) {
     return notificationsFetcher.startFetching({ store, credentials })
-  }
+  },
 
-  const startFetchingFollowRequest = ({ store }) => {
+  fetchAndUpdateNotifications ({ store }) {
+    return notificationsFetcher.fetchAndUpdate({ store, credentials })
+  },
+
+  startFetchingFollowRequest ({ store }) {
     return followRequestFetcher.startFetching({ store, credentials })
-  }
+  },
 
-  // eslint-disable-next-line camelcase
-  const tagUser = ({ screen_name }, tag) => {
-    return apiService.tagUser({ screen_name, tag, credentials })
-  }
+  startUserSocket ({ store }) {
+    const serv = store.rootState.instance.server.replace('http', 'ws')
+    const url = serv + getMastodonSocketURI({ credentials, stream: 'user' })
+    return ProcessedWS({ url, id: 'User' })
+  },
 
-  // eslint-disable-next-line camelcase
-  const untagUser = ({ screen_name }, tag) => {
-    return apiService.untagUser({ screen_name, tag, credentials })
-  }
+  ...Object.entries(apiService).reduce((acc, [key, func]) => {
+    return {
+      ...acc,
+      [key]: (args) => func({ credentials, ...args })
+    }
+  }, {}),
 
-  // eslint-disable-next-line camelcase
-  const addRight = ({ screen_name }, right) => {
-    return apiService.addRight({ screen_name, right, credentials })
-  }
-
-  // eslint-disable-next-line camelcase
-  const deleteRight = ({ screen_name }, right) => {
-    return apiService.deleteRight({ screen_name, right, credentials })
-  }
-
-  // eslint-disable-next-line camelcase
-  const activateUser = ({ screen_name }) => {
-    return apiService.activateUser({ screen_name, credentials })
-  }
-
-  // eslint-disable-next-line camelcase
-  const deactivateUser = ({ screen_name }) => {
-    return apiService.deactivateUser({ screen_name, credentials })
-  }
-
-  // eslint-disable-next-line camelcase
-  const deleteUser = ({ screen_name }) => {
-    return apiService.deleteUser({ screen_name, credentials })
-  }
-
-  const vote = (pollId, choices) => {
-    return apiService.vote({ credentials, pollId, choices })
-  }
-
-  const fetchPoll = (pollId) => {
-    return apiService.fetchPoll({ credentials, pollId })
-  }
-
-  const updateNotificationSettings = ({ settings }) => {
-    return apiService.updateNotificationSettings({ credentials, settings })
-  }
-
-  const fetchMutes = () => apiService.fetchMutes({ credentials })
-  const muteUser = (id) => apiService.muteUser({ credentials, id })
-  const unmuteUser = (id) => apiService.unmuteUser({ credentials, id })
-  const subscribeUser = (id) => apiService.subscribeUser({ credentials, id })
-  const unsubscribeUser = (id) => apiService.unsubscribeUser({ credentials, id })
-  const fetchBlocks = () => apiService.fetchBlocks({ credentials })
-  const fetchOAuthTokens = () => apiService.fetchOAuthTokens({ credentials })
-  const revokeOAuthToken = (id) => apiService.revokeOAuthToken({ id, credentials })
-  const fetchPinnedStatuses = (id) => apiService.fetchPinnedStatuses({ credentials, id })
-  const pinOwnStatus = (id) => apiService.pinOwnStatus({ credentials, id })
-  const unpinOwnStatus = (id) => apiService.unpinOwnStatus({ credentials, id })
-  const muteConversation = (id) => apiService.muteConversation({ credentials, id })
-  const unmuteConversation = (id) => apiService.unmuteConversation({ credentials, id })
-
-  const getCaptcha = () => apiService.getCaptcha()
-  const register = (params) => apiService.register({ credentials, params })
-  const updateAvatar = ({ avatar }) => apiService.updateAvatar({ credentials, avatar })
-  const updateBg = ({ background }) => apiService.updateBg({ credentials, background })
-  const updateBanner = ({ banner }) => apiService.updateBanner({ credentials, banner })
-  const updateProfile = ({ params }) => apiService.updateProfile({ credentials, params })
-
-  const importBlocks = (file) => apiService.importBlocks({ file, credentials })
-  const importFollows = (file) => apiService.importFollows({ file, credentials })
-
-  const deleteAccount = ({ password }) => apiService.deleteAccount({ credentials, password })
-  const changeEmail = ({ email, password }) => apiService.changeEmail({ credentials, email, password })
-  const changePassword = ({ password, newPassword, newPasswordConfirmation }) =>
-    apiService.changePassword({ credentials, password, newPassword, newPasswordConfirmation })
-
-  const fetchSettingsMFA = () => apiService.settingsMFA({ credentials })
-  const generateMfaBackupCodes = () => apiService.generateMfaBackupCodes({ credentials })
-  const mfaSetupOTP = () => apiService.mfaSetupOTP({ credentials })
-  const mfaConfirmOTP = ({ password, token }) => apiService.mfaConfirmOTP({ credentials, password, token })
-  const mfaDisableOTP = ({ password }) => apiService.mfaDisableOTP({ credentials, password })
-
-  const fetchFavoritedByUsers = (id) => apiService.fetchFavoritedByUsers({ id })
-  const fetchRebloggedByUsers = (id) => apiService.fetchRebloggedByUsers({ id })
-  const reportUser = (params) => apiService.reportUser({ credentials, ...params })
-
-  const favorite = (id) => apiService.favorite({ id, credentials })
-  const unfavorite = (id) => apiService.unfavorite({ id, credentials })
-  const retweet = (id) => apiService.retweet({ id, credentials })
-  const unretweet = (id) => apiService.unretweet({ id, credentials })
-  const search2 = ({ q, resolve, limit, offset, following }) =>
-    apiService.search2({ credentials, q, resolve, limit, offset, following })
-  const searchUsers = (query) => apiService.searchUsers({ query, credentials })
-
-  const backendInteractorServiceInstance = {
-    fetchStatus,
-    fetchConversation,
-    fetchFriends,
-    exportFriends,
-    fetchFollowers,
-    followUser,
-    unfollowUser,
-    blockUser,
-    unblockUser,
-    fetchUser,
-    fetchUserRelationship,
-    verifyCredentials: apiService.verifyCredentials,
-    startFetchingTimeline,
-    startFetchingNotifications,
-    startFetchingFollowRequest,
-    fetchMutes,
-    muteUser,
-    unmuteUser,
-    subscribeUser,
-    unsubscribeUser,
-    fetchBlocks,
-    fetchOAuthTokens,
-    revokeOAuthToken,
-    fetchPinnedStatuses,
-    pinOwnStatus,
-    unpinOwnStatus,
-    muteConversation,
-    unmuteConversation,
-    tagUser,
-    untagUser,
-    addRight,
-    deleteRight,
-    deleteUser,
-    activateUser,
-    deactivateUser,
-    register,
-    getCaptcha,
-    updateAvatar,
-    updateBg,
-    updateBanner,
-    updateProfile,
-    importBlocks,
-    importFollows,
-    deleteAccount,
-    changeEmail,
-    changePassword,
-    fetchSettingsMFA,
-    generateMfaBackupCodes,
-    mfaSetupOTP,
-    mfaConfirmOTP,
-    mfaDisableOTP,
-    approveUser,
-    denyUser,
-    vote,
-    fetchPoll,
-    fetchFavoritedByUsers,
-    fetchRebloggedByUsers,
-    reportUser,
-    favorite,
-    unfavorite,
-    retweet,
-    unretweet,
-    updateNotificationSettings,
-    search2,
-    searchUsers
-  }
-
-  return backendInteractorServiceInstance
-}
+  verifyCredentials: apiService.verifyCredentials
+})
 
 export default backendInteractorService
diff --git a/src/services/follow_manipulate/follow_manipulate.js b/src/services/follow_manipulate/follow_manipulate.js
index 598cb5f7..29b38a0f 100644
--- a/src/services/follow_manipulate/follow_manipulate.js
+++ b/src/services/follow_manipulate/follow_manipulate.js
@@ -39,7 +39,7 @@ export const requestFollow = (user, store) => new Promise((resolve, reject) => {
 })
 
 export const requestUnfollow = (user, store) => new Promise((resolve, reject) => {
-  store.state.api.backendInteractor.unfollowUser(user.id)
+  store.state.api.backendInteractor.unfollowUser({ id: user.id })
     .then((updated) => {
       store.commit('updateUserRelationship', [updated])
       resolve({