actualq
Read an Actual Budget export from the command line.
No server. No credentials. No dependencies. One file of standard-library Python.
$ actualq accounts
account on budget closed balance transactions first last
------------ --------- ------ -------- ------------ ---------- ----------
Checking yes 4,182.03 593 2023-10-18 2026-08-18
Credit Card yes -514.60 610 2023-10-24 2026-08-18
Savings yes 12,000.00 184 2025-07-28 2026-07-31
$ actualq txns --since 2026-08 --category Grocery --json
[
{
"date": "2026-08-14",
"account": "Checking",
"payee": "Costco",
"category": "Everyday / Grocery",
"amount": -142.87,
"amount_cents": -14287,
...
}
]
Why
Every other way to get at Actual data programmatically — the official
@actual-app/cli, actualpy, the community CLIs — needs a running
actual-server and its password. That is a lot of setup to answer "what did I
spend on groceries in July".
You already have the answer on disk. Actual's export (Settings → Export data) is
a zip holding an ordinary SQLite database. actualq reads it.
That makes it a good fit for LLM agents in particular: one binary-free command, JSON out, nothing to authenticate, and no way to damage the budget.
Install
uv tool install actualq # or: pipx install actualq
Or run it once without installing anything:
uvx actualq accounts
Or take the file. It has no dependencies and needs only Python 3.11+, so there is nothing to install if you would rather not:
curl -O https://raw.githubusercontent.com/madhurdeepjain/actualq/main/actualq.py
python3 actualq.py accounts
Use
With no -f, it reads the newest .zip in the current directory, so the usual
session is cd to wherever you export and start asking.
actualq accounts # balances, ranges, row counts
actualq txns --since 2026-01 --account Checking # transactions
actualq categories --since 2026-07 --until 2026-07
actualq payees --limit 20
actualq sql "select ..." # anything else
actualq schema # the field guide
txns filters: --since --until --account --category --payee
--search --limit --uncategorized --no-transfers --splits.
Dates take a year, a month or a day — --since 2026, --since 2026-08,
--since 2026-08-22 — and --until means the last day of whatever you name.
Output is an aligned table by default, --json for an array, --csv for CSV.
What it will not do
It does not write. There is no import, no edit, no sync. The export is copied to
a temp directory and opened read-only, so nothing here can reach the budget you
actually use, and actualq sql "delete from ..." fails rather than working.
It does not compute budget-vs-actual. The rollover arithmetic behind Actual's
budget screen is genuinely involved, and a number that is nearly right is worse
than no number in a tool you would use to check yourself. The raw
zero_budgets / reflect_budgets tables are there for actualq sql.
The part worth reading: actualq schema
Actual's schema has four traps, and every one of them fails quietly — you get plausible numbers that are wrong. This is most of why this tool exists.
1. Read v_transactions, never transactions. Payees and categories are
indirected through mapping tables (payee_mapping.targetId,
category_mapping.transferId). transactions.description holds a payee id that
may have been merged away, so joining payees directly returns NULL for every
payee you ever renamed or merged — and those rows read as having no payee at all.
Categories do the same and read as uncategorised.
2. Splits are counted twice if you let them. A split is a parent row carrying
the full amount plus children carrying the pieces. Both are real rows. Actual
computes balances over is_parent = 0. On a real file:
--splits leaves 5724 rows matches the account balances <- the default
--splits parents 5718 rows matches them too, one row per bank transaction
--splits all 5728 rows over by the whole value of every split
3. Dates and amounts are integers. date is 20260822, an INTEGER, not a
string. amount is cents, negative for money out. actualq gives you both
amount (readable) and amount_cents (exact) so nothing has to round twice.
4. Deleted rows are still in the file. Nothing is deleted, it gets
tombstone = 1, because Actual syncs by CRDT and has to remember the deletion.
Query accounts, categories or payees yourself without WHERE tombstone = 0
and you report on things the user threw away.
actualq schema prints all of this, plus the tables worth knowing about, so an
agent can read it before writing SQL.
Tests
python3 -m unittest discover
schema.sql is Actual's own schema, structure only, no data. The tests build a
synthetic budget from it containing one of each thing that is easy to get wrong,
and pin the behaviour above.
Compatibility
Written against Actual's schema as of 2026. The views it depends on
(v_transactions, v_payees) are what the app itself reads, so they are the
most stable surface available — but this is not a documented API, and a future
migration could change it.
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file actualq-0.1.1.tar.gz.
File metadata
- Download URL: actualq-0.1.1.tar.gz
- Upload date:
- Size: 16.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
aff39c4e115dabe9b82b7fd12bc2337ad4930e00cb12029e719852a50f0ca7f1
|
|
| MD5 |
68a17baab89d0ca39867b3486164fda2
|
|
| BLAKE2b-256 |
e73c718ec371275bf5d316214b00acb336fea343a1a15abe8f185c9c109d7a03
|
Provenance
The following attestation bundles were made for actualq-0.1.1.tar.gz:
Publisher:
release.yml on madhurdeepjain/actualq
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
actualq-0.1.1.tar.gz -
Subject digest:
aff39c4e115dabe9b82b7fd12bc2337ad4930e00cb12029e719852a50f0ca7f1 - Sigstore transparency entry: 2590182072
- Sigstore integration time:
-
Permalink:
madhurdeepjain/actualq@7d6ae17a1af9b9a7ba3e7db131056fc061d9ab93 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/madhurdeepjain
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7d6ae17a1af9b9a7ba3e7db131056fc061d9ab93 -
Trigger Event:
push
-
Statement type:
File details
Details for the file actualq-0.1.1-py3-none-any.whl.
File metadata
- Download URL: actualq-0.1.1-py3-none-any.whl
- Upload date:
- Size: 11.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c5aaf9132dc81a747ee784e495c694410f46f79a415cd3edac8513cf7eb2c560
|
|
| MD5 |
82ae81da6c6816508221fd1839735aba
|
|
| BLAKE2b-256 |
8b7a30cecd912f887ccafe7a9198c71e65670d9a2aa3314fc12d3c94ef1df1d2
|
Provenance
The following attestation bundles were made for actualq-0.1.1-py3-none-any.whl:
Publisher:
release.yml on madhurdeepjain/actualq
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
actualq-0.1.1-py3-none-any.whl -
Subject digest:
c5aaf9132dc81a747ee784e495c694410f46f79a415cd3edac8513cf7eb2c560 - Sigstore transparency entry: 2590182337
- Sigstore integration time:
-
Permalink:
madhurdeepjain/actualq@7d6ae17a1af9b9a7ba3e7db131056fc061d9ab93 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/madhurdeepjain
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7d6ae17a1af9b9a7ba3e7db131056fc061d9ab93 -
Trigger Event:
push
-
Statement type: