Skip to content

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

MethodPathNotes
GET/receiptsList with filters: approval_status, reconciliation_status, submitted_by, fiscal_period
POST/receiptsCreate (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

MethodPathNotes
GET/invoicesList with filter document_type=issued or received
POST/invoicesCreate
GET/invoices/{id}Detail
PATCH/invoices/{id}Update / re-submit
DELETE/invoices/{id}Delete
POST/invoices/{id}/sendMark issued invoice as sent
POST/invoices/bulk-actionBulk delete / send (issued invoices only)
POST/invoices/extract-pdfOCR extract from a PDF without saving

Approvals

MethodPathNotes
GET/approvals/rulesList approval rules
POST/approvals/rulesCreate rule (admin)
PATCH/approvals/rules/{id}Update rule (admin)
DELETE/approvals/rules/{id}Delete rule (admin)
POST/approvals/rules/previewPreview which rule applies for a candidate document
GET/approvals/pending-for-meDocuments pending the caller's approval (`?document_type=receipt
POST/approvals/actionsApprove / reject / return — payload: {document_type, document_id, action, step, comment, added_steps} where action ∈ {approved, rejected, returned}
GET/approvals/eventsApproval & match events (legacy)
GET/approvals/audit-logsAudit trail

Reconciliation

MethodPathNotes
GET/matches/candidatesAI-scored candidate pairs for a transaction
POST/matchesExecute 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-differenceSuggest reasons for amount mismatches

Butler Chat (Advisor)

MethodPathNotes
GET/advisor/greetingLogin greeting
GET/advisor/historyChat history
POST/advisor/chatSend message; returns {response: string} with embedded [[TOOL:...]] markup
POST/advisor/tools/pending_listRead tool — pending documents
POST/advisor/tools/document_detailRead tool — document detail
POST/advisor/tools/search_clientRead tool — find client by name
POST/advisor/tools/approval_statusRead tool — approval status
POST/advisor/tools/budget_comparisonRead tool — budget vs actual
POST/advisor/tools/read_fileRead tool — Gemini OCR analysis
POST/advisor/tools/suggest_journal_entrySuggest tool
POST/advisor/tools/suggest_reconciliationSuggest tool
POST/advisor/tools/draft_expense_claimDraft tool
POST/advisor/tools/draft_invoiceDraft tool
POST/advisor/tools/submit_expense_claimExec tool — requires confirmed: true
POST/advisor/tools/send_invoiceExec tool
POST/advisor/tools/approve_documentExec tool
POST/advisor/tools/execute_reconciliationExec tool
POST/advisor/tools/export_csvExec tool — returns download_url
POST/advisor/tools/export_zenginExec tool
POST/advisor/tools/notify_tax_advisorExec tool

Tool URLs use the bare action name (e.g. approve_document). The Python function names use the exec_ prefix internally (e.g. exec_approve_document) but the URL does not.

Butler Law Chat

MethodPathNotes
POST/api/chatNon-streaming Q&A; returns {response, session_id, source_refs}. Used by Butler Tax escalation
POST/api/chat/streamSSE streaming Q&A used by the Butler Law frontend (see events below)
GET/api/sessionsChat sessions for the authenticated user
GET/api/sessions/{id}/messagesPaginated session history (20/page)
GET/api/agentsAgents visible to the caller (see authorization below)
GET/api/agents/{id}Agent detail; 404 outside the caller's domains
GET/api/knowledge/lawsLaw list; subscribers see only laws mapped to their domains' agents
GET/api/knowledge/ntaNTA Tax Answer list; requires the tax_law domain
GET/api/knowledge/tsutatsuBasic-circular list (search / series filter); requires tax_law (#158)
GET/api/knowledge/tsutatsu/{id}/viewCircular content as HTML (iframe viewer)
GET/api/knowledge/shitsugiQ&A case list (search / category filter); requires tax_law (#159)
GET/api/knowledge/shitsugi/{id}/viewQ&A case as HTML (two-pane viewer; 関係法令通達 open in the right pane)
GET/api/knowledge/revisionsStatute revision feed — changes detected by the daily e-Gov sync, newest first (#160; input for the regulatory-change proposals, #161)
GET/api/internal/revisionsService-to-service revision feed (X-Internal-Service-Token); polled daily by the platform law-watch (#161)
GET/api/laws/{id}/rawLaw 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_01tax_law, labor_01labor_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):

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

MethodPathNotes
POST/ledger/journal-entriesBook 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-entriesList (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-balancesReplace opening balances for a fiscal start date. Must net to zero (debit-positive signed amounts); BS accounts only
GET/ledger/opening-balancesList for a fiscal start date
POST/ledger/auto-journal/sweepGenerate 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/accountsThe 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-startsDistinct opening-balance fiscal start dates (descending) — populates the fiscal-period select on the reports page
GET/ledger/periodsMonthly close states
POST/ledger/periods/close / /reopenClose / 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)

MethodPathDescription
PATCH/receipts/{id}/asset-flagManual 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-flagSame for received invoices (manual only per design v1.1)
GET/fixed-assets/mastersCategories (rates joined) + thresholds + the corporate's accounting settings (SME election, fiscal start) for the registration UI
GET/fixed-assets/candidatesApproved + 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/manualDirect 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-registerValidate 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-assetsRegister 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_amount required). The schedule is cut off at the event month and re-saved as version+1 (history kept, old rows is_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). Stores disposal_book_value for the manual loss/gain entry; the disposal/sale journal itself is manual in v1 | | GET | /fixed-assets/journal/preview | Per-asset depreciation totals for period_from..period_to (current schedule rows × business-use ratio), resolved 減価償却費 / 減価償却累計額 codes, and already_booked flags | | POST | /fixed-assets/journal/generate | Create per-asset draft entries Dr. 減価償却費 / Cr. 減価償却累計額 dated period_to, source_type=depreciation, idempotent source_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.

MethodPathNotes
GET / POST/account-customizationsCorporate 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-customizationsTax-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).

MethodPathNotes
POST/payroll/importImport 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/summariesMonthly summaries (aggregate totals only — no personal data)
GET/payroll/{year_month}/summaryOne month's summary
GET/payroll/{year_month}/detailsPer-employee detail (PII — strict gate)
DELETE/payroll/{year_month}Remove a month's details + summary + its draft journal

Exports

MethodPathNotes
GET/exports/csvExport CSV
GET/exports/journal-csvConfirmed 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/zenginExport Zengin payment file

PII (Firestore-backed)

MethodPathNotes
GET / POST / PATCH / DELETE/company-profiles[/{id}]Company profiles (PII)
PATCH/company-profiles/{id}/accountingCorporate 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-defaultAtomic 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-namesAdd display-name pattern (uses Firestore ArrayUnion)
DELETE/clients/{id}/bank-display-names/{pattern}Remove display-name pattern

Permission Settings

MethodPathNotes
GET/permission-settingsList all feature → min_role overrides
PUT/permission-settingsReplace 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).

MethodPathNotes
GET/studio-admin/tax-rate-masterList tax rates. Query: `kind=main
GET/studio-admin/tax-rate-master/{id}Get a single tax rate
POST/studio-admin/tax-rate-masterCreate. 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}/archiveSoft-archive (is_active=false)
PATCH/studio-admin/tax-rate-master/{id}/restoreRestore. 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:

MethodPathDescription
GET/studio-admin/law-watch/rulesWatch rules (law ↔ master/config mapping)
POST/studio-admin/law-watch/rulesCreate rule (target_kind: tax_rate_master requires target_id, or manual)
PATCH/studio-admin/law-watch/rules/{id}/activeEnable / disable a rule
GET/studio-admin/law-watch/proposalsUpdate proposals (?status=pending etc.)
POST/studio-admin/law-watch/checkRun the revision check now (same as the daily 6:00 JST job)
POST/studio-admin/law-watch/proposals/{id}/decideapply (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}/reopenUndo 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.

MethodPathDescription
GET/studio-admin/account-masterAccount list (?major=, ?statement=BS|PL, include_inactive defaults true)
POST/studio-admin/account-masterCreate 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-masterService-to-service read (X-Internal-Service-Token); active accounts only
GET/studio-admin/asset-useful-livesAsset 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/ratesDepreciation-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/thresholdsSmall-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-livesService-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

MethodPathDescription
GET/studio-admin/email-templates/settingsCurrent FROM address (master or env fallback) + SendGrid verified-sender list
PUT/studio-admin/email-templates/settingsUpdate the FROM address; rejected unless it is a SendGrid-verified sender (audited as email_settings.update_from)

Butler Series — Saikoku Studio