Actual Budget OpenClaw Plugin & Skill | ClawHub
Looking to integrate Actual Budget into your AI workflows? This free OpenClaw plugin from ClawHub helps you automate productivity & tasks tasks instantly, without having to write custom tools from scratch.
What this skill does
Query and manage personal finances via the official Actual Budget Node.js API. Use for budget queries, transaction imports/exports, account management, categorization, rules, schedules, and bank sync with self-hosted Actual Budget instances.
Install
npx clawhub@latest install actual-budgetFull SKILL.md
Open original| name | description |
|---|---|
| actual-budget | Query and manage personal finances via the official Actual Budget Node.js API. Use for budget queries, transaction imports/exports, account management, categorization, rules, schedules, and bank sync with self-hosted Actual Budget instances. |
SKILL.md content below is scrollable.
Actual Budget API
Official Node.js API for Actual Budget. Runs headless — works on local budget data synced from your server.
Installation
npm install @actual-app/api
Environment Variables
| Variable | Required | Description |
|---|---|---|
ACTUAL_SERVER_URL |
Yes | Server URL (e.g., https://actual.example.com) |
ACTUAL_PASSWORD |
Yes | Server password |
ACTUAL_SYNC_ID |
Yes | Budget Sync ID (Settings → Advanced → Sync ID) |
ACTUAL_DATA_DIR |
No | Local cache directory for budget data (defaults to cwd) |
ACTUAL_ENCRYPTION_PASSWORD |
No | E2E encryption password, if enabled |
NODE_EXTRA_CA_CERTS |
No | Path to CA certificate file for self-signed certs |
Self-Signed Certificates
If your Actual Budget server uses a self-signed certificate:
- Recommended: Add your CA to the system trust store, or
- Alternative: Set
NODE_EXTRA_CA_CERTS=/path/to/your-ca.pemto trust your specific CA
Avoid disabling TLS verification entirely — it exposes you to man-in-the-middle attacks.
Quick Start
const api = require('@actual-app/api');
await api.init({
dataDir: process.env.ACTUAL_DATA_DIR || '/tmp/actual-cache',
serverURL: process.env.ACTUAL_SERVER_URL,
password: process.env.ACTUAL_PASSWORD,
});
await api.downloadBudget(
process.env.ACTUAL_SYNC_ID,
process.env.ACTUAL_ENCRYPTION_PASSWORD ? { password: process.env.ACTUAL_ENCRYPTION_PASSWORD } : undefined
);
// ... do work ...
await api.shutdown();
Core Concepts
- Amounts are integers in cents:
$50.00=5000,-1200= expense of $12.00 - Dates use
YYYY-MM-DD, months useYYYY-MM - IDs are UUIDs — use
getIDByName(type, name)to look up by name - Convert with
api.utils.amountToInteger(123.45)→12345
Common Operations
Get Budget Overview
const months = await api.getBudgetMonths(); // ['2026-01', '2026-02', ...]
const jan = await api.getBudgetMonth('2026-01'); // { categoryGroups, incomeAvailable, ... }
Accounts
const accounts = await api.getAccounts();
const balance = await api.getAccountBalance(accountId);
const newId = await api.createAccount({ name: 'Checking', type: 'checking' }, 50000); // $500 initial
await api.closeAccount(id, transferToAccountId); // transfer remaining balance
Transactions
// Get transactions for date range
const txns = await api.getTransactions(accountId, '2026-01-01', '2026-01-31');
// Import with deduplication + rules (preferred for bank imports)
const { added, updated } = await api.importTransactions(accountId, [
{ date: '2026-01-15', amount: -2500, payee_name: 'Grocery Store', notes: 'Weekly run' },
{ date: '2026-01-16', amount: -1200, payee_name: 'Coffee Shop', imported_id: 'bank-123' },
]);
// Update a transaction
await api.updateTransaction(txnId, { category: categoryId, cleared: true });
Categories & Payees
const categories = await api.getCategories();
const groups = await api.getCategoryGroups();
const payees = await api.getPayees();
// Create
const catId = await api.createCategory({ name: 'Subscriptions', group_id: groupId });
const payeeId = await api.createPayee({ name: 'Netflix', category: catId });
Budget Amounts
await api.setBudgetAmount('2026-01', categoryId, 30000); // budget $300
await api.setBudgetCarryover('2026-01', categoryId, true);
Rules
const rules = await api.getRules();
await api.createRule({
stage: 'pre',
conditionsOp: 'and',
conditions: [{ field: 'payee', op: 'is', value: payeeId }],
actions: [{ op: 'set', field: 'category', value: categoryId }],
});
Schedules
const schedules = await api.getSchedules();
await api.createSchedule({
payee: payeeId,
account: accountId,
amount: -1500,
date: { frequency: 'monthly', start: '2026-01-01', interval: 1, endMode: 'never' },
});
Bank Sync
await api.runBankSync({ accountId }); // GoCardless/SimpleFIN
Sync & Shutdown
await api.sync(); // push/pull changes to server
await api.shutdown(); // always call when done
ActualQL Queries
For complex queries, use ActualQL:
const { q, runQuery } = require('@actual-app/api');
// Sum expenses by category this month
const { data } = await runQuery(
q('transactions')
.filter({
date: [{ $gte: '2026-01-01' }, { $lte: '2026-01-31' }],
amount: { $lt: 0 },
})
.groupBy('category.name')
.select(['category.name', { total: { $sum: '$amount' } }])
);
// Search transactions
const { data } = await runQuery(
q('transactions')
.filter({ 'payee.name': { $like: '%grocery%' } })
.select(['date', 'amount', 'payee.name', 'category.name'])
.orderBy({ date: 'desc' })
.limit(20)
);
Operators: $eq, $lt, $lte, $gt, $gte, $ne, $oneof, $regex, $like, $notlike
Splits: .options({ splits: 'inline' | 'grouped' | 'all' })
Helpers
// Look up ID by name
const acctId = await api.getIDByName('accounts', 'Checking');
const catId = await api.getIDByName('categories', 'Food');
const payeeId = await api.getIDByName('payees', 'Amazon');
// List budgets
const budgets = await api.getBudgets(); // local + remote files
Transfers
Transfers use special payees. Find transfer payee by transfer_acct field:
const payees = await api.getPayees();
const transferPayee = payees.find(p => p.transfer_acct === targetAccountId);
await api.importTransactions(fromAccountId, [
{ date: '2026-01-15', amount: -10000, payee: transferPayee.id }
]);
Split Transactions
await api.importTransactions(accountId, [{
date: '2026-01-15',
amount: -5000,
payee_name: 'Costco',
subtransactions: [
{ amount: -3000, category: groceryCatId },
{ amount: -2000, category: householdCatId },
]
}]);
Bulk Import (New Budget)
For migrating from another app:
await api.runImport('My-New-Budget', async () => {
for (const acct of myData.accounts) {
const id = await api.createAccount(acct);
await api.addTransactions(id, myData.transactions.filter(t => t.acctId === id));
}
});
Reference
Full API: https://actualbudget.org/docs/api/reference ActualQL: https://actualbudget.org/docs/api/actual-ql