diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index a51f895e..0a4ec857 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -198,6 +198,7 @@ const getNodeInfo = async ({ store }) => {
       store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
 
       store.dispatch('setInstanceOption', { name: 'restrictedNicknames', value: metadata.restrictedNicknames })
+      store.dispatch('setInstanceOption', { name: 'postFormats', value: metadata.postFormats })
 
       const suggestions = metadata.suggestions
       store.dispatch('setInstanceOption', { name: 'suggestionsEnabled', value: suggestions.enabled })
@@ -212,6 +213,16 @@ const getNodeInfo = async ({ store }) => {
 }
 
 const afterStoreSetup = async ({ store, i18n }) => {
+  if (store.state.config.customTheme) {
+    // This is a hack to deal with async loading of config.json and themes
+    // See: style_setter.js, setPreset()
+    window.themeLoaded = true
+    store.dispatch('setOption', {
+      name: 'customTheme',
+      value: store.state.config.customTheme
+    })
+  }
+
   const apiConfig = await getStatusnetConfig({ store })
   const staticConfig = await getStaticConfig()
   await setSettings({ store, apiConfig, staticConfig })
diff --git a/src/components/block_card/block_card.js b/src/components/block_card/block_card.js
index 11fa27b4..c459ff1b 100644
--- a/src/components/block_card/block_card.js
+++ b/src/components/block_card/block_card.js
@@ -9,7 +9,7 @@ const BlockCard = {
   },
   computed: {
     user () {
-      return this.$store.getters.userById(this.userId)
+      return this.$store.getters.findUser(this.userId)
     },
     blocked () {
       return this.user.statusnet_blocking
diff --git a/src/components/mute_card/mute_card.js b/src/components/mute_card/mute_card.js
index 5dd0a9e5..65c9cfb5 100644
--- a/src/components/mute_card/mute_card.js
+++ b/src/components/mute_card/mute_card.js
@@ -9,7 +9,7 @@ const MuteCard = {
   },
   computed: {
     user () {
-      return this.$store.getters.userById(this.userId)
+      return this.$store.getters.findUser(this.userId)
     },
     muted () {
       return this.user.muted
diff --git a/src/components/status/status.js b/src/components/status/status.js
index 9e18fe15..c90da6d4 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -145,11 +145,11 @@ const Status = {
       return !!(this.status.in_reply_to_status_id && this.status.in_reply_to_user_id)
     },
     replyToName () {
-      const user = this.$store.state.users.usersObject[this.status.in_reply_to_user_id]
-      if (user) {
-        return user.screen_name
-      } else {
+      if (this.status.in_reply_to_screen_name) {
         return this.status.in_reply_to_screen_name
+      } else {
+        const user = this.$store.getters.findUser(this.status.in_reply_to_user_id)
+        return user && user.screen_name
       }
     },
     hideReply () {
diff --git a/src/components/user_avatar/user_avatar.js b/src/components/user_avatar/user_avatar.js
index e513b993..e6fed3b5 100644
--- a/src/components/user_avatar/user_avatar.js
+++ b/src/components/user_avatar/user_avatar.js
@@ -23,6 +23,11 @@ const UserAvatar = {
     imageLoadError () {
       this.showPlaceholder = true
     }
+  },
+  watch: {
+    src () {
+      this.showPlaceholder = false
+    }
   }
 }
 
diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js
index 80d15a27..43a77f45 100644
--- a/src/components/user_card/user_card.js
+++ b/src/components/user_card/user_card.js
@@ -15,6 +15,9 @@ export default {
       betterShadow: this.$store.state.interface.browserSupport.cssFilter
     }
   },
+  created () {
+    this.$store.dispatch('fetchUserRelationship', this.user.id)
+  },
   computed: {
     classes () {
       return [{
diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js
index 54126514..82df4510 100644
--- a/src/components/user_profile/user_profile.js
+++ b/src/components/user_profile/user_profile.js
@@ -9,7 +9,7 @@ import withList from '../../hocs/with_list/with_list'
 const FollowerList = compose(
   withLoadMore({
     fetch: (props, $store) => $store.dispatch('addFollowers', props.userId),
-    select: (props, $store) => get($store.getters.userById(props.userId), 'followers', []),
+    select: (props, $store) => get($store.getters.findUser(props.userId), 'followers', []),
     destory: (props, $store) => $store.dispatch('clearFollowers', props.userId),
     childPropName: 'entries',
     additionalPropNames: ['userId']
@@ -20,7 +20,7 @@ const FollowerList = compose(
 const FriendList = compose(
   withLoadMore({
     fetch: (props, $store) => $store.dispatch('addFriends', props.userId),
-    select: (props, $store) => get($store.getters.userById(props.userId), 'friends', []),
+    select: (props, $store) => get($store.getters.findUser(props.userId), 'friends', []),
     destory: (props, $store) => $store.dispatch('clearFriends', props.userId),
     childPropName: 'entries',
     additionalPropNames: ['userId']
@@ -31,28 +31,16 @@ const FriendList = compose(
 const UserProfile = {
   data () {
     return {
-      error: false
+      error: false,
+      fetchedUserId: null
     }
   },
   created () {
-    this.$store.commit('clearTimeline', { timeline: 'user' })
-    this.$store.commit('clearTimeline', { timeline: 'favorites' })
-    this.$store.commit('clearTimeline', { timeline: 'media' })
-    this.$store.dispatch('startFetching', { timeline: 'user', userId: this.fetchBy })
-    this.$store.dispatch('startFetching', { timeline: 'media', userId: this.fetchBy })
-    this.startFetchFavorites()
     if (!this.user.id) {
-      this.$store.dispatch('fetchUser', this.fetchBy)
-        .catch((reason) => {
-          const errorMessage = get(reason, 'error.error')
-          if (errorMessage === 'No user with such user_id') { // Known error
-            this.error = this.$t('user_profile.profile_does_not_exist')
-          } else if (errorMessage) {
-            this.error = errorMessage
-          } else {
-            this.error = this.$t('user_profile.profile_loading_error')
-          }
-        })
+      this.fetchUserId()
+        .then(() => this.startUp())
+    } else {
+      this.startUp()
     }
   },
   destroyed () {
@@ -69,7 +57,7 @@ const UserProfile = {
       return this.$store.state.statuses.timelines.media
     },
     userId () {
-      return this.$route.params.id || this.user.id
+      return this.$route.params.id || this.user.id || this.fetchedUserId
     },
     userName () {
       return this.$route.params.name || this.user.screen_name
@@ -79,10 +67,9 @@ const UserProfile = {
         this.userId === this.$store.state.users.currentUser.id
     },
     userInStore () {
-      if (this.isExternal) {
-        return this.$store.getters.userById(this.userId)
-      }
-      return this.$store.getters.userByName(this.userName)
+      const routeParams = this.$route.params
+      // This needs fetchedUserId so that computed will be refreshed when user is fetched
+      return this.$store.getters.findUser(this.fetchedUserId || routeParams.name || routeParams.id)
     },
     user () {
       if (this.timeline.statuses[0]) {
@@ -93,9 +80,6 @@ const UserProfile = {
       }
       return {}
     },
-    fetchBy () {
-      return this.isExternal ? this.userId : this.userName
-    },
     isExternal () {
       return this.$route.name === 'external-user-profile'
     },
@@ -109,14 +93,38 @@ const UserProfile = {
   methods: {
     startFetchFavorites () {
       if (this.isUs) {
-        this.$store.dispatch('startFetching', { timeline: 'favorites', userId: this.fetchBy })
+        this.$store.dispatch('startFetching', { timeline: 'favorites', userId: this.userId })
       }
     },
+    fetchUserId () {
+      let fetchPromise
+      if (this.userId && !this.$route.params.name) {
+        fetchPromise = this.$store.dispatch('fetchUser', this.userId)
+      } else {
+        fetchPromise = this.$store.dispatch('fetchUser', this.userName)
+          .then(({ id }) => {
+            this.fetchedUserId = id
+          })
+      }
+      return fetchPromise
+        .catch((reason) => {
+          const errorMessage = get(reason, 'error.error')
+          if (errorMessage === 'No user with such user_id') { // Known error
+            this.error = this.$t('user_profile.profile_does_not_exist')
+          } else if (errorMessage) {
+            this.error = errorMessage
+          } else {
+            this.error = this.$t('user_profile.profile_loading_error')
+          }
+        })
+        .then(() => this.startUp())
+    },
     startUp () {
-      this.$store.dispatch('startFetching', { timeline: 'user', userId: this.fetchBy })
-      this.$store.dispatch('startFetching', { timeline: 'media', userId: this.fetchBy })
-
-      this.startFetchFavorites()
+      if (this.userId) {
+        this.$store.dispatch('startFetching', { timeline: 'user', userId: this.userId })
+        this.$store.dispatch('startFetching', { timeline: 'media', userId: this.userId })
+        this.startFetchFavorites()
+      }
     },
     cleanUp () {
       this.$store.dispatch('stopFetching', 'user')
@@ -128,19 +136,19 @@ const UserProfile = {
     }
   },
   watch: {
-    userName () {
-      if (this.isExternal) {
-        return
+    // userId can be undefined if we don't know it yet
+    userId (newVal) {
+      if (newVal) {
+        this.cleanUp()
+        this.startUp()
       }
-      this.cleanUp()
-      this.startUp()
     },
-    userId () {
-      if (!this.isExternal) {
-        return
+    userName () {
+      if (this.$route.params.name) {
+        this.fetchUserId()
+        this.cleanUp()
+        this.startUp()
       }
-      this.cleanUp()
-      this.startUp()
     },
     $route () {
       this.$refs.tabSwitcher.activateTab(0)()
diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue
index 7d4a8b1f..d449eb85 100644
--- a/src/components/user_profile/user_profile.vue
+++ b/src/components/user_profile/user_profile.vue
@@ -11,7 +11,7 @@
         :title="$t('user_profile.timeline_title')"
         :timeline="timeline"
         :timeline-name="'user'"
-        :user-id="fetchBy"
+        :user-id="userId"
       />
       <div :label="$t('user_card.followees')" v-if="followsTabVisible" :disabled="!user.friends_count">
         <FriendList :userId="userId" />
@@ -25,7 +25,7 @@
         :embedded="true" :title="$t('user_card.media')"
         timeline-name="media"
         :timeline="media"
-        :user-id="fetchBy"
+        :user-id="userId"
       />
       <Timeline
         v-if="isUs"
diff --git a/src/i18n/ar.json b/src/i18n/ar.json
index 242dab78..72e3010f 100644
--- a/src/i18n/ar.json
+++ b/src/i18n/ar.json
@@ -49,7 +49,7 @@
         "account_not_locked_warning_link": "مقفل",
         "attachments_sensitive": "اعتبر المرفقات كلها كمحتوى حساس",
         "content_type": {
-            "plain_text": "نص صافٍ"
+            "text/plain": "نص صافٍ"
         },
         "content_warning": "الموضوع (اختياري)",
         "default": "وصلت للتوّ إلى لوس أنجلس.",
diff --git a/src/i18n/ca.json b/src/i18n/ca.json
index d2f285df..8fa3a88b 100644
--- a/src/i18n/ca.json
+++ b/src/i18n/ca.json
@@ -49,7 +49,7 @@
     "account_not_locked_warning_link": "bloquejat",
     "attachments_sensitive": "Marca l'adjunt com a delicat",
     "content_type": {
-      "plain_text": "Text pla"
+      "text/plain": "Text pla"
     },
     "content_warning": "Assumpte (opcional)",
     "default": "Em sento…",
diff --git a/src/i18n/cs.json b/src/i18n/cs.json
index 51e9d342..020092a6 100644
--- a/src/i18n/cs.json
+++ b/src/i18n/cs.json
@@ -71,7 +71,7 @@
     "account_not_locked_warning_link": "uzamčen",
     "attachments_sensitive": "Označovat přílohy jako citlivé",
     "content_type": {
-      "plain_text": "Prostý text",
+      "text/plain": "Prostý text",
       "text/html": "HTML",
       "text/markdown": "Markdown"
     },
diff --git a/src/i18n/de.json b/src/i18n/de.json
index 07d44348..fa9db16c 100644
--- a/src/i18n/de.json
+++ b/src/i18n/de.json
@@ -55,7 +55,7 @@
     "account_not_locked_warning_link": "gesperrt",
     "attachments_sensitive": "Anhänge als heikel markieren",
     "content_type": {
-      "plain_text": "Nur Text"
+      "text/plain": "Nur Text"
     },
     "content_warning": "Betreff (optional)",
     "default": "Sitze gerade im Hofbräuhaus.",
diff --git a/src/i18n/eo.json b/src/i18n/eo.json
index 34851a44..6c5b3a74 100644
--- a/src/i18n/eo.json
+++ b/src/i18n/eo.json
@@ -71,7 +71,7 @@
     "account_not_locked_warning_link": "ŝlosita",
     "attachments_sensitive": "Marki kunsendaĵojn kiel konsternajn",
     "content_type": {
-      "plain_text": "Plata teksto"
+      "text/plain": "Plata teksto"
     },
     "content_warning": "Temo (malnepra)",
     "default": "Ĵus alvenis al la Universala Kongreso!",
diff --git a/src/i18n/es.json b/src/i18n/es.json
index fe96dd08..a692eef9 100644
--- a/src/i18n/es.json
+++ b/src/i18n/es.json
@@ -61,7 +61,7 @@
     "account_not_locked_warning_link": "bloqueada",
     "attachments_sensitive": "Contenido sensible",
     "content_type": {
-      "plain_text": "Texto Plano"
+      "text/plain": "Texto Plano"
     },
     "content_warning": "Tema (opcional)",
     "default": "Acabo de aterrizar en L.A.",
diff --git a/src/i18n/fi.json b/src/i18n/fi.json
index 4f0ffb4b..fbe676cf 100644
--- a/src/i18n/fi.json
+++ b/src/i18n/fi.json
@@ -60,7 +60,7 @@
     "account_not_locked_warning_link": "lukittu",
     "attachments_sensitive": "Merkkaa liitteet arkaluonteisiksi",
     "content_type": {
-      "plain_text": "Tavallinen teksti"
+      "text/plain": "Tavallinen teksti"
     },
     "content_warning": "Aihe (valinnainen)",
     "default": "Tulin juuri saunasta.",
diff --git a/src/i18n/fr.json b/src/i18n/fr.json
index 1209556a..8f9f243e 100644
--- a/src/i18n/fr.json
+++ b/src/i18n/fr.json
@@ -51,7 +51,7 @@
     "account_not_locked_warning_link": "verrouillé",
     "attachments_sensitive": "Marquer le média comme sensible",
     "content_type": {
-      "plain_text": "Texte brut"
+      "text/plain": "Texte brut"
     },
     "content_warning": "Sujet (optionnel)",
     "default": "Écrivez ici votre prochain statut.",
diff --git a/src/i18n/ga.json b/src/i18n/ga.json
index 5be9297a..31250876 100644
--- a/src/i18n/ga.json
+++ b/src/i18n/ga.json
@@ -49,7 +49,7 @@
     "account_not_locked_warning_link": "faoi glas",
     "attachments_sensitive": "Marcáil ceangaltán mar íogair",
     "content_type": {
-      "plain_text": "Gnáth-théacs"
+      "text/plain": "Gnáth-théacs"
     },
     "content_warning": "Teideal (roghnach)",
     "default": "Lá iontach anseo i nGaillimh",
diff --git a/src/i18n/he.json b/src/i18n/he.json
index 213e6170..ea581e05 100644
--- a/src/i18n/he.json
+++ b/src/i18n/he.json
@@ -49,7 +49,7 @@
     "account_not_locked_warning_link": "נעול",
     "attachments_sensitive": "סמן מסמכים מצורפים כלא בטוחים לצפייה",
     "content_type": {
-      "plain_text": "טקסט פשוט"
+      "text/plain": "טקסט פשוט"
     },
     "content_warning": "נושא (נתון לבחירה)",
     "default": "הרגע נחת ב-ל.א.",
diff --git a/src/i18n/it.json b/src/i18n/it.json
index 385d21aa..f441292e 100644
--- a/src/i18n/it.json
+++ b/src/i18n/it.json
@@ -175,7 +175,7 @@
     "account_not_locked_warning_link": "bloccato",
     "attachments_sensitive": "Segna allegati come sensibili",
     "content_type": {
-      "plain_text": "Testo normale"
+      "text/plain": "Testo normale"
     },
     "content_warning": "Oggetto (facoltativo)",
     "default": "Appena atterrato in L.A.",
diff --git a/src/i18n/ja.json b/src/i18n/ja.json
index f39a5a7c..b77f5531 100644
--- a/src/i18n/ja.json
+++ b/src/i18n/ja.json
@@ -61,7 +61,7 @@
     "account_not_locked_warning_link": "ロックされたアカウント",
     "attachments_sensitive": "ファイルをNSFWにする",
     "content_type": {
-      "plain_text": "プレーンテキスト"
+      "text/plain": "プレーンテキスト"
     },
     "content_warning": "せつめい (かかなくてもよい)",
     "default": "はねだくうこうに、つきました。",
diff --git a/src/i18n/ko.json b/src/i18n/ko.json
index 336e464f..402a354c 100644
--- a/src/i18n/ko.json
+++ b/src/i18n/ko.json
@@ -56,7 +56,7 @@
     "account_not_locked_warning_link": "잠김",
     "attachments_sensitive": "첨부물을 민감함으로 설정",
     "content_type": {
-      "plain_text": "평문"
+      "text/plain": "평문"
     },
     "content_warning": "주제 (필수 아님)",
     "default": "LA에 도착!",
diff --git a/src/i18n/nb.json b/src/i18n/nb.json
index 39e054f7..298dc0b9 100644
--- a/src/i18n/nb.json
+++ b/src/i18n/nb.json
@@ -49,7 +49,7 @@
     "account_not_locked_warning_link": "låst",
     "attachments_sensitive": "Merk vedlegg som sensitive",
     "content_type": {
-      "plain_text": "Klar tekst"
+      "text/plain": "Klar tekst"
     },
     "content_warning": "Tema (valgfritt)",
     "default": "Landet akkurat i L.A.",
diff --git a/src/i18n/nl.json b/src/i18n/nl.json
index 799e22b9..7e2f0604 100644
--- a/src/i18n/nl.json
+++ b/src/i18n/nl.json
@@ -57,7 +57,7 @@
     "account_not_locked_warning_link": "gesloten",
     "attachments_sensitive": "Markeer bijlage als gevoelig",
     "content_type": {
-      "plain_text": "Gewone tekst"
+      "text/plain": "Gewone tekst"
     },
     "content_warning": "Onderwerp (optioneel)",
     "default": "Tijd voor een pauze!",
diff --git a/src/i18n/oc.json b/src/i18n/oc.json
index fd5ccc97..baac3d25 100644
--- a/src/i18n/oc.json
+++ b/src/i18n/oc.json
@@ -71,7 +71,7 @@
     "account_not_locked_warning_link": "clavat",
     "attachments_sensitive": "Marcar las pèças juntas coma sensiblas",
     "content_type": {
-      "plain_text": "Tèxte brut"
+      "text/plain": "Tèxte brut"
     },
     "content_warning": "Avís de contengut (opcional)",
     "default": "Escrivètz aquí vòstre estatut.",
diff --git a/src/i18n/pt.json b/src/i18n/pt.json
index cbc2c9a3..29ab995b 100644
--- a/src/i18n/pt.json
+++ b/src/i18n/pt.json
@@ -71,7 +71,7 @@
     "account_not_locked_warning_link": "fechada",
     "attachments_sensitive": "Marcar anexos como sensíveis",
     "content_type": {
-      "plain_text": "Texto puro"
+      "text/plain": "Texto puro"
     },
     "content_warning": "Assunto (opcional)",
     "default": "Acabei de chegar no Rio!",
diff --git a/src/i18n/zh.json b/src/i18n/zh.json
index 089a98e2..da6dae5f 100644
--- a/src/i18n/zh.json
+++ b/src/i18n/zh.json
@@ -49,7 +49,7 @@
     "account_not_locked_warning_link": "上锁",
     "attachments_sensitive": "标记附件为敏感内容",
     "content_type": {
-      "plain_text": "纯文本"
+      "text/plain": "纯文本"
     },
     "content_warning": "主题(可选)",
     "default": "刚刚抵达上海",
diff --git a/src/lib/persisted_state.js b/src/lib/persisted_state.js
index e828a74b..7ab89c12 100644
--- a/src/lib/persisted_state.js
+++ b/src/lib/persisted_state.js
@@ -60,18 +60,6 @@ export default function createPersistedState ({
             merge({}, store.state, savedState)
           )
         }
-        if (store.state.config.customTheme) {
-          // This is a hack to deal with async loading of config.json and themes
-          // See: style_setter.js, setPreset()
-          window.themeLoaded = true
-          store.dispatch('setOption', {
-            name: 'customTheme',
-            value: store.state.config.customTheme
-          })
-        }
-        if (store.state.oauth.token) {
-          store.dispatch('loginUser', store.state.oauth.token)
-        }
         loaded = true
       } catch (e) {
         console.log("Couldn't load state")
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index 21d21e89..e18b0d1f 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -1,4 +1,4 @@
-import { remove, slice, each, find, maxBy, minBy, merge, first, last, isArray } from 'lodash'
+import { remove, slice, each, find, maxBy, minBy, merge, first, last, isArray, omitBy } from 'lodash'
 import apiService from '../services/api/api.service.js'
 // import parse from '../services/status_parser/status_parser.js'
 
@@ -72,7 +72,9 @@ const mergeOrAdd = (arr, obj, item) => {
 
   if (oldItem) {
     // We already have this, so only merge the new info.
-    merge(oldItem, item)
+    // We ignore null values to avoid overwriting existing properties with missing data
+    // we also skip 'user' because that is handled by users module
+    merge(oldItem, omitBy(item, (v, k) => v === null || k === 'user'))
     // Reactivity fix.
     oldItem.attachments.splice(oldItem.attachments.length)
     return {item: oldItem, new: false}
diff --git a/src/modules/users.js b/src/modules/users.js
index 26884750..1fe12fc8 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -18,7 +18,7 @@ export const mergeOrAdd = (arr, obj, item) => {
     arr.push(item)
     obj[item.id] = item
     if (item.screen_name && !item.screen_name.includes('@')) {
-      obj[item.screen_name] = item
+      obj[item.screen_name.toLowerCase()] = item
     }
     return { item, new: true }
   }
@@ -91,6 +91,17 @@ export const mutations = {
   addNewUsers (state, users) {
     each(users, (user) => mergeOrAdd(state.users, state.usersObject, user))
   },
+  updateUserRelationship (state, relationships) {
+    relationships.forEach((relationship) => {
+      const user = state.usersObject[relationship.id]
+      if (user) {
+        user.follows_you = relationship.followed_by
+        user.following = relationship.following
+        user.muted = relationship.muting
+        user.statusnet_blocking = relationship.blocking
+      }
+    })
+  },
   saveBlocks (state, blockIds) {
     state.currentUser.blockIds = blockIds
   },
@@ -122,12 +133,14 @@ export const mutations = {
 }
 
 export const getters = {
-  userById: state => id =>
-    state.users.find(user => user.id === id),
-  userByName: state => name =>
-    state.users.find(user => user.screen_name &&
-      (user.screen_name.toLowerCase() === name.toLowerCase())
-    )
+  findUser: state => query => {
+    const result = state.usersObject[query]
+    // In case it's a screen_name, we can try searching case-insensitive
+    if (!result && typeof query === 'string') {
+      return state.usersObject[query.toLowerCase()]
+    }
+    return result
+  }
 }
 
 export const defaultState = {
@@ -147,7 +160,14 @@ const users = {
   actions: {
     fetchUser (store, id) {
       return store.rootState.api.backendInteractor.fetchUser({ id })
-        .then((user) => store.commit('addNewUsers', [user]))
+        .then((user) => {
+          store.commit('addNewUsers', [user])
+          return user
+        })
+    },
+    fetchUserRelationship (store, id) {
+      return store.rootState.api.backendInteractor.fetchUserRelationship({ id })
+        .then((relationships) => store.commit('updateUserRelationship', relationships))
     },
     fetchBlocks (store) {
       return store.rootState.api.backendInteractor.fetchBlocks()
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 03553d75..fab48266 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -28,11 +28,9 @@ const BG_UPDATE_URL = '/api/qvitter/update_background_image.json'
 const BANNER_UPDATE_URL = '/api/account/update_profile_banner.json'
 const PROFILE_UPDATE_URL = '/api/account/update_profile.json'
 const EXTERNAL_PROFILE_URL = '/api/externalprofile/show.json'
-const QVITTER_USER_TIMELINE_URL = '/api/qvitter/statuses/user_timeline.json'
 const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications/read.json'
 const BLOCKING_URL = '/api/blocks/create.json'
 const UNBLOCKING_URL = '/api/blocks/destroy.json'
-const USER_URL = '/api/users/show.json'
 const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import'
 const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account'
 const CHANGE_PASSWORD_URL = '/api/pleroma/change_password'
@@ -43,6 +41,9 @@ const SUGGESTIONS_URL = '/api/v1/suggestions'
 
 const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites'
 const MASTODON_USER_NOTIFICATIONS_URL = '/api/v1/notifications'
+const MASTODON_USER_URL = '/api/v1/accounts'
+const MASTODON_USER_RELATIONSHIPS_URL = '/api/v1/accounts/relationships'
+const MASTODON_USER_TIMELINE_URL = id => `/api/v1/accounts/${id}/statuses`
 
 import { each, map } from 'lodash'
 import { parseStatus, parseUser, parseNotification } from '../entity_normalizer/entity_normalizer.service.js'
@@ -243,7 +244,7 @@ const denyUser = ({id, credentials}) => {
 }
 
 const fetchUser = ({id, credentials}) => {
-  let url = `${USER_URL}?user_id=${id}`
+  let url = `${MASTODON_USER_URL}/${id}`
   return fetch(url, { headers: authHeaders(credentials) })
     .then((response) => {
       return new Promise((resolve, reject) => response.json()
@@ -257,6 +258,20 @@ const fetchUser = ({id, credentials}) => {
     .then((data) => parseUser(data))
 }
 
+const fetchUserRelationship = ({id, credentials}) => {
+  let url = `${MASTODON_USER_RELATIONSHIPS_URL}/?id=${id}`
+  return fetch(url, { headers: authHeaders(credentials) })
+    .then((response) => {
+      return new Promise((resolve, reject) => response.json()
+        .then((json) => {
+          if (!response.ok) {
+            return reject(new StatusCodeError(response.status, json, { url }, response))
+          }
+          return resolve(json)
+        }))
+    })
+}
+
 const fetchFriends = ({id, page, credentials}) => {
   let url = `${FRIENDS_URL}?user_id=${id}`
   if (page) {
@@ -347,8 +362,8 @@ const fetchTimeline = ({timeline, credentials, since = false, until = false, use
     dms: DM_TIMELINE_URL,
     notifications: MASTODON_USER_NOTIFICATIONS_URL,
     'publicAndExternal': PUBLIC_AND_EXTERNAL_TIMELINE_URL,
-    user: QVITTER_USER_TIMELINE_URL,
-    media: QVITTER_USER_TIMELINE_URL,
+    user: MASTODON_USER_TIMELINE_URL,
+    media: MASTODON_USER_TIMELINE_URL,
     favorites: MASTODON_USER_FAVORITES_TIMELINE_URL,
     tag: TAG_TIMELINE_URL
   }
@@ -357,15 +372,16 @@ const fetchTimeline = ({timeline, credentials, since = false, until = false, use
 
   let url = timelineUrls[timeline]
 
+  if (timeline === 'user' || timeline === 'media') {
+    url = url(userId)
+  }
+
   if (since) {
     params.push(['since_id', since])
   }
   if (until) {
     params.push(['max_id', until])
   }
-  if (userId) {
-    params.push(['user_id', userId])
-  }
   if (tag) {
     url += `/${tag}.json`
   }
@@ -588,6 +604,7 @@ const apiService = {
   blockUser,
   unblockUser,
   fetchUser,
+  fetchUserRelationship,
   favorite,
   unfavorite,
   retweet,
diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js
index 7e972d7b..cbd0b733 100644
--- a/src/services/backend_interactor_service/backend_interactor_service.js
+++ b/src/services/backend_interactor_service/backend_interactor_service.js
@@ -30,6 +30,10 @@ const backendInteractorService = (credentials) => {
     return apiService.fetchUser({id, credentials})
   }
 
+  const fetchUserRelationship = ({id}) => {
+    return apiService.fetchUserRelationship({id, credentials})
+  }
+
   const followUser = (id) => {
     return apiService.followUser({credentials, id})
   }
@@ -92,6 +96,7 @@ const backendInteractorService = (credentials) => {
     blockUser,
     unblockUser,
     fetchUser,
+    fetchUserRelationship,
     fetchAllFollowing,
     verifyCredentials: apiService.verifyCredentials,
     startFetching,
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index 874fb5de..c31496e8 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -39,10 +39,10 @@ export const parseUser = (data) => {
       return output
     }
 
-    output.name = null // missing
+    // output.name = ??? missing
     output.name_html = data.display_name
 
-    output.description = null // missing
+    // output.description = ??? missing
     output.description_html = data.note
 
     // Utilize avatar_static for gif avatars?
@@ -59,10 +59,14 @@ export const parseUser = (data) => {
     output.statusnet_profile_url = data.url
 
     if (data.pleroma) {
-      const pleroma = data.pleroma
-      output.follows_you = pleroma.follows_you
-      output.statusnet_blocking = pleroma.statusnet_blocking
-      output.muted = pleroma.muted
+      const relationship = data.pleroma.relationship
+
+      if (relationship) {
+        output.follows_you = relationship.followed_by
+        output.following = relationship.following
+        output.statusnet_blocking = relationship.blocking
+        output.muted = relationship.muting
+      }
     }
 
     // TODO: handle is_local
@@ -83,7 +87,7 @@ export const parseUser = (data) => {
 
     output.friends_count = data.friends_count
 
-    output.bot = null // missing
+    // output.bot = ??? missing
 
     output.statusnet_profile_url = data.statusnet_profile_url
 
@@ -134,7 +138,7 @@ const parseAttachment = (data) => {
     output.meta = data.meta // not present in BE yet
   } else {
     output.mimetype = data.mimetype
-    output.meta = null // missing
+    // output.meta = ??? missing
   }
 
   output.url = data.url
@@ -166,7 +170,7 @@ export const parseStatus = (data) => {
     output.in_reply_to_user_id = data.in_reply_to_account_id
 
     // Missing!! fix in UI?
-    output.in_reply_to_screen_name = null
+    // output.in_reply_to_screen_name = ???
 
     // Not exactly the same but works
     output.statusnet_conversation_id = data.id
@@ -178,8 +182,6 @@ export const parseStatus = (data) => {
     output.summary = data.spoiler_text
     output.summary_html = data.spoiler_text
     output.external_url = data.url
-
-    // TODO: handle is_local
     output.is_local = data.pleroma.local
   } else {
     output.favorited = data.favorited
@@ -271,6 +273,7 @@ export const parseNotification = (data) => {
 
   if (masto) {
     output.type = mastoDict[data.type] || data.type
+
     output.seen = null // missing
     output.status = output.type === 'follow'
       ? parseFollow(data)
diff --git a/test/unit/specs/components/user_profile.spec.js b/test/unit/specs/components/user_profile.spec.js
index 41fd9cd0..847481f3 100644
--- a/test/unit/specs/components/user_profile.spec.js
+++ b/test/unit/specs/components/user_profile.spec.js
@@ -12,9 +12,13 @@ const mutations = {
   setError: () => {}
 }
 
+const actions = {
+  fetchUser: () => {},
+  fetchUserByScreenName: () => {}
+}
+
 const testGetters = {
-  userByName: state => getters.userByName(state.users),
-  userById: state => getters.userById(state.users)
+  findUser: state => getters.findUser(state.users)
 }
 
 const localUser = {
@@ -31,6 +35,7 @@ const extUser = {
 
 const externalProfileStore = new Vuex.Store({
   mutations,
+  actions,
   getters: testGetters,
   state: {
     api: {
@@ -89,7 +94,7 @@ const externalProfileStore = new Vuex.Store({
       currentUser: {
         credentials: ''
       },
-      usersObject: [extUser],
+      usersObject: { 100: extUser },
       users: [extUser]
     }
   }
@@ -97,6 +102,7 @@ const externalProfileStore = new Vuex.Store({
 
 const localProfileStore = new Vuex.Store({
   mutations,
+  actions,
   getters: testGetters,
   state: {
     api: {
@@ -155,7 +161,7 @@ const localProfileStore = new Vuex.Store({
       currentUser: {
         credentials: ''
       },
-      usersObject: [localUser],
+      usersObject: { 100: localUser, 'testuser': localUser },
       users: [localUser]
     }
   }
diff --git a/test/unit/specs/modules/users.spec.js b/test/unit/specs/modules/users.spec.js
index 4d49ee24..c8bc0ae7 100644
--- a/test/unit/specs/modules/users.spec.js
+++ b/test/unit/specs/modules/users.spec.js
@@ -34,40 +34,31 @@ describe('The users module', () => {
     })
   })
 
-  describe('getUserByName', () => {
+  describe('findUser', () => {
     it('returns user with matching screen_name', () => {
+      const user = { screen_name: 'Guy', id: '1' }
       const state = {
-        users: [
-          { screen_name: 'Guy', id: '1' }
-        ]
+        usersObject: {
+          1: user,
+          guy: user
+        }
       }
       const name = 'Guy'
       const expected = { screen_name: 'Guy', id: '1' }
-      expect(getters.userByName(state)(name)).to.eql(expected)
+      expect(getters.findUser(state)(name)).to.eql(expected)
     })
 
-    it('returns user with matching screen_name with different case', () => {
-      const state = {
-        users: [
-          { screen_name: 'guy', id: '1' }
-        ]
-      }
-      const name = 'Guy'
-      const expected = { screen_name: 'guy', id: '1' }
-      expect(getters.userByName(state)(name)).to.eql(expected)
-    })
-  })
-
-  describe('getUserById', () => {
     it('returns user with matching id', () => {
+      const user = { screen_name: 'Guy', id: '1' }
       const state = {
-        users: [
-          { screen_name: 'Guy', id: '1' }
-        ]
+        usersObject: {
+          1: user,
+          guy: user
+        }
       }
       const id = '1'
       const expected = { screen_name: 'Guy', id: '1' }
-      expect(getters.userById(state)(id)).to.eql(expected)
+      expect(getters.findUser(state)(id)).to.eql(expected)
     })
   })
 })