Skip to main content

akt — Akaunting CLI toolbox

Drive your Akaunting accounting instance entirely from the command line

akt gives you full create / read / update / delete for customers, vendors, items, invoices, bills, payments, banks, categories, taxes, currencies and transfers — plus double-entry journal entries and the chart of accounts, and a raw escape hatch for any other endpoint. Built and tested against Akaunting 3.1.x; works with any 3.x deployment that exposes the REST API.

PyPI Version Tests Integration Publish Codecov

GitHub Release Downloads Python Version License: MIT

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):

  1. CLI flags: --base-url, --email, --password, --company (given before the subcommand, e.g. akt --company 2 customer list).
  2. Environment: AKT_BASE_URL, AKT_EMAIL, AKT_PASSWORD, AKT_COMPANY, AKT_THROTTLE.
  3. 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
journal-entry journal-entry double-entry general-ledger entry (module)
account chart-of-accounts GL accounts (general ledger) — read via API, CRUD via web
bank accounts bank / cash accounts (the money, not the GL)
item, category, tax, currency, transfer as named

journal-entry and account require the Double-Entry module installed on the instance. The module publishes chart-of-accounts read-only on the /api surface (index/show); its create/update/delete live only on the session/CSRF web route. akt account gives you the full verb set anyway — list/get hit /api, while create/update/delete transparently drive the web CRUD with your admin session (the same mechanism download-attachment already uses).

The contacts and documents endpoints derive their permission from a search=type:<x> query param. akt injects this automatically — calling them raw without it returns 403 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

# Bank / cash accounts (the money side — see the COA section below for GL accounts)
akt bank create --name "Business Checking" --number 1001 --currency-code USD
akt bank list

# 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

# Double-entry general ledger (requires the Double-Entry module)
akt account list                                       # read the chart of accounts
akt account get 12

# Build the chart of accounts as code (create/update/delete run via the web
# session; type-id is the double-entry account-type id — copy it from an
# existing account's `type_id`)
akt account create --name "Cash on Hand" --code 1010 --type-id 6
akt account create --name "Petty Cash" --code 1011 --type-id 6 --parent-id 12
akt account update 12 --code 1000 --description "Operating cash"
akt account delete 12

# Post a balanced journal entry (>= 2 lines; debits must equal credits;
# journal number auto-generated, basis defaults to accrual)
akt journal-entry create --description "Owner capital contribution" \
    --item 'account_id=10,debit=5000' \
    --item 'account_id=30,credit=5000'
akt journal-entry list
akt journal-entry update 4 --description "Corrected memo"
akt journal-entry create --description "Vendor bill accrual" --basis accrual \
    --item 'account_id=60,debit=250' --item 'account_id=21,credit=250' \
    --attachment ./invoice.pdf

## COA config: link categories and accounts (Xero-style)

Akaunting keeps *categories* (required on every transaction) separate from the
double-entry *chart of accounts*. Point akt at a COA config and it keeps them in
lockstep: one list to maintain (accounts), a 1:1 mirror of categories generated
from it, and `payment create` coded by `--account`  with the mirror category
filled in automatically.

akt finds the config at `--coa FILE`  `AKT_COA_FILE`  `./coa.toml` `~/.config/akt/coa.toml`. Minimal schema (extra keys are ignored):

```toml
[[account]]
code    = 400
name    = "API Subscription Revenue"
type_id = 13            # DoubleEntry account type; income/expense class -> category type
# optional: category = "Revenue"   (override the mirror name)
# optional: mirror   = false        (skip mirroring, e.g. bank/AR/AP accounts)
akt coa diff                 # preview: accounts/categories to create or rename
akt coa sync                 # apply (create + rename; idempotent)
akt coa sync --prune         # also DISABLE accounts/categories absent from the config

# code a transaction by GL account — akt auto-fills the mirror category:
akt payment create --type expense --bank 1 --amount 120 --account 628

--account takes a GL code or name (the double-entry account; see --bank for the bank/cash account). An explicit --set de_account_id= still wins.

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** — `contacts` and `documents` need `search=type:<x>` on
  *every* verb or the API returns `403 necessary access rights`.
* **Doubled totals** — Akaunting recomputes a document's total from its line
  items and *adds* it to the `amount` you send. `akt` always sends `amount: 0`
  so the server-computed total is authoritative.
* **Item `description`** — line items need a `description` key even when empty,
  or creation 500s with `Undefined array key "description"`.
* **Updates wipe items** — a document update deletes and recreates all line
  items from the request. `akt` resends 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 flat `transactions` endpoint rejects it.
  The same applies to *updating* a document-linked payment (e.g. attaching a
  file to it) — `akt` picks the nested route automatically.
* **Multipart uploads** — attachments switch the request from JSON to
  `multipart/form-data` with the `attachment[]` field; updates are sent as
  `POST` + `_method=PATCH` because PHP won't populate `$_FILES` on a real `PUT`.
* **Attachment download isn't on `/api`** — Akaunting only serves attachment
  bytes from the session-authenticated web route `/{company}/uploads/{id}/download`.
  `akt download-attachment` transparently logs in a web session with your admin
  credentials (reused for the process) to fetch the file; metadata (id, name,
  size) comes from the `/api` record itself.
* **Full-replace updates** — Akaunting PUT re-validates required fields, so
  `akt` merges your changes onto the current record.
* **Journal entries must balance** — a `journal-entry` needs >= 2 lines whose
  debits equal its credits; `akt` validates this client-side (clear error)
  before hitting the API. Each line carries both a `debit` and a `credit` key
  (the unused side sent as `0`) because Akaunting validates both as required.
* **Journal updates re-derive ledgers** — like documents, a journal update
  deletes any ledger line absent from the request. `akt` resends the existing
  lines (with their ledger ids) so an update that only changes a field doesn't
  wipe the entry, and auto-generates the `journal_number` from the module's
  `double-entry.journal.number_*` settings when you don't pass one.
* **Chart-of-accounts CRUD is web-only** — the Double-Entry module exposes
  accounts read-only on `/api`; create/update/delete exist solely on the
  session/CSRF web route. `akt account create|update|delete` logs in a
  web session (reusing your admin credentials, cached for the process), attaches
  the CSRF token, and unwraps Akaunting's `{success, error, data, message}` AJAX
  envelope — so a server-side block (e.g. *deleting an account that has ledgers*)
  surfaces as a normal error. Updates resend `name` (required by Akaunting on
  update) from the current record when you don't pass one.

### 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.

## Contributing

Developing, testing, or releasing `akt`? See [CONTRIBUTING.md](CONTRIBUTING.md)
for the test suite, CI/CD, release process, and a map of the source files.

## License

[MIT](LICENSE) © AsyncAlchemist

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

akt_cli-0.4.1.tar.gz (34.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

akt_cli-0.4.1-py3-none-any.whl (39.1 kB view details)

Uploaded Python 3

File details

Details for the file akt_cli-0.4.1.tar.gz.

File metadata

  • Download URL: akt_cli-0.4.1.tar.gz
  • Upload date:
  • Size: 34.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for akt_cli-0.4.1.tar.gz
Algorithm Hash digest
SHA256 4ed78e2dd742c6f297561ca4aa9a9c31620e3c01e3718e20296cdf4d4bd7e800
MD5 b4868e1b35a076372cd752b0f340d14c
BLAKE2b-256 abfee01630ae183139e91fce3615f811bfe063e92f48f9d4206111596ba7ac45

See more details on using hashes here.

Provenance

The following attestation bundles were made for akt_cli-0.4.1.tar.gz:

Publisher: publish.yml on AsyncAlchemist/akt-cli

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file akt_cli-0.4.1-py3-none-any.whl.

File metadata

  • Download URL: akt_cli-0.4.1-py3-none-any.whl
  • Upload date:
  • Size: 39.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for akt_cli-0.4.1-py3-none-any.whl
Algorithm Hash digest
SHA256 4341b89d161ff24785599b42b472cd1b887f4e1c632d7e066b244c230c19e2ba
MD5 a204d48274fb026a80022d91790f340a
BLAKE2b-256 f6b7088a7bb79ee28cefbab4d133d255533afc623ab94bf5b558e2be0a0d25c1

See more details on using hashes here.

Provenance

The following attestation bundles were made for akt_cli-0.4.1-py3-none-any.whl:

Publisher: publish.yml on AsyncAlchemist/akt-cli

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.13.0

2 files

0.12.1

2 files

0.12.0

2 files

0.11.0

2 files

0.10.1

2 files

0.10.0

2 files

0.9.2

2 files

0.9.1

2 files

0.9.0

2 files

0.8.2

2 files

0.8.1

2 files

0.8.0

2 files

0.7.0

2 files

0.6.0

2 files

0.5.0

2 files

This release

0.4.1 This release

2 files

0.4.0

2 files

0.3.0

2 files

0.2.0

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page