Working with Claude

Describe the work.
Claude does the bookkeeping.

Klaai gives Claude a real set of double-entry books to write into, and the rules to keep them straight. You review what it did, in plain English, before anything is final.

Start free Download the Claude skill

Every entry Claude records lands as pending review. Nothing posts behind your back.

Three ways people actually use it

Not demos. The situations customers turn up with.

Catching up

“I’m eighteen months behind and the filing deadline is real.”

A shoebox of PDFs, a year of bank exports, and no idea where to start. Point Claude at the folder. It reads each document, works out what it is, and writes the entries — flagging anything it can’t decide instead of guessing.

  • Reads statements, receipts and invoices straight from a folder
  • Opens the right accounts as it goes
  • Leaves a to-do wherever a human has to make the call
  • Every entry balanced, dated, and attributed to Claude
chat

YouHere’s ~/Books/2025 — bank exports, receipts, the lot. Can you get 2025 caught up?

ClaudeFound 412 documents. Reading them now.

Claude2025 is still open, so I can post to it. Working through the bank export first, so the receipts have something to match against.

Claude380 filed. 32 I’m not sure about — I’ve left them for you rather than guessing:

□ high    $4,200 “NW Holdings” — capital or rent?
□ normal  14 personal-card charges — reimburse?
□ low     3 receipts with no readable total

YouNW Holdings is rent. Skip the personal card.

ClaudeDone. FY2025 balances — debits $486,220.14, credits $486,220.14. Trial balance and GST/HST summary are ready to review.

Getting your evenings back

“I lose a night a week to this, and I still don’t trust the numbers.”

Forward a receipt to your books’ email address and carry on. Claude reads it, files it, and asks when something is genuinely ambiguous. You approve a week’s worth in the time it used to take to categorise one afternoon.

  • Forward from your phone — Claude picks it up from the inbox
  • Ask questions in plain English instead of building reports
  • Invoices, payments and sales tax handled as you go
  • Your accountant gets books that already reconcile
chat

YouAnything waiting on me?

ClaudeSix receipts came into the inbox this week. I’ve filed five:

R-1043  Staples        $242.39  Office Supplies
R-1044  Corner Bistro   $72.32  Meals & Ent.
R-1045  to savings   $2,500.00  Cash → Cash
R-1046  Adobe           $90.39  Software
R-1047  45 King      $2,034.00  Rent

ClaudeOne I couldn’t call — the Corner Bistro lunch. Meals & Entertainment, or billable back to Northbound Media?

YouBillable. And how are we doing this month?

ClaudeRe-filed against Northbound. July so far: $27,232 cash, $9,718 owed to you, $1,452 HST set aside. Two invoices are past due — want me to draft reminders?

For bookkeepers

“Twelve clients, the same month-end twelve times.”

Every client is a separate tenant with its own books, chart and permissions. Claude switches between them from the command line, so a routine that runs month-end for one client runs it for all of them — and reports back on the exceptions only.

  • One login, many client companies, strictly separated
  • Scheduled routines instead of a manual checklist
  • You get an exception list, not twelve clean reports to read
  • Give each client Read Only access to their own books
month-end routine

YouRun month-end for every client and tell me what needs me.

Claude12 client companies. Processing each inbox, reconciling, then a trial balance on each.

Maple & Main        ✓ 14 filed   ✓ balances
Northwind Studio    ✓  6 filed   ✓ balances
Harbourview Dental  ✓ 22 filed   ⚠ 1 to review
Pacific Rim Coffee  ✓  9 filed   ✓ balances
… 8 more                          ✓ balances

ClaudeEleven are clean. One needs you:

Harbourview Dental
  □ $8,400 equipment purchase — expense or capitalise
    and depreciate? Invoice attached.

YouCapitalise it, five year straight line.

ClaudePosted to Equipment with the depreciation schedule. All twelve balance. GST/HST summaries exported.

Why it can be trusted with your books

