diff --git a/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx b/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx
index da3dcb2239..d29c1c8a48 100644
--- a/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx
+++ b/invokeai/frontend/web/src/app/contexts/AddImageToBoardContext.tsx
@@ -3,6 +3,7 @@ import { useAppDispatch } from 'app/store/storeHooks';
 import { PropsWithChildren, createContext, useCallback, useState } from 'react';
 import { ImageDTO } from 'services/api';
 import { imageAddedToBoard } from '../../services/thunks/board';
+import { useAddImageToBoardMutation } from 'services/apiSlice';
 
 export type ImageUsage = {
   isInitialImage: boolean;
@@ -43,6 +44,8 @@ export const AddImageToBoardContextProvider = (props: Props) => {
   const dispatch = useAppDispatch();
   const { isOpen, onOpen, onClose } = useDisclosure();
 
+  const [addImageToBoard, result] = useAddImageToBoardMutation();
+
   // Clean up after deleting or dismissing the modal
   const closeAndClearImageToDelete = useCallback(() => {
     setImageToMove(undefined);
@@ -63,18 +66,14 @@ export const AddImageToBoardContextProvider = (props: Props) => {
   const handleAddToBoard = useCallback(
     (boardId: string) => {
       if (imageToMove) {
-        dispatch(
-          imageAddedToBoard({
-            requestBody: {
-              board_id: boardId,
-              image_name: imageToMove.image_name,
-            },
-          })
-        );
+        addImageToBoard({
+          board_id: boardId,
+          image_name: imageToMove.image_name,
+        });
         closeAndClearImageToDelete();
       }
     },
-    [closeAndClearImageToDelete, dispatch, imageToMove]
+    [addImageToBoard, closeAndClearImageToDelete, imageToMove]
   );
 
   return (
diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts
index 8c073e81d6..15fd48fbb2 100644
--- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts
+++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts
@@ -73,6 +73,10 @@ import { addImageCategoriesChangedListener } from './listeners/imageCategoriesCh
 import { addControlNetImageProcessedListener } from './listeners/controlNetImageProcessed';
 import { addControlNetAutoProcessListener } from './listeners/controlNetAutoProcess';
 import { addUpdateImageUrlsOnConnectListener } from './listeners/updateImageUrlsOnConnect';
+import {
+  addImageAddedToBoardFulfilledListener,
+  addImageAddedToBoardRejectedListener,
+} from './listeners/imageAddedToBoard';
 
 export const listenerMiddleware = createListenerMiddleware();
 
@@ -183,3 +187,7 @@ addControlNetAutoProcessListener();
 
 // Update image URLs on connect
 addUpdateImageUrlsOnConnectListener();
+
+// Boards
+addImageAddedToBoardFulfilledListener();
+addImageAddedToBoardRejectedListener();
diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageAddedToBoard.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageAddedToBoard.ts
new file mode 100644
index 0000000000..0f404cab68
--- /dev/null
+++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageAddedToBoard.ts
@@ -0,0 +1,40 @@
+import { log } from 'app/logging/useLogger';
+import { startAppListening } from '..';
+import { imageMetadataReceived } from 'services/thunks/image';
+import { api } from 'services/apiSlice';
+
+const moduleLog = log.child({ namespace: 'boards' });
+
+export const addImageAddedToBoardFulfilledListener = () => {
+  startAppListening({
+    matcher: api.endpoints.addImageToBoard.matchFulfilled,
+    effect: (action, { getState, dispatch }) => {
+      const { board_id, image_name } = action.meta.arg.originalArgs;
+
+      moduleLog.debug(
+        { data: { board_id, image_name } },
+        'Image added to board'
+      );
+
+      dispatch(
+        imageMetadataReceived({
+          imageName: image_name,
+        })
+      );
+    },
+  });
+};
+
+export const addImageAddedToBoardRejectedListener = () => {
+  startAppListening({
+    matcher: api.endpoints.addImageToBoard.matchRejected,
+    effect: (action, { getState, dispatch }) => {
+      const { board_id, image_name } = action.meta.arg.originalArgs;
+
+      moduleLog.debug(
+        { data: { board_id, image_name } },
+        'Problem adding image to board'
+      );
+    },
+  });
+};
diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts
index 4c0c057242..9792137bbe 100644
--- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts
+++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts
@@ -13,6 +13,7 @@ import { resetCanvas } from 'features/canvas/store/canvasSlice';
 import { controlNetReset } from 'features/controlNet/store/controlNetSlice';
 import { clearInitialImage } from 'features/parameters/store/generationSlice';
 import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
+import { api } from 'services/apiSlice';
 
 const moduleLog = log.child({ namespace: 'addRequestedImageDeletionListener' });
 
@@ -22,7 +23,7 @@ const moduleLog = log.child({ namespace: 'addRequestedImageDeletionListener' });
 export const addRequestedImageDeletionListener = () => {
   startAppListening({
     actionCreator: requestedImageDeletion,
-    effect: (action, { dispatch, getState }) => {
+    effect: async (action, { dispatch, getState, condition }) => {
       const { image, imageUsage } = action.payload;
 
       const { image_name } = image;
@@ -30,7 +31,7 @@ export const addRequestedImageDeletionListener = () => {
       const state = getState();
       const selectedImage = state.gallery.selectedImage;
 
-      if (selectedImage && selectedImage.image_name === image_name) {
+      if (selectedImage && selectedImage === image_name) {
         const ids = selectImagesIds(state);
         const entities = selectImagesEntities(state);
 
@@ -51,7 +52,7 @@ export const addRequestedImageDeletionListener = () => {
         const newSelectedImage = entities[newSelectedImageId];
 
         if (newSelectedImageId) {
-          dispatch(imageSelected(newSelectedImage));
+          dispatch(imageSelected(newSelectedImageId));
         } else {
           dispatch(imageSelected());
         }
@@ -79,7 +80,19 @@ export const addRequestedImageDeletionListener = () => {
       dispatch(imageRemoved(image_name));
 
       // Delete from server
-      dispatch(imageDeleted({ imageName: image_name }));
+      const { requestId } = dispatch(imageDeleted({ imageName: image_name }));
+
+      // Wait for successful deletion, then trigger boards to re-fetch
+      const wasImageDeleted = await condition(
+        (action) => action.meta.requestId === requestId,
+        30000
+      );
+
+      if (wasImageDeleted) {
+        dispatch(
+          api.util.invalidateTags([{ type: 'Board', id: image.board_id }])
+        );
+      }
     },
   });
 };
diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts
index 4032db3159..a9011f9356 100644
--- a/invokeai/frontend/web/src/app/store/store.ts
+++ b/invokeai/frontend/web/src/app/store/store.ts
@@ -33,6 +33,7 @@ import { actionsDenylist } from './middleware/devtools/actionsDenylist';
 import { serialize } from './enhancers/reduxRemember/serialize';
 import { unserialize } from './enhancers/reduxRemember/unserialize';
 import { LOCALSTORAGE_PREFIX } from './constants';
+import { api } from 'services/apiSlice';
 
 const allReducers = {
   canvas: canvasReducer,
@@ -49,6 +50,7 @@ const allReducers = {
   images: imagesReducer,
   controlNet: controlNetReducer,
   boards: boardsReducer,
+  [api.reducerPath]: api.reducer,
   // session: sessionReducer,
 };
 
@@ -87,6 +89,7 @@ export const store = configureStore({
       immutableCheck: false,
       serializableCheck: false,
     })
+      .concat(api.middleware)
       .concat(dynamicMiddlewares)
       .prepend(listenerMiddleware.middleware),
   devTools: {
diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx
index d8828fe736..284e6558ac 100644
--- a/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx
@@ -1,19 +1,20 @@
-import { Flex, Icon, Text } from '@chakra-ui/react';
+import { Flex, Icon, Spinner, Text } from '@chakra-ui/react';
 import { useCallback } from 'react';
 import { FaPlus } from 'react-icons/fa';
-import { useAppDispatch } from '../../../../app/store/storeHooks';
-import { boardCreated } from '../../../../services/thunks/board';
+import { useCreateBoardMutation } from 'services/apiSlice';
+
+const DEFAULT_BOARD_NAME = 'My Board';
 
 const AddBoardButton = () => {
-  const dispatch = useAppDispatch();
+  const [createBoard, { isLoading }] = useCreateBoardMutation();
 
   const handleCreateBoard = useCallback(() => {
-    dispatch(boardCreated({ requestBody: 'My Board' }));
-  }, [dispatch]);
+    createBoard(DEFAULT_BOARD_NAME);
+  }, [createBoard]);
 
   return (
     <Flex
-      onClick={handleCreateBoard}
+      onClick={isLoading ? undefined : handleCreateBoard}
       sx={{
         flexDir: 'column',
         justifyContent: 'space-between',
@@ -36,7 +37,11 @@ const AddBoardButton = () => {
           aspectRatio: '1/1',
         }}
       >
-        <Icon boxSize={8} color="base.700" as={FaPlus} />
+        {isLoading ? (
+          <Spinner />
+        ) : (
+          <Icon boxSize={8} color="base.700" as={FaPlus} />
+        )}
       </Flex>
       <Text sx={{ color: 'base.200', fontSize: 'xs' }}>New Board</Text>
     </Flex>
diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx
index 1f84d3be0e..be849e625e 100644
--- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx
@@ -25,6 +25,7 @@ import { searchBoardsSelector } from '../../store/boardSelectors';
 import { useSelector } from 'react-redux';
 import IAICollapse from '../../../../common/components/IAICollapse';
 import { CloseIcon } from '@chakra-ui/icons';
+import { useListBoardsQuery } from 'services/apiSlice';
 
 const selector = createSelector(
   [selectBoardsAll, boardsSelector],
@@ -40,9 +41,17 @@ const selector = createSelector(
 const BoardsList = () => {
   const dispatch = useAppDispatch();
   const { selectedBoard, searchText } = useAppSelector(selector);
-  const filteredBoards = useSelector(searchBoardsSelector);
+  // const filteredBoards = useSelector(searchBoardsSelector);
   const { isOpen, onToggle } = useDisclosure();
 
+  const { data } = useListBoardsQuery({ offset: 0, limit: 8 });
+
+  const filteredBoards = searchText
+    ? data?.items.filter((board) =>
+        board.board_name.toLowerCase().includes(searchText.toLowerCase())
+      )
+    : data.items;
+
   const [searchMode, setSearchMode] = useState(false);
 
   const handleBoardSearch = (searchTerm: string) => {
@@ -100,13 +109,14 @@ const BoardsList = () => {
                 <AllImagesBoard isSelected={!selectedBoard} />
               </>
             )}
-            {filteredBoards.map((board) => (
-              <HoverableBoard
-                key={board.board_id}
-                board={board}
-                isSelected={selectedBoard?.board_id === board.board_id}
-              />
-            ))}
+            {filteredBoards &&
+              filteredBoards.map((board) => (
+                <HoverableBoard
+                  key={board.board_id}
+                  board={board}
+                  isSelected={selectedBoard?.board_id === board.board_id}
+                />
+              ))}
           </Grid>
         </OverlayScrollbarsComponent>
       </>
diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx
index fdde7528cb..7ae864f55b 100644
--- a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx
@@ -26,6 +26,10 @@ import IAIDndImage from '../../../../common/components/IAIDndImage';
 import { defaultSelectorOptions } from '../../../../app/store/util/defaultMemoizeOptions';
 import { createSelector } from '@reduxjs/toolkit';
 import { RootState } from '../../../../app/store/store';
+import {
+  useDeleteBoardMutation,
+  useUpdateBoardMutation,
+} from 'services/apiSlice';
 
 const coverImageSelector = (imageName: string | undefined) =>
   createSelector(
@@ -59,19 +63,20 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => {
     dispatch(boardIdSelected(board_id));
   }, [board_id, dispatch]);
 
-  const handleDeleteBoard = useCallback(() => {
-    dispatch(boardDeleted(board_id));
-  }, [board_id, dispatch]);
+  const [updateBoard, { isLoading: isUpdateBoardLoading }] =
+    useUpdateBoardMutation();
+
+  const [deleteBoard, { isLoading: isDeleteBoardLoading }] =
+    useDeleteBoardMutation();
 
   const handleUpdateBoardName = (newBoardName: string) => {
-    dispatch(
-      boardUpdated({
-        boardId: board_id,
-        requestBody: { board_name: newBoardName },
-      })
-    );
+    updateBoard({ board_id, changes: { board_name: newBoardName } });
   };
 
+  const handleDeleteBoard = useCallback(() => {
+    deleteBoard(board_id);
+  }, [board_id, deleteBoard]);
+
   const handleDrop = useCallback(
     (droppedImage: ImageDTO) => {
       if (droppedImage.board_id === board_id) {
diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx
index 9136e23e03..edd4d215af 100644
--- a/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/Boards/UpdateImageBoardModal.tsx
@@ -9,6 +9,7 @@ import {
   Divider,
   Flex,
   Select,
+  Spinner,
   Text,
 } from '@chakra-ui/react';
 import IAIButton from 'common/components/IAIButton';
@@ -19,9 +20,11 @@ import { useSelector } from 'react-redux';
 import { selectBoardsAll } from '../../store/boardSlice';
 import IAISelect from '../../../../common/components/IAISelect';
 import IAIMantineSelect from 'common/components/IAIMantineSelect';
+import { useListAllBoardsQuery } from 'services/apiSlice';
 
 const UpdateImageBoardModal = () => {
-  const boards = useSelector(selectBoardsAll);
+  // const boards = useSelector(selectBoardsAll);
+  const { data: boards, isFetching } = useListAllBoardsQuery();
   const { isOpen, onClose, handleAddToBoard, image } = useContext(
     AddImageToBoardContext
   );
@@ -29,9 +32,9 @@ const UpdateImageBoardModal = () => {
 
   const cancelRef = useRef<HTMLButtonElement>(null);
 
-  const currentBoard = boards.filter(
+  const currentBoard = boards?.find(
     (board) => board.board_id === image?.board_id
-  )[0];
+  );
 
   return (
     <AlertDialog
@@ -55,15 +58,19 @@ const UpdateImageBoardModal = () => {
                     <strong>{currentBoard.board_name}</strong> to
                   </Text>
                 )}
-                <IAIMantineSelect
-                  placeholder="Select Board"
-                  onChange={(v) => setSelectedBoard(v)}
-                  value={selectedBoard}
-                  data={boards.map((board) => ({
-                    label: board.board_name,
-                    value: board.board_id,
-                  }))}
-                />
+                {isFetching ? (
+                  <Spinner />
+                ) : (
+                  <IAIMantineSelect
+                    placeholder="Select Board"
+                    onChange={(v) => setSelectedBoard(v)}
+                    value={selectedBoard}
+                    data={(boards ?? []).map((board) => ({
+                      label: board.board_name,
+                      value: board.board_id,
+                    }))}
+                  />
+                )}
               </Flex>
             </Box>
           </AlertDialogBody>
@@ -73,7 +80,9 @@ const UpdateImageBoardModal = () => {
               isDisabled={!selectedBoard}
               colorScheme="accent"
               onClick={() => {
-                if (selectedBoard) handleAddToBoard(selectedBoard);
+                if (selectedBoard) {
+                  handleAddToBoard(selectedBoard);
+                }
               }}
               ml={3}
             >
diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx
index a5eaeb4c71..169a965be0 100644
--- a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx
@@ -51,9 +51,12 @@ import { useAppToaster } from 'app/components/Toaster';
 import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
 import { DeleteImageContext } from 'app/contexts/DeleteImageContext';
 import { DeleteImageButton } from './DeleteImageModal';
+import { selectImagesById } from '../store/imagesSlice';
+import { RootState } from 'app/store/store';
 
 const currentImageButtonsSelector = createSelector(
   [
+    (state: RootState) => state,
     systemSelector,
     gallerySelector,
     postprocessingSelector,
@@ -61,7 +64,7 @@ const currentImageButtonsSelector = createSelector(
     lightboxSelector,
     activeTabNameSelector,
   ],
-  (system, gallery, postprocessing, ui, lightbox, activeTabName) => {
+  (state, system, gallery, postprocessing, ui, lightbox, activeTabName) => {
     const {
       isProcessing,
       isConnected,
@@ -81,6 +84,8 @@ const currentImageButtonsSelector = createSelector(
       shouldShowProgressInViewer,
     } = ui;
 
+    const imageDTO = selectImagesById(state, gallery.selectedImage ?? '');
+
     const { selectedImage } = gallery;
 
     return {
@@ -97,10 +102,10 @@ const currentImageButtonsSelector = createSelector(
       activeTabName,
       isLightboxOpen,
       shouldHidePreview,
-      image: selectedImage,
-      seed: selectedImage?.metadata?.seed,
-      prompt: selectedImage?.metadata?.positive_conditioning,
-      negativePrompt: selectedImage?.metadata?.negative_conditioning,
+      image: imageDTO,
+      seed: imageDTO?.metadata?.seed,
+      prompt: imageDTO?.metadata?.positive_conditioning,
+      negativePrompt: imageDTO?.metadata?.negative_conditioning,
       shouldShowProgressInViewer,
     };
   },
diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx
index c591206a27..649cae7682 100644
--- a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx
@@ -15,6 +15,8 @@ import { imageSelected } from '../store/gallerySlice';
 import IAIDndImage from 'common/components/IAIDndImage';
 import { ImageDTO } from 'services/api';
 import { IAIImageFallback } from 'common/components/IAIImageFallback';
+import { RootState } from 'app/store/store';
+import { selectImagesById } from '../store/imagesSlice';
 
 export const imagesSelector = createSelector(
   [uiSelector, gallerySelector, systemSelector],
@@ -29,7 +31,7 @@ export const imagesSelector = createSelector(
     return {
       shouldShowImageDetails,
       shouldHidePreview,
-      image: selectedImage,
+      selectedImage,
       progressImage,
       shouldShowProgressInViewer,
       shouldAntialiasProgressImage,
@@ -45,11 +47,16 @@ export const imagesSelector = createSelector(
 const CurrentImagePreview = () => {
   const {
     shouldShowImageDetails,
-    image,
+    selectedImage,
     progressImage,
     shouldShowProgressInViewer,
     shouldAntialiasProgressImage,
   } = useAppSelector(imagesSelector);
+
+  const image = useAppSelector((state: RootState) =>
+    selectImagesById(state, selectedImage ?? '')
+  );
+
   const dispatch = useAppDispatch();
 
   const handleDrop = useCallback(
@@ -57,7 +64,7 @@ const CurrentImagePreview = () => {
       if (droppedImage.image_name === image?.image_name) {
         return;
       }
-      dispatch(imageSelected(droppedImage));
+      dispatch(imageSelected(droppedImage.image_name));
     },
     [dispatch, image?.image_name]
   );
diff --git a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx
index b21c62785b..86ec3436f0 100644
--- a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx
@@ -72,17 +72,10 @@ interface HoverableImageProps {
   isSelected: boolean;
 }
 
-const memoEqualityCheck = (
-  prev: HoverableImageProps,
-  next: HoverableImageProps
-) =>
-  prev.image.image_name === next.image.image_name &&
-  prev.isSelected === next.isSelected;
-
 /**
  * Gallery image component with delete/use all/use seed buttons on hover.
  */
-const HoverableImage = memo((props: HoverableImageProps) => {
+const HoverableImage = (props: HoverableImageProps) => {
   const dispatch = useAppDispatch();
   const {
     activeTabName,
@@ -121,7 +114,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
   const handleMouseOut = () => setIsHovered(false);
 
   const handleSelectImage = useCallback(() => {
-    dispatch(imageSelected(image));
+    dispatch(imageSelected(image.image_name));
   }, [image, dispatch]);
 
   // Recall parameters handlers
@@ -260,7 +253,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
               </MenuItem>
             )}
             <MenuItem icon={<FaFolder />} onClickCapture={handleAddToBoard}>
-              Add to Board
+              {image.board_id ? 'Change Board' : 'Add to Board'}
             </MenuItem>
             <MenuItem
               sx={{ color: 'error.300' }}
@@ -357,8 +350,6 @@ const HoverableImage = memo((props: HoverableImageProps) => {
       </ContextMenu>
     </Box>
   );
-}, memoEqualityCheck);
+};
 
-HoverableImage.displayName = 'HoverableImage';
-
-export default HoverableImage;
+export default memo(HoverableImage);
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx
index adb7791afb..48bd2bde74 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx
@@ -201,12 +201,6 @@ const ImageGalleryContent = () => {
     dispatch(setGalleryView('boards'));
   }, [dispatch]);
 
-  const [newBoardName, setNewBoardName] = useState('');
-
-  const handleCreateNewBoard = () => {
-    dispatch(boardCreated({ requestBody: newBoardName }));
-  };
-
   return (
     <Flex
       sx={{
@@ -323,9 +317,7 @@ const ImageGalleryContent = () => {
                       <HoverableImage
                         key={`${item.image_name}-${item.thumbnail_url}`}
                         image={item}
-                        isSelected={
-                          selectedImage?.image_name === item?.image_name
-                        }
+                        isSelected={selectedImage === item?.image_name}
                       />
                     </Flex>
                   )}
@@ -344,9 +336,7 @@ const ImageGalleryContent = () => {
                     <HoverableImage
                       key={`${item.image_name}-${item.thumbnail_url}`}
                       image={item}
-                      isSelected={
-                        selectedImage?.image_name === item?.image_name
-                      }
+                      isSelected={selectedImage === item?.image_name}
                     />
                   )}
                 />
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer.tsx
index 892516a3cc..e5cb4cf4a8 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer.tsx
@@ -93,19 +93,11 @@ type ImageMetadataViewerProps = {
   image: ImageDTO;
 };
 
-// TODO: I don't know if this is needed.
-const memoEqualityCheck = (
-  prev: ImageMetadataViewerProps,
-  next: ImageMetadataViewerProps
-) => prev.image.image_name === next.image.image_name;
-
-// TODO: Show more interesting information in this component.
-
 /**
  * Image metadata viewer overlays currently selected image and provides
  * access to any of its metadata for use in processing.
  */
-const ImageMetadataViewer = memo(({ image }: ImageMetadataViewerProps) => {
+const ImageMetadataViewer = ({ image }: ImageMetadataViewerProps) => {
   const dispatch = useAppDispatch();
   const {
     recallBothPrompts,
@@ -333,8 +325,6 @@ const ImageMetadataViewer = memo(({ image }: ImageMetadataViewerProps) => {
       </Flex>
     </Flex>
   );
-}, memoEqualityCheck);
+};
 
-ImageMetadataViewer.displayName = 'ImageMetadataViewer';
-
-export default ImageMetadataViewer;
+export default memo(ImageMetadataViewer);
diff --git a/invokeai/frontend/web/src/features/gallery/components/NextPrevImageButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/NextPrevImageButtons.tsx
index 82e7a0d623..b1f06ad433 100644
--- a/invokeai/frontend/web/src/features/gallery/components/NextPrevImageButtons.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/NextPrevImageButtons.tsx
@@ -42,7 +42,7 @@ export const nextPrevImageButtonsSelector = createSelector(
     }
 
     const currentImageIndex = filteredImageIds.findIndex(
-      (i) => i === selectedImage.image_name
+      (i) => i === selectedImage
     );
 
     const nextImageIndex = clamp(
@@ -71,6 +71,8 @@ export const nextPrevImageButtonsSelector = createSelector(
         !isNaN(currentImageIndex) && currentImageIndex === imagesLength - 1,
       nextImage,
       prevImage,
+      nextImageId,
+      prevImageId,
     };
   },
   {
@@ -84,7 +86,7 @@ const NextPrevImageButtons = () => {
   const dispatch = useAppDispatch();
   const { t } = useTranslation();
 
-  const { isOnFirstImage, isOnLastImage, nextImage, prevImage } =
+  const { isOnFirstImage, isOnLastImage, nextImageId, prevImageId } =
     useAppSelector(nextPrevImageButtonsSelector);
 
   const [shouldShowNextPrevButtons, setShouldShowNextPrevButtons] =
@@ -99,19 +101,19 @@ const NextPrevImageButtons = () => {
   }, []);
 
   const handlePrevImage = useCallback(() => {
-    dispatch(imageSelected(prevImage));
-  }, [dispatch, prevImage]);
+    dispatch(imageSelected(prevImageId));
+  }, [dispatch, prevImageId]);
 
   const handleNextImage = useCallback(() => {
-    dispatch(imageSelected(nextImage));
-  }, [dispatch, nextImage]);
+    dispatch(imageSelected(nextImageId));
+  }, [dispatch, nextImageId]);
 
   useHotkeys(
     'left',
     () => {
       handlePrevImage();
     },
-    [prevImage]
+    [prevImageId]
   );
 
   useHotkeys(
@@ -119,7 +121,7 @@ const NextPrevImageButtons = () => {
     () => {
       handleNextImage();
     },
-    [nextImage]
+    [nextImageId]
   );
 
   return (
diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts
index a8237a711d..b07ab487ae 100644
--- a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts
+++ b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts
@@ -7,7 +7,7 @@ import { imageUrlsReceived } from 'services/thunks/image';
 type GalleryImageObjectFitType = 'contain' | 'cover';
 
 export interface GalleryState {
-  selectedImage?: ImageDTO;
+  selectedImage?: string;
   galleryImageMinimumWidth: number;
   galleryImageObjectFit: GalleryImageObjectFitType;
   shouldAutoSwitchToNewImages: boolean;
@@ -27,7 +27,7 @@ export const gallerySlice = createSlice({
   name: 'gallery',
   initialState: initialGalleryState,
   reducers: {
-    imageSelected: (state, action: PayloadAction<ImageDTO | undefined>) => {
+    imageSelected: (state, action: PayloadAction<string | undefined>) => {
       state.selectedImage = action.payload;
       // TODO: if the user selects an image, disable the auto switch?
       // state.shouldAutoSwitchToNewImages = false;
@@ -63,17 +63,17 @@ export const gallerySlice = createSlice({
         state.shouldAutoSwitchToNewImages &&
         action.payload.image_category === 'general'
       ) {
-        state.selectedImage = action.payload;
+        state.selectedImage = action.payload.image_name;
       }
     });
-    builder.addCase(imageUrlsReceived.fulfilled, (state, action) => {
-      const { image_name, image_url, thumbnail_url } = action.payload;
+    // builder.addCase(imageUrlsReceived.fulfilled, (state, action) => {
+    //   const { image_name, image_url, thumbnail_url } = action.payload;
 
-      if (state.selectedImage?.image_name === image_name) {
-        state.selectedImage.image_url = image_url;
-        state.selectedImage.thumbnail_url = thumbnail_url;
-      }
-    });
+    //   if (state.selectedImage?.image_name === image_name) {
+    //     state.selectedImage.image_url = image_url;
+    //     state.selectedImage.thumbnail_url = thumbnail_url;
+    //   }
+    // });
   },
 });
 
diff --git a/invokeai/frontend/web/src/services/api/models/BoardDTO.ts b/invokeai/frontend/web/src/services/api/models/BoardDTO.ts
index 1b72f452ac..ee3c29a797 100644
--- a/invokeai/frontend/web/src/services/api/models/BoardDTO.ts
+++ b/invokeai/frontend/web/src/services/api/models/BoardDTO.ts
@@ -23,13 +23,9 @@ export type BoardDTO = {
    */
   updated_at: string;
   /**
-   * The name of the cover image of the board.
+   * The name of the board's cover image.
    */
   cover_image_name?: string;
-  /**
-   * The URL of the thumbnail of the board's cover image.
-   */
-  cover_image_url?: string;
   /**
    * The number of images in the board.
    */
diff --git a/invokeai/frontend/web/src/services/api/services/BoardsService.ts b/invokeai/frontend/web/src/services/api/services/BoardsService.ts
index 9108e3fd51..bda2b70e75 100644
--- a/invokeai/frontend/web/src/services/api/services/BoardsService.ts
+++ b/invokeai/frontend/web/src/services/api/services/BoardsService.ts
@@ -17,13 +17,18 @@ export class BoardsService {
   /**
    * List Boards
    * Gets a list of boards
-   * @returns OffsetPaginatedResults_BoardDTO_ Successful Response
+   * @returns any Successful Response
    * @throws ApiError
    */
   public static listBoards({
+    all,
     offset,
-    limit = 10,
+    limit,
   }: {
+    /**
+     * Whether to list all boards
+     */
+    all?: boolean,
     /**
      * The page offset
      */
@@ -32,11 +37,12 @@ export class BoardsService {
      * The number of boards per page
      */
     limit?: number,
-  }): CancelablePromise<OffsetPaginatedResults_BoardDTO_> {
+  }): CancelablePromise<(OffsetPaginatedResults_BoardDTO_ | Array<BoardDTO>)> {
     return __request(OpenAPI, {
       method: 'GET',
       url: '/api/v1/boards/',
       query: {
+        'all': all,
         'offset': offset,
         'limit': limit,
       },
@@ -53,15 +59,19 @@ export class BoardsService {
    * @throws ApiError
    */
   public static createBoard({
-    requestBody,
+    boardName,
   }: {
-    requestBody: string,
+    /**
+     * The name of the board to create
+     */
+    boardName: string,
   }): CancelablePromise<BoardDTO> {
     return __request(OpenAPI, {
       method: 'POST',
       url: '/api/v1/boards/',
-      body: requestBody,
-      mediaType: 'application/json',
+      query: {
+        'board_name': boardName,
+      },
       errors: {
         422: `Validation Error`,
       },
diff --git a/invokeai/frontend/web/src/services/apiSlice.ts b/invokeai/frontend/web/src/services/apiSlice.ts
new file mode 100644
index 0000000000..09eb061e29
--- /dev/null
+++ b/invokeai/frontend/web/src/services/apiSlice.ts
@@ -0,0 +1,144 @@
+import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
+import { BoardDTO } from './api/models/BoardDTO';
+import { OffsetPaginatedResults_BoardDTO_ } from './api/models/OffsetPaginatedResults_BoardDTO_';
+import { BoardChanges } from './api/models/BoardChanges';
+import { OffsetPaginatedResults_ImageDTO_ } from './api/models/OffsetPaginatedResults_ImageDTO_';
+
+type ListBoardsArg = { offset: number; limit: number };
+type UpdateBoardArg = { board_id: string; changes: BoardChanges };
+type AddImageToBoardArg = { board_id: string; image_name: string };
+type RemoveImageFromBoardArg = { board_id: string; image_name: string };
+type ListBoardImagesArg = { board_id: string; offset: number; limit: number };
+
+export const api = createApi({
+  baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:5173/api/v1/' }),
+  reducerPath: 'api',
+  tagTypes: ['Board'],
+  endpoints: (build) => ({
+    /**
+     * Boards Queries
+     */
+    listBoards: build.query<OffsetPaginatedResults_BoardDTO_, ListBoardsArg>({
+      query: (arg) => ({ url: 'boards/', params: arg }),
+      providesTags: (result, error, arg) => {
+        if (!result) {
+          // Provide the broad 'Board' tag until there is a response
+          return ['Board'];
+        }
+
+        // Provide the broad 'Board' tab, and individual tags for each board
+        return [
+          ...result.items.map(({ board_id }) => ({
+            type: 'Board' as const,
+            id: board_id,
+          })),
+          'Board',
+        ];
+      },
+    }),
+
+    listAllBoards: build.query<Array<BoardDTO>, void>({
+      query: () => ({
+        url: 'boards/',
+        params: { all: true },
+      }),
+      providesTags: (result, error, arg) => {
+        if (!result) {
+          // Provide the broad 'Board' tag until there is a response
+          return ['Board'];
+        }
+
+        // Provide the broad 'Board' tab, and individual tags for each board
+        return [
+          ...result.map(({ board_id }) => ({
+            type: 'Board' as const,
+            id: board_id,
+          })),
+          'Board',
+        ];
+      },
+    }),
+
+    /**
+     * Boards Mutations
+     */
+
+    createBoard: build.mutation<BoardDTO, string>({
+      query: (board_name) => ({
+        url: `boards/`,
+        method: 'POST',
+        params: { board_name },
+      }),
+      invalidatesTags: ['Board'],
+    }),
+
+    updateBoard: build.mutation<BoardDTO, UpdateBoardArg>({
+      query: ({ board_id, changes }) => ({
+        url: `boards/${board_id}`,
+        method: 'PATCH',
+        body: changes,
+      }),
+      invalidatesTags: (result, error, arg) => [
+        { type: 'Board', id: arg.board_id },
+      ],
+    }),
+
+    deleteBoard: build.mutation<void, string>({
+      query: (board_id) => ({ url: `boards/${board_id}`, method: 'DELETE' }),
+      invalidatesTags: (result, error, arg) => [{ type: 'Board', id: arg }],
+    }),
+
+    /**
+     * Board Images Queries
+     */
+
+    listBoardImages: build.query<
+      OffsetPaginatedResults_ImageDTO_,
+      ListBoardImagesArg
+    >({
+      query: ({ board_id, offset, limit }) => ({
+        url: `board_images/${board_id}`,
+        method: 'DELETE',
+        body: { offset, limit },
+      }),
+    }),
+
+    /**
+     * Board Images Mutations
+     */
+
+    addImageToBoard: build.mutation<void, AddImageToBoardArg>({
+      query: ({ board_id, image_name }) => ({
+        url: `board_images/`,
+        method: 'POST',
+        body: { board_id, image_name },
+      }),
+      invalidatesTags: ['Board'],
+      // invalidatesTags: (result, error, arg) => [
+      //   { type: 'Board', id: arg.board_id },
+      // ],
+    }),
+
+    removeImageFromBoard: build.mutation<void, RemoveImageFromBoardArg>({
+      query: ({ board_id, image_name }) => ({
+        url: `board_images/`,
+        method: 'DELETE',
+        body: { board_id, image_name },
+      }),
+      invalidatesTags: (result, error, arg) => [
+        { type: 'Board', id: arg.board_id },
+      ],
+    }),
+  }),
+});
+
+export const {
+  useListBoardsQuery,
+  useListAllBoardsQuery,
+  useCreateBoardMutation,
+  useUpdateBoardMutation,
+  useDeleteBoardMutation,
+  useAddImageToBoardMutation,
+  useRemoveImageFromBoardMutation,
+  useListBoardImagesQuery,
+} = api;
diff --git a/invokeai/frontend/web/src/services/thunks/board.ts b/invokeai/frontend/web/src/services/thunks/board.ts
index 4535081e47..03c59dba10 100644
--- a/invokeai/frontend/web/src/services/thunks/board.ts
+++ b/invokeai/frontend/web/src/services/thunks/board.ts
@@ -42,7 +42,7 @@ export const boardUpdated = createAppAsyncThunk(
 
 type ImageAddedToBoardArg = Parameters<
   (typeof BoardsService)['createBoardImage']
->[0];
+>[0]['requestBody'];
 
 export const imageAddedToBoard = createAppAsyncThunk(
   'api/imageAddedToBoard',