Post

Automating Actual Budget Exports into Open WebUI

Automating Actual Budget Exports into Open WebUI

budgeting

Giving Open WebUI access to my budget

I wanted Open WebUI to be able to answer questions about my finances without manually exporting files from Actual Budget every time. The goal was to create a clean pipeline that automatically exports financial data from Actual Budget, places it somewhere durable, syncs it into an Open WebUI Knowledge Base, and keeps everything updated on a schedule.

The final setup uses:

  • Actual Budget as the source of financial data
  • A small Dockerized exporter container
  • A Nextcloud WebDAV mount as the handoff location
  • oikb to sync the exported files into Open WebUI
  • A dedicated Open WebUI Knowledge Base for the Actual Budget exports

The end result is a repeatable pipeline:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Actual Budget
   |
   | Actual Budget API
   v
actual-exporter container
   |
   | CSV / JSON / TXT exports
   v
Nextcloud WebDAV mount
   |
   | Local folder on Docker host
   v
oikb
   |
   | Incremental Knowledge Base sync
   v
Open WebUI

Environment Summary

Actual Budget is running in Docker on the Debian host.

The running Actual Budget container is:

1
actualbudget-actualbudget-1

The image is:

1
actualbudget/actual-server:latest

Actual Budget is exposed on the host at:

1
0.0.0.0:5006->5006/tcp

The public Actual Budget URL is:

1
https://budget.cjs-cloud.com

The Nextcloud WebDAV folder used for exports is:

1
ActualBudget Exports

The local mounted path on the Debian host is:

1
/mnt/nextcloud-actualbudget-exports

The Open WebUI Knowledge Base UUID for the Actual Budget exports is:

1
84256f82-f55d-4c82-9357-ddfc0eae873e

Final Working Setup

Nextcloud Export Folder

The Nextcloud folder is:

1
ActualBudget Exports

The WebDAV base URL for the Nextcloud user is:

1
https://nextcloud.cjs-cloud.com/remote.php/dav/files/csadmin

The full WebDAV path for the export folder is:

1
https://nextcloud.cjs-cloud.com/remote.php/dav/files/csadmin/ActualBudget%20Exports/

On the Debian host, this folder is mounted at:

1
/mnt/nextcloud-actualbudget-exports

The mount can be verified with:

1
mount | grep nextcloud-actualbudget-exports

Expected output should show something similar to:

1
https://nextcloud.cjs-cloud.com/remote.php/dav/files/csadmin/ActualBudget Exports/ on /mnt/nextcloud-actualbudget-exports type fuse

WebDAV Mount Configuration

Required Package

The host uses davfs2 for the WebDAV mount:

1
sudo apt install davfs2

Mount Point

The local mount point is:

1
/mnt/nextcloud-actualbudget-exports

It was created with:

1
sudo mkdir -p /mnt/nextcloud-actualbudget-exports

/etc/fstab

The working /etc/fstab entry is:

1
https://nextcloud.cjs-cloud.com/remote.php/dav/files/csadmin/ActualBudget\040Exports/ /mnt/nextcloud-actualbudget-exports davfs rw,_netdev,uid=1000,gid=1000,file_mode=0660,dir_mode=0770 0 0

The important part is that the space in the folder name is represented as:

1
\040

So this:

1
ActualBudget Exports

becomes this in /etc/fstab:

1
ActualBudget\040Exports

/etc/davfs2/secrets

The WebDAV credentials are stored in:

1
/etc/davfs2/secrets

The working format uses the mount point instead of the full URL:

1
/mnt/nextcloud-actualbudget-exports csadmin "your-nextcloud-app-password"

The file is locked down with:

1
2
sudo chown root:root /etc/davfs2/secrets
sudo chmod 600 /etc/davfs2/secrets

/etc/davfs2/davfs2.conf

WebDAV locks are disabled with:

1
use_locks 0

This avoids lock-related warnings when mounting the Nextcloud WebDAV folder.


Mount Commands

Reload systemd after editing /etc/fstab:

1
sudo systemctl daemon-reload

Mount the folder:

1
sudo mount /mnt/nextcloud-actualbudget-exports

Unmount the folder:

1
sudo umount /mnt/nextcloud-actualbudget-exports

Mount all configured filesystems:

1
sudo mount -a

Actual Budget Exporter

Host Config Directory

The exporter lives on the Docker host at:

1
/opt/actual-exporter

The directory contains:

