API Reference
Butler Tax Base URL: http://localhost:8001/api/v1
Authentication
All endpoints require either a butler_session cookie or a Firebase JWT in the Authorization header:
Authorization: Bearer <firebase_jwt>In development, the mock tokens test-token (corporate) and tax-test-token (tax firm) are accepted when ENVIRONMENT=development.
Tax Firm Impersonation
Append ?as_corporate={firebase_uid} to any endpoint to act on behalf of a client company. See Authentication & Database.
Documents
Receipts
| Method | Path | Notes |
|---|---|---|
| GET | /receipts | List with filters: approval_status, reconciliation_status, submitted_by, fiscal_period |
| POST | /receipts | Create (saved as pending_approval by default) |
| GET | /receipts/{id} | Detail |
| PATCH | /receipts/{id} | Update (also used to re-submit rejected receipts → approval_status: 'pending_approval') |
| DELETE | /receipts/{id} | Delete (own drafts only, or admin) |
Invoices
| Method | Path | Notes |
|---|---|---|
| GET | /invoices | List with filter document_type=issued or received |
| POST | /invoices | Create |
| GET | /invoices/{id} | Detail |
| PATCH | /invoices/{id} | Update / re-submit |
| DELETE | /invoices/{id} | Delete |
| POST | /invoices/{id}/send | Mark issued invoice as sent |
| POST | /invoices/bulk-action | Bulk delete / send (issued invoices only) |
| POST | /invoices/extract-pdf | OCR extract from a PDF without saving |
Approvals
| Method | Path | Notes |
|---|---|---|
| GET | /approvals/rules | List approval rules |
| POST | /approvals/rules | Create rule (admin) |
| PATCH | /approvals/rules/{id} | Update rule (admin) |
| DELETE | /approvals/rules/{id} | Delete rule (admin) |
| POST | /approvals/rules/preview | Preview which rule applies for a candidate document |
| GET | /approvals/pending-for-me | Documents pending the caller's approval (`?document_type=receipt |
| POST | /approvals/actions | Approve / reject / return — payload: {document_type, document_id, action, step, comment, added_steps} where action ∈ {approved, rejected, returned} |
| GET | /approvals/events | Approval & match events (legacy) |
| GET | /approvals/audit-logs | Audit trail |
Reconciliation
| Method | Path | Notes |
|---|---|---|
| GET | /matches/candidates | AI-scored candidate pairs for a transaction |
| POST | /matches | Execute reconciliation. match_type: "reconciliation" with empty document_ids is direct journalization (#180): a document-less transaction (loan, interest, tax debit, deposit) is classified by account_subject (+ optional tax_division, memo) and a draft journal entry is generated (inbound: Dr 普通預金 / Cr account; outbound: Dr account / Cr 普通預金). auto_expense matches created at bank import (fees/interest/social-insurance rules) and ATM cash transfer matches are journalized the same way; own-account transfers are not |
| DELETE | /matches/{id} | Undo |
| POST | /matches/analyze-difference | Suggest reasons for amount mismatches |
Butler Chat (Advisor)
| Method | Path | Notes |
|---|---|---|
| GET | /advisor/greeting | Login greeting |
| GET | /advisor/history | Chat history |
| POST | /advisor/chat | Send message; returns {response: string} with embedded [[TOOL:...]] markup |
| POST | /advisor/tools/pending_list | Read tool — pending documents |
| POST | /advisor/tools/document_detail | Read tool — document detail |
| POST | /advisor/tools/search_client | Read tool — find client by name |
| POST | /advisor/tools/approval_status | Read tool — approval status |
| POST | /advisor/tools/budget_comparison | Read tool — budget vs actual |
| POST | /advisor/tools/read_file | Read tool — Gemini OCR analysis |
| POST | /advisor/tools/suggest_journal_entry | Suggest tool |
| POST | /advisor/tools/suggest_reconciliation | Suggest tool |
| POST | /advisor/tools/draft_expense_claim | Draft tool |
| POST | /advisor/tools/draft_invoice | Draft tool |
| POST | /advisor/tools/submit_expense_claim | Exec tool — requires confirmed: true |
| POST | /advisor/tools/send_invoice | Exec tool |
| POST | /advisor/tools/approve_document | Exec tool |
| POST | /advisor/tools/execute_reconciliation | Exec tool |
| POST | /advisor/tools/export_csv | Exec tool — returns download_url |
| POST | /advisor/tools/export_zengin | Exec tool |
| POST | /advisor/tools/notify_tax_advisor | Exec tool |
Tool URLs use the bare action name (e.g.
approve_document). The Python function names use theexec_prefix internally (e.g.exec_approve_document) but the URL does not.
Butler Law Chat
| Method | Path | Notes |
|---|---|---|
| POST | /api/chat | Non-streaming Q&A; returns {response, session_id, source_refs}. Used by Butler Tax escalation |
| POST | /api/chat/stream | SSE streaming Q&A used by the Butler Law frontend (see events below) |
| GET | /api/sessions | Chat sessions for the authenticated user |
| GET | /api/sessions/{id}/messages | Paginated session history (20/page) |
| GET | /api/agents | Agents visible to the caller (see authorization below) |
| GET | /api/agents/{id} | Agent detail; 404 outside the caller's domains |
| GET | /api/knowledge/laws | Law list; subscribers see only laws mapped to their domains' agents |
| GET | /api/knowledge/nta | NTA Tax Answer list; requires the tax_law domain |
| GET | /api/knowledge/tsutatsu | Basic-circular list (search / series filter); requires tax_law (#158) |
| GET | /api/knowledge/tsutatsu/{id}/view | Circular content as HTML (iframe viewer) |
| GET | /api/knowledge/shitsugi | Q&A case list (search / category filter); requires tax_law (#159) |
| GET | /api/knowledge/shitsugi/{id}/view | Q&A case as HTML (two-pane viewer; 関係法令通達 open in the right pane) |
| GET | /api/knowledge/revisions | Statute revision feed — changes detected by the daily e-Gov sync, newest first (#160; input for the regulatory-change proposals, #161) |
| GET | /api/internal/revisions | Service-to-service revision feed (X-Internal-Service-Token); polled daily by the platform law-watch (#161) |
| GET | /api/laws/{id}/raw | Law full text (public e-Gov data; any subscriber or admin) |
Read-endpoint authorization (require_law_read): a caller passes if they are a studio admin (sees everything) or a Butler Law subscriber (/auth/verify returns non-empty available_domains; agents and knowledge are filtered to those domains — tax_01 ↔ tax_law, labor_01 ↔ labor_law). Management operations (sync triggers, agent CRUD, library mappings) remain studio-admin only.
SSE events on /api/chat/stream (auth, credit consumption and the 10 req/min rate limit are identical to /api/chat; pre-stream failures return normal HTTP errors):
| Event | Data | Meaning |
|---|---|---|
token | {"text": string} | Answer fragment (markdown) |
done | {"session_id", "source_refs"} | Stream complete; citation badges resolve here |
error | {"message": string} | Fatal error after the stream started |
Answers are markdown. Law-article quotes use a blockquote whose first line is 【条文】<law name> <article> (or 【FAQ】No.xxxx <title>), which the frontend renders as a collapsible citation card.
Ledger / Financial statements (#175)
The reporting backbone: every statement is derived from journal_entries (confirmed only) + opening_balances — there is no report-only data store. All endpoints require role accounting or above.
| Method | Path | Notes |
|---|---|---|
| POST | /ledger/journal-entries | Book an entry. Multi-line, debit total must equal credit total (422 otherwise); account codes validated against the effective account set; rejected if the month is closed. source_id acts as an idempotency key (duplicate → 409) — used by the closing-entries page (#181) |
| GET | /ledger/journal-entries | List (filters: period_start, source_type, status) |
| PATCH | /ledger/journal-entries/{id} | Draft only — confirm (status: confirmed) or edit. Confirmed entries are immutable (409; book a reversing entry) |
| DELETE | /ledger/journal-entries/{id} | Draft only (409 for confirmed) |
| PUT | /ledger/opening-balances | Replace opening balances for a fiscal start date. Must net to zero (debit-positive signed amounts); BS accounts only |
| GET | /ledger/opening-balances | List for a fiscal start date |
| POST | /ledger/auto-journal/sweep | Generate draft journal entries from approved documents and confirmed matches (#175 S5). Idempotent — already-generated sources (unique source_type+source_id) are skipped. Also runs automatically after approval finalization, auto-approved invoice creation, the recurring-invoice batch, and match creation |
| GET | /ledger/accounts | The corporate's effective account set (#175 S6): platform standard + advising tax firm template + corporate customs, each row tagged with source (standard / tax_firm / corporate). Populates account selects and the chart-of-accounts page |
| GET | /ledger/fiscal-starts | Distinct opening-balance fiscal start dates (descending) — populates the fiscal-period select on the reports page |
| GET | /ledger/periods | Monthly close states |
| POST | /ledger/periods/close / /reopen | Close / reopen a month (closed months reject bookings) |
| GET | /ledger/reports/trial-balance | 残高試算表 — carry-forward / period debit / credit / closing per account, balanced invariant flag |
| GET | /ledger/reports/profit-loss | 損益計算書 — waterfall totals (gross profit → operating → ordinary → pretax → net income) |
| GET | /ledger/reports/balance-sheet | 貸借対照表 — sections by major/minor, 当期純損益 in equity, balanced = assets == liabilities + equity + net income |
| GET | /ledger/reports/financial-statements | 決算報告書一式 (#190): balance_sheet + profit_loss + 株主資本等変動計算書(equity_statement, ending ties to BS equity-with-income) + 個別注記表(notes: 会計方針 from #186 tax method / depreciation / SME). fiscal_start_date + as_of. Frontend renders a print-styled 決算報告書 tab (print→PDF) |
| GET | /ledger/reports/consumption-tax | 消費税集計表 (#188) — confirmed journal lines with a tax_category grouped by (type, rate); base/tax normalized per the entry's tax_accounting_method snapshot (or the corporate default). Returns per-rate sales/purchase rows (base, tax, count), totals, and net_tax (output − input). Reference figures — the filing recomputes from the floored taxable base × rate |
| GET | /ledger/reports/general-ledger | 総勘定元帳 (one account) — running balance, counter account (諸口 for compound entries), source traceability |
Fixed assets — candidate list & bulk registration (#184 D3)
| Method | Path | Description |
|---|---|---|
| PATCH | /receipts/{id}/asset-flag | Manual fixed-asset candidate flag (accounting+). Clears is_asset_auto (manual override of the OCR suggestion). issued receipts 400; already-registered documents 409. The generic PATCH silently ignores is_asset / fixed_asset_id |
| PATCH | /invoices/{id}/asset-flag | Same for received invoices (manual only per design v1.1) |
| GET | /fixed-assets/masters | Categories (rates joined) + thresholds + the corporate's accounting settings (SME election, fiscal start) for the registration UI |
| GET | /fixed-assets/candidates | Approved + flagged + unregistered receipts/received invoices, with a server-side classification suggestion (expense / small_special / bulk / normal) and allowed_classes — the user may override within them (e.g. ¥100k–200k: immediate / lump-sum / normal; an out-of-range choice is a row error) |
| POST | /fixed-assets/manual | Direct registration of an existing asset (migration / pre-adoption ownership, source_type=manual, no document). Past acquisition dates reproduce the schedule from acquisition; entries are only generated for the closing period, so past depreciation (already in opening balances) is never double-booked. Same classification/validation as bulk-register minus the capex requirement |
| POST | /fixed-assets/bulk-register | Validate all rows, then write all rows (no partial success — row errors come back in a 200 body as row_errors and nothing is written). Re-classifies server-side; rejects small-amount expenses; capex choice required at/above the lump-sum threshold; used assets get the simplified useful life; creates fixed_assets + depreciation_schedules v1 (monthly rows, FY sums = annual tax limits) and links fixed_asset_id on the source document. Unique (corporate_id, source_type, source_id) prevents double registration |
| GET | /fixed-assets | Register list with to-date accumulated depreciation and book value (current schedule rows up to the current month) |
Asset-flagged documents are not expensed by the auto-journal sweep (#187); registering a document-sourced asset books an acquisition draft (借)asset account /(貸)未払金 (現金 for cash receipts, 買掛金 for received invoices), tax-aware (税抜 splits 仮払消費税). Manual (existing-asset) registrations book none (opening balances). Cancel removes the acquisition draft; confirmed entries block cancel. | GET |
/fixed-assets/{id}/schedule| Current-version monthly schedule for one asset | | DELETE |/fixed-assets/{id}| Cancel a (mis)registration: deletes all schedule versions and draft depreciation entries, logically deletes the asset, returns the source document to the candidates list for re-registration. 409 when confirmed depreciation entries exist (book a reversal first) | | POST |/fixed-assets/{id}/dispose| Record disposal (kind: disposal) or sale (kind: sale,sale_amountrequired). The schedule is cut off at the event month and re-saved as version+1 (history kept, old rowsis_current=false). Guards: 409 if the event month is ledger-closed, or if later closed months still carry current schedule rows (retroactive disposal — reopen first). Storesdisposal_book_valuefor the manual loss/gain entry; the disposal/sale journal itself is manual in v1 | | GET |/fixed-assets/journal/preview| Per-asset depreciation totals forperiod_from..period_to(current schedule rows × business-use ratio), resolved 減価償却費 / 減価償却累計額 codes, andalready_bookedflags | | POST |/fixed-assets/journal/generate| Create per-asset draft entries Dr. 減価償却費 / Cr. 減価償却累計額 datedperiod_to,source_type=depreciation, idempotentsource_id=depreciation:<asset>:<from>:<to>(re-runs skip). 422 when the two accounts are missing from the effective set; 409 when the entry month is closed |
The OCR pipeline (/ocr/classify-and-extract) now returns is_asset_suggestion (Gemini judges item + amount: durable goods at/above the small-asset threshold); the receipt-create payload carries is_asset / is_asset_auto.
Account customizations — 3-layer chart-of-accounts governance (#175 S6)
Layers (lowest priority first): platform account_master (Studio Admin) → tax-firm template → corporate customs. Classification (major / minor / normal_side) and code are immutable at every layer — deactivate and recreate on mistakes. Codes are never reused; code/name conflicts are rejected against the whole effective set at creation.
| Method | Path | Notes |
|---|---|---|
| GET / POST | /account-customizations | Corporate customs (role accounting+). POST validates conflicts against the corporate's effective set |
| PATCH | /account-customizations/{id} | Name / default tax type / is_active only |
| DELETE | /account-customizations/{id} | Hard delete only when unused by journal entries and opening balances (409 otherwise — deactivate instead) |
| GET / POST | /tax-firm-account-customizations | Tax-firm template (firm account only). Distributed to every advised corporate's effective set |
| PATCH | /tax-firm-account-customizations/{id} | Name / default tax type / is_active. No hard delete at the firm layer (cannot check usage across all clients) |
Payroll — journalization with a fully separated datastore (#178)
Payroll data (per-employee details and monthly totals) lives in a dedicated payroll MongoDB, fully isolated from accounting business data (PAYROLL_MONGODB_URI / PAYROLL_DB_NAME). Butler Tax does not calculate payroll (Butler Labor domain) — it ingests results and journalizes them. The only accounting contact point is the payroll journal entry. Import is the primary path; manual entry is a secondary path producing the same per-row records. Gated by the dedicated payroll feature permission — default admin only; corporates can lower it (e.g. to accounting) via permission settings. The UI lives under 財務管理 → 帳簿・決算 → 給与 (CSV import + inline manual rows, monthly summaries, per-employee detail modal).
| Method | Path | Notes |
|---|---|---|
| POST | /payroll/import | Import a month (CSV/manual share this). Per employee: salary_category (regular/officer/casual/bonus → 給料手当/役員報酬/雑給/賞与), gross, withholding_tax, resident_tax, social_insurance_employee, other_deductions, net_pay, social_insurance_employer. Net must equal gross − deductions (422). Re-import replaces the month. Generates a draft payroll journal in the main DB (source_type=payroll): Dr 給料手当 / 法定福利費 Cr 預り金 / 普通預金, totals only, dated month-end. Re-import rebuilds the draft; a confirmed entry is left untouched. Returns the summary + journal status |
| GET | /payroll/summaries | Monthly summaries (aggregate totals only — no personal data) |
| GET | /payroll/{year_month}/summary | One month's summary |
| GET | /payroll/{year_month}/details | Per-employee detail (PII — strict gate) |
| DELETE | /payroll/{year_month} | Remove a month's details + summary + its draft journal |
Exports
| Method | Path | Notes |
|---|---|---|
| GET | /exports/csv | Export CSV |
| GET | /exports/journal-csv | Confirmed journal_entries → 弥生取込形式 CSV (#189): 伝票番号/日付/借方科目/借方金額/借方税区分/貸方科目/貸方金額/貸方税区分/摘要. Codes resolved to names; compound entries share 伝票番号 (諸口). period_from/period_to (YYYY-MM-DD); include_drafts optional. Exports the actual ledger (manual/closing/depreciation/payroll/direct bank journals), unlike /exports/csv which is document-based |
| GET | /exports/zengin | Export Zengin payment file |
PII (Firestore-backed)
| Method | Path | Notes |
|---|---|---|
| GET / POST / PATCH / DELETE | /company-profiles[/{id}] | Company profiles (PII) |
| PATCH | /company-profiles/{id}/accounting | Corporate accounting settings: sme_special_treatment (SME immediate-expensing toggle, default true), fiscal_start_month (1–12, default 4), and tax_accounting_method (inclusive | exclusive, default inclusive; #186). Allowed on the default profile, like the numbering/tax-rate endpoints. Changing tax_accounting_method on the default profile is 409 when auto-journals already exist under a different method (a fiscal year cannot mix methods — clear drafts / reverse confirmed first). The setting is forward-looking; confirmed entries are never rewritten |
| GET / POST / PATCH / DELETE | /bank-accounts[/{id}] | Bank accounts (PII) |
| PATCH | /bank-accounts/{id}/set-default | Atomic default flip |
| GET / POST / PATCH / DELETE | /clients[/{id}] | Client master (PII) |
| GET | /clients/lookup-houjin?number=... | Japan corporate-number lookup proxy |
| POST | /clients/{id}/bank-display-names | Add display-name pattern (uses Firestore ArrayUnion) |
| DELETE | /clients/{id}/bank-display-names/{pattern} | Remove display-name pattern |
Permission Settings
| Method | Path | Notes |
|---|---|---|
| GET | /permission-settings | List all feature → min_role overrides |
| PUT | /permission-settings | Replace settings (admin) |
feature_key examples: client_management, journal_rule_settings, matching_rule_settings, report_view, budget_comparison_view, ai_chat_basic, ai_chat_accounting.
Platform — Studio Admin
These endpoints live on the Platform API (butler-platform/auth/backend) and require is_studio_admin = true on the caller's platform_accounts record.
Tax Rate Master (Issue #85)
Platform-wide tax-rate master, managed from Studio Admin. Consumed by butler-tax for invoice / receipt / journal tax-rate selection (rolled out in PR3 of #85).
| Method | Path | Notes |
|---|---|---|
| GET | /studio-admin/tax-rate-master | List tax rates. Query: `kind=main |
| GET | /studio-admin/tax-rate-master/{id} | Get a single tax rate |
| POST | /studio-admin/tax-rate-master | Create. Body: {rate, kind, label?}. 409 on active (rate, kind) duplicate |
| PATCH | /studio-admin/tax-rate-master/{id} | Update label / display_order / is_active. rate and kind are immutable |
| PATCH | /studio-admin/tax-rate-master/{id}/archive | Soft-archive (is_active=false) |
| PATCH | /studio-admin/tax-rate-master/{id}/restore | Restore. 409 if same (rate, kind) is already active |
kind values:
main— standard taxable rate (e.g. 10%)reduced— reduced taxable rate (e.g. 8%)exempt— non-taxable (e.g. 0%)
Storage: butler_platform.tax_rate_master MongoDB collection. The (rate, kind) uniqueness is enforced as a partial index that only applies when is_active = true, so an archived entry can coexist with a new active entry sharing the same (rate, kind).
Seed: cd butler-platform/auth/backend && uv run python -m scripts.seed_tax_rate_master (no-op if any record exists; creates the initial 10% main / 8% reduced / 0% exempt rows).
Studio Admin — Revision watch (#161)
Platform endpoints (require_studio_admin) connecting detected statute revisions to master/config update proposals:
| Method | Path | Description |
|---|---|---|
| GET | /studio-admin/law-watch/rules | Watch rules (law ↔ master/config mapping) |
| POST | /studio-admin/law-watch/rules | Create rule (target_kind: tax_rate_master requires target_id, or manual) |
| PATCH | /studio-admin/law-watch/rules/{id}/active | Enable / disable a rule |
| GET | /studio-admin/law-watch/proposals | Update proposals (?status=pending etc.) |
| POST | /studio-admin/law-watch/check | Run the revision check now (same as the daily 6:00 JST job) |
| POST | /studio-admin/law-watch/proposals/{id}/decide | apply (tax rate: archive old + create new + audit log; respects the immutable rate/kind rule), acknowledge, or dismiss with note |
| POST | /studio-admin/law-watch/proposals/{id}/reopen | Undo a mis-decision: returns dismissed/acknowledged proposals to pending (audited). applied cannot be reopened — revert via the tax rate master |
Studio Admin — Account master (#175)
Chart of accounts driving the financial-reporting backbone (journal → trial balance → BS/PL). Same governance as the tax rate master: values live in the master, never hardcoded in services.
| Method | Path | Description |
|---|---|---|
| GET | /studio-admin/account-master | Account list (?major=, ?statement=BS|PL, include_inactive defaults true) |
| POST | /studio-admin/account-master | Create account (code must be unique; reuse of an archived code is rejected) |
| PATCH | /studio-admin/account-master/{code} | Update name / default tax / display order / is_active. code/major/minor/normal_side are immutable |
| GET | /internal/account-master | Service-to-service read (X-Internal-Service-Token); active accounts only |
| GET | /studio-admin/asset-useful-lives | Asset categories (種別/細目 → statutory useful life, straight_line_only for tax-mandated SL assets). POST/PATCH for CRUD; (category, subcategory) unique |
| GET | /studio-admin/asset-useful-lives/rates | Depreciation-rate table seeded from the NTA official tables (別表第八 straight-line / 別表第十 200% DB / 別表第九 250% DB *_250 columns, lives 2–50: rate / revised rate / guaranteed rate). The 200%/250% switch is by acquisition date (250% = acquisitions 2007-04-01..2012-03-31, 200% = on/after 2012-04-01). PATCH /rates/{years} applies tax-accountant corrections; the seed is non-destructive (it only backfills *_250 fields that are physically absent) so corrections survive reseeding |
| GET | /studio-admin/asset-useful-lives/thresholds | Small-asset / SME special-measure amount thresholds (singleton; seeded ¥100k expense / ¥200k lump-sum / ¥300k SME immediate / ¥8M annual cap + optional acquisition-date expiry). PATCH updates them when tax law changes; explicit null expiry disables the date check |
| GET | /internal/asset-useful-lives | Service-to-service read; active categories joined with their rates (+ /rates for the full table, + /thresholds for the depreciation engine's classification branches — returns 503 until seeded) |
Studio Admin — Email settings
| Method | Path | Description |
|---|---|---|
| GET | /studio-admin/email-templates/settings | Current FROM address (master or env fallback) + SendGrid verified-sender list |
| PUT | /studio-admin/email-templates/settings | Update the FROM address; rejected unless it is a SendGrid-verified sender (audited as email_settings.update_from) |