The controls are the product. The AI is how you drive it.

Nothing posts silently

Everything Claude records arrives as pending review, attributed to Claude, with the source document attached. You approve it or you don’t.

Real double entry

The server refuses an entry that doesn’t balance — not the AI, the books themselves. Debits equal credits to the cent or nothing is written.

It asks instead of guessing

When a document is genuinely ambiguous, Claude leaves a to-do explaining the choice rather than picking one and moving on.

Closed years stay closed

Close a year and it’s read-only — for you and for Claude. Reopening is deliberate and logged.

Roles that hold

Administrator, Accountant, Read Only — enforced on the server, so Claude has exactly the access the person driving it has.

Your books leave with you

Full export to plain SQL any time. No lock-in, no proprietary format, no negotiating to get your own data back.

Get the Klaai skill

The skill teaches Claude to keep proper books — balanced entries, the right accounts, GST/HST handled correctly. Download it and unzip it into ~/.claude/skills/, or after signing up let Claude install it for you from AI Setup.

⬇ Download the skill View raw
Preview SKILL.md
---
name: klaai
description: Do bookkeeping in Klaai via its `klaai` CLI — record expenses and receipts, create and pay invoices, manage customers/vendors/accounts, process the receipts inbox, post journal entries, and run financial reports (balance sheet, income statement, GST/HST return, aging). Also loads a year of books from a spreadsheet, bank/credit-card export or statement. Use when the user asks to record a transaction, file a receipt, bill a customer, import or load their books/bookkeeping/financials from a file, reconcile a year, check cash/receivables/tax owing, or produce financial statements for a Canadian small business.
---

# Klaai

Klaai is double-entry bookkeeping for Canadian small business. You operate it
through the `klaai` CLI; the human reviews your work in the web app.

## The one thing to understand

**Every transaction is a balanced journal entry.** Documents (invoices,
receipts, payments) are the user-facing records that *generate* those entries:

```
invoice   →  Dr Accounts Receivable      Cr Revenue, Cr GST/HST Payable
payment   →  Dr Bank                     Cr Accounts Receivable
expense   →  Dr Expense, Dr ITC          Cr Bank / Credit Card / Accounts Payable
transfer  →  Dr destination              Cr source
```

You almost never post raw journal entries. Prefer the document command
(`invoices create`, `receipts create`, `invoices pay`) — it writes the document
*and* its entry, and keeps them in sync. Reach for `journal create` only for
things that aren't a document: opening balances, year-end adjustments,
corrections, interest, depreciation.

## Before you start

**First, make sure the `klaai` CLI is installed — if it's missing, install it
yourself. Never send the user to a terminal to do it.**

```bash
command -v klaai >/dev/null 2>&1 || npm install -g @buzzerboyinc/klaai-cli
klaai --version                # confirm it's on the PATH
```

`klaai` is an npm package and needs **Node 18+**. If `npm` isn't found either,
install Node first (from https://nodejs.org, or the user's package manager —
e.g. `brew install node`, `apt install nodejs npm`) and retry. If a global
install is blocked by permissions, install Node through a version manager
(nvm or fnm) so `npm install -g` works without sudo.

The CLI connects to **www.klaai.co** by default. For a local or self-hosted
server, point it there first: `klaai config set-url <url>`.

Then check you're signed in:

```bash
klaai login status --json      # confirm signed in and which company
```

If not signed in, **run the sign-in yourself**:

```bash
klaai login url --device
```

This opens a browser page and waits. Tell the user what to do in one sentence —
*"I've opened a sign-in page; click Approve and I'll carry on"* — and show them
the code it prints so they can check it matches the page. The command returns on
its own once they approve. It blocks for up to 10 minutes, so run it and wait
rather than polling yourself.

**Never** ask them to open a terminal, run a command, or paste a token into the
chat. `klaai login url` *without* `--device` stops and waits for a human to type
a code, so it will hang forever if you run it — that form is for people working
in a terminal themselves.

