diff --git a/enterprise/frontend/src/metabase-enterprise/moderation/components/ModerationReviewBanner/ModerationReviewBanner.jsx b/enterprise/frontend/src/metabase-enterprise/moderation/components/ModerationReviewBanner/ModerationReviewBanner.jsx
index ed9ee06a0285d77200d207017e3de4aa5a059b63..27b494827d0cfa98c968120eafbce7b4e8332cee 100644
--- a/enterprise/frontend/src/metabase-enterprise/moderation/components/ModerationReviewBanner/ModerationReviewBanner.jsx
+++ b/enterprise/frontend/src/metabase-enterprise/moderation/components/ModerationReviewBanner/ModerationReviewBanner.jsx
@@ -2,7 +2,7 @@ import PropTypes from "prop-types";
 import { connect } from "react-redux";
 import _ from "underscore";
 
-import User from "metabase/entities/users";
+import Users from "metabase/entities/users";
 import { color, alpha } from "metabase/lib/colors";
 import { getRelativeTime } from "metabase/lib/time";
 import { getUser } from "metabase/selectors/user";
@@ -26,7 +26,7 @@ const mapStateToProps = (state, props) => ({
 });
 
 export default _.compose(
-  User.load({
+  Users.load({
     id: (state, props) => props.moderationReview.moderator_id,
     loadingAndErrorWrapper: false,
   }),
diff --git a/frontend/src/metabase-types/api/user.ts b/frontend/src/metabase-types/api/user.ts
index 435f3f295702ee26b208b823a799899dbd0e2228..f367f10a893e6e7a86bbd53aaf6fc35f90bb5c13 100644
--- a/frontend/src/metabase-types/api/user.ts
+++ b/frontend/src/metabase-types/api/user.ts
@@ -79,3 +79,43 @@ export type UserLoginHistoryItem = {
 };
 
 export type UserLoginHistory = UserLoginHistoryItem[];
+
+export type CreateUserRequest = {
+  email: string;
+  first_name?: string;
+  last_name?: string;
+  user_group_memberships?: { id: number; is_group_manager: boolean }[];
+  login_attributes?: Record<UserAttribute, UserAttribute>;
+};
+
+export type UpdatePasswordRequest = {
+  id: UserId;
+  password: string;
+  old_password?: string;
+};
+
+export type ListUsersRequest = {
+  status?: "deactivated" | "all";
+  query?: string;
+  group_id?: number;
+  include_deactivated?: boolean;
+};
+
+export type ListUsersResponse = {
+  data: User[];
+  total: number;
+  limit: number | null;
+  offset: number | null;
+};
+
+export type UpdateUserRequest = {
+  id: UserId;
+  email?: string | null;
+  first_name?: string | null;
+  last_name?: string | null;
+  locale?: string | null;
+  is_group_manager?: boolean;
+  is_superuser?: boolean;
+  login_attributes?: Record<UserAttribute, UserAttribute> | null;
+  user_group_memberships?: { id: number; is_group_manager: boolean }[];
+};
diff --git a/frontend/src/metabase/account/password/actions.ts b/frontend/src/metabase/account/password/actions.ts
index 34a0b00ebf55bc1bf39bfe2fce18e834cc318d3a..a706d087b880ad4c8998da3765220b2b345103a4 100644
--- a/frontend/src/metabase/account/password/actions.ts
+++ b/frontend/src/metabase/account/password/actions.ts
@@ -1,10 +1,7 @@
 import { getIn } from "icepick";
 
 import MetabaseSettings from "metabase/lib/settings";
-import { UserApi, UtilApi } from "metabase/services";
-import type { User } from "metabase-types/api";
-
-import type { UserPasswordData } from "./types";
+import { UtilApi } from "metabase/services";
 
 export const validatePassword = async (password: string) => {
   const error = MetabaseSettings.passwordComplexityDescription(password);
@@ -18,11 +15,3 @@ export const validatePassword = async (password: string) => {
     return getIn(error, ["data", "errors", "password"]);
   }
 };
-
-export const updatePassword = async (user: User, data: UserPasswordData) => {
-  await UserApi.update_password({
-    id: user.id,
-    password: data.password,
-    old_password: data.old_password,
-  });
-};
diff --git a/frontend/src/metabase/account/password/components/UserPasswordForm/UserPasswordForm.tsx b/frontend/src/metabase/account/password/components/UserPasswordForm/UserPasswordForm.tsx
index 2f5962640ec03aaf852837b83e8db8e87119f6dd..3d11e45b3e27b82ae616756d31e3822cf068a4ec 100644
--- a/frontend/src/metabase/account/password/components/UserPasswordForm/UserPasswordForm.tsx
+++ b/frontend/src/metabase/account/password/components/UserPasswordForm/UserPasswordForm.tsx
@@ -3,6 +3,7 @@ import { t } from "ttag";
 import _ from "underscore";
 import * as Yup from "yup";
 
+import { useUpdatePasswordMutation } from "metabase/api";
 import {
   Form,
   FormProvider,
@@ -34,13 +35,11 @@ const USER_PASSWORD_SCHEMA = Yup.object({
 export interface UserPasswordFormProps {
   user: User;
   onValidatePassword: (password: string) => Promise<string | undefined>;
-  onSubmit: (user: User, data: UserPasswordData) => void;
 }
 
 export const UserPasswordForm = ({
   user,
   onValidatePassword,
-  onSubmit,
 }: UserPasswordFormProps): JSX.Element => {
   const initialValues = useMemo(() => {
     return USER_PASSWORD_SCHEMA.getDefault();
@@ -51,11 +50,18 @@ export const UserPasswordForm = ({
     [onValidatePassword],
   );
 
+  const [updatePassword] = useUpdatePasswordMutation();
+
   const handleSubmit = useCallback(
-    (data: UserPasswordData) => {
-      return onSubmit(user, data);
+    async (data: UserPasswordData) => {
+      const { old_password, password } = data;
+      return await updatePassword({
+        id: user.id,
+        old_password,
+        password,
+      }).unwrap();
     },
-    [user, onSubmit],
+    [user, updatePassword],
   );
 
   return (
diff --git a/frontend/src/metabase/account/password/containers/UserPasswordApp/UserPasswordApp.tsx b/frontend/src/metabase/account/password/containers/UserPasswordApp/UserPasswordApp.tsx
index 656e49841b07498dc4bb613858d48e09863cc95b..3c67481550601a50ccb5bd6b896684dbb1307251 100644
--- a/frontend/src/metabase/account/password/containers/UserPasswordApp/UserPasswordApp.tsx
+++ b/frontend/src/metabase/account/password/containers/UserPasswordApp/UserPasswordApp.tsx
@@ -4,13 +4,12 @@ import { checkNotNull } from "metabase/lib/types";
 import { getUser } from "metabase/selectors/user";
 import type { State } from "metabase-types/store";
 
-import { updatePassword, validatePassword } from "../../actions";
+import { validatePassword } from "../../actions";
 import { UserPasswordForm } from "../../components/UserPasswordForm";
 
 const mapStateToProps = (state: State) => ({
   user: checkNotNull(getUser(state)),
   onValidatePassword: validatePassword,
-  onSubmit: updatePassword,
 });
 
 // eslint-disable-next-line import/no-default-export -- deprecated usage
diff --git a/frontend/src/metabase/admin/people/components/GroupMembersTable/GroupMembersTable.tsx b/frontend/src/metabase/admin/people/components/GroupMembersTable/GroupMembersTable.tsx
index a108f5684f08df6e0d28ddb4cb7bec6bfa43aa43..1e24b196ff50c75556927fb5d8fb13d65d0bd3c7 100644
--- a/frontend/src/metabase/admin/people/components/GroupMembersTable/GroupMembersTable.tsx
+++ b/frontend/src/metabase/admin/people/components/GroupMembersTable/GroupMembersTable.tsx
@@ -8,7 +8,7 @@ import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
 import PaginationControls from "metabase/components/PaginationControls";
 import Link from "metabase/core/components/Link";
 import CS from "metabase/css/core/index.css";
-import User from "metabase/entities/users";
+import Users from "metabase/entities/users";
 import { isAdminGroup, isDefaultGroup } from "metabase/lib/groups";
 import { isNotNull } from "metabase/lib/types";
 import { getFullName } from "metabase/lib/user";
@@ -150,7 +150,7 @@ function GroupMembersTable({
 }
 
 // eslint-disable-next-line import/no-default-export -- deprecated usage
-export default User.loadList({
+export default Users.loadList({
   reload: true,
   pageSize: 25,
   listName: "groupUsers",
diff --git a/frontend/src/metabase/admin/people/components/PeopleList.jsx b/frontend/src/metabase/admin/people/components/PeopleList.jsx
index 9792e800b10014f6fe7378913ed1864c306fd075..58f16d11716e2ed34028c631f7414da0e85a7f22 100644
--- a/frontend/src/metabase/admin/people/components/PeopleList.jsx
+++ b/frontend/src/metabase/admin/people/components/PeopleList.jsx
@@ -10,7 +10,7 @@ import PaginationControls from "metabase/components/PaginationControls";
 import AdminS from "metabase/css/admin.module.css";
 import CS from "metabase/css/core/index.css";
 import Group from "metabase/entities/groups";
-import User from "metabase/entities/users";
+import Users from "metabase/entities/users";
 import { useConfirmation } from "metabase/hooks/use-confirmation";
 import { PLUGIN_GROUP_MANAGERS } from "metabase/plugins";
 import { getUser, getUserIsAdmin } from "metabase/selectors/user";
@@ -302,7 +302,7 @@ export default _.compose(
   Group.loadList({
     reload: true,
   }),
-  User.loadList({
+  Users.loadList({
     reload: true,
     query: (_, { query }) => ({
       query: query.searchText,
diff --git a/frontend/src/metabase/admin/people/containers/EditUserModal.tsx b/frontend/src/metabase/admin/people/containers/EditUserModal.tsx
index 554f75cdc6efb4ea95b810f60a5e3e3a61aec48f..ee913ed2b93bd12e4af91e39ddb02e4f33d98045 100644
--- a/frontend/src/metabase/admin/people/containers/EditUserModal.tsx
+++ b/frontend/src/metabase/admin/people/containers/EditUserModal.tsx
@@ -4,7 +4,7 @@ import type { Params } from "react-router/lib/Router";
 import { useUserQuery } from "metabase/common/hooks";
 import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
 import ModalContent from "metabase/components/ModalContent";
-import User from "metabase/entities/users";
+import Users from "metabase/entities/users";
 import { useDispatch } from "metabase/lib/redux";
 import type { User as UserType } from "metabase-types/api";
 
@@ -25,7 +25,7 @@ export const EditUserModal = ({ onClose, params }: EditUserModalProps) => {
   const initialValues = useMemo(() => getInitialValues(user), [user]);
 
   const handleSubmit = async (val: Partial<UserType>) => {
-    await dispatch(User.actions.update({ id: user?.id, ...val }));
+    await dispatch(Users.actions.update({ id: user?.id, ...val }));
     onClose();
   };
 
diff --git a/frontend/src/metabase/admin/people/containers/GroupDetailApp.jsx b/frontend/src/metabase/admin/people/containers/GroupDetailApp.jsx
index 4bc135020a6c45d8d02dfed8a576309f7fb59d2a..c9d4b9b7d93a5a03cac1d37adbe53318da95885d 100644
--- a/frontend/src/metabase/admin/people/containers/GroupDetailApp.jsx
+++ b/frontend/src/metabase/admin/people/containers/GroupDetailApp.jsx
@@ -2,7 +2,7 @@ import { Component } from "react";
 import _ from "underscore";
 
 import Group from "metabase/entities/groups";
-import User from "metabase/entities/users";
+import Users from "metabase/entities/users";
 
 import GroupDetail from "../components/GroupDetail";
 
@@ -13,6 +13,6 @@ class GroupDetailApp extends Component {
 }
 
 export default _.compose(
-  User.loadList(),
+  Users.loadList(),
   Group.load({ id: (_state, props) => props.params.groupId, reload: true }),
 )(GroupDetailApp);
diff --git a/frontend/src/metabase/admin/people/containers/NewUserModal.tsx b/frontend/src/metabase/admin/people/containers/NewUserModal.tsx
index e470388785faafd1d705f92c4670e51d35d687d1..2573bec824544ba3f22f01b2cde2564d2a1bb281 100644
--- a/frontend/src/metabase/admin/people/containers/NewUserModal.tsx
+++ b/frontend/src/metabase/admin/people/containers/NewUserModal.tsx
@@ -2,7 +2,7 @@ import { push } from "react-router-redux";
 import { t } from "ttag";
 
 import ModalContent from "metabase/components/ModalContent";
-import User from "metabase/entities/users";
+import Users from "metabase/entities/users";
 import { useDispatch } from "metabase/lib/redux";
 import * as Urls from "metabase/lib/urls";
 import type { User as UserType } from "metabase-types/api";
@@ -19,7 +19,7 @@ export const NewUserModal = ({ onClose }: NewUserModalProps) => {
   const handleSubmit = async (vals: Partial<UserType>) => {
     const {
       payload: { id: userId },
-    } = await dispatch(User.actions.create(vals));
+    } = await dispatch(Users.actions.create(vals));
 
     await dispatch(push(Urls.newUserSuccess(userId)));
   };
diff --git a/frontend/src/metabase/admin/people/containers/UserActivationModal.jsx b/frontend/src/metabase/admin/people/containers/UserActivationModal.jsx
index c31cda5bd7ed0311d58c1283168b45df6f1b59fe..48fa9d693430a5d258325e6d691dbcbb420509cc 100644
--- a/frontend/src/metabase/admin/people/containers/UserActivationModal.jsx
+++ b/frontend/src/metabase/admin/people/containers/UserActivationModal.jsx
@@ -8,7 +8,7 @@ import ModalContent from "metabase/components/ModalContent";
 import Text from "metabase/components/type/Text";
 import Button from "metabase/core/components/Button";
 import CS from "metabase/css/core/index.css";
-import User from "metabase/entities/users";
+import Users from "metabase/entities/users";
 
 // NOTE: we have to load the list of users because /api/user/:id doesn't return deactivated users
 // but that's ok because it's probably already loaded through the people PeopleListingApp
@@ -58,7 +58,7 @@ class UserActivationModalInner extends Component {
 }
 
 const UserActivationModal = _.compose(
-  User.loadList({
+  Users.loadList({
     query: { include_deactivated: true },
     wrapped: true,
   }),
diff --git a/frontend/src/metabase/admin/people/containers/UserPasswordResetModal.jsx b/frontend/src/metabase/admin/people/containers/UserPasswordResetModal.jsx
index 2751a85083a834d404e6f8ef81bc5d9a7803e66b..a936f01067e91b97f0f23edc19b82499f328e9cd 100644
--- a/frontend/src/metabase/admin/people/containers/UserPasswordResetModal.jsx
+++ b/frontend/src/metabase/admin/people/containers/UserPasswordResetModal.jsx
@@ -10,7 +10,7 @@ import ModalContent from "metabase/components/ModalContent";
 import PasswordReveal from "metabase/components/PasswordReveal";
 import Button from "metabase/core/components/Button";
 import CS from "metabase/css/core/index.css";
-import User from "metabase/entities/users";
+import Users from "metabase/entities/users";
 import MetabaseSettings from "metabase/lib/settings";
 
 import { clearTemporaryPassword } from "../people";
@@ -77,7 +77,7 @@ class UserPasswordResetModal extends Component {
 }
 
 export default _.compose(
-  User.load({
+  Users.load({
     id: (state, props) => props.params.userId,
     wrapped: true,
   }),
diff --git a/frontend/src/metabase/admin/people/containers/UserSuccessModal.jsx b/frontend/src/metabase/admin/people/containers/UserSuccessModal.jsx
index 60dea02d411295cf116556452f44f0291b197f2c..1eab57646ab977df5fc0cd37b880ab671c98ab3a 100644
--- a/frontend/src/metabase/admin/people/containers/UserSuccessModal.jsx
+++ b/frontend/src/metabase/admin/people/containers/UserSuccessModal.jsx
@@ -11,7 +11,7 @@ import PasswordReveal from "metabase/components/PasswordReveal";
 import Button from "metabase/core/components/Button";
 import Link from "metabase/core/components/Link";
 import CS from "metabase/css/core/index.css";
-import User from "metabase/entities/users";
+import Users from "metabase/entities/users";
 import MetabaseSettings from "metabase/lib/settings";
 
 import { clearTemporaryPassword } from "../people";
@@ -88,7 +88,7 @@ const PasswordSuccess = ({ user, temporaryPassword }) => (
 );
 
 export default _.compose(
-  User.load({
+  Users.load({
     id: (state, props) => props.params.userId,
   }),
   connect(
diff --git a/frontend/src/metabase/api/index.ts b/frontend/src/metabase/api/index.ts
index d92d76958861d8ab1f17bae7f216b7003c6c93b0..57b5a083a165ebb5034fd56920eda93a6bb1f998 100644
--- a/frontend/src/metabase/api/index.ts
+++ b/frontend/src/metabase/api/index.ts
@@ -17,3 +17,4 @@ export * from "./session";
 export * from "./table";
 export * from "./timeline";
 export * from "./timeline-event";
+export * from "./user";
diff --git a/frontend/src/metabase/api/session.ts b/frontend/src/metabase/api/session.ts
index 7fc7b55df1ee0fe78a62da24096ce1031201ab6c..6f999771a60e5365be68897443ed8b3ede518d6d 100644
--- a/frontend/src/metabase/api/session.ts
+++ b/frontend/src/metabase/api/session.ts
@@ -14,7 +14,15 @@ export const sessionApi = Api.injectEndpoints({
         body: { token },
       }),
     }),
+    forgotPassword: builder.query<void, string>({
+      query: email => ({
+        method: "POST",
+        url: "/api/session/forgot_password",
+        body: { email },
+      }),
+    }),
   }),
 });
 
-export const { useGetPasswordResetTokenStatusQuery } = sessionApi;
+export const { useGetPasswordResetTokenStatusQuery, useForgotPasswordQuery } =
+  sessionApi;
diff --git a/frontend/src/metabase/api/tags/utils.ts b/frontend/src/metabase/api/tags/utils.ts
index 5257e44ffe44c3a22ee7ce6705fc7128145bbac8..d00a19569670715a975122f8bd2cf14cbbb8547c 100644
--- a/frontend/src/metabase/api/tags/utils.ts
+++ b/frontend/src/metabase/api/tags/utils.ts
@@ -323,6 +323,12 @@ export function provideTimelineTags(
   ];
 }
 
+export function provideUserListTags(
+  users: UserInfo[],
+): TagDescription<TagType>[] {
+  return [listTag("user"), ...users.flatMap(user => provideUserTags(user))];
+}
+
 export function provideUserTags(user: UserInfo): TagDescription<TagType>[] {
   return [idTag("user", user.id)];
 }
diff --git a/frontend/src/metabase/api/user.ts b/frontend/src/metabase/api/user.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3a1919446706930007625caa281896f97e5a2c9e
--- /dev/null
+++ b/frontend/src/metabase/api/user.ts
@@ -0,0 +1,100 @@
+import type {
+  CreateUserRequest,
+  ListUsersRequest,
+  ListUsersResponse,
+  UpdatePasswordRequest,
+  UserId,
+  User,
+  UpdateUserRequest,
+} from "metabase-types/api";
+
+import { Api } from "./api";
+import {
+  idTag,
+  listTag,
+  invalidateTags,
+  provideUserTags,
+  provideUserListTags,
+} from "./tags";
+
+export const userApi = Api.injectEndpoints({
+  endpoints: builder => ({
+    listUsers: builder.query<ListUsersResponse, ListUsersRequest | void>({
+      query: body => ({
+        method: "GET",
+        url: "/api/user",
+        body,
+      }),
+      providesTags: response =>
+        response ? provideUserListTags(response.data) : [],
+    }),
+    listUserRecipients: builder.query<ListUsersResponse, void>({
+      query: () => ({
+        method: "GET",
+        url: "/api/user/recipients",
+      }),
+      providesTags: response =>
+        response ? provideUserListTags(response.data) : [],
+    }),
+    getUser: builder.query<User, UserId>({
+      query: id => ({
+        method: "GET",
+        url: `/api/user/${id}`,
+      }),
+      providesTags: user => (user ? provideUserTags(user) : []),
+    }),
+    createUser: builder.mutation<User, CreateUserRequest>({
+      query: body => ({
+        method: "POST",
+        url: "/api/user",
+        body,
+      }),
+      invalidatesTags: (_, error) => invalidateTags(error, [listTag("user")]),
+    }),
+    updatePassword: builder.mutation<void, UpdatePasswordRequest>({
+      query: ({ id, old_password, password }) => ({
+        method: "PUT",
+        url: `/api/user/${id}/password`,
+        body: { old_password, password },
+      }),
+      invalidatesTags: (_, error, { id }) =>
+        invalidateTags(error, [listTag("user"), idTag("user", id)]),
+    }),
+    deactivateUser: builder.mutation<void, UserId>({
+      query: id => ({
+        method: "DELETE",
+        url: `/api/user/${id}`,
+      }),
+      invalidatesTags: (_, error, id) =>
+        invalidateTags(error, [listTag("user"), idTag("user", id)]),
+    }),
+    reactivateUser: builder.mutation<User, UserId>({
+      query: id => ({
+        method: "PUT",
+        url: `/api/user/${id}/reactivate`,
+      }),
+      invalidatesTags: (_, error, id) =>
+        invalidateTags(error, [listTag("user"), idTag("user", id)]),
+    }),
+    updateUser: builder.mutation<User, UpdateUserRequest>({
+      query: ({ id, ...body }) => ({
+        method: "PUT",
+        url: `/api/user/${id}`,
+        body,
+      }),
+      invalidatesTags: (_, error, { id }) =>
+        invalidateTags(error, [listTag("user"), idTag("user", id)]),
+    }),
+  }),
+});
+
+export const {
+  useListUsersQuery,
+  useListUserRecipientsQuery,
+  useGetUserQuery,
+  useCreateUserMutation,
+  useUpdatePasswordMutation,
+  useDeactivateUserMutation,
+  useReactivateUserMutation,
+  useUpdateUserMutation,
+} = userApi;
diff --git a/frontend/src/metabase/common/components/EntityPicker/components/EntityPickerModal/EntityPickerModal.unit.spec.tsx b/frontend/src/metabase/common/components/EntityPicker/components/EntityPickerModal/EntityPickerModal.unit.spec.tsx
index 16c29ce73cc1d8f7c6a9018be6da2174d233d442..c2db194980766a39902736694b43d1769b7ad8fc 100644
--- a/frontend/src/metabase/common/components/EntityPicker/components/EntityPickerModal/EntityPickerModal.unit.spec.tsx
+++ b/frontend/src/metabase/common/components/EntityPicker/components/EntityPickerModal/EntityPickerModal.unit.spec.tsx
@@ -149,7 +149,7 @@ describe("EntityPickerModal", () => {
       }),
     );
 
-    fetchMock.get("path:/api/user/recipients", []);
+    fetchMock.get("path:/api/user/recipients", { data: [] });
 
     const onItemSelect = jest.fn();
     const onConfirm = jest.fn();
diff --git a/frontend/src/metabase/containers/UserCollectionList.unit.spec.tsx b/frontend/src/metabase/containers/UserCollectionList.unit.spec.tsx
index bceddc0a434700e623cc48cce2e0b666efa24249..442c0051e733f3821daac8ef0be1d2f3628b8e14 100644
--- a/frontend/src/metabase/containers/UserCollectionList.unit.spec.tsx
+++ b/frontend/src/metabase/containers/UserCollectionList.unit.spec.tsx
@@ -19,7 +19,7 @@ const setup = () => {
     const params = new URL(request.url).searchParams;
     const limit = parseInt(params.get("limit") ?? "0");
     const offset = parseInt(params.get("offset") ?? "0");
-    return MockUsers.slice(offset, offset + limit);
+    return { data: MockUsers.slice(offset, offset + limit) };
   });
   renderWithProviders(<UserCollectionList />);
 };
diff --git a/frontend/src/metabase/entities/users.js b/frontend/src/metabase/entities/users.js
index 08cfedc5334ce8be63ca717d2eda5afc1dc79952..e34ee73f01dc0efd588bd9ffba3bb5600c5cadc9 100644
--- a/frontend/src/metabase/entities/users.js
+++ b/frontend/src/metabase/entities/users.js
@@ -1,12 +1,11 @@
 import { assocIn } from "icepick";
 
+import { userApi, sessionApi } from "metabase/api";
 import * as MetabaseAnalytics from "metabase/lib/analytics";
-import { GET } from "metabase/lib/api";
-import { createEntity } from "metabase/lib/entities";
+import { createEntity, entityCompatibleQuery } from "metabase/lib/entities";
 import { generatePassword } from "metabase/lib/security";
 import MetabaseSettings from "metabase/lib/settings";
 import { UserSchema } from "metabase/schema";
-import { UserApi, SessionApi } from "metabase/services";
 
 export const DEACTIVATE = "metabase/entities/users/DEACTIVATE";
 export const REACTIVATE = "metabase/entities/users/REACTIVATE";
@@ -14,15 +13,16 @@ export const PASSWORD_RESET_EMAIL =
   "metabase/entities/users/PASSWORD_RESET_EMAIL";
 export const PASSWORD_RESET_MANUAL =
   "metabase/entities/users/RESET_PASSWORD_MANUAL";
-export const RESEND_INVITE = "metabase/entities/users/RESEND_INVITE";
 
 // TODO: It'd be nice to import loadMemberships, but we need to resolve a circular dependency
 function loadMemberships() {
   return require("metabase/admin/people/people").loadMemberships();
 }
 
-const getUserList = GET("/api/user");
-const getRecipientsList = GET("/api/user/recipients");
+const getUserList = (query = {}, dispatch) =>
+  entityCompatibleQuery(query, dispatch, userApi.endpoints.listUsers);
+const getRecipientsList = (query = {}, dispatch) =>
+  entityCompatibleQuery(query, dispatch, userApi.endpoints.listUserRecipients);
 
 /**
  * @deprecated use "metabase/api" instead
@@ -35,8 +35,31 @@ const Users = createEntity({
   path: "/api/user",
 
   api: {
-    list: ({ recipients = false, ...args }) =>
-      recipients ? getRecipientsList() : getUserList(args),
+    list: ({ recipients = false, ...args }, dispatch) =>
+      recipients
+        ? getRecipientsList({}, dispatch)
+        : getUserList(args, dispatch),
+    create: (entityQuery, dispatch) =>
+      entityCompatibleQuery(
+        entityQuery,
+        dispatch,
+        userApi.endpoints.createUser,
+      ),
+    get: (entityQuery, options, dispatch) =>
+      entityCompatibleQuery(
+        entityQuery.id,
+        dispatch,
+        userApi.endpoints.getUser,
+      ),
+    update: (entityQuery, dispatch) =>
+      entityCompatibleQuery(
+        entityQuery,
+        dispatch,
+        userApi.endpoints.updateUser,
+      ),
+    delete: () => {
+      throw new TypeError("Users.api.delete is not supported");
+    },
   },
 
   objectSelectors: {
@@ -48,7 +71,6 @@ const Users = createEntity({
     REACTIVATE,
     PASSWORD_RESET_EMAIL,
     PASSWORD_RESET_MANUAL,
-    RESEND_INVITE,
   },
 
   actionDecorators: {
@@ -80,50 +102,74 @@ const Users = createEntity({
   },
 
   objectActions: {
-    resentInvite: async ({ id }) => {
-      MetabaseAnalytics.trackStructEvent("People Admin", "Resent Invite");
-      await UserApi.send_invite({ id });
-      return { type: RESEND_INVITE };
-    },
-    resetPasswordEmail: async ({ email }) => {
-      MetabaseAnalytics.trackStructEvent(
-        "People Admin",
-        "Trigger User Password Reset",
-      );
-      await SessionApi.forgot_password({ email });
-      return { type: PASSWORD_RESET_EMAIL };
-    },
-    resetPasswordManual: async ({ id }, password = generatePassword()) => {
-      MetabaseAnalytics.trackStructEvent(
-        "People Admin",
-        "Manual Password Reset",
-      );
-      await UserApi.update_password({ id, password });
-      return { type: PASSWORD_RESET_MANUAL, payload: { id, password } };
-    },
-    deactivate: async ({ id }) => {
-      MetabaseAnalytics.trackStructEvent("People Admin", "User Removed");
-      // TODO: move these APIs from services to this file
-      await UserApi.delete({ userId: id });
-      return { type: DEACTIVATE, payload: { id } };
-    },
-    reactivate: async ({ id }) => {
-      MetabaseAnalytics.trackStructEvent("People Admin", "User Reactivated");
-      // TODO: move these APIs from services to this file
-      const user = await UserApi.reactivate({ userId: id });
-      return { type: REACTIVATE, payload: user };
-    },
+    resetPasswordEmail:
+      ({ email }) =>
+      async dispatch => {
+        MetabaseAnalytics.trackStructEvent(
+          "People Admin",
+          "Trigger User Password Reset",
+        );
+        await entityCompatibleQuery(
+          email,
+          dispatch,
+          sessionApi.endpoints.forgotPassword,
+        );
+        dispatch({ type: PASSWORD_RESET_EMAIL });
+      },
+    resetPasswordManual:
+      async ({ id }, password = generatePassword()) =>
+      async dispatch => {
+        MetabaseAnalytics.trackStructEvent(
+          "People Admin",
+          "Manual Password Reset",
+        );
+        await entityCompatibleQuery(
+          { id, password },
+          dispatch,
+          userApi.endpoints.updatePassword,
+        );
+        dispatch({ type: PASSWORD_RESET_MANUAL, payload: { id, password } });
+      },
+    deactivate:
+      ({ id }) =>
+      async dispatch => {
+        MetabaseAnalytics.trackStructEvent("People Admin", "User Removed");
+
+        await entityCompatibleQuery(
+          id,
+          dispatch,
+          userApi.endpoints.deactivateUser,
+        );
+        dispatch({ type: DEACTIVATE, payload: { id } });
+      },
+    reactivate:
+      ({ id }) =>
+      async dispatch => {
+        MetabaseAnalytics.trackStructEvent("People Admin", "User Reactivated");
+
+        const user = await entityCompatibleQuery(
+          id,
+          dispatch,
+          userApi.endpoints.reactivateUser,
+        );
+        dispatch({ type: REACTIVATE, payload: user });
+      },
   },
 
   reducer: (state = {}, { type, payload, error }) => {
-    if (type === DEACTIVATE && !error) {
-      return assocIn(state, [payload.id, "is_active"], false);
-    } else if (type === REACTIVATE && !error) {
-      return assocIn(state, [payload.id, "is_active"], true);
-    } else if (type === PASSWORD_RESET_MANUAL && !error) {
-      return assocIn(state, [payload.id, "password"], payload.password);
+    if (error) {
+      return state;
+    }
+    switch (type) {
+      case DEACTIVATE:
+        return assocIn(state, [payload.id, "is_active"], false);
+      case REACTIVATE:
+        return assocIn(state, [payload.id, "is_active"], true);
+      case PASSWORD_RESET_MANUAL:
+        return assocIn(state, [payload.id, "password"], payload.password);
+      default:
+        return state;
     }
-    return state;
   },
 });
 
diff --git a/frontend/src/metabase/query_builder/components/AlertModals/AlertModals.jsx b/frontend/src/metabase/query_builder/components/AlertModals/AlertModals.jsx
index 88c2e2b20cadcb7a478de680be479aac76393e24..5917dd2e83c3f24470072e253eda795151dd3708 100644
--- a/frontend/src/metabase/query_builder/components/AlertModals/AlertModals.jsx
+++ b/frontend/src/metabase/query_builder/components/AlertModals/AlertModals.jsx
@@ -17,7 +17,7 @@ import Button from "metabase/core/components/Button";
 import Radio from "metabase/core/components/Radio";
 import ButtonsS from "metabase/css/components/buttons.module.css";
 import CS from "metabase/css/core/index.css";
-import User from "metabase/entities/users";
+import Users from "metabase/entities/users";
 import { alertIsValid } from "metabase/lib/alert";
 import * as MetabaseAnalytics from "metabase/lib/analytics";
 import MetabaseCookies from "metabase/lib/cookies";
@@ -636,7 +636,7 @@ class AlertEditChannelsInner extends Component {
 }
 
 export const AlertEditChannels = _.compose(
-  User.loadList(),
+  Users.loadList(),
   connect(
     (state, props) => ({
       user: getUser(state),
diff --git a/frontend/src/metabase/services.js b/frontend/src/metabase/services.js
index c78b3afa2623254440ff0d09052971d1105341d3..c799f8328c9afdda7f0c7e84fbbcc7c5b885a6b9 100644
--- a/frontend/src/metabase/services.js
+++ b/frontend/src/metabase/services.js
@@ -444,11 +444,7 @@ export const SetupApi = {
 export const UserApi = {
   list: GET("/api/user/recipients"),
   current: GET("/api/user/current"),
-  update_password: PUT("/api/user/:id/password"),
   update_qbnewb: PUT("/api/user/:id/modal/qbnewb"),
-  delete: DELETE("/api/user/:userId"),
-  reactivate: PUT("/api/user/:userId/reactivate"),
-  send_invite: POST("/api/user/:id/send_invite"),
 };
 
 export const UtilApi = {
diff --git a/frontend/test/__support__/server-mocks/user.ts b/frontend/test/__support__/server-mocks/user.ts
index 3dd4831faec89d22580a2e877d64e5bebeed315f..f49dd1eb1b5e183e5fcc2413ebf22844dc36e791 100644
--- a/frontend/test/__support__/server-mocks/user.ts
+++ b/frontend/test/__support__/server-mocks/user.ts
@@ -8,7 +8,7 @@ export function setupUserEndpoints(user: UserListResult) {
 
 export function setupUsersEndpoints(users: UserListResult[]) {
   users.forEach(user => setupUserEndpoints(user));
-  return fetchMock.get("path:/api/user", users);
+  return fetchMock.get("path:/api/user", { data: users });
 }
 
 export function setupCurrentUserEndpoint(user: User) {