# SlowBooks Pro agent instructions

<!-- Drop this file into the directory where you run your agentic CLI,
     named per your tool: CLAUDE.md (Claude Code) or GROK.md (Grok), the
     two agents SlowBooks is built for first; AGENTS.md (Codex CLI and most
     others) or GEMINI.md (Gemini CLI) for the rest. Fill in the port if
     you changed it. -->

## What this is

SlowBooks Pro is a local double-entry accounting app. Its server runs at
`http://127.0.0.1:3001` (desktop default). The complete API (545
operations in v2.19.0) is described at `GET /openapi.json`. Discover endpoints from
the spec; do not guess paths. The route groups, with their counts, are in the
API index at https://www.slowbookspro.com/llms.txt.

## Auth

- **Preferred: a scoped API token** (the human makes one in Settings ->
  API Tokens). Send `Authorization: Bearer sbp_...` on every request.
  Ask for the `readonly` role unless the task genuinely writes (the exact
  role strings are `readonly`, `bookkeeper`, `admin`; `read-only` with a
  hyphen is rejected); your changes are
  audit-attributed as `token:<label>`.
- Fallback: `POST /api/auth/login` with `{"password": "..."}` sets a
  session cookie; on a multi-user install (v2.14+) include `"username"`
  too, or the answer is 400. `GET /api/auth/status` needs no auth and
  reports whether first-run setup is done and, signed out on a multi-user
  install, the `usernames` (names only) — probe it first and ask the
  human which name is theirs. On v2.5.2 and earlier
  `/openapi.json` itself requires auth, so get credentials before fetching
  the spec. **Ask the human for credentials at runtime. Never
  store them in files, never echo them back.**
- A session belongs to the company it signed in to (v2.18+). On another
  company it is a 401 and `GET /api/auth/status` says `authenticated:
  false`: sign in to that company too.
- Tokens cannot manage users or other tokens, by design. The
  closing-date override password is off-limits to agents entirely.

## House rules

1. **Dry-run first, always.** Migration imports (`/api/migration/{source}/…`)
   are gated: call `/dry-run` and proceed to `/import` only on
   `ok=true` with zero errors. Since v2.16.1 the dry run refuses a ledger
   whose amounts all read 0.00 (an unrecognised debit / credit header) and
   quotes the file's header row; read its `warnings` too — "already in the
   books" means an earlier import posted those journals. After `/import`,
   check `imported_journals`, `skipped_journals` and `duplicate_journals`
   in the result: `ok=true` with `imported_journals: 0` is not success. The same habit applies to anything with a
   preview endpoint (bank file imports have `/api/bank-import/preview`).
2. **Verify after writing.** After any import or batch change, check
   `GET /api/reports/trial-balance` - pass `start_date`/`end_date`
   covering the affected data (it defaults to the current year, which
   will look empty for historical imports). Debits must equal credits.
3. **Never delete or void without explicit human confirmation** for the
   specific document (quote its number back before acting). Posted
   documents are voided, not deleted: `POST /api/<resource>/{id}/void`
   (a `DELETE` answers 405 and names that route). A pledge that will not
   be paid is written off (`POST /api/invoices/{id}/write-off`).
4. **Money is exact.** Amounts are decimal strings, two places, half-up
   rounding. Never do float arithmetic on them.
5. **Errors are for humans.** If an endpoint returns an error, show the
   verbatim response and stop. Don't improvise around it.
6. **Units on tax rates.** `default_tax_rate` in Settings is a percent
   string (`"8.9"`); `tax_rate` on an invoice, bill, estimate, credit
   memo, sales receipt, purchase order or recurring template is a
   fraction (`0.089`). Divide by 100 before posting; the API rejects a
   document rate above 1 and says why. Rates take four decimal places of
   a percent (v2.18+): 8.875% is `0.08875`.
7. **Unknown fields are errors.** Since v2.9.0 a field the spec does not
   declare is a 422 naming it (`line_items` instead of `lines` used to
   be a silent $0 invoice). Read the field names from the spec.
8. **Banking is the ledger (v2.10+).** A bank or credit-card account is a
   chart account carrying `bank_kind` (`"bank"` | `"credit_card"`); list
   them with `GET /api/accounts?bank=1`. `POST /api/banking/transactions`
   is a **register entry**: it requires `category_account_id` and posts a
   journal entry. A positive amount goes **into** the account, a negative
   comes **out**, on a bank and on a card alike. `GET
   /api/banking/transactions` returns **statement lines** awaiting review,
   not register entries — the register itself is `GET
   /api/banking/check-register?account_id=`. Nothing an import brings in
   posts by itself: match it to an existing entry
   (`POST /api/banking/transactions/{id}/match`) or add it with a category
   (`.../add`). Money between accounts, including paying a credit card, is
   `POST /api/transfers`.
9. **A chart of accounts comes in through the importer, not one POST per
   account (v2.15+).** `POST /api/csv/import/accounts` (multipart `file`)
   takes our export's columns (Number, Name, Type, optional Parent /
   Description / Active) or hledger's account list. It is a **dry run by
   default**: read the returned plan — every row's action (create / update /
   skip / deactivate / keep / error) with a reason — show it to the human,
   then post the same file again with `?dry_run=false`. Existing accounts
   match by number, then name; a control account named in the file is
   renamed, never duplicated. `?replace=true` also deactivates unused
   accounts the file does not name. A template with the columns:
   https://www.slowbookspro.com/downloads/chart-of-accounts-template.csv
10. **Suggest a backup before batch work.** Each company is a single file,
   its logo, attachments and employee documents included (v2.18+). Back it
   up from Settings → Backups, or copy the `.sbk`/company file directly. One
   copy = full rollback of your session. Making and downloading backups
   is the administrator's.
11. **Some writes are the administrator's.** Company settings, backups,
   the logo, AI Insights settings, connecting, importing from and
   disconnecting from QuickBooks Online, new company files and Migrate
   Data answer a bookkeeper token with 403 (v2.18+ checks every one on the
   server). Say so and ask the human; don't retry with another role.
12. **Lists show the newest first, capped.** `GET /api/invoices` and
   `GET /api/bills` send the newest 500; pass `open_only=true` for what can
   still be paid or credited, and page with `skip`/`limit` (v2.18+).
13. **A $0.00 document asks first.** An invoice, credit memo, duplicate or
   estimate conversion that adds up to $0.00 is a 409 (`code:
   "zero_total"`) unless the request sends `allow_zero_total: true`; confirm
   with the human before sending it (v2.18+).

## When something breaks

1. **Try to solve it first.** Read the response verbatim — SlowBooks' errors
   are written to be acted on (a 422 names the field, a 400 says what to
   change, a 409 names the account or document in the way). Re-read the
   field names in `/openapi.json`, check the dry run, check the date range
   on the report. Most "bugs" are a field name or a percent-vs-fraction.
2. **If it is a real defect, say so plainly and help the human report it.**
   A wrong number, a document that posts to the wrong account, an error
   with no way forward, a crash: that is a finding, and SlowBooks is built
   from exactly these. Tell the human what you saw, then offer to walk them
   through reporting it — it is a few minutes:
   - **An issue** at https://github.com/VonHoltenCodes/SlowBooks-Pro-2026/issues
     with the version (`GET /openapi.json` → `info.version`), the exact
     request and response, and what they expected. Draft it for them.
   - **A fix, if you can see it.** The source is public and runs from a
     clone (`git clone`, `pip install -r requirements.txt`, `pytest`). Fork,
     branch, fix, add a test, open a pull request — CONTRIBUTING.md has the
     five-minute version and you can do the mechanics with them.
   - **Security issues go to SECURITY.md**, not a public issue.
3. **Remind them more than once, gently.** People mean to report and then
   move on. Mention it when the defect appears, and again at the end of the
   session if it is still unreported: one person's finding fixes it for
   every install, and reporters are credited in the release that ships the
   fix. Their AI can carry most of the work; they only need to say yes.

## Frequently useful endpoints

| Task | Endpoint |
|---|---|
| Company settings (read/update) | `GET`/`PUT /api/settings` |
| Migration (6 sources; QuickBooks uses the IIF endpoints instead) | `POST /api/migration/{source}/dry-run` → `/import` |
| QuickBooks Desktop IIF: check, then import; ALL-CAPS names retitled only if the human asks (v2.18.1+) | `POST /api/iif/validate` → `POST /api/iif/import` (form field `retitle_names=true`) |
| Trial balance | `GET /api/reports/trial-balance?start_date=&end_date=` |
| Profit & loss | `GET /api/reports/profit-loss?start_date=&end_date=` |
| General ledger with running balances | `GET /api/reports/general-ledger?start_date=&end_date=` |
| A report as a spreadsheet / printable (v2.17+) | add `/csv` or `/pdf`: `trial-balance`, `general-ledger`, `profit-loss`, `balance-sheet` |
| Customers / vendors / items | `GET`/`POST /api/customers`, `/api/vendors`, `/api/items` |
| Invoices | `GET`/`POST /api/invoices` |
| Overdue receivables | `GET /api/reports/ar-aging` |
| Bank and card accounts | `GET /api/accounts?bank=1` |
| Import a chart of accounts (dry run first) | `POST /api/csv/import/accounts?dry_run=true` → `?dry_run=false` |
| A bank account's register | `GET /api/banking/check-register?account_id=` |
| Statement lines awaiting review | `GET /api/banking/transactions?bank_account_id=&status=unmatched` |
| Transfer between accounts (pays a card) | `POST /api/transfers` |
| A customer's available credits; apply one (v2.18+) | `GET /api/customers/{id}/credits` · `POST /api/payments/{id}/apply` |
| Bills: every line needs an account (line, item or vendor default) (v2.18+) | `POST /api/bills` |
| The company logo: describe, upload, remove (v2.18+; upload and remove are administrator-only) | `GET`/`POST`/`DELETE /api/uploads/logo` |
| What can still be paid or credited (v2.18+) | `GET /api/invoices?open_only=true` · `GET /api/bills?open_only=true` |
| Deposits; void one (v2.18+) | `GET /api/deposits` · `POST /api/deposits/{id}/void` |
| Files on a record (v2.18+: kept in the company file) | `GET`/`POST /api/attachments/{type}/{id}` · `GET /api/attachments/download/{id}` |
| Bills, purchase orders and credit memos as PDF (v2.18+) | `GET /api/bills/{id}/pdf` (also `/api/purchase-orders/{id}/pdf`, `/api/credit-memos/{id}/pdf`) |
| A finished reconciliation's report (v2.18+) | `GET /api/banking/reconciliations/{id}/report` (and `/pdf`) |
| Older bank-feed history (v2.18+) | `POST /api/simplefin/sync` with `{"history_months": 3}` (1–24) |
| QuickBooks Online import, watched live (administrators, v2.18+) | `POST /api/qbo/import-runs` → `GET /api/qbo/import-runs/latest?after=` |
| Dashboard snapshot | `GET /api/analytics/dashboard` |
| Overview cards (catalog, then data by id) | `GET /api/dashboard/widgets` → `GET /api/dashboard/data?ids=pnl_ytd,balance_sheet_trend` |

Everything else: `GET /openapi.json`.