1
2
3
4
5
/opt/actual-exporter/.env
/opt/actual-exporter/Dockerfile
/opt/actual-exporter/docker-compose.yml
/opt/actual-exporter/package.json
/opt/actual-exporter/export.js

Verify the files:

1
ls -la /opt/actual-exporter

Expected files:

1
2
3
4
5
.env
Dockerfile
docker-compose.yml
package.json
export.js

package.json

The exporter uses the official Actual Budget API package:

1
2
3
4
5
6
7
8
9
{
  "name": "actual-budget-openwebui-exporter",
  "version": "1.0.0",
  "private": true,
  "type": "commonjs",
  "dependencies": {
    "@actual-app/api": "latest"
  }
}

Dockerfile

The final working Dockerfile is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
FROM node:20-bookworm-slim

WORKDIR /app

RUN apt-get update \
  && apt-get install -y --no-install-recommends \
    python3 \
    make \
    g++ \
    ca-certificates \
  && rm -rf /var/lib/apt/lists/*

COPY package.json ./
RUN npm install --omit=dev

COPY export.js ./

CMD ["node", "/app/export.js"]

The build tools are included because the Actual Budget API dependency tree can require native Node modules to compile during install.


docker-compose.yml

The exporter runs as its own small Docker Compose project.

The working Compose file is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
services:
  actual-exporter:
    build: .
    env_file:
      - .env
    volumes:
      - actual_exporter_cache:/cache
      - /mnt/nextcloud-actualbudget-exports:/out:rw
    restart: "no"
    extra_hosts:
      - "host.docker.internal:host-gateway"

volumes:
  actual_exporter_cache:

What This Configuration Does

The exporter container writes output to:

1
/out

That path is mapped to the host folder:

1
/mnt/nextcloud-actualbudget-exports

Because that host folder is the Nextcloud WebDAV mount, any files written by the exporter appear in Nextcloud under:

1
ActualBudget Exports

The exporter also uses a persistent cache volume:

1
actual_exporter_cache

That cache is mounted inside the container at:

1
/cache

.env

The exporter environment file is:

1
/opt/actual-exporter/.env

The working format is:

ACTUAL_SERVER_URL=https://budget.cjs-cloud.com
ACTUAL_PASSWORD=your-actual-budget-server-password
ACTUAL_BUDGET_ID=80b43e52-baa2-47bf-b026-7253b7868d74
LOOKBACK_MONTHS=36
EXPORT_DIR=/out
ACTUAL_DATA_DIR=/cache

If the Actual Budget file itself is encrypted separately, this can also be added:

ACTUAL_BUDGET_ENCRYPTION_PASSWORD=your-budget-file-encryption-password

Important Variables

ACTUAL_SERVER_URL

The Actual Budget server URL is:

ACTUAL_SERVER_URL=https://budget.cjs-cloud.com

ACTUAL_PASSWORD

This is the Actual Budget server password.

It is not:

  • the Nextcloud password
  • the Debian user password
  • the OIDC password
  • the Cloudflare password

ACTUAL_BUDGET_ID

The Actual Budget ID is:

ACTUAL_BUDGET_ID=80b43e52-baa2-47bf-b026-7253b7868d74

LOOKBACK_MONTHS

The exporter currently exports the last 36 months:

LOOKBACK_MONTHS=36

EXPORT_DIR

The exporter writes to:

EXPORT_DIR=/out

Inside the container, /out maps to:

1
/mnt/nextcloud-actualbudget-exports

ACTUAL_DATA_DIR

The Actual API cache lives at:

ACTUAL_DATA_DIR=/cache

Inside Docker, /cache maps to the named volume:

1
actual_exporter_cache

Exported Files

A successful exporter run writes these files:

1
2
3
4
5
6
7
actual-transactions.csv
actual-monthly-summary.csv
actual-accounts.csv
actual-categories.csv
actual-payees.csv
actual-export-metadata.json
README-for-openwebui.txt

These files are written to:

1
/mnt/nextcloud-actualbudget-exports

And appear in Nextcloud under:

1
ActualBudget Exports

actual-transactions.csv

This contains individual transaction-level records.

It is intended for questions about:

  • individual transactions
  • merchants
  • payees
  • notes
  • dates
  • accounts
  • transaction lookup

actual-monthly-summary.csv

This contains deterministic monthly totals by category and account.

It is intended for questions about:

  • monthly totals
  • category totals
  • income
  • expenses
  • spending trends
  • account-level summaries

actual-accounts.csv

This contains Actual Budget account metadata.

actual-categories.csv

This contains Actual Budget category metadata.

actual-payees.csv

This contains payee metadata if available from the Actual Budget API.

actual-export-metadata.json

This contains export metadata, including:

  • generated timestamp
  • budget ID
  • start date
  • end date
  • lookback window
  • exported transaction count
  • account count
  • category count
  • payee count

README-for-openwebui.txt

This gives Open WebUI guidance on which files to use for different types of financial questions.


Exporter Commands

Build the exporter:

1
2
cd /opt/actual-exporter
docker compose build

Run the exporter manually:

1
2
cd /opt/actual-exporter
docker compose run --rm actual-exporter

A successful run ends with output similar to:

1
2
3
Export complete.
Transactions exported: 410
Files written to: /out

Verify the exported files:

1
ls -lah /mnt/nextcloud-actualbudget-exports

Preview transactions:

1
head -n 5 /mnt/nextcloud-actualbudget-exports/actual-transactions.csv

Preview monthly summaries:

1
head -n 5 /mnt/nextcloud-actualbudget-exports/actual-monthly-summary.csv

View export metadata:

1
cat /mnt/nextcloud-actualbudget-exports/actual-export-metadata.json

oikb Knowledge Base Sync

oikb is already deployed as part of the Open WebUI stack.

The existing oikb config directory is:

1
/opt/appdata/oikb

The important files are:

1
2
/opt/appdata/oikb/.env
/opt/appdata/oikb/.oikb.yaml

oikb mirrors external content into Open WebUI Knowledge Bases and performs incremental sync instead of one-time uploads. It hashes files, compares them against the Knowledge Base state, uploads new or modified files, and removes deleted or stale files.


Updated .oikb.yaml

The Actual Budget export source was added to .oikb.yaml.

The source entry is:

1
2
3
4
5
6
7
8
9
10
  - name: actualbudget-exports
    source: /sources/actualbudget-exports
    kb-id: 84256f82-f55d-4c82-9357-ddfc0eae873e
    filter:
      include:
        - "*.csv"
        - "*.json"
        - "*.txt"
      exclude:
        - "*.tmp"

The full .oikb.yaml should keep the existing sources and include the new Actual Budget source.

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
defaults:
  interval: 1h
  concurrency: 4
  filter:
    max-size: 50mb

sources:
  - name: nextcloud-documents
    source: nextcloud:/Documents
    kb-id: ${KB_NEXTCLOUD_ID}

  - name: nextcloud-onyx
    source: nextcloud:/onyx
    kb-id: ${KB_NEXTCLOUD_ID}

  - name: actualbudget-exports
    source: /sources/actualbudget-exports
    kb-id: 84256f82-f55d-4c82-9357-ddfc0eae873e
    filter:
      include:
        - "*.csv"
        - "*.json"
        - "*.txt"
      exclude:
        - "*.tmp"

Why the Source Path Is Different

On the Docker host, the export folder is:

1
/mnt/nextcloud-actualbudget-exports

Inside the oikb container, it is mounted as:

1
/sources/actualbudget-exports

So .oikb.yaml must use the container-visible path:

1
source: /sources/actualbudget-exports

not the host path:

1
source: /mnt/nextcloud-actualbudget-exports

Updated oikb Docker Compose Service

The oikb container needs access to the mounted Nextcloud export folder.

The existing oikb service already mounts the config directory:

1
- /opt/appdata/oikb:/config:ro

The Actual Budget export folder was added as a read-only bind mount:

1
- /mnt/nextcloud-actualbudget-exports:/sources/actualbudget-exports:ro

The final oikb service format is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  oikb:
    image: ghcr.io/open-webui/oikb:latest
    container_name: oikb
    restart: unless-stopped
    environment:
      - TZ=America/New_York
      - OPEN_WEBUI_URL=http://openwebui:8080
      - OPEN_WEBUI_API_KEY=your-openwebui-api-key
      - OIKB_API_KEY=your-oikb-daemon-api-key
      - KB_NEXTCLOUD_ID=your-nextcloud-kb-id
      - NEXTCLOUD_URL=https://nextcloud.cjs-cloud.com
      - NEXTCLOUD_USER=csadmin
      - NEXTCLOUD_PASSWORD=your-nextcloud-app-password
      - LOG_FORMAT=json
    volumes:
      - /opt/appdata/oikb:/config:ro
      - /mnt/nextcloud-actualbudget-exports:/sources/actualbudget-exports:ro
    working_dir: /config
    command: daemon
    ports:
      - "8035:8080"
    depends_on:
      - openwebui
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health/ready"]
      interval: 30s
      timeout: 5s

Why This Works

The oikb config directory is mounted into the container here:

1
/config

The container starts in that directory:

1
working_dir: /config

The daemon reads:

1
/config/.oikb.yaml

The Actual Budget export folder is mounted read-only into the container here:

1
/sources/actualbudget-exports

The .oikb.yaml source points to that same path:

1
source: /sources/actualbudget-exports

This lets oikb sync the exported Actual Budget CSV, JSON, and TXT files into the dedicated Open WebUI Knowledge Base.


Deploy / Redeploy Commands

After editing the oikb Docker Compose service:

1
2
3
docker rm -f oikb
docker compose up -d oikb
docker logs oikb -f

If using Portainer, redeploy the stack from Portainer after editing the stack YAML.


Verify oikb Can See the Export Files

Check the running container mounts:

1
docker inspect oikb --format ''

Expected mount entries should include:

1
2
/opt/appdata/oikb -> /config
/mnt/nextcloud-actualbudget-exports -> /sources/actualbudget-exports

Check the files from inside the oikb container:

1
docker exec -it oikb ls -lah /sources/actualbudget-exports

Expected files:

1
2
3
4
5
6
7
actual-transactions.csv
actual-monthly-summary.csv
actual-accounts.csv
actual-categories.csv
actual-payees.csv
actual-export-metadata.json
README-for-openwebui.txt

Validate the oikb config:

1
docker exec -it oikb oikb validate

Deep validation:

1
docker exec -it oikb oikb validate --deep

Run a dry run for the Actual Budget source:

1
docker exec -it oikb oikb sync --name actualbudget-exports --dry-run

Run a manual sync:

1
docker exec -it oikb oikb sync --name actualbudget-exports

List files in the Actual Budget Knowledge Base:

1
docker exec -it oikb oikb ls --kb-id 84256f82-f55d-4c82-9357-ddfc0eae873e

Check Knowledge Base status:

1
docker exec -it oikb oikb status --kb-id 84256f82-f55d-4c82-9357-ddfc0eae873e

Scheduling

There are two scheduled components:

  1. Actual Budget export
  2. oikb sync into Open WebUI

Actual Budget Export Schedule

The exporter can be scheduled with root cron:

1
sudo crontab -e

Daily export at 3:17 AM:

17 3 * * * mountpoint -q /mnt/nextcloud-actualbudget-exports && cd /opt/actual-exporter && /usr/bin/flock -n /tmp/actual-exporter.lock /usr/bin/docker compose run --rm actual-exporter >> /var/log/actual-exporter.log 2>&1

This does three things:

  • confirms the Nextcloud WebDAV mount exists
  • runs the exporter from /opt/actual-exporter
  • prevents overlapping exporter runs with flock

Exporter logs are written to:

1
/var/log/actual-exporter.log

View logs:

1
sudo tail -f /var/log/actual-exporter.log

oikb Sync Schedule

The oikb daemon uses the interval in .oikb.yaml.

The default interval is:

1
2
defaults:
  interval: 1h

This means all configured sources without a per-source override sync every hour.

A per-source override can be added to the Actual Budget source if desired:

1
2
3
4
5
6
7
8
9
10
11
  - name: actualbudget-exports
    source: /sources/actualbudget-exports
    kb-id: 84256f82-f55d-4c82-9357-ddfc0eae873e
    interval: 1h
    filter:
      include:
        - "*.csv"
        - "*.json"
        - "*.txt"
      exclude:
        - "*.tmp"

Because the exporter currently runs daily at 3:17 AM, the hourly oikb daemon schedule is sufficient. After the exporter updates the files, oikb will pick up the modified files on its next scheduled sync.


Open WebUI Knowledge Base

A dedicated Open WebUI Knowledge Base is used for the Actual Budget exports.

The Knowledge Base UUID is:

1
84256f82-f55d-4c82-9357-ddfc0eae873e

The synced files are:

1
2
3
4
5
6
7
actual-transactions.csv
actual-monthly-summary.csv
actual-accounts.csv
actual-categories.csv
actual-payees.csv
actual-export-metadata.json
README-for-openwebui.txt

This Knowledge Base should be attached to models or chats that need financial context.


Recommended Open WebUI Instructions

Use these instructions for the model or Knowledge Base guidance:

1
2
3
4
5
6
7
8
9
10
11
You have access to Actual Budget exports.

Use actual-monthly-summary.csv for totals, category spending, income, expenses, monthly summaries, trends, and comparisons.

Use actual-transactions.csv for individual transaction details, merchant/payee questions, account-specific questions, dates, notes, and transaction lookups.

Use actual-export-metadata.json to determine the available date range.

Amounts are decimal currency values. Negative transaction amounts are expenses or outflows. Positive transaction amounts are income, refunds, or inflows.

If the requested date range is outside the available export range, say that the data does not fully cover the requested period.

Final Architecture

The final architecture looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Actual Budget Docker container
   |
   | API access using @actual-app/api
   v
actual-exporter Docker Compose project
   |
   | Writes CSV, JSON, and TXT files
   v
/mnt/nextcloud-actualbudget-exports
   |
   | WebDAV-mounted Nextcloud folder
   v
Nextcloud / ActualBudget Exports
   |
   | Mounted read-only into oikb container
   v
/sources/actualbudget-exports
   |
   | oikb incremental sync
   v
Open WebUI Knowledge Base
   |
   | Attached to chat/model
   v
Financial questions in Open WebUI

Useful Commands

Actual Exporter

Run exporter manually:

1
2
cd /opt/actual-exporter
docker compose run --rm actual-exporter

Build exporter:

1
2
cd /opt/actual-exporter
docker compose build

View exported files:

1
ls -lah /mnt/nextcloud-actualbudget-exports

View exporter logs:

1
sudo tail -f /var/log/actual-exporter.log

WebDAV Mount

Check mount:

1
mount | grep nextcloud-actualbudget-exports

Mount manually:

1
sudo mount /mnt/nextcloud-actualbudget-exports

Unmount manually:

1
sudo umount /mnt/nextcloud-actualbudget-exports

Mount everything from /etc/fstab:

1
sudo mount -a

oikb

View oikb logs:

1
docker logs oikb -f

Validate config:

1
docker exec -it oikb oikb validate

Deep validate config:

1
docker exec -it oikb oikb validate --deep

Dry run Actual Budget sync:

1
docker exec -it oikb oikb sync --name actualbudget-exports --dry-run

Run Actual Budget sync:

1
docker exec -it oikb oikb sync --name actualbudget-exports

List files in the Actual Budget KB:

1
docker exec -it oikb oikb ls --kb-id 84256f82-f55d-4c82-9357-ddfc0eae873e

Check KB status:

1
docker exec -it oikb oikb status --kb-id 84256f82-f55d-4c82-9357-ddfc0eae873e

Best Practices Going Forward

Finances Use a Dedicated Knowledge Base

The Actual Budget exports should stay in their own dedicated Knowledge Base:

1
Actual Budget Exports

This keeps financial data separate from general notes, documents, GitHub repositories, and manually uploaded files. This should also help with creating a model later that will be specializing in financial advice.

Prefer Summary Files for Totals

For financial totals, monthly summaries, and category analysis, prefer:

1
actual-monthly-summary.csv

This avoids relying on the model to manually total hundreds or thousands of individual transaction rows.

For individual transaction lookup, use:

1
actual-transactions.csv

Keep the Export Folder Simple

The export folder should contain only files that should be synced into the Actual Budget Knowledge Base.

Current expected contents:

1
2
3
4
5
6
7
actual-transactions.csv
actual-monthly-summary.csv
actual-accounts.csv
actual-categories.csv
actual-payees.csv
actual-export-metadata.json
README-for-openwebui.txt

Temporary files are excluded from oikb sync:

1
2
exclude:
  - "*.tmp"

Final Notes

The final setup successfully exports Actual Budget data into a Nextcloud-backed folder and syncs that folder into Open WebUI using oikb.

The working path on the Docker host is:

1
/mnt/nextcloud-actualbudget-exports

The exporter writes to:

1
/out

The oikb container reads from:

1
/sources/actualbudget-exports

The target Open WebUI Knowledge Base is:

1
84256f82-f55d-4c82-9357-ddfc0eae873e

The pipeline is now automated, repeatable, and ready for financial questions inside Open WebUI. We’ll see if this actually curbs any negative spending habits…

This post is licensed under CC BY 4.0 by the author.