Then orient yourself. **Do this before recording anything**, because account IDs
are per-company and cannot be guessed:

```bash
klaai accounts list --json     # ids, codes, subtypes, `funding` flag
klaai customers list --json
klaai vendors list --json
```

Match by `code` or `name`, then use the `id`. Never invent an ID like
`acc_1000` — that is demo data, not this company's chart.

## Always use `--json`

Every command takes `--json` and returns the raw API response. Use it for
everything you parse. Reserve plain output for showing the user a result.

## Recording work

```bash
# expense paid by card or bank
klaai receipts create --description "Microsoft 365" --amount 7.99 --tax 1.04 \
  --category <expenseAccountId> --paid-from <cardOrBankAccountId> \
  --vendor <vendorId> --date 2025-01-09 --by claude --json

# money coming back from a vendor
klaai receipts refund --description "Microsoft 365 refund" --amount 13.85 --tax 1.80 \
  --category <expenseAccountId> --to <accountRefundedInto> --date 2025-06-02 --by claude --json

# a bill (owed, not yet paid): omit --paid-from → credits Accounts Payable
klaai receipts create --description "Rent — July" --amount 1800 --tax 234 \
  --category <rentAccountId> --date 2025-07-01 --by claude --json

# money moved between your own accounts (incl. paying a credit card)
klaai receipts create --description "Visa payment" --amount 1756.28 \
  --from <bankAccountId> --to <creditCardAccountId> --date 2025-01-02 --by claude --json

# invoice a customer, then record the money when it lands
klaai invoices create --customer <customerId> --date 2025-01-31 --terms net_15 \
  --tax 802.14 --line "Consulting — January|<revenueAccountId>|6170.27" \
  --notes "Source invoice 2024-118" --json
klaai invoices pay <invoiceId> --amount 6972.41 --account <bankAccountId> \
  --date 2025-02-14 --method e-transfer --json

# the inbox: documents the user forwarded, waiting to be filed
klaai inbox check                # pull new mail from the configured mailbox
klaai inbox list --json
klaai inbox process <emailId> --description "Bell — July" --amount 92.50 \
  --tax 12.03 --category <expenseAccountId> --paid-from <bankAccountId> --json
```

`inbox check` is the ONLY way mail is fetched — there is no scheduler, so run it
when the user asks whether anything new has arrived. It is safe to re-run:
messages already imported are skipped.

Pass **`--by claude`** on `receipts create` and `inbox process` so the web app's
review queue shows who recorded it. Anything you record arrives as
`pending_review` — that is correct and intended. Do not approve your own work
unless the user explicitly asks.

## Filing documents against a record

Statements, signed contracts, anything worth keeping. Attach to an **account**,
`invoice`, `receipt`, `vendor`, `customer` or `journal_entry`.

```bash
klaai attachments list --for account:acc_1000 --json
klaai attachments add file_85adac --for account:acc_1000 \
  --label "January 2026 statement" --period-start 2026-01-01 --period-end 2026-01-31
klaai attachments remove att_81b117
```

`add` takes a **file id that already exists** — typically one that arrived in the
inbox (`klaai inbox list --json` shows `file_id`). There is no upload command;
uploading is something the user does in the web app.

Set `--period-start`/`--period-end` on anything covering a period. A statement
without one is much harder to find later, and it's what makes "do we have all of
2026?" answerable.

`remove` deletes the underlying file too, unless another record still points at
it. Don't remove a document you didn't file.

## When you can't decide: leave a to-do

**Guessing puts a wrong number in someone's books. A to-do costs them ten
seconds.** When a document is genuinely ambiguous — you can't tell which account
it belongs to, whether a cost is billable, whether a charge is personal — record
the question instead of picking an answer.

