diff --git a/packages/bsky/src/api/app/bsky/actor/getProfile.ts b/packages/bsky/src/api/app/bsky/actor/getProfile.ts
index 51cbd313..e1db6abf 100644
--- a/packages/bsky/src/api/app/bsky/actor/getProfile.ts
+++ b/packages/bsky/src/api/app/bsky/actor/getProfile.ts
@@ -23,10 +23,17 @@ export default function (server: Server, ctx: AppContext) {
           'AccountTakedown',
         )
       }
+      const profile = await actorService.views.profileDetailed(
+        actorRes,
+        requester,
+      )
+      if (!profile) {
+        throw new InvalidRequestError('Profile not found')
+      }
 
       return {
         encoding: 'application/json',
-        body: await actorService.views.profileDetailed(actorRes, requester),
+        body: profile,
       }
     },
   })
diff --git a/packages/bsky/src/api/app/bsky/actor/getProfiles.ts b/packages/bsky/src/api/app/bsky/actor/getProfiles.ts
index ad676f01..3b97a034 100644
--- a/packages/bsky/src/api/app/bsky/actor/getProfiles.ts
+++ b/packages/bsky/src/api/app/bsky/actor/getProfiles.ts
@@ -15,7 +15,7 @@ export default function (server: Server, ctx: AppContext) {
       return {
         encoding: 'application/json',
         body: {
-          profiles: await actorService.views.profileDetailed(
+          profiles: await actorService.views.hydrateProfilesDetailed(
             actorsRes,
             requester,
           ),
diff --git a/packages/bsky/src/api/app/bsky/actor/getSuggestions.ts b/packages/bsky/src/api/app/bsky/actor/getSuggestions.ts
index 37448cbf..ba51957c 100644
--- a/packages/bsky/src/api/app/bsky/actor/getSuggestions.ts
+++ b/packages/bsky/src/api/app/bsky/actor/getSuggestions.ts
@@ -55,7 +55,10 @@ export default function (server: Server, ctx: AppContext) {
         encoding: 'application/json',
         body: {
           cursor: suggestionsRes.at(-1)?.did,
-          actors: await actorService.views.profile(suggestionsRes, viewer),
+          actors: await actorService.views.hydrateProfiles(
+            suggestionsRes,
+            viewer,
+          ),
         },
       }
     },
diff --git a/packages/bsky/src/api/app/bsky/actor/searchActors.ts b/packages/bsky/src/api/app/bsky/actor/searchActors.ts
index d2911443..9adc0b05 100644
--- a/packages/bsky/src/api/app/bsky/actor/searchActors.ts
+++ b/packages/bsky/src/api/app/bsky/actor/searchActors.ts
@@ -24,7 +24,9 @@ export default function (server: Server, ctx: AppContext) {
         : []
       const keyset = new SearchKeyset(sql``, sql``)
 
-      const actors = await services.actor(db).views.profile(results, requester)
+      const actors = await services
+        .actor(db)
+        .views.hydrateProfiles(results, requester)
       const filtered = actors.filter(
         (actor) => !actor.viewer?.blocking && !actor.viewer?.blockedBy,
       )
diff --git a/packages/bsky/src/api/app/bsky/actor/searchActorsTypeahead.ts b/packages/bsky/src/api/app/bsky/actor/searchActorsTypeahead.ts
index 9148634f..a20ecd16 100644
--- a/packages/bsky/src/api/app/bsky/actor/searchActorsTypeahead.ts
+++ b/packages/bsky/src/api/app/bsky/actor/searchActorsTypeahead.ts
@@ -22,7 +22,7 @@ export default function (server: Server, ctx: AppContext) {
 
       const actors = await services
         .actor(db)
-        .views.profileBasic(results, requester)
+        .views.hydrateProfilesBasic(results, requester)
 
       const filtered = actors.filter(
         (actor) => !actor.viewer?.blocking && !actor.viewer?.blockedBy,
diff --git a/packages/bsky/src/api/app/bsky/feed/getActorFeeds.ts b/packages/bsky/src/api/app/bsky/feed/getActorFeeds.ts
index 5ca0a141..a255bc94 100644
--- a/packages/bsky/src/api/app/bsky/feed/getActorFeeds.ts
+++ b/packages/bsky/src/api/app/bsky/feed/getActorFeeds.ts
@@ -37,6 +37,9 @@ export default function (server: Server, ctx: AppContext) {
         feedsQb.execute(),
         actorService.views.profile(creatorRes, viewer),
       ])
+      if (!creatorProfile) {
+        throw new InvalidRequestError(`Actor not found: ${actor}`)
+      }
       const profiles = { [creatorProfile.did]: creatorProfile }
 
       const feeds = feedsRes.map((row) => {
diff --git a/packages/bsky/src/api/app/bsky/feed/getLikes.ts b/packages/bsky/src/api/app/bsky/feed/getLikes.ts
index 4476b3bb..7a0d0551 100644
--- a/packages/bsky/src/api/app/bsky/feed/getLikes.ts
+++ b/packages/bsky/src/api/app/bsky/feed/getLikes.ts
@@ -1,3 +1,4 @@
+import { mapDefined } from '@atproto/common'
 import { Server } from '../../../../lexicon'
 import { paginate, TimeCidKeyset } from '../../../../db/pagination'
 import AppContext from '../../../../context'
@@ -37,7 +38,19 @@ export default function (server: Server, ctx: AppContext) {
       })
 
       const likesRes = await builder.execute()
-      const actors = await services.actor(db).views.profile(likesRes, requester)
+      const actors = await services
+        .actor(db)
+        .views.profiles(likesRes, requester)
+
+      const likes = mapDefined(likesRes, (row) =>
+        actors[row.did]
+          ? {
+              createdAt: row.createdAt,
+              indexedAt: row.indexedAt,
+              actor: actors[row.did],
+            }
+          : undefined,
+      )
 
       return {
         encoding: 'application/json',
@@ -45,11 +58,7 @@ export default function (server: Server, ctx: AppContext) {
           uri,
           cid,
           cursor: keyset.packFromResult(likesRes),
-          likes: likesRes.map((row, i) => ({
-            createdAt: row.createdAt,
-            indexedAt: row.indexedAt,
-            actor: actors[i],
-          })),
+          likes,
         },
       }
     },
diff --git a/packages/bsky/src/api/app/bsky/feed/getRepostedBy.ts b/packages/bsky/src/api/app/bsky/feed/getRepostedBy.ts
index c76e19b3..ff635641 100644
--- a/packages/bsky/src/api/app/bsky/feed/getRepostedBy.ts
+++ b/packages/bsky/src/api/app/bsky/feed/getRepostedBy.ts
@@ -34,7 +34,7 @@ export default function (server: Server, ctx: AppContext) {
       const repostedByRes = await builder.execute()
       const repostedBy = await services
         .actor(db)
-        .views.profile(repostedByRes, requester)
+        .views.hydrateProfiles(repostedByRes, requester)
 
       return {
         encoding: 'application/json',
diff --git a/packages/bsky/src/api/app/bsky/graph/getBlocks.ts b/packages/bsky/src/api/app/bsky/graph/getBlocks.ts
index e2142deb..45af81a7 100644
--- a/packages/bsky/src/api/app/bsky/graph/getBlocks.ts
+++ b/packages/bsky/src/api/app/bsky/graph/getBlocks.ts
@@ -33,7 +33,10 @@ export default function (server: Server, ctx: AppContext) {
       const blocksRes = await blocksReq.execute()
 
       const actorService = services.actor(db)
-      const blocks = await actorService.views.profile(blocksRes, requester)
+      const blocks = await actorService.views.hydrateProfiles(
+        blocksRes,
+        requester,
+      )
 
       return {
         encoding: 'application/json',
diff --git a/packages/bsky/src/api/app/bsky/graph/getFollowers.ts b/packages/bsky/src/api/app/bsky/graph/getFollowers.ts
index 6908d989..85f1dd78 100644
--- a/packages/bsky/src/api/app/bsky/graph/getFollowers.ts
+++ b/packages/bsky/src/api/app/bsky/graph/getFollowers.ts
@@ -37,9 +37,12 @@ export default function (server: Server, ctx: AppContext) {
 
       const followersRes = await followersReq.execute()
       const [followers, subject] = await Promise.all([
-        actorService.views.profile(followersRes, requester),
+        actorService.views.hydrateProfiles(followersRes, requester),
         actorService.views.profile(subjectRes, requester),
       ])
+      if (!subject) {
+        throw new InvalidRequestError(`Actor not found: ${actor}`)
+      }
 
       return {
         encoding: 'application/json',
diff --git a/packages/bsky/src/api/app/bsky/graph/getFollows.ts b/packages/bsky/src/api/app/bsky/graph/getFollows.ts
index 2db016da..3fdb45f2 100644
--- a/packages/bsky/src/api/app/bsky/graph/getFollows.ts
+++ b/packages/bsky/src/api/app/bsky/graph/getFollows.ts
@@ -37,9 +37,12 @@ export default function (server: Server, ctx: AppContext) {
 
       const followsRes = await followsReq.execute()
       const [follows, subject] = await Promise.all([
-        actorService.views.profile(followsRes, requester),
+        actorService.views.hydrateProfiles(followsRes, requester),
         actorService.views.profile(creatorRes, requester),
       ])
+      if (!subject) {
+        throw new InvalidRequestError(`Actor not found: ${actor}`)
+      }
 
       return {
         encoding: 'application/json',
diff --git a/packages/bsky/src/api/app/bsky/graph/getList.ts b/packages/bsky/src/api/app/bsky/graph/getList.ts
index 2814cf99..1a12baa5 100644
--- a/packages/bsky/src/api/app/bsky/graph/getList.ts
+++ b/packages/bsky/src/api/app/bsky/graph/getList.ts
@@ -2,7 +2,6 @@ import { InvalidRequestError } from '@atproto/xrpc-server'
 import { Server } from '../../../../lexicon'
 import { paginate, TimeCidKeyset } from '../../../../db/pagination'
 import AppContext from '../../../../context'
-import { ProfileView } from '../../../../lexicon/types/app/bsky/actor/defs'
 
 export default function (server: Server, ctx: AppContext) {
   server.app.bsky.graph.getList({
@@ -40,20 +39,17 @@ export default function (server: Server, ctx: AppContext) {
       const itemsRes = await itemsReq.execute()
 
       const actorService = services.actor(db)
-      const profiles = await actorService.views.profile(itemsRes, requester)
-      const profilesMap = profiles.reduce(
-        (acc, cur) => ({
-          ...acc,
-          [cur.did]: cur,
-        }),
-        {} as Record<string, ProfileView>,
+      const profiles = await actorService.views.hydrateProfiles(
+        itemsRes,
+        requester,
       )
 
-      const items = itemsRes.map((item) => ({
-        subject: profilesMap[item.did],
-      }))
+      const items = profiles.map((subject) => ({ subject }))
 
       const creator = await actorService.views.profile(listRes, requester)
+      if (!creator) {
+        throw new InvalidRequestError(`Actor not found: ${listRes.handle}`)
+      }
 
       const subject = {
         uri: listRes.uri,
diff --git a/packages/bsky/src/api/app/bsky/graph/getListMutes.ts b/packages/bsky/src/api/app/bsky/graph/getListMutes.ts
index f8f353bc..d85b86f9 100644
--- a/packages/bsky/src/api/app/bsky/graph/getListMutes.ts
+++ b/packages/bsky/src/api/app/bsky/graph/getListMutes.ts
@@ -1,7 +1,6 @@
 import { Server } from '../../../../lexicon'
 import { paginate, TimeCidKeyset } from '../../../../db/pagination'
 import AppContext from '../../../../context'
-import { ProfileView } from '../../../../lexicon/types/app/bsky/actor/defs'
 
 export default function (server: Server, ctx: AppContext) {
   server.app.bsky.graph.getListMutes({
@@ -33,17 +32,10 @@ export default function (server: Server, ctx: AppContext) {
       const listsRes = await listsReq.execute()
 
       const actorService = ctx.services.actor(ctx.db)
-      const profiles = await actorService.views.profile(listsRes, requester)
-      const profilesMap = profiles.reduce(
-        (acc, cur) => ({
-          ...acc,
-          [cur.did]: cur,
-        }),
-        {} as Record<string, ProfileView>,
-      )
+      const profiles = await actorService.views.profiles(listsRes, requester)
 
       const lists = listsRes.map((row) =>
-        graphService.formatListView(row, profilesMap),
+        graphService.formatListView(row, profiles),
       )
 
       return {
diff --git a/packages/bsky/src/api/app/bsky/graph/getLists.ts b/packages/bsky/src/api/app/bsky/graph/getLists.ts
index b325a8a4..2f2d7878 100644
--- a/packages/bsky/src/api/app/bsky/graph/getLists.ts
+++ b/packages/bsky/src/api/app/bsky/graph/getLists.ts
@@ -35,6 +35,9 @@ export default function (server: Server, ctx: AppContext) {
         listsReq.execute(),
         actorService.views.profile(creatorRes, requester),
       ])
+      if (!creator) {
+        throw new InvalidRequestError(`Actor not found: ${actor}`)
+      }
       const profileMap = {
         [creator.did]: creator,
       }
diff --git a/packages/bsky/src/api/app/bsky/graph/getMutes.ts b/packages/bsky/src/api/app/bsky/graph/getMutes.ts
index fa8b53f0..07250896 100644
--- a/packages/bsky/src/api/app/bsky/graph/getMutes.ts
+++ b/packages/bsky/src/api/app/bsky/graph/getMutes.ts
@@ -38,7 +38,7 @@ export default function (server: Server, ctx: AppContext) {
         encoding: 'application/json',
         body: {
           cursor: keyset.packFromResult(mutesRes),
-          mutes: await actorService.views.profile(mutesRes, requester),
+          mutes: await actorService.views.hydrateProfiles(mutesRes, requester),
         },
       }
     },
diff --git a/packages/bsky/src/api/app/bsky/notification/listNotifications.ts b/packages/bsky/src/api/app/bsky/notification/listNotifications.ts
index 9aec05ed..463b9a6f 100644
--- a/packages/bsky/src/api/app/bsky/notification/listNotifications.ts
+++ b/packages/bsky/src/api/app/bsky/notification/listNotifications.ts
@@ -1,5 +1,6 @@
 import { InvalidRequestError } from '@atproto/xrpc-server'
 import { jsonStringToLex } from '@atproto/lexicon'
+import { mapDefined } from '@atproto/common'
 import { Server } from '../../../../lexicon'
 import { paginate, TimeCidKeyset } from '../../../../db/pagination'
 import AppContext from '../../../../context'
@@ -78,7 +79,7 @@ export default function (server: Server, ctx: AppContext) {
       const labelService = ctx.services.label(ctx.db)
       const recordUris = notifs.map((notif) => notif.uri)
       const [authors, labels] = await Promise.all([
-        actorService.views.profile(
+        actorService.views.profiles(
           notifs.map((notif) => ({
             did: notif.authorDid,
             handle: notif.authorHandle,
@@ -90,17 +91,21 @@ export default function (server: Server, ctx: AppContext) {
         labelService.getLabelsForUris(recordUris),
       ])
 
-      const notifications = notifs.map((notif, i) => ({
-        uri: notif.uri,
-        cid: notif.cid,
-        author: authors[i],
-        reason: notif.reason,
-        reasonSubject: notif.reasonSubject || undefined,
-        record: jsonStringToLex(notif.recordJson) as Record<string, unknown>,
-        isRead: seenAt ? notif.indexedAt <= seenAt : false,
-        indexedAt: notif.indexedAt,
-        labels: labels[notif.uri] ?? [],
-      }))
+      const notifications = mapDefined(notifs, (notif) => {
+        const author = authors[notif.authorDid]
+        if (!author) return undefined
+        return {
+          uri: notif.uri,
+          cid: notif.cid,
+          author,
+          reason: notif.reason,
+          reasonSubject: notif.reasonSubject || undefined,
+          record: jsonStringToLex(notif.recordJson) as Record<string, unknown>,
+          isRead: seenAt ? notif.indexedAt <= seenAt : false,
+          indexedAt: notif.indexedAt,
+          labels: labels[notif.uri] ?? [],
+        }
+      })
 
       return {
         encoding: 'application/json',
diff --git a/packages/bsky/src/services/actor/views.ts b/packages/bsky/src/services/actor/views.ts
index 4bf75bef..7f0cc86e 100644
--- a/packages/bsky/src/services/actor/views.ts
+++ b/packages/bsky/src/services/actor/views.ts
@@ -1,4 +1,4 @@
-import { ArrayEl } from '@atproto/common'
+import { mapDefined } from '@atproto/common'
 import { INVALID_HANDLE } from '@atproto/identifier'
 import {
   ProfileViewDetailed,
@@ -10,32 +10,22 @@ import { noMatch, notSoftDeletedClause } from '../../db/util'
 import { Actor } from '../../db/tables/actor'
 import { ImageUriBuilder } from '../../image/uri'
 import { LabelService } from '../label'
-import { ListViewBasic } from '../../lexicon/types/app/bsky/graph/defs'
+import { GraphService } from '../graph'
 
 export class ActorViews {
   constructor(private db: Database, private imgUriBuilder: ImageUriBuilder) {}
 
   services = {
-    label: LabelService.creator(),
+    label: LabelService.creator()(this.db),
+    graph: GraphService.creator(this.imgUriBuilder)(this.db),
   }
 
-  profileDetailed(
-    result: ActorResult,
+  async profilesDetailed(
+    results: ActorResult[],
     viewer: string | null,
     opts?: { skipLabels?: boolean; includeSoftDeleted?: boolean },
-  ): Promise<ProfileViewDetailed>
-  profileDetailed(
-    result: ActorResult[],
-    viewer: string | null,
-    opts?: { skipLabels?: boolean; includeSoftDeleted?: boolean },
-  ): Promise<ProfileViewDetailed[]>
-  async profileDetailed(
-    result: ActorResult | ActorResult[],
-    viewer: string | null,
-    opts?: { skipLabels?: boolean; includeSoftDeleted?: boolean },
-  ): Promise<ProfileViewDetailed | ProfileViewDetailed[]> {
-    const results = Array.isArray(result) ? result : [result]
-    if (results.length === 0) return []
+  ): Promise<Record<string, ProfileViewDetailed>> {
+    if (results.length === 0) return {}
 
     const { ref } = this.db.db.dynamic
     const { skipLabels = false, includeSoftDeleted = false } = opts ?? {}
@@ -51,6 +41,7 @@ export class ActorViews {
       )
       .select([
         'actor.did as did',
+        'actor.handle as handle',
         'profile.uri as profileUri',
         'profile.displayName as displayName',
         'profile.description as description',
@@ -95,79 +86,101 @@ export class ActorViews {
           .where('mutedByDid', '=', viewer ?? '')
           .select('subjectDid')
           .as('requesterMuted'),
+        this.db.db
+          .selectFrom('list_item')
+          .if(!viewer, (q) => q.where(noMatch))
+          .innerJoin('list_mute', 'list_mute.listUri', 'list_item.listUri')
+          .where('list_mute.mutedByDid', '=', viewer ?? '')
+          .whereRef('list_item.subjectDid', '=', ref('actor.did'))
+          .select('list_item.listUri')
+          .limit(1)
+          .as('requesterMutedByList'),
       ])
 
-    const [profileInfos, labels, listMutes] = await Promise.all([
+    const [profileInfos, labels] = await Promise.all([
       profileInfosQb.execute(),
-      this.services.label(this.db).getLabelsForSubjects(skipLabels ? [] : dids),
-      this.getListMutes(dids, viewer),
+      this.services.label.getLabelsForSubjects(skipLabels ? [] : dids),
     ])
 
-    const profileInfoByDid = profileInfos.reduce((acc, info) => {
-      return Object.assign(acc, { [info.did]: info })
-    }, {} as Record<string, ArrayEl<typeof profileInfos>>)
+    const listUris: string[] = profileInfos
+      .map((a) => a.requesterMutedByList)
+      .filter((list) => !!list)
+    const listViews = await this.services.graph.getListViews(listUris, viewer)
 
-    const views = results.map((result) => {
-      const profileInfo = profileInfoByDid[result.did]
-      const avatar = profileInfo?.avatarCid
+    return profileInfos.reduce((acc, cur) => {
+      const actorLabels = labels[cur.did] ?? []
+      const avatar = cur?.avatarCid
         ? this.imgUriBuilder.getCommonSignedUri(
             'avatar',
-            profileInfo.did,
-            profileInfo.avatarCid,
+            cur.did,
+            cur.avatarCid,
           )
         : undefined
-      const banner = profileInfo?.bannerCid
+      const banner = cur?.bannerCid
         ? this.imgUriBuilder.getCommonSignedUri(
             'banner',
-            profileInfo.did,
-            profileInfo.bannerCid,
+            cur.did,
+            cur.bannerCid,
           )
         : undefined
-      return {
-        did: result.did,
-        handle: result.handle ?? INVALID_HANDLE,
-        displayName: profileInfo?.displayName || undefined,
-        description: profileInfo?.description || undefined,
+      const mutedByList =
+        cur.requesterMutedByList && listViews[cur.requesterMutedByList]
+          ? this.services.graph.formatListViewBasic(
+              listViews[cur.requesterMutedByList],
+            )
+          : undefined
+      const profile = {
+        did: cur.did,
+        handle: cur.handle ?? INVALID_HANDLE,
+        displayName: cur?.displayName || undefined,
+        description: cur?.description || undefined,
         avatar,
         banner,
-        followsCount: profileInfo?.followsCount ?? 0,
-        followersCount: profileInfo?.followersCount ?? 0,
-        postsCount: profileInfo?.postsCount ?? 0,
-        indexedAt: profileInfo?.indexedAt || undefined,
+        followsCount: cur?.followsCount ?? 0,
+        followersCount: cur?.followersCount ?? 0,
+        postsCount: cur?.postsCount ?? 0,
+        indexedAt: cur?.indexedAt || undefined,
         viewer: viewer
           ? {
-              following: profileInfo?.requesterFollowing || undefined,
-              followedBy: profileInfo?.requesterFollowedBy || undefined,
-              muted: !!profileInfo?.requesterMuted || !!listMutes[result.did],
-              mutedByList: listMutes[result.did],
-              blockedBy: !!profileInfo.requesterBlockedBy,
-              blocking: profileInfo.requesterBlocking || undefined,
+              following: cur?.requesterFollowing || undefined,
+              followedBy: cur?.requesterFollowedBy || undefined,
+              muted: !!cur?.requesterMuted || !!cur.requesterMutedByList,
+              mutedByList,
+              blockedBy: !!cur.requesterBlockedBy,
+              blocking: cur.requesterBlocking || undefined,
             }
           : undefined,
-        labels: labels[result.did] ?? [],
+        labels: skipLabels ? undefined : actorLabels,
       }
-    })
-
-    return Array.isArray(result) ? views : views[0]
+      acc[cur.did] = profile
+      return acc
+    }, {} as Record<string, ProfileViewDetailed>)
   }
 
-  profile(
+  async hydrateProfilesDetailed(
+    results: ActorResult[],
+    viewer: string | null,
+    opts?: { skipLabels?: boolean; includeSoftDeleted?: boolean },
+  ): Promise<ProfileViewDetailed[]> {
+    const profiles = await this.profilesDetailed(results, viewer, opts)
+    return mapDefined(results, (result) => profiles[result.did])
+  }
+
+  async profileDetailed(
     result: ActorResult,
     viewer: string | null,
     opts?: { skipLabels?: boolean; includeSoftDeleted?: boolean },
-  ): Promise<ProfileView>
-  profile(
-    result: ActorResult[],
+  ): Promise<ProfileViewDetailed | null> {
+    const profiles = await this.profilesDetailed([result], viewer, opts)
+    return profiles[result.did] ?? null
+  }
+
+  async profiles(
+    results: ActorResult[],
     viewer: string | null,
     opts?: { skipLabels?: boolean; includeSoftDeleted?: boolean },
-  ): Promise<ProfileView[]>
-  async profile(
-    result: ActorResult | ActorResult[],
-    viewer: string | null,
-    opts?: { skipLabels?: boolean; includeSoftDeleted?: boolean },
-  ): Promise<ProfileView | ProfileView[]> {
-    const results = Array.isArray(result) ? result : [result]
-    if (results.length === 0) return []
+  ): Promise<Record<string, ProfileView>> {
+    if (results.length === 0) return {}
 
     const { ref } = this.db.db.dynamic
     const { skipLabels = false, includeSoftDeleted = false } = opts ?? {}
@@ -182,6 +195,7 @@ export class ActorViews {
       )
       .select([
         'actor.did as did',
+        'actor.handle as handle',
         'profile.uri as profileUri',
         'profile.displayName as displayName',
         'profile.description as description',
@@ -222,120 +236,121 @@ export class ActorViews {
           .where('mutedByDid', '=', viewer ?? '')
           .select('subjectDid')
           .as('requesterMuted'),
+        this.db.db
+          .selectFrom('list_item')
+          .if(!viewer, (q) => q.where(noMatch))
+          .innerJoin('list_mute', 'list_mute.listUri', 'list_item.listUri')
+          .where('list_mute.mutedByDid', '=', viewer ?? '')
+          .whereRef('list_item.subjectDid', '=', ref('actor.did'))
+          .select('list_item.listUri')
+          .limit(1)
+          .as('requesterMutedByList'),
       ])
 
-    const [profileInfos, labels, listMutes] = await Promise.all([
+    const [profileInfos, labels] = await Promise.all([
       profileInfosQb.execute(),
-      this.services.label(this.db).getLabelsForSubjects(skipLabels ? [] : dids),
-      this.getListMutes(dids, viewer),
+      this.services.label.getLabelsForSubjects(skipLabels ? [] : dids),
     ])
 
-    const profileInfoByDid = profileInfos.reduce((acc, info) => {
-      return Object.assign(acc, { [info.did]: info })
-    }, {} as Record<string, ArrayEl<typeof profileInfos>>)
+    const listUris: string[] = profileInfos
+      .map((a) => a.requesterMutedByList)
+      .filter((list) => !!list)
+    const listViews = await this.services.graph.getListViews(listUris, viewer)
 
-    const views = results.map((result) => {
-      const profileInfo = profileInfoByDid[result.did]
-      const avatar = profileInfo?.avatarCid
+    return profileInfos.reduce((acc, cur) => {
+      const actorLabels = labels[cur.did] ?? []
+      const avatar = cur?.avatarCid
         ? this.imgUriBuilder.getCommonSignedUri(
             'avatar',
-            profileInfo.did,
-            profileInfo.avatarCid,
+            cur.did,
+            cur.avatarCid,
           )
         : undefined
-      return {
-        did: result.did,
-        handle: result.handle ?? INVALID_HANDLE,
-        displayName: profileInfo?.displayName || undefined,
-        description: profileInfo?.description || undefined,
+      const mutedByList =
+        cur.requesterMutedByList && listViews[cur.requesterMutedByList]
+          ? this.services.graph.formatListViewBasic(
+              listViews[cur.requesterMutedByList],
+            )
+          : undefined
+      const profile = {
+        did: cur.did,
+        handle: cur.handle ?? INVALID_HANDLE,
+        displayName: cur?.displayName || undefined,
+        description: cur?.description || undefined,
         avatar,
-        indexedAt: profileInfo?.indexedAt || undefined,
+        indexedAt: cur?.indexedAt || undefined,
         viewer: viewer
           ? {
-              muted: !!profileInfo?.requesterMuted || !!listMutes[result.did],
-              mutedByList: listMutes[result.did],
-              blockedBy: !!profileInfo.requesterBlockedBy,
-              blocking: profileInfo.requesterBlocking || undefined,
-              following: profileInfo?.requesterFollowing || undefined,
-              followedBy: profileInfo?.requesterFollowedBy || undefined,
+              muted: !!cur?.requesterMuted || !!cur.requesterMutedByList,
+              mutedByList,
+              blockedBy: !!cur.requesterBlockedBy,
+              blocking: cur.requesterBlocking || undefined,
+              following: cur?.requesterFollowing || undefined,
+              followedBy: cur?.requesterFollowedBy || undefined,
             }
           : undefined,
-        labels: labels[result.did] ?? [],
+        labels: skipLabels ? undefined : actorLabels,
       }
-    })
-
-    return Array.isArray(result) ? views : views[0]
+      acc[cur.did] = profile
+      return acc
+    }, {} as Record<string, ProfileView>)
   }
 
-  // @NOTE keep in sync with feedService.getActorViews()
-  profileBasic(
+  async hydrateProfiles(
+    results: ActorResult[],
+    viewer: string | null,
+    opts?: { skipLabels?: boolean; includeSoftDeleted?: boolean },
+  ): Promise<ProfileView[]> {
+    const profiles = await this.profiles(results, viewer, opts)
+    return mapDefined(results, (result) => profiles[result.did])
+  }
+
+  async profile(
     result: ActorResult,
     viewer: string | null,
     opts?: { skipLabels?: boolean; includeSoftDeleted?: boolean },
-  ): Promise<ProfileViewBasic>
-  profileBasic(
-    result: ActorResult[],
-    viewer: string | null,
-    opts?: { skipLabels?: boolean; includeSoftDeleted?: boolean },
-  ): Promise<ProfileViewBasic[]>
-  async profileBasic(
-    result: ActorResult | ActorResult[],
-    viewer: string | null,
-    opts?: { skipLabels?: boolean; includeSoftDeleted?: boolean },
-  ): Promise<ProfileViewBasic | ProfileViewBasic[]> {
-    const results = Array.isArray(result) ? result : [result]
-    if (results.length === 0) return []
-
-    const profiles = await this.profile(results, viewer, opts)
-    const views = profiles.map((view) => ({
-      did: view.did,
-      handle: view.handle,
-      displayName: view.displayName,
-      avatar: view.avatar,
-      viewer: view.viewer,
-    }))
-
-    return Array.isArray(result) ? views : views[0]
+  ): Promise<ProfileView | null> {
+    const profiles = await this.profiles([result], viewer, opts)
+    return profiles[result.did] ?? null
   }
 
-  async getListMutes(
-    subjects: string[],
-    mutedBy: string | null,
-  ): Promise<Record<string, ListViewBasic>> {
-    if (mutedBy === null) return {}
-    if (subjects.length < 1) return {}
-    const res = await this.db.db
-      .selectFrom('list_item')
-      .innerJoin('list_mute', 'list_mute.listUri', 'list_item.listUri')
-      .innerJoin('list', 'list.uri', 'list_item.listUri')
-      .where('list_mute.mutedByDid', '=', mutedBy)
-      .where('list_item.subjectDid', 'in', subjects)
-      .selectAll('list')
-      .select('list_item.subjectDid as subjectDid')
-      .execute()
-    return res.reduce(
-      (acc, cur) => ({
-        ...acc,
-        [cur.subjectDid]: {
-          uri: cur.uri,
-          cid: cur.cid,
-          name: cur.name,
-          purpose: cur.purpose,
-          avatar: cur.avatarCid
-            ? this.imgUriBuilder.getCommonSignedUri(
-                'avatar',
-                cur.creator,
-                cur.avatarCid,
-              )
-            : undefined,
-          viewer: {
-            muted: true,
-          },
-          indexedAt: cur.indexedAt,
-        },
-      }),
-      {} as Record<string, ListViewBasic>,
-    )
+  // @NOTE keep in sync with feedService.getActorViews()
+  async profilesBasic(
+    results: ActorResult[],
+    viewer: string | null,
+    opts?: { skipLabels?: boolean; includeSoftDeleted?: boolean },
+  ): Promise<Record<string, ProfileViewBasic>> {
+    if (results.length === 0) return {}
+    const profiles = await this.profiles(results, viewer, opts)
+    return Object.values(profiles).reduce((acc, cur) => {
+      const profile = {
+        did: cur.did,
+        handle: cur.handle,
+        displayName: cur.displayName,
+        avatar: cur.avatar,
+        viewer: cur.viewer,
+      }
+      acc[cur.did] = profile
+      return acc
+    }, {} as Record<string, ProfileViewBasic>)
+  }
+
+  async hydrateProfilesBasic(
+    results: ActorResult[],
+    viewer: string | null,
+    opts?: { skipLabels?: boolean; includeSoftDeleted?: boolean },
+  ): Promise<ProfileViewBasic[]> {
+    const profiles = await this.profilesBasic(results, viewer, opts)
+    return mapDefined(results, (result) => profiles[result.did])
+  }
+
+  async profileBasic(
+    result: ActorResult,
+    viewer: string | null,
+    opts?: { skipLabels?: boolean; includeSoftDeleted?: boolean },
+  ): Promise<ProfileViewBasic | null> {
+    const profiles = await this.profilesBasic([result], viewer, opts)
+    return profiles[result.did] ?? null
   }
 }
 
diff --git a/packages/bsky/src/services/feed/index.ts b/packages/bsky/src/services/feed/index.ts
index c2156d2c..10d42731 100644
--- a/packages/bsky/src/services/feed/index.ts
+++ b/packages/bsky/src/services/feed/index.ts
@@ -191,27 +191,30 @@ export class FeedService {
     const listViews = await this.services.graph.getListViews(listUris, viewer)
     return actors.reduce((acc, cur) => {
       const actorLabels = labels[cur.did] ?? []
+      const avatar = cur.avatarCid
+        ? this.imgUriBuilder.getCommonSignedUri(
+            'avatar',
+            cur.did,
+            cur.avatarCid,
+          )
+        : undefined
+      const mutedByList =
+        cur.requesterMutedByList && listViews[cur.requesterMutedByList]
+          ? this.services.graph.formatListViewBasic(
+              listViews[cur.requesterMutedByList],
+            )
+          : undefined
       return {
         ...acc,
         [cur.did]: {
           did: cur.did,
           handle: cur.handle ?? INVALID_HANDLE,
           displayName: cur.displayName ?? undefined,
-          avatar: cur.avatarCid
-            ? this.imgUriBuilder.getCommonSignedUri(
-                'avatar',
-                cur.did,
-                cur.avatarCid,
-              )
-            : undefined,
+          avatar,
           viewer: viewer
             ? {
                 muted: !!cur?.requesterMuted || !!cur?.requesterMutedByList,
-                mutedByList: cur.requesterMutedByList
-                  ? this.services.graph.formatListViewBasic(
-                      listViews[cur.requesterMutedByList],
-                    )
-                  : undefined,
+                mutedByList,
                 blockedBy: !!cur?.requesterBlockedBy,
                 blocking: cur?.requesterBlocking || undefined,
                 following: cur?.requesterFollowing || undefined,
diff --git a/packages/common-web/src/arrays.ts b/packages/common-web/src/arrays.ts
new file mode 100644
index 00000000..51598fc8
--- /dev/null
+++ b/packages/common-web/src/arrays.ts
@@ -0,0 +1,13 @@
+export const mapDefined = <T, S>(
+  arr: T[],
+  fn: (obj: T) => S | undefined,
+): S[] => {
+  const output: S[] = []
+  for (const item of arr) {
+    const val = fn(item)
+    if (val !== undefined) {
+      output.push(val)
+    }
+  }
+  return output
+}
diff --git a/packages/common-web/src/index.ts b/packages/common-web/src/index.ts
index 8be988e7..e1256774 100644
--- a/packages/common-web/src/index.ts
+++ b/packages/common-web/src/index.ts
@@ -1,6 +1,7 @@
 export * as check from './check'
 export * as util from './util'
 
+export * from './arrays'
 export * from './async'
 export * from './util'
 export * from './tid'
diff --git a/packages/pds/src/app-view/api/app/bsky/actor/getProfile.ts b/packages/pds/src/app-view/api/app/bsky/actor/getProfile.ts
index df37174f..f84ecb7e 100644
--- a/packages/pds/src/app-view/api/app/bsky/actor/getProfile.ts
+++ b/packages/pds/src/app-view/api/app/bsky/actor/getProfile.ts
@@ -34,10 +34,17 @@ export default function (server: Server, ctx: AppContext) {
           'AccountTakedown',
         )
       }
+      const profile = await actorService.views.profileDetailed(
+        actorRes,
+        requester,
+      )
+      if (!profile) {
+        throw new InvalidRequestError('Profile not found')
+      }
 
       return {
         encoding: 'application/json',
-        body: await actorService.views.profileDetailed(actorRes, requester),
+        body: profile,
       }
     },
   })
diff --git a/packages/pds/src/app-view/api/app/bsky/actor/getProfiles.ts b/packages/pds/src/app-view/api/app/bsky/actor/getProfiles.ts
index 673868df..78719975 100644
--- a/packages/pds/src/app-view/api/app/bsky/actor/getProfiles.ts
+++ b/packages/pds/src/app-view/api/app/bsky/actor/getProfiles.ts
@@ -26,7 +26,7 @@ export default function (server: Server, ctx: AppContext) {
       return {
         encoding: 'application/json',
         body: {
-          profiles: await actorService.views.profileDetailed(
+          profiles: await actorService.views.hydrateProfilesDetailed(
             actorsRes,
             requester,
           ),
diff --git a/packages/pds/src/app-view/api/app/bsky/actor/getSuggestions.ts b/packages/pds/src/app-view/api/app/bsky/actor/getSuggestions.ts
index 540f7795..edd426fe 100644
--- a/packages/pds/src/app-view/api/app/bsky/actor/getSuggestions.ts
+++ b/packages/pds/src/app-view/api/app/bsky/actor/getSuggestions.ts
@@ -64,14 +64,13 @@ export default function (server: Server, ctx: AppContext) {
       }
 
       const suggestionsRes = await suggestionsQb.execute()
-
       return {
         encoding: 'application/json',
         body: {
           cursor: suggestionsRes.at(-1)?.did,
           actors: await services.appView
             .actor(ctx.db)
-            .views.profile(suggestionsRes, requester),
+            .views.hydrateProfiles(suggestionsRes, requester),
         },
       }
     },
diff --git a/packages/pds/src/app-view/api/app/bsky/actor/searchActors.ts b/packages/pds/src/app-view/api/app/bsky/actor/searchActors.ts
index 380fea56..20516204 100644
--- a/packages/pds/src/app-view/api/app/bsky/actor/searchActors.ts
+++ b/packages/pds/src/app-view/api/app/bsky/actor/searchActors.ts
@@ -52,7 +52,7 @@ export default function (server: Server, ctx: AppContext) {
 
       const actors = await services.appView
         .actor(db)
-        .views.profile(results, requester)
+        .views.hydrateProfiles(results, requester)
 
       const filtered = actors.filter(
         (actor) => !actor.viewer?.blocking && !actor.viewer?.blockedBy,
diff --git a/packages/pds/src/app-view/api/app/bsky/actor/searchActorsTypeahead.ts b/packages/pds/src/app-view/api/app/bsky/actor/searchActorsTypeahead.ts
index 2cf879af..9a133c59 100644
--- a/packages/pds/src/app-view/api/app/bsky/actor/searchActorsTypeahead.ts
+++ b/packages/pds/src/app-view/api/app/bsky/actor/searchActorsTypeahead.ts
@@ -48,7 +48,7 @@ export default function (server: Server, ctx: AppContext) {
 
       const actors = await services.appView
         .actor(db)
-        .views.profileBasic(results, requester)
+        .views.hydrateProfilesBasic(results, requester)
 
       const filtered = actors.filter(
         (actor) => !actor.viewer?.blocking && !actor.viewer?.blockedBy,
diff --git a/packages/pds/src/app-view/api/app/bsky/feed/getActorFeeds.ts b/packages/pds/src/app-view/api/app/bsky/feed/getActorFeeds.ts
index 6cc8becb..4ddbe491 100644
--- a/packages/pds/src/app-view/api/app/bsky/feed/getActorFeeds.ts
+++ b/packages/pds/src/app-view/api/app/bsky/feed/getActorFeeds.ts
@@ -48,6 +48,10 @@ export default function (server: Server, ctx: AppContext) {
         feedsQb.execute(),
         actorService.views.profile(creatorRes, requester),
       ])
+      if (!creatorProfile) {
+        throw new InvalidRequestError(`Actor not found: ${actor}`)
+      }
+
       const profiles = { [creatorProfile.did]: creatorProfile }
 
       const feeds = feedsRes.map((row) =>
diff --git a/packages/pds/src/app-view/api/app/bsky/feed/getLikes.ts b/packages/pds/src/app-view/api/app/bsky/feed/getLikes.ts
index 262dd678..63d82d4d 100644
--- a/packages/pds/src/app-view/api/app/bsky/feed/getLikes.ts
+++ b/packages/pds/src/app-view/api/app/bsky/feed/getLikes.ts
@@ -1,3 +1,4 @@
+import { mapDefined } from '@atproto/common'
 import { Server } from '../../../../../lexicon'
 import { paginate, TimeCidKeyset } from '../../../../../db/pagination'
 import AppContext from '../../../../../context'
@@ -57,7 +58,17 @@ export default function (server: Server, ctx: AppContext) {
       const likesRes = await builder.execute()
       const actors = await services.appView
         .actor(db)
-        .views.profile(likesRes, requester)
+        .views.profiles(likesRes, requester)
+
+      const likes = mapDefined(likesRes, (row) =>
+        actors[row.did]
+          ? {
+              createdAt: row.createdAt,
+              indexedAt: row.indexedAt,
+              actor: actors[row.did],
+            }
+          : undefined,
+      )
 
       return {
         encoding: 'application/json',
@@ -65,11 +76,7 @@ export default function (server: Server, ctx: AppContext) {
           uri,
           cid,
           cursor: keyset.packFromResult(likesRes),
-          likes: likesRes.map((row, i) => ({
-            createdAt: row.createdAt,
-            indexedAt: row.indexedAt,
-            actor: actors[i],
-          })),
+          likes,
         },
       }
     },
diff --git a/packages/pds/src/app-view/api/app/bsky/feed/getRepostedBy.ts b/packages/pds/src/app-view/api/app/bsky/feed/getRepostedBy.ts
index 4632883d..fb243809 100644
--- a/packages/pds/src/app-view/api/app/bsky/feed/getRepostedBy.ts
+++ b/packages/pds/src/app-view/api/app/bsky/feed/getRepostedBy.ts
@@ -58,7 +58,7 @@ export default function (server: Server, ctx: AppContext) {
       const repostedByRes = await builder.execute()
       const repostedBy = await services.appView
         .actor(db)
-        .views.profile(repostedByRes, requester)
+        .views.hydrateProfiles(repostedByRes, requester)
 
       return {
         encoding: 'application/json',
diff --git a/packages/pds/src/app-view/api/app/bsky/graph/getBlocks.ts b/packages/pds/src/app-view/api/app/bsky/graph/getBlocks.ts
index e76eb296..b3eaef40 100644
--- a/packages/pds/src/app-view/api/app/bsky/graph/getBlocks.ts
+++ b/packages/pds/src/app-view/api/app/bsky/graph/getBlocks.ts
@@ -56,7 +56,10 @@ export default function (server: Server, ctx: AppContext) {
       const blocksRes = await blocksReq.execute()
 
       const actorService = services.appView.actor(db)
-      const blocks = await actorService.views.profile(blocksRes, requester)
+      const blocks = await actorService.views.hydrateProfiles(
+        blocksRes,
+        requester,
+      )
 
       return {
         encoding: 'application/json',
diff --git a/packages/pds/src/app-view/api/app/bsky/graph/getFollowers.ts b/packages/pds/src/app-view/api/app/bsky/graph/getFollowers.ts
index 93bf7307..d74a4c26 100644
--- a/packages/pds/src/app-view/api/app/bsky/graph/getFollowers.ts
+++ b/packages/pds/src/app-view/api/app/bsky/graph/getFollowers.ts
@@ -60,9 +60,12 @@ export default function (server: Server, ctx: AppContext) {
 
       const followersRes = await followersReq.execute()
       const [followers, subject] = await Promise.all([
-        actorService.views.profile(followersRes, requester),
+        actorService.views.hydrateProfiles(followersRes, requester),
         actorService.views.profile(subjectRes, requester),
       ])
+      if (!subject) {
+        throw new InvalidRequestError(`Actor not found: ${actor}`)
+      }
 
       return {
         encoding: 'application/json',
diff --git a/packages/pds/src/app-view/api/app/bsky/graph/getFollows.ts b/packages/pds/src/app-view/api/app/bsky/graph/getFollows.ts
index 75edf58c..703b8355 100644
--- a/packages/pds/src/app-view/api/app/bsky/graph/getFollows.ts
+++ b/packages/pds/src/app-view/api/app/bsky/graph/getFollows.ts
@@ -60,9 +60,12 @@ export default function (server: Server, ctx: AppContext) {
 
       const followsRes = await followsReq.execute()
       const [follows, subject] = await Promise.all([
-        actorService.views.profile(followsRes, requester),
+        actorService.views.hydrateProfiles(followsRes, requester),
         actorService.views.profile(creatorRes, requester),
       ])
+      if (!subject) {
+        throw new InvalidRequestError(`Actor not found: ${actor}`)
+      }
 
       return {
         encoding: 'application/json',
diff --git a/packages/pds/src/app-view/api/app/bsky/graph/getList.ts b/packages/pds/src/app-view/api/app/bsky/graph/getList.ts
index c711ea93..00eeaba8 100644
--- a/packages/pds/src/app-view/api/app/bsky/graph/getList.ts
+++ b/packages/pds/src/app-view/api/app/bsky/graph/getList.ts
@@ -2,7 +2,6 @@ import { InvalidRequestError } from '@atproto/xrpc-server'
 import { Server } from '../../../../../lexicon'
 import { paginate, TimeCidKeyset } from '../../../../../db/pagination'
 import AppContext from '../../../../../context'
-import { ProfileView } from '../../../../../lexicon/types/app/bsky/actor/defs'
 
 export default function (server: Server, ctx: AppContext) {
   server.app.bsky.graph.getList({
@@ -51,20 +50,17 @@ export default function (server: Server, ctx: AppContext) {
       const itemsRes = await itemsReq.execute()
 
       const actorService = services.appView.actor(db)
-      const profiles = await actorService.views.profile(itemsRes, requester)
-      const profilesMap = profiles.reduce(
-        (acc, cur) => ({
-          ...acc,
-          [cur.did]: cur,
-        }),
-        {} as Record<string, ProfileView>,
+      const profiles = await actorService.views.hydrateProfiles(
+        itemsRes,
+        requester,
       )
 
-      const items = itemsRes.map((item) => ({
-        subject: profilesMap[item.did],
-      }))
+      const items = profiles.map((subject) => ({ subject }))
 
       const creator = await actorService.views.profile(listRes, requester)
+      if (!creator) {
+        throw new InvalidRequestError(`Actor not found: ${listRes.handle}`)
+      }
 
       const subject = {
         uri: listRes.uri,
diff --git a/packages/pds/src/app-view/api/app/bsky/graph/getListMutes.ts b/packages/pds/src/app-view/api/app/bsky/graph/getListMutes.ts
index 89501548..21a64437 100644
--- a/packages/pds/src/app-view/api/app/bsky/graph/getListMutes.ts
+++ b/packages/pds/src/app-view/api/app/bsky/graph/getListMutes.ts
@@ -1,7 +1,6 @@
 import { Server } from '../../../../../lexicon'
 import { paginate, TimeCidKeyset } from '../../../../../db/pagination'
 import AppContext from '../../../../../context'
-import { ProfileView } from '../../../../../lexicon/types/app/bsky/actor/defs'
 
 export default function (server: Server, ctx: AppContext) {
   server.app.bsky.graph.getListMutes({
@@ -44,17 +43,10 @@ export default function (server: Server, ctx: AppContext) {
       const listsRes = await listsReq.execute()
 
       const actorService = ctx.services.appView.actor(ctx.db)
-      const profiles = await actorService.views.profile(listsRes, requester)
-      const profilesMap = profiles.reduce(
-        (acc, cur) => ({
-          ...acc,
-          [cur.did]: cur,
-        }),
-        {} as Record<string, ProfileView>,
-      )
+      const profiles = await actorService.views.profiles(listsRes, requester)
 
       const lists = listsRes.map((row) =>
-        graphService.formatListView(row, profilesMap),
+        graphService.formatListView(row, profiles),
       )
 
       return {
diff --git a/packages/pds/src/app-view/api/app/bsky/graph/getLists.ts b/packages/pds/src/app-view/api/app/bsky/graph/getLists.ts
index 7dc77e6c..58b83a20 100644
--- a/packages/pds/src/app-view/api/app/bsky/graph/getLists.ts
+++ b/packages/pds/src/app-view/api/app/bsky/graph/getLists.ts
@@ -46,6 +46,9 @@ export default function (server: Server, ctx: AppContext) {
         listsReq.execute(),
         actorService.views.profile(creatorRes, requester),
       ])
+      if (!creator) {
+        throw new InvalidRequestError(`Actor not found: ${actor}`)
+      }
       const profileMap = {
         [creator.did]: creator,
       }
diff --git a/packages/pds/src/app-view/api/app/bsky/graph/getMutes.ts b/packages/pds/src/app-view/api/app/bsky/graph/getMutes.ts
index 51950515..c26b7b1d 100644
--- a/packages/pds/src/app-view/api/app/bsky/graph/getMutes.ts
+++ b/packages/pds/src/app-view/api/app/bsky/graph/getMutes.ts
@@ -51,7 +51,7 @@ export default function (server: Server, ctx: AppContext) {
         encoding: 'application/json',
         body: {
           cursor: keyset.packFromResult(mutesRes),
-          mutes: await actorService.views.profile(mutesRes, requester),
+          mutes: await actorService.views.hydrateProfiles(mutesRes, requester),
         },
       }
     },
diff --git a/packages/pds/src/app-view/api/app/bsky/notification/listNotifications.ts b/packages/pds/src/app-view/api/app/bsky/notification/listNotifications.ts
index 2967c069..e97f5499 100644
--- a/packages/pds/src/app-view/api/app/bsky/notification/listNotifications.ts
+++ b/packages/pds/src/app-view/api/app/bsky/notification/listNotifications.ts
@@ -112,7 +112,7 @@ export default function (server: Server, ctx: AppContext) {
       const recordUris = notifs.map((notif) => notif.uri)
       const [blocks, authors, labels] = await Promise.all([
         blocksQb ? blocksQb.execute() : emptyBlocksResult,
-        actorService.views.profile(
+        actorService.views.profiles(
           notifs.map((notif) => ({
             did: notif.authorDid,
             handle: notif.authorHandle,
@@ -127,13 +127,14 @@ export default function (server: Server, ctx: AppContext) {
         return acc
       }, {} as Record<string, Uint8Array>)
 
-      const notifications = notifs.flatMap((notif, i) => {
+      const notifications = common.mapDefined(notifs, (notif) => {
         const bytes = bytesByCid[notif.cid]
-        if (!bytes) return [] // Filter out
+        const author = authors[notif.authorDid]
+        if (!bytes || !author) return undefined
         return {
           uri: notif.uri,
           cid: notif.cid,
-          author: authors[i],
+          author: authors[notif.authorDid],
           reason: notif.reason,
           reasonSubject: notif.reasonSubject || undefined,
           record: common.cborBytesToRecord(bytes),
diff --git a/packages/pds/src/app-view/services/actor/views.ts b/packages/pds/src/app-view/services/actor/views.ts
index a00915b8..6f0e6a8d 100644
--- a/packages/pds/src/app-view/services/actor/views.ts
+++ b/packages/pds/src/app-view/services/actor/views.ts
@@ -1,4 +1,4 @@
-import { ArrayEl } from '@atproto/common'
+import { mapDefined } from '@atproto/common'
 import {
   ProfileViewDetailed,
   ProfileView,
@@ -24,23 +24,12 @@ export class ActorViews {
     graph: GraphService.creator(this.imgUriBuilder)(this.db),
   }
 
-  profileDetailed(
-    result: ActorResult,
+  async profilesDetailed(
+    results: ActorResult[],
     viewer: string,
     opts?: { skipLabels?: boolean; includeSoftDeleted?: boolean },
-  ): Promise<ProfileViewDetailed>
-  profileDetailed(
-    result: ActorResult[],
-    viewer: string,
-    opts?: { skipLabels?: boolean; includeSoftDeleted?: boolean },
-  ): Promise<ProfileViewDetailed[]>
-  async profileDetailed(
-    result: ActorResult | ActorResult[],
-    viewer: string,
-    opts?: { skipLabels?: boolean; includeSoftDeleted?: boolean },
-  ): Promise<ProfileViewDetailed | ProfileViewDetailed[]> {
-    const results = Array.isArray(result) ? result : [result]
-    if (results.length === 0) return []
+  ): Promise<Record<string, ProfileViewDetailed>> {
+    if (results.length === 0) return {}
 
     const { ref } = this.db.db.dynamic
     const { skipLabels = false, includeSoftDeleted = false } = opts ?? {}
@@ -58,6 +47,7 @@ export class ActorViews {
       )
       .select([
         'did_handle.did as did',
+        'did_handle.handle as handle',
         'profile.uri as profileUri',
         'profile.displayName as displayName',
         'profile.description as description',
@@ -112,72 +102,75 @@ export class ActorViews {
       this.services.label.getLabelsForSubjects(skipLabels ? [] : dids),
     ])
 
-    const profileInfoByDid = profileInfos.reduce((acc, info) => {
-      return Object.assign(acc, { [info.did]: info })
-    }, {} as Record<string, ArrayEl<typeof profileInfos>>)
-
     const listUris: string[] = profileInfos
       .map((a) => a.requesterMutedByList)
       .filter((list) => !!list)
     const listViews = await this.services.graph.getListViews(listUris, viewer)
 
-    const views = results.map((result) => {
-      const profileInfo = profileInfoByDid[result.did]
-      const avatar = profileInfo?.avatarCid
-        ? this.imgUriBuilder.getCommonSignedUri('avatar', profileInfo.avatarCid)
+    return profileInfos.reduce((acc, cur) => {
+      const actorLabels = labels[cur.did] ?? []
+      const avatar = cur?.avatarCid
+        ? this.imgUriBuilder.getCommonSignedUri('avatar', cur.avatarCid)
         : undefined
-      const banner = profileInfo?.bannerCid
-        ? this.imgUriBuilder.getCommonSignedUri('banner', profileInfo.bannerCid)
+      const banner = cur?.bannerCid
+        ? this.imgUriBuilder.getCommonSignedUri('banner', cur.bannerCid)
         : undefined
-      return {
-        did: result.did,
-        handle: result.handle,
-        displayName: truncateUtf8(profileInfo?.displayName, 64) || undefined,
-        description: truncateUtf8(profileInfo?.description, 256) || undefined,
+      const mutedByList =
+        cur.requesterMutedByList && listViews[cur.requesterMutedByList]
+          ? this.services.graph.formatListViewBasic(
+              listViews[cur.requesterMutedByList],
+            )
+          : undefined
+      const profile = {
+        did: cur.did,
+        handle: cur.handle,
+        displayName: truncateUtf8(cur?.displayName, 64) || undefined,
+        description: truncateUtf8(cur?.description, 256) || undefined,
         avatar,
         banner,
-        followsCount: profileInfo?.followsCount || 0,
-        followersCount: profileInfo?.followersCount || 0,
-        postsCount: profileInfo?.postsCount || 0,
-        indexedAt: profileInfo?.indexedAt || undefined,
+        followsCount: cur?.followsCount || 0,
+        followersCount: cur?.followersCount || 0,
+        postsCount: cur?.postsCount || 0,
+        indexedAt: cur?.indexedAt || undefined,
         viewer: {
-          muted:
-            !!profileInfo?.requesterMuted ||
-            !!profileInfo?.requesterMutedByList,
-          mutedByList: profileInfo.requesterMutedByList
-            ? this.services.graph.formatListViewBasic(
-                listViews[profileInfo.requesterMutedByList],
-              )
-            : undefined,
-          blockedBy: !!profileInfo.requesterBlockedBy,
-          blocking: profileInfo.requesterBlocking || undefined,
-          following: profileInfo?.requesterFollowing || undefined,
-          followedBy: profileInfo?.requesterFollowedBy || undefined,
+          muted: !!cur?.requesterMuted || !!cur?.requesterMutedByList,
+          mutedByList,
+          blockedBy: !!cur.requesterBlockedBy,
+          blocking: cur.requesterBlocking || undefined,
+          following: cur?.requesterFollowing || undefined,
+          followedBy: cur?.requesterFollowedBy || undefined,
         },
-        labels: labels[result.did] ?? [],
+        labels: skipLabels ? undefined : actorLabels,
       }
-    })
-
-    return Array.isArray(result) ? views : views[0]
+      acc[cur.did] = profile
+      return acc
+    }, {} as Record<string, ProfileViewDetailed>)
   }
 
-  profile(
+  async hydrateProfilesDetailed(
+    results: ActorResult[],
+    viewer: string,
+    opts?: { skipLabels?: boolean; includeSoftDeleted?: boolean },
+  ): Promise<ProfileViewDetailed[]> {
+    const profiles = await this.profilesDetailed(results, viewer, opts)
+    return mapDefined(results, (result) => profiles[result.did])
+  }
+
+  async profileDetailed(
     result: ActorResult,
     viewer: string,
     opts?: { skipLabels?: boolean; includeSoftDeleted?: boolean },
-  ): Promise<ProfileView>
-  profile(
-    result: ActorResult[],
+  ): Promise<ProfileViewDetailed | null> {
+    const profiles = await this.profilesDetailed([result], viewer, opts)
+    return profiles[result.did] ?? null
+  }
+
+  async profiles(
+    results: ActorResult[],
     viewer: string,
     opts?: { skipLabels?: boolean; includeSoftDeleted?: boolean },
-  ): Promise<ProfileView[]>
-  async profile(
-    result: ActorResult | ActorResult[],
-    viewer: string,
-    opts?: { skipLabels?: boolean; includeSoftDeleted?: boolean },
-  ): Promise<ProfileView | ProfileView[]> {
-    const results = Array.isArray(result) ? result : [result]
-    if (results.length === 0) return []
+  ): Promise<Record<string, ProfileView>> {
+    if (results.length === 0) return {}
 
     const { ref } = this.db.db.dynamic
     const { skipLabels = false, includeSoftDeleted = false } = opts ?? {}
@@ -193,6 +186,7 @@ export class ActorViews {
       )
       .select([
         'did_handle.did as did',
+        'did_handle.handle as handle',
         'profile.uri as profileUri',
         'profile.displayName as displayName',
         'profile.description as description',
@@ -243,78 +237,100 @@ export class ActorViews {
       this.services.label.getLabelsForSubjects(skipLabels ? [] : dids),
     ])
 
-    const profileInfoByDid = profileInfos.reduce((acc, info) => {
-      return Object.assign(acc, { [info.did]: info })
-    }, {} as Record<string, ArrayEl<typeof profileInfos>>)
-
     const listUris: string[] = profileInfos
       .map((a) => a.requesterMutedByList)
       .filter((list) => !!list)
     const listViews = await this.services.graph.getListViews(listUris, viewer)
 
-    const views = results.map((result) => {
-      const profileInfo = profileInfoByDid[result.did]
-      const avatar = profileInfo?.avatarCid
-        ? this.imgUriBuilder.getCommonSignedUri('avatar', profileInfo.avatarCid)
+    return profileInfos.reduce((acc, cur) => {
+      const actorLabels = labels[cur.did] ?? []
+      const avatar = cur.avatarCid
+        ? this.imgUriBuilder.getCommonSignedUri('avatar', cur.avatarCid)
         : undefined
-      return {
-        did: result.did,
-        handle: result.handle,
-        displayName: truncateUtf8(profileInfo?.displayName, 64) || undefined,
-        description: truncateUtf8(profileInfo?.description, 256) || undefined,
+      const mutedByList =
+        cur.requesterMutedByList && listViews[cur.requesterMutedByList]
+          ? this.services.graph.formatListViewBasic(
+              listViews[cur.requesterMutedByList],
+            )
+          : undefined
+      const profile = {
+        did: cur.did,
+        handle: cur.handle,
+        displayName: truncateUtf8(cur?.displayName, 64) || undefined,
+        description: truncateUtf8(cur?.description, 256) || undefined,
         avatar,
-        indexedAt: profileInfo?.indexedAt || undefined,
+        indexedAt: cur?.indexedAt || undefined,
         viewer: {
-          muted:
-            !!profileInfo?.requesterMuted ||
-            !!profileInfo?.requesterMutedByList,
-          mutedByList: profileInfo.requesterMutedByList
-            ? this.services.graph.formatListViewBasic(
-                listViews[profileInfo.requesterMutedByList],
-              )
-            : undefined,
-          blockedBy: !!profileInfo.requesterBlockedBy,
-          blocking: profileInfo.requesterBlocking || undefined,
-          following: profileInfo?.requesterFollowing || undefined,
-          followedBy: profileInfo?.requesterFollowedBy || undefined,
+          muted: !!cur?.requesterMuted || !!cur?.requesterMutedByList,
+          mutedByList,
+          blockedBy: !!cur.requesterBlockedBy,
+          blocking: cur.requesterBlocking || undefined,
+          following: cur?.requesterFollowing || undefined,
+          followedBy: cur?.requesterFollowedBy || undefined,
         },
-        labels: labels[result.did] ?? [],
+        labels: skipLabels ? undefined : actorLabels,
       }
-    })
-
-    return Array.isArray(result) ? views : views[0]
+      acc[cur.did] = profile
+      return acc
+    }, {} as Record<string, ProfileView>)
   }
 
-  // @NOTE keep in sync with feedService.getActorViews()
-  profileBasic(
+  async hydrateProfiles(
+    results: ActorResult[],
+    viewer: string,
+    opts?: { skipLabels?: boolean; includeSoftDeleted?: boolean },
+  ): Promise<ProfileView[]> {
+    const profiles = await this.profiles(results, viewer, opts)
+    return mapDefined(results, (result) => profiles[result.did])
+  }
+
+  async profile(
     result: ActorResult,
     viewer: string,
     opts?: { skipLabels?: boolean; includeSoftDeleted?: boolean },
-  ): Promise<ProfileViewBasic>
-  profileBasic(
-    result: ActorResult[],
+  ): Promise<ProfileView | null> {
+    const profiles = await this.profiles([result], viewer, opts)
+    return profiles[result.did] ?? null
+  }
+
+  // @NOTE keep in sync with feedService.getActorViews()
+  async profilesBasic(
+    results: ActorResult[],
     viewer: string,
     opts?: { skipLabels?: boolean; includeSoftDeleted?: boolean },
-  ): Promise<ProfileViewBasic[]>
+  ): Promise<Record<string, ProfileViewBasic>> {
+    if (results.length === 0) return {}
+    const profiles = await this.profiles(results, viewer, opts)
+    return Object.values(profiles).reduce((acc, cur) => {
+      const profile = {
+        did: cur.did,
+        handle: cur.handle,
+        displayName: truncateUtf8(cur.displayName, 64) || undefined,
+        avatar: cur.avatar,
+        viewer: cur.viewer,
+        labels: cur.labels,
+      }
+      acc[cur.did] = profile
+      return acc
+    }, {} as Record<string, ProfileViewBasic>)
+  }
+
+  async hydrateProfilesBasic(
+    results: ActorResult[],
+    viewer: string,
+    opts?: { skipLabels?: boolean; includeSoftDeleted?: boolean },
+  ): Promise<ProfileViewBasic[]> {
+    const profiles = await this.profilesBasic(results, viewer, opts)
+    return mapDefined(results, (result) => profiles[result.did])
+  }
+
   async profileBasic(
-    result: ActorResult | ActorResult[],
+    result: ActorResult,
     viewer: string,
     opts?: { skipLabels?: boolean; includeSoftDeleted?: boolean },
-  ): Promise<ProfileViewBasic | ProfileViewBasic[]> {
-    const results = Array.isArray(result) ? result : [result]
-    if (results.length === 0) return []
-
-    const profiles = await this.profile(results, viewer, opts)
-    const views = profiles.map((view) => ({
-      did: view.did,
-      handle: view.handle,
-      displayName: truncateUtf8(view.displayName, 64) || undefined,
-      avatar: view.avatar,
-      viewer: view.viewer,
-      labels: view.labels,
-    }))
-
-    return Array.isArray(result) ? views : views[0]
+  ): Promise<ProfileViewBasic | null> {
+    const profiles = await this.profilesBasic([result], viewer, opts)
+    return profiles[result.did] ?? null
   }
 }
 
diff --git a/packages/pds/src/app-view/services/feed/index.ts b/packages/pds/src/app-view/services/feed/index.ts
index 3f1fc54a..d26f43d3 100644
--- a/packages/pds/src/app-view/services/feed/index.ts
+++ b/packages/pds/src/app-view/services/feed/index.ts
@@ -192,22 +192,25 @@ export class FeedService {
     )
     return actors.reduce((acc, cur) => {
       const actorLabels = labels[cur.did] ?? []
+      const avatar = cur.avatarCid
+        ? this.imgUriBuilder.getCommonSignedUri('avatar', cur.avatarCid)
+        : undefined
+      const mutedByList =
+        cur.requesterMutedByList && listViews[cur.requesterMutedByList]
+          ? this.services.graph.formatListViewBasic(
+              listViews[cur.requesterMutedByList],
+            )
+          : undefined
       return {
         ...acc,
         [cur.did]: {
           did: cur.did,
           handle: cur.handle,
           displayName: truncateUtf8(cur.displayName, 64) || undefined,
-          avatar: cur.avatarCid
-            ? this.imgUriBuilder.getCommonSignedUri('avatar', cur.avatarCid)
-            : undefined,
+          avatar,
           viewer: {
             muted: !!cur?.requesterMuted || !!cur?.requesterMutedByList,
-            mutedByList: cur.requesterMutedByList
-              ? this.services.graph.formatListViewBasic(
-                  listViews[cur.requesterMutedByList],
-                )
-              : undefined,
+            mutedByList,
             blockedBy: !!cur?.requesterBlockedBy,
             blocking: cur?.requesterBlocking || undefined,
             following: cur?.requesterFollowing || undefined,