Install any skill in seconds. Free to start, no credit card required.
Get Started Free →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.
.claude/skills/sundial-org-actual-budget/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 43% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 23% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 14% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 109% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 148% | 0% |
Official Node.js API for Actual Budget. Runs headless — works on local budget data synced from your server.
bashnpm install @actual-app/api
| 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_TLS_REJECT_UNAUTHORIZED | No | Set to 0 for self-signed certs |
javascriptconst 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();
$50.00 = 5000, -1200 = expense of $12.00YYYY-MM-DD, months use YYYY-MMgetIDByName(type, name) to look up by nameapi.utils.amountToInteger(123.45) → 12345javascriptconst months = await api.getBudgetMonths(); // ['2026-01', '2026-02', ...] const jan = await api.getBudgetMonth('2026-01'); // { categoryGroups, incomeAvailable, ... }
javascriptconst 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
javascript// 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 });
javascriptconst 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 });
javascriptawait api.setBudgetAmount('2026-01', categoryId, 30000); // budget $300 await api.setBudgetCarryover('2026-01', categoryId, true);
javascriptconst 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 }], });
javascriptconst schedules = await api.getSchedules(); await api.createSchedule({ payee: payeeId, account: accountId, amount: -1500, date: { frequency: 'monthly', start: '2026-01-01', interval: 1, endMode: 'never' }, });
javascriptawait api.runBankSync({ accountId }); // GoCardless/SimpleFIN
javascriptawait api.sync(); // push/pull changes to server await api.shutdown(); // always call when done
For complex queries, use ActualQL:
javascriptconst { 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' })
javascript// 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 use special payees. Find transfer payee by transfer_acct field:
javascriptconst 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 } ]);
javascriptawait api.importTransactions(accountId, [{ date: '2026-01-15', amount: -5000, payee_name: 'Costco', subtransactions: [ { amount: -3000, category: groceryCatId }, { amount: -2000, category: householdCatId }, ] }]);
For migrating from another app:
javascriptawait 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)); } });
Full API: https://actualbudget.org/docs/api/reference ActualQL: https://actualbudget.org/docs/api/actual-ql
Other measured skills in the registry, with their headline benchmark lift.