```bash
# the common case: a receipt you can't confidently categorise
klaai todos create "R-1043 includes a \$400 chair — Office Supplies or a fixed asset?" \
  --link receipt:rcp_03 --priority high

klaai todos create "Confirm the home-office proration — I used 15%" --due 2026-08-31

klaai todos list --json                 # open items (default)
klaai todos list --status all --json    # includes resolved
klaai todos show tod_4f2a --json
klaai todos done tod_4f2a               # only when the user has actually decided
```

`--link <type:id>` points the item at what it's about, so the user can click
straight through to it: `receipt`, `invoice`, `vendor`, `customer`, `account` or
`journal_entry`. Use it whenever the question is about a specific record.
`--priority` is `low | normal | high` (default `normal`); reserve `high` for
things that change a filing or a total.

Items you create are attributed to you automatically — no `--by` flag. They show
up in the web app under **To do** with a badge, which is where the user clears
them.

**Still record the transaction.** A to-do is a question about work you have
already done, not a substitute for doing it. File the receipt with your best
guess *and* flag the uncertainty — never leave the books empty pending an answer.
The exception is when you cannot proceed at all (no funding account exists, say);
then say so directly rather than only leaving a to-do the user might not read.

Don't resolve your own to-dos. `todos done` is for after the user has told you
the answer.

## Rules that will bite you

1. **Amounts are tax-exclusive.** `--amount` is the subtotal; `--tax` is the tax
   in dollars, not a rate. A $226.00 receipt at 13% HST is
   `--amount 200 --tax 26`.
2. **Invoices are only paid when a payment is recorded.** Setting a status does
   not move cash. Use `invoices pay`.
3. **Never guess an account ID.** List accounts first (see above).
4. **Use a `funding` account for `--paid-from` / `--account`.** `klaai accounts
   list --json` marks these — it includes credit cards and lines of credit, not
   just banks.
5. **Journal entries must balance**, and the server enforces it. Debits = credits
   to the cent, or you get `Out of balance: …`.
6. **Optional fields take `null`, never `""`.** Omit the flag instead.
7. **Amounts may be messy text.** `$1,234.56`, `-$799.00` and `(65.00)` all
   parse. But a **negative amount is never a plain expense** — it's a refund
   (`receipts refund`) or a card payment (`receipts create --from/--to`). The
   CLI rejects negatives on `receipts create` and tells you which to use.
8. **Owner money in or out** goes to the *Due to / from Shareholder* account
   (`shareholder_loan`): the company owes them when it's a credit balance, they
   owe the company when it's a debit. Use `journal create`.
9. **Interest earned is revenue**, not a negative expense — post it to an
   `interest_income` account.
10. **Ambiguity is not your call to make.** If two accounts are both defensible,
    file it with your best guess and raise a to-do (see above). Silently
    choosing one is how a misclassification survives to the tax return.

## Fiscal years

**A year stays editable until it is explicitly closed** — the calendar rolling
over changes nothing. So importing prior-year history just works: the record's
**date** decides which year it lands in.

```bash
klaai years list                       # which years are open or closed
klaai years close 2025                 # make FY2025 read-only
klaai years reopen 2025                # let the accountant correct it
klaai reports trial-balance --fy 2025  # `--fy` is a read filter
```

Writing into a closed year returns *"Fiscal year 2025 is closed."* — tell the
user and ask before reopening; a closed year usually means it's been filed.

### HST method

Set **per fiscal year**: `quick` or `detailed`. This records how the books
account for HST — nothing more.

```bash
klaai years hst-method                 # every year, and where its method came from
klaai years hst-method 2026 quick      # use the Quick Method from 2026 on
klaai years hst-method 2026 --reset    # go back to inheriting
```

A year with no setting of its own **takes the most recent earlier year that has
one**, so a new fiscal year carries on however the last one was kept and this is
normally set once. A **closed year cannot be changed** — its books are read-only.

**Don't change it yourself.** An Administrator or Accountant decides which method
the books are on. If the figures look like the wrong one is set, raise a to-do
saying so rather than switching it.

## Checking the books

