akt — a CLI toolbox to fully drive an Akaunting accounting instance
Project description
akt — Akaunting CLI toolbox
Drive your Akaunting accounting instance entirely from the command line
aktgives you full create / read / update / delete for customers, vendors, items, invoices, bills, payments, accounts, categories, taxes, currencies and transfers — plus arawescape hatch for any other endpoint. Built and tested against Akaunting 3.1.x; works with any 3.x deployment that exposes the REST API.
Install
From PyPI (the distribution is akt-cli; the command is akt):
uv tool install akt-cli # installs the `akt` command globally
# or
pip install akt-cli
# or run without installing
uvx --from akt-cli akt --help
From a checkout (the project is managed with uv):
uv sync # create .venv and install
uv run akt --help # run without activating
uv tool install . # install the `akt` command from source
Configuration
akt needs a base URL, an admin email + password, and a company id. They are
resolved in this order (first wins):
- CLI flags:
--base-url,--email,--password,--company(given before the subcommand, e.g.akt --company 2 customer list). - Environment:
AKT_BASE_URL,AKT_EMAIL,AKT_PASSWORD,AKT_COMPANY,AKT_THROTTLE. - A dotenv file —
$AKT_ENV_FILE, then./.env, then~/.config/akt/akt.env. Akaunting's own install keys are recognised too:APP_URL,AKAUNTING_ADMIN_EMAIL,AKAUNTING_ADMIN_PASSWORD.
Minimal ~/.config/akt/akt.env:
AKT_BASE_URL=https://accounting.example.com
AKT_EMAIL=admin@example.com
AKT_PASSWORD=your-password
AKT_COMPANY=1
Then:
uv run akt ping
uv run akt company
Authentication is HTTP Basic against your Akaunting admin user.
Concepts mapped to Akaunting
Akaunting folds several nouns onto shared endpoints; akt hides that:
| akt noun | API endpoint | notes |
|---|---|---|
customer |
contacts |
contact of type customer |
vendor |
contacts |
contact of type vendor (supplier) |
invoice |
documents |
document of type invoice |
bill |
documents |
document of type bill |
payment |
transactions |
income (invoice) or expense (bill) transaction |
item, account, category, tax, currency, transfer |
as named |
The
contactsanddocumentsendpoints derive their permission from asearch=type:<x>query param.aktinjects this automatically — calling them raw without it returns403 necessary access rights.
Verbs
Every resource supports:
akt <noun> list [--search 'field:value'] [--all] [--limit N] [--json]
akt <noun> get <id>
akt <noun> create --field value ...
akt <noun> update <id> --field value ...
akt <noun> delete <id>
akt <noun> enable <id> # where applicable
akt <noun> disable <id>
Bills, invoices and payments additionally support attachments (scanned bills, receipts, PDFs):
akt <noun> create ... --attachment ./file.pdf # repeatable; upload on create
akt <noun> update <id> --attachment ./file.pdf # attach to an existing record
akt <noun> update <id> --remove-attachment # clear existing attachment(s)
akt <noun> attachments <id> # list attached files (id, name, size)
akt <noun> download-attachment <id> [--out DIR] [--media-id ID]
Output is a table by default; add --json (works before or after the verb) for
raw JSON suitable for piping into jq.
Three ways to set body fields on create/update:
- typed flags shown by
akt <noun> create --help --set key=value(repeatable; values are JSON-coerced, so--set enabled=0)--data '<json>'or--data @file.json(merged last, wins over everything)
Examples
# Contacts
akt customer create --name "Northwind Traders" --email ar@northwind.com --currency-code USD
akt vendor create --name "Office Supply Co" --email billing@osc.com
akt customer list --search 'name:Northwind'
akt customer update 12 --phone "555-2000"
akt customer disable 12
# Items, categories, taxes
akt item create --name "Consulting Hour" --sale-price 150 --purchase-price 0
akt category create --name "Services" --type income
akt tax create --name "Sales Tax" --rate 8.25
# Invoice with line items (totals computed server-side; number auto-generated)
akt invoice create --contact 12 \
--item 'name=Consulting,price=150,quantity=10,item_id=2' \
--item 'name=Setup fee,price=500,quantity=1' \
--status sent
# Record a customer payment against that invoice (amount defaults to amount due)
akt payment create --invoice 34
# Partial payment of a specific amount via bank transfer
akt payment create --invoice 34 --amount 750 \
--payment-method offline-payments.bank_transfer.2
# Bills and vendor payments work the same way
akt bill create --contact 13 --item 'name=Paper,price=40,quantity=5'
akt payment create --bill 41
# Attachments: upload the source PDF/scan and fetch it back later
akt bill create --contact 13 --item 'name=Paper,price=40,quantity=5' \
--attachment ./supplier-bill.pdf
akt payment update 57 --attachment ./receipt.pdf # attach to an existing payment
akt bill attachments 41 # list attached files
akt bill download-attachment 41 --out ./downloads # save to disk
akt payment update 57 --remove-attachment # clear attachments
# Anything else: raw API access
akt raw GET reports
akt raw POST items --data '{"name":"Ad-hoc","type":"service","sale_price":99}'
akt company
akt settings --search 'key:default.account'
Akaunting gotchas akt handles for you
Driving Akaunting's API directly has sharp edges; akt papers over these:
- Type-scoped ACL —
contactsanddocumentsneedsearch=type:<x>on every verb or the API returns403 necessary access rights. - Doubled totals — Akaunting recomputes a document's total from its line
items and adds it to the
amountyou send.aktalways sendsamount: 0so the server-computed total is authoritative. - Item
description— line items need adescriptionkey even when empty, or creation 500s withUndefined array key "description". - Updates wipe items — a document update deletes and recreates all line
items from the request.
aktresends the existing items on a partial update so they aren't lost. - Nested payment route — paying a document must POST to
documents/{id}/transactions; the flattransactionsendpoint rejects it. The same applies to updating a document-linked payment (e.g. attaching a file to it) —aktpicks the nested route automatically. - Multipart uploads — attachments switch the request from JSON to
multipart/form-datawith theattachment[]field; updates are sent asPOST+_method=PATCHbecause PHP won't populate$_FILESon a realPUT. - Attachment download isn't on
/api— Akaunting only serves attachment bytes from the session-authenticated web route/{company}/uploads/{id}/download.akt download-attachmenttransparently logs in a web session with your admin credentials (reused for the process) to fetch the file; metadata (id, name, size) comes from the/apirecord itself. - Full-replace updates — Akaunting PUT re-validates required fields, so
aktmerges your changes onto the current record.
Invoice creation may be gated by a plan check
In Akaunting 3.x, CreateDocument::authorize() gates invoice creation (only
type == invoice) behind a call to api.akaunting.com/plans/limits using the
apps.api_key setting. If that key is unset or the host can't reach
api.akaunting.com, invoice creation fails closed with
500 Not able to create a new user — in the web UI too, not just akt.
Bills, payments, contacts, items and transfers are unaffected. Fix it by setting
a valid apps.api_key (and allowing outbound HTTPS to api.akaunting.com).
Host bot-protection / throttling
Some hosts (e.g. cPanel with Imunify360) greylist an IP that issues a burst of
automated requests, returning an Access denied by … bot-protection page or
timing out. akt retries throttle/WAF responses with backoff, and
--throttle SECONDS (or AKT_THROTTLE) enforces a minimum gap between calls —
use --throttle 1 for bulk work. A durable fix is to whitelist your IP in the
host firewall.
Testing
Tests are split in two:
tests/unit/— offline tests for the body builders and arg parsing. No network. This is what CI runs by default and what gates pull requests.tests/integration/— drive the realaktCLI against a live Akaunting instance, exercising the full surface (contacts, items, bill → payment → paid, transfers, …). Every record they create is deleted on teardown — even on failure — so no invoices, bills or payments are left behind.
uv run pytest tests/unit # fast, offline (default)
uv run pytest # integration tests auto-skip without creds
# Run integration tests against a deployment:
AKT_BASE_URL=https://accounting.example.com \
AKT_EMAIL=admin@example.com \
AKT_PASSWORD=… \
uv run pytest tests/integration -v
Invoice creation is
xfail-ed when the host's plan-limit gate is active (see above); the rest of the suite must pass.
CI / CD
- CI (
.github/workflows/ci.yml) runs on every push and PR: unit tests + coverage, uploaded to Codecov. - Release (
.github/workflows/release.yml) runs the live integration suite on published releases (and via Run workflow). Connection details come from GitHub Actions secrets (AKT_BASE_URL,AKT_EMAIL,AKT_PASSWORD) — they are never committed and are masked in logs. - Publish (
.github/workflows/publish.yml) builds the sdist + wheel and uploads them via Trusted Publishing (OIDC) — no API token is stored. Run workflow publishes to TestPyPI; a published release publishes to PyPI.
Releasing to PyPI
One-time setup — add a pending publisher on each index (Account → Publishing → Add a pending publisher) with:
| Field | TestPyPI | PyPI |
|---|---|---|
| Project Name | akt-cli |
akt-cli |
| Owner | AsyncAlchemist |
AsyncAlchemist |
| Repository name | akt-cli |
akt-cli |
| Workflow name | publish.yml |
publish.yml |
| Environment name | testpypi |
pypi |
Then:
- Verify — Actions → Publish (PyPI) → Run workflow uploads the current version to TestPyPI.
- Release — bump
versioninpyproject.toml, push, and publish a GitHub Release. That runs the live integration suite and publishes to PyPI.
The code is small and declarative:
| file | purpose |
|---|---|
config.py |
credential resolution (flags / env / dotenv) |
client.py |
HTTP, auth, company scoping, pagination, retries |
resources.py |
field specs + body builders (documents, payments) |
registry.py |
the concrete list of resources and their columns |
commands.py |
generic list/get/create/update/delete/toggle handlers |
cli.py |
argparse wiring and entrypoint |
output.py |
JSON / table rendering |
License
MIT © AsyncAlchemist
Project details
Release history Release notifications | RSS feed
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 akt_cli-0.2.0.tar.gz.
File metadata
- Download URL: akt_cli-0.2.0.tar.gz
- Upload date:
- Size: 26.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
26440e44224011d4491a6d652ec9b9ccda2f9ef47557f883750168a6471b15b5
|
|
| MD5 |
2e40b20857783fc90008c5b0a281bd4b
|
|
| BLAKE2b-256 |
475b46a9a1be2b7fdb48fd45361bdb527a51be9bc9e3c256ed36321dff6d077d
|
Provenance
The following attestation bundles were made for akt_cli-0.2.0.tar.gz:
Publisher:
publish.yml on AsyncAlchemist/akt-cli
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
akt_cli-0.2.0.tar.gz -
Subject digest:
26440e44224011d4491a6d652ec9b9ccda2f9ef47557f883750168a6471b15b5 - Sigstore transparency entry: 2037589147
- Sigstore integration time:
-
Permalink:
AsyncAlchemist/akt-cli@07beec0be8c7a318ece40a656835727d447a48be -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/AsyncAlchemist
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@07beec0be8c7a318ece40a656835727d447a48be -
Trigger Event:
release
-
Statement type:
File details
Details for the file akt_cli-0.2.0-py3-none-any.whl.
File metadata
- Download URL: akt_cli-0.2.0-py3-none-any.whl
- Upload date:
- Size: 30.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
976e3c56d2063700919e8e568114f13df893ee258c66fceb371de9daa8ec8eb6
|
|
| MD5 |
f81b036f57f9288046c8af269bf621f7
|
|
| BLAKE2b-256 |
74f3608267fc82fbd10e2f528c1755879049b0df02ffa8b17efe374355ec1489
|
Provenance
The following attestation bundles were made for akt_cli-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on AsyncAlchemist/akt-cli
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
akt_cli-0.2.0-py3-none-any.whl -
Subject digest:
976e3c56d2063700919e8e568114f13df893ee258c66fceb371de9daa8ec8eb6 - Sigstore transparency entry: 2037589270
- Sigstore integration time:
-
Permalink:
AsyncAlchemist/akt-cli@07beec0be8c7a318ece40a656835727d447a48be -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/AsyncAlchemist
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@07beec0be8c7a318ece40a656835727d447a48be -
Trigger Event:
release
-
Statement type: