Skip to content

Experimental features

Danger

Experimental features do not have all the testing necessary to ensure correctness compared to the files generated by the JavaScript API. This means that these operations could, in theory, corrupt your data. Make sure you have backups of your data before trying any of these operations.

Bootstrapping a new server and uploading a first file

The following script will generate a new empty budget on the Actual server, even if the server was not bootstrapped with an initial password:

from actual import Actual

with Actual(base_url="http://localhost:5006", password="mypass", bootstrap=True) as actual:
    actual.create_budget("My budget")
    actual.upload_budget()

You will then have a freshly created new budget to use:

created-budget

If the encryption_password is set, the budget will additionally also be encrypted on the upload step to the server.

Updating transactions using Bank Sync

If you have either goCardless or SimpleFIN integration configured, you can update transactions using only the Python API. This is possible because the actual queries to the third-party service are handled on the server, so the client doesn't need to make custom API queries.

To sync your account, call the Actual.run_bank_sync method:

from actual import Actual

with Actual(base_url="http://localhost:5006", password="mypass") as actual:
    synchronized_transactions = actual.run_bank_sync()
    for transaction in synchronized_transactions:
        print(f"Added of modified {transaction}")
    # sync changes back to the server
    actual.commit()

Running Rules

Rules can be run individually via the library. You can filter which rules will run and check beforehand which rules will actually execute, similar to the preview function in Actual.

The simplest case is to run all rules for all transactions at once. This is equivalent to clicking "Apply Actions" for all rules in the frontend:

from actual import Actual
from actual.queries import get_ruleset

with Actual(base_url="http://localhost:5006", password="mypass", file="My budget") as actual:
    # print all rules and their human-readable descriptions
    print(get_ruleset(actual.session))
    # run all rules
    actual.run_rules()
    # sync changes back to the server
    actual.commit()

If you're running bank sync, you can also run rules directly on the imported transactions after performing the sync:

from actual import Actual

with Actual(base_url="http://localhost:5006", password="mypass") as actual:
    synchronized_transactions = actual.run_bank_sync(run_rules=True)

You can also manipulate rules individually and validate each rule that runs for each transaction, allowing you to debug rules. This can be useful when multiple rules are modifying the same transaction, but the order of operations is incorrect:

from actual import Actual
from actual.queries import get_ruleset, get_transactions

with Actual(base_url="http://localhost:5006", password="mypass", file="My budget") as actual:
    ruleset = get_ruleset(actual.session)
    transactions = get_transactions(actual.session)
    for rule in ruleset:
        for t in transactions:
            if rule.evaluate(t):
                print(f"Rule {rule} matches for {t}")
                # if you are happy with the result from the rule, apply it
                rule.run(t)
    # if you want to sync the changes back to the server, uncomment the following line
    # actual.commit()

When importing transactions, rules don't run automatically: you might need to run them individually. Use the RuleSet.run method to run rules after creating/importing a transaction:

from actual import Actual
from actual.queries import get_ruleset, reconcile_transaction
from datetime import date

with Actual(base_url="http://localhost:5006", password="mypass", file="My budget") as actual:
    # we create one transaction
    t = reconcile_transaction(actual.session, date.today(), "Bank", "", notes="Coffee", amount=-4.50)
    # run the rules on the newly created transaction
    ruleset = get_ruleset(actual.session)
    ruleset.run(t)
    # send the changes back to the server
    actual.commit()