```bash
klaai dashboard --json                       # cash, A/R, tax owing, net income
klaai reports trial-balance --json           # debits must equal credits
klaai reports balance-sheet --json           # assets = liabilities + equity
klaai reports income-statement --json
klaai reports gst-hst --json                 # CRA lines 101/105/108/109
klaai reports gst-hst --from 2025-07-01 --to 2025-09-30 --json
klaai reports ar-aging --json                # who owes, by bucket
klaai accounts ledger <accountId> --json     # one account, running balance
```

After any batch of work, **verify the trial balance still balances** and report
the figure to the user. If `total_debit ≠ total_credit`, stop and investigate —
that is a bug, not a rounding artifact.

## Setting up a company's chart of accounts

Every company starts with a small standard chart. Add real bank, card and
investment accounts before importing:

```bash
klaai accounts subtypes --json     # the allowed --type values
klaai accounts create --code 1005 --name "Business Chequing" --type cash_chequing \
  --institution "Example Bank" --account-no 0000 --json
klaai accounts create --code 2210 --name "Business Visa" --type credit_card --json
klaai accounts create --code 1025 --name "GIC — 12 month" --type investment_gic --json
```

The **subtype decides the accounting type** — a `credit_card` is always a
liability. You never specify debits/credits when creating an account.

Five accounts are tagged `system_role` (`ar`, `ap`, `tax_collected`, `tax_paid`,
`default_bank`) and are used automatically when posting. Don't delete or retype
them.

## Errors

Messages are written to be read. Common ones:

| Message | What to do |
|---|---|
| `Out of balance: debits X ≠ credits Y` | Fix the postings; don't retry unchanged |
| `Fiscal year YYYY is closed` | Ask the user before `klaai years reopen YYYY` |
| `Amount must be positive` | It's a refund or a card payment — see rule 7 |
| `Can't read "…" as an amount` | Clean the value; don't guess at it |
| `That overpays the document…` | Check the outstanding balance first |
| `Can't delete … N posting(s) reference it` | Deliberate guard — archive instead |
| `Not signed in` / `token has expired` | Ask the user to run `klaai login url` |
| `No company selected` | `klaai companies list` then `klaai companies use <id>` |

Surface the message to the user rather than working around it. These guards
exist to protect the ledger.

## Working across companies

```bash
klaai companies list --json
klaai companies use <companyId>          # persists in ~/.klaai/config.json
klaai invoices list --company <id> --json  # or override per command
```

## Importing books from a file

If the user points you at a spreadsheet, export or statement — *"load my
books from this file"* — **read `references/bulk-import.md` first and follow
it**. Do not start posting transactions from a file without it.

The short version: **profile the file, present a plan, get one confirmation,
then load.** Never ask the user to describe their file up front — work it out
yourself and confirm your reading of it.

### Reading the file

Pick the tool from what's actually installed rather than assuming:

```bash
python3 -c "import openpyxl, sys; print(openpyxl.__version__)"   # .xlsx/.xlsm
python3 -c "import pandas; print(pandas.__version__)"            # optional
csvkit --version 2>/dev/null; ssconvert --version 2>/dev/null    # fallbacks
```

- **.xlsx / .xlsm** → `openpyxl` with `load_workbook(path, data_only=True)`.
  `data_only=True` is essential: it returns cached *values* instead of formula
  strings. If openpyxl is missing, install it (`pip3 install --user openpyxl`)
  or convert to CSV with LibreOffice/`ssconvert`.
- **.csv / .tsv** → read directly; sniff the delimiter and encoding.
- **.pdf statements** → extract text first; if the layout won't parse cleanly,
  say so rather than guessing at columns.

Warnings like *"Conditional Formatting extension is not supported"* are noise —
ignore them.

Read the file **once** into memory and work from that. Don't re-open it per row.

Point Claude at your books.

Solo is free, and your books export with you if it isn’t for you.

Curious how Claude connects to it?

Create your company