diff --git a/packages/api/methods.test.ts b/packages/api/methods.test.ts
index ff8830010d2debac89e3325a270402b5f69ef5e4..7d14d596875ba418b2dc04c97eb34c7d88ff383c 100644
--- a/packages/api/methods.test.ts
+++ b/packages/api/methods.test.ts
@@ -346,6 +346,214 @@ describe('API CRUD operations', () => {
     );
   });
 
+  // apis: getRules, getPayeeRules, createRule, updateRule, deleteRule
+  test('Rules: successfully update rules', async () => {
+    await api.createPayee({ name: 'test-payee' });
+    await api.createPayee({ name: 'test-payee2' });
+
+    // create our test rules
+    const rule = await api.createRule({
+      stage: 'pre',
+      conditionsOp: 'and',
+      conditions: [
+        {
+          field: 'payee',
+          op: 'is',
+          value: 'test-payee',
+        },
+      ],
+      actions: [
+        {
+          op: 'set',
+          field: 'category',
+          value: 'fc3825fd-b982-4b72-b768-5b30844cf832',
+        },
+      ],
+    });
+    const rule2 = await api.createRule({
+      stage: 'pre',
+      conditionsOp: 'and',
+      conditions: [
+        {
+          field: 'payee',
+          op: 'is',
+          value: 'test-payee2',
+        },
+      ],
+      actions: [
+        {
+          op: 'set',
+          field: 'category',
+          value: 'fc3825fd-b982-4b72-b768-5b30844cf832',
+        },
+      ],
+    });
+
+    // get existing rules
+    const rules = await api.getRules();
+    expect(rules).toEqual(
+      expect.arrayContaining([
+        expect.objectContaining({
+          actions: expect.arrayContaining([
+            expect.objectContaining({
+              field: 'category',
+              op: 'set',
+              type: 'id',
+              value: 'fc3825fd-b982-4b72-b768-5b30844cf832',
+            }),
+          ]),
+          conditions: expect.arrayContaining([
+            expect.objectContaining({
+              field: 'payee',
+              op: 'is',
+              type: 'id',
+              value: 'test-payee2',
+            }),
+          ]),
+          conditionsOp: 'and',
+          id: rule2.id,
+          stage: 'pre',
+        }),
+        expect.objectContaining({
+          actions: expect.arrayContaining([
+            expect.objectContaining({
+              field: 'category',
+              op: 'set',
+              type: 'id',
+              value: 'fc3825fd-b982-4b72-b768-5b30844cf832',
+            }),
+          ]),
+          conditions: expect.arrayContaining([
+            expect.objectContaining({
+              field: 'payee',
+              op: 'is',
+              type: 'id',
+              value: 'test-payee',
+            }),
+          ]),
+          conditionsOp: 'and',
+          id: rule.id,
+          stage: 'pre',
+        }),
+      ]),
+    );
+
+    // get by payee
+    expect(await api.getPayeeRules('test-payee')).toEqual(
+      expect.arrayContaining([
+        expect.objectContaining({
+          actions: expect.arrayContaining([
+            expect.objectContaining({
+              field: 'category',
+              op: 'set',
+              type: 'id',
+              value: 'fc3825fd-b982-4b72-b768-5b30844cf832',
+            }),
+          ]),
+          conditions: expect.arrayContaining([
+            expect.objectContaining({
+              field: 'payee',
+              op: 'is',
+              type: 'id',
+              value: 'test-payee',
+            }),
+          ]),
+          conditionsOp: 'and',
+          id: rule.id,
+          stage: 'pre',
+        }),
+      ]),
+    );
+
+    expect(await api.getPayeeRules('test-payee2')).toEqual(
+      expect.arrayContaining([
+        expect.objectContaining({
+          actions: expect.arrayContaining([
+            expect.objectContaining({
+              field: 'category',
+              op: 'set',
+              type: 'id',
+              value: 'fc3825fd-b982-4b72-b768-5b30844cf832',
+            }),
+          ]),
+          conditions: expect.arrayContaining([
+            expect.objectContaining({
+              field: 'payee',
+              op: 'is',
+              type: 'id',
+              value: 'test-payee2',
+            }),
+          ]),
+          conditionsOp: 'and',
+          id: rule2.id,
+          stage: 'pre',
+        }),
+      ]),
+    );
+
+    // update one rule
+    const updatedRule = {
+      ...rule,
+      stage: 'post',
+      conditionsOp: 'or',
+    };
+    expect(await api.updateRule(updatedRule)).toEqual(updatedRule);
+
+    expect(await api.getRules()).toEqual(
+      expect.arrayContaining([
+        expect.objectContaining({
+          actions: expect.arrayContaining([
+            expect.objectContaining({
+              field: 'category',
+              op: 'set',
+              type: 'id',
+              value: 'fc3825fd-b982-4b72-b768-5b30844cf832',
+            }),
+          ]),
+          conditions: expect.arrayContaining([
+            expect.objectContaining({
+              field: 'payee',
+              op: 'is',
+              type: 'id',
+              value: 'test-payee',
+            }),
+          ]),
+          conditionsOp: 'or',
+          id: rule.id,
+          stage: 'post',
+        }),
+        expect.objectContaining({
+          actions: expect.arrayContaining([
+            expect.objectContaining({
+              field: 'category',
+              op: 'set',
+              type: 'id',
+              value: 'fc3825fd-b982-4b72-b768-5b30844cf832',
+            }),
+          ]),
+          conditions: expect.arrayContaining([
+            expect.objectContaining({
+              field: 'payee',
+              op: 'is',
+              type: 'id',
+              value: 'test-payee2',
+            }),
+          ]),
+          conditionsOp: 'and',
+          id: rule2.id,
+          stage: 'pre',
+        }),
+      ]),
+    );
+
+    // delete rules
+    await api.deleteRule(rules[1]);
+    expect(await api.getRules()).toHaveLength(1);
+
+    await api.deleteRule(rules[0]);
+    expect(await api.getRules()).toHaveLength(0);
+  });
+
   // apis: addTransactions, getTransactions, importTransactions, updateTransaction, deleteTransaction
   test('Transactions: successfully update transactions', async () => {
     const accountId = await api.createAccount({ name: 'test-account' }, 0);
diff --git a/packages/api/methods.ts b/packages/api/methods.ts
index cf8a6aa46257bfd7daf7b1589c0bbd21c169f6c6..bc69223e2672ee0dfd67437d0101ac2fa51a5c7a 100644
--- a/packages/api/methods.ts
+++ b/packages/api/methods.ts
@@ -168,3 +168,23 @@ export function updatePayee(id, fields) {
 export function deletePayee(id) {
   return send('api/payee-delete', { id });
 }
+
+export function getRules() {
+  return send('api/rules-get');
+}
+
+export function getPayeeRules(id) {
+  return send('api/payee-rules-get', { id });
+}
+
+export function createRule(rule) {
+  return send('api/rule-create', { rule });
+}
+
+export function updateRule(rule) {
+  return send('api/rule-update', { rule });
+}
+
+export function deleteRule(id) {
+  return send('api/rule-delete', { id });
+}
diff --git a/packages/desktop-client/src/components/ManageRules.tsx b/packages/desktop-client/src/components/ManageRules.tsx
index d48c6a080405b8cfcbe3cb73413c74606015b473..65131e41aa40bbb83b98fcd7a820eccc3c8adb21 100644
--- a/packages/desktop-client/src/components/ManageRules.tsx
+++ b/packages/desktop-client/src/components/ManageRules.tsx
@@ -15,7 +15,7 @@ import { send } from 'loot-core/src/platform/client/fetch';
 import * as undo from 'loot-core/src/platform/client/undo';
 import { mapField, friendlyOp } from 'loot-core/src/shared/rules';
 import { describeSchedule } from 'loot-core/src/shared/schedules';
-import { type RuleEntity } from 'loot-core/src/types/models';
+import { type NewRuleEntity } from 'loot-core/src/types/models';
 
 import { useAccounts } from '../hooks/useAccounts';
 import { useCategories } from '../hooks/useCategories';
@@ -210,7 +210,7 @@ function ManageRulesContent({
   }, []);
 
   function onCreateRule() {
-    const rule: RuleEntity = {
+    const rule: NewRuleEntity = {
       stage: null,
       conditionsOp: 'and',
       conditions: [
diff --git a/packages/loot-core/src/client/state-types/modals.d.ts b/packages/loot-core/src/client/state-types/modals.d.ts
index f3112e48973ca440f46ae7c3c504444bfb0740fc..4f0b190dcabdae9de61f040bb87d9304eb490ba7 100644
--- a/packages/loot-core/src/client/state-types/modals.d.ts
+++ b/packages/loot-core/src/client/state-types/modals.d.ts
@@ -5,7 +5,7 @@ import type {
   CategoryGroupEntity,
   GoCardlessToken,
 } from '../../types/models';
-import type { RuleEntity } from '../../types/models/rule';
+import type { NewRuleEntity, RuleEntity } from '../../types/models/rule';
 import type { EmptyObject, StripNever } from '../../types/util';
 import type * as constants from '../constants';
 export type ModalType = keyof FinanceModals;
@@ -51,7 +51,7 @@ type FinanceModals = {
 
   'manage-rules': { payeeId?: string };
   'edit-rule': {
-    rule: RuleEntity;
+    rule: RuleEntity | NewRuleEntity;
     onSave: (rule: RuleEntity) => void;
   };
   'merge-unused-payees': {
diff --git a/packages/loot-core/src/server/api.ts b/packages/loot-core/src/server/api.ts
index 13c731235a0b59e1546524a2e35e5c940099a361..d66c5071d5dd7058096823a69dfcfffc502f02d8 100644
--- a/packages/loot-core/src/server/api.ts
+++ b/packages/loot-core/src/server/api.ts
@@ -600,6 +600,43 @@ handlers['api/payee-delete'] = withMutation(async function ({ id }) {
   return handlers['payees-batch-change']({ deleted: [{ id }] });
 });
 
+handlers['api/rules-get'] = async function () {
+  checkFileOpen();
+  return handlers['rules-get']();
+};
+
+handlers['api/payee-rules-get'] = async function ({ id }) {
+  checkFileOpen();
+  return handlers['payees-get-rules']({ id });
+};
+
+handlers['api/rule-create'] = withMutation(async function ({ rule }) {
+  checkFileOpen();
+  const addedRule = await handlers['rule-add'](rule);
+
+  if ('error' in addedRule) {
+    throw APIError('Failed creating a new rule', addedRule.error);
+  }
+
+  return addedRule;
+});
+
+handlers['api/rule-update'] = withMutation(async function ({ rule }) {
+  checkFileOpen();
+  const updatedRule = handlers['rule-update'](rule);
+
+  if ('error' in updatedRule) {
+    throw APIError('Failed updating the rule', updatedRule.error);
+  }
+
+  return updatedRule;
+});
+
+handlers['api/rule-delete'] = withMutation(async function ({ id }) {
+  checkFileOpen();
+  return handlers['rule-delete'](id);
+});
+
 export function installAPI(serverHandlers: ServerHandlers) {
   const merged = Object.assign({}, serverHandlers, handlers);
   handlers = merged as Handlers;
diff --git a/packages/loot-core/src/server/errors.ts b/packages/loot-core/src/server/errors.ts
index 1edf89a9b6838d8ccec547e41f07c274446b6706..d7228baab8e83a9c3336dec637c8312d7774fbff 100644
--- a/packages/loot-core/src/server/errors.ts
+++ b/packages/loot-core/src/server/errors.ts
@@ -62,8 +62,9 @@ export class RuleError extends Error {
   }
 }
 
-export function APIError(msg: string) {
-  return { type: 'APIError', message: msg };
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export function APIError(msg: string, meta?: Record<string, any>) {
+  return { type: 'APIError', message: msg, meta };
 }
 
 export function FileDownloadError(
diff --git a/packages/loot-core/src/server/rules/app.ts b/packages/loot-core/src/server/rules/app.ts
index abd02e0c15ca4d9a32f04ec2450542811c99d398..dd8bd11c68215eb5f96cf7ed9922f6d8e699caf5 100644
--- a/packages/loot-core/src/server/rules/app.ts
+++ b/packages/loot-core/src/server/rules/app.ts
@@ -92,7 +92,7 @@ app.method(
     }
 
     const id = await rules.insertRule(rule);
-    return { id };
+    return { id, ...rule };
   }),
 );
 
@@ -105,7 +105,7 @@ app.method(
     }
 
     await rules.updateRule(rule);
-    return {};
+    return rule;
   }),
 );
 
diff --git a/packages/loot-core/src/types/api-handlers.d.ts b/packages/loot-core/src/types/api-handlers.d.ts
index 5fc820e4a2289dc07f40a096cf277d930e8b8ed3..d5fbd3ba5cdcbdbb7e7e180cec3a3e596a8af5e0 100644
--- a/packages/loot-core/src/types/api-handlers.d.ts
+++ b/packages/loot-core/src/types/api-handlers.d.ts
@@ -1,12 +1,12 @@
 import { type batchUpdateTransactions } from '../server/accounts/transactions';
 import type {
-  APICategoryEntity,
   APIAccountEntity,
+  APICategoryEntity,
   APICategoryGroupEntity,
   APIPayeeEntity,
 } from '../server/api-models';
 
-import type { TransactionEntity } from './models';
+import type { NewRuleEntity, RuleEntity, TransactionEntity } from './models';
 import { type ServerHandlers } from './server-handlers';
 
 export interface ApiHandlers {
@@ -143,4 +143,14 @@ export interface ApiHandlers {
   'api/payee-update': (arg: { id; fields }) => Promise<unknown>;
 
   'api/payee-delete': (arg: { id }) => Promise<unknown>;
+
+  'api/rules-get': () => Promise<RuleEntity[]>;
+
+  'api/payee-rules-get': (arg: { id: string }) => Promise<RuleEntity[]>;
+
+  'api/rule-create': (arg: { rule: NewRuleEntity }) => Promise<RuleEntity>;
+
+  'api/rule-update': (arg: { rule: RuleEntity }) => Promise<RuleEntity>;
+
+  'api/rule-delete': (arg: { id: string }) => Promise<boolean>;
 }
diff --git a/packages/loot-core/src/types/models/rule.d.ts b/packages/loot-core/src/types/models/rule.d.ts
index 86e9348d30a0e718edc62d99c48be16c9a2154cd..026aef47a259e06a33321af21fefbcbfdd42ff01 100644
--- a/packages/loot-core/src/types/models/rule.d.ts
+++ b/packages/loot-core/src/types/models/rule.d.ts
@@ -1,7 +1,6 @@
 import { type ScheduleEntity } from './schedule';
 
-export interface RuleEntity {
-  id?: string;
+export interface NewRuleEntity {
   stage: string;
   conditionsOp: 'any' | 'and';
   conditions: RuleConditionEntity[];
@@ -9,6 +8,10 @@ export interface RuleEntity {
   tombstone?: boolean;
 }
 
+export interface RuleEntity extends NewRuleEntity {
+  id: string;
+}
+
 export type RuleConditionOp =
   | 'is'
   | 'isNot'
diff --git a/packages/loot-core/src/types/server-handlers.d.ts b/packages/loot-core/src/types/server-handlers.d.ts
index 78c51f399ae86f33cf1ba1698a4f1a4944def4da..88370eaf38135187fc3c0fe90bbefdacaf807d32 100644
--- a/packages/loot-core/src/types/server-handlers.d.ts
+++ b/packages/loot-core/src/types/server-handlers.d.ts
@@ -14,6 +14,7 @@ import {
   GoCardlessToken,
   GoCardlessInstitution,
   SimpleFinAccount,
+  RuleEntity,
   PayeeEntity,
 } from './models';
 import { GlobalPrefs, LocalPrefs } from './prefs';
@@ -118,7 +119,7 @@ export interface ServerHandlers {
 
   'payees-check-orphaned': (arg: { ids }) => Promise<unknown>;
 
-  'payees-get-rules': (arg: { id }) => Promise<unknown>;
+  'payees-get-rules': (arg: { id: string }) => Promise<RuleEntity[]>;
 
   'make-filters-from-conditions': (arg: {
     conditions;
diff --git a/upcoming-release-notes/2568.md b/upcoming-release-notes/2568.md
new file mode 100644
index 0000000000000000000000000000000000000000..3df69fb597183441ac21fc68dd5d2439ffc09d8b
--- /dev/null
+++ b/upcoming-release-notes/2568.md
@@ -0,0 +1,6 @@
+---
+category: Enhancements
+authors: [psybers]
+---
+
+Add API for working with rules.