B-01 · Onboard a new bookkeeping client (cold path)
SOP:
SOP_AI_Bookkeeping_Automation.md§2 (Client master) + §4 (Phase 1 setup)Actors: Platform Admin (admin@spade.local). Pre-state: Empty system (or seed not yet run for ZENITH). Post-state: A new client withenabledProducts = [BOOKKEEPING], populated chart of accounts + at least one vendor master, an active accounting platform mapping, and reviewer wired up.
This is the cold-path onboarding flow. For the warm path (using the seeded ZENITH client), skip to B-03 · Ingest a document.
0. Prerequisites
- Environment up per
_shared/00-environment-setup.md. - Logged in as
admin@spade.local(PLATFORM_ADMIN).
1. Steps
1.1 Create the client
Web: /dashboard/clients/new → fill in:
- Code:
TESTBK - Legal name:
Testbook Pte Ltd - Timezone:
Asia/Singapore - Default schedule day: any value (bookkeeping does not yet auto-trigger on schedule day, but the field is required)
Or via API:
POST /admin/clients
Authorization: Bearer <admin-jwt>
{
"clientCode": "TESTBK",
"legalName": "Testbook Pte Ltd",
"timezone": "Asia/Singapore",
"defaultScheduleDay": 20
}1.2 Enable bookkeeping
PATCH /admin/clients/<clientId>
Authorization: Bearer <admin-jwt>
{
"enabledProducts": ["BOOKKEEPING"],
"bookkeepingConfig": {
"platform": "XERO",
"baseCurrency": "SGD",
"reviewerEmail": "reviewer@spade.sg"
},
"materialityThresholdSgd": "100.00"
}The client detail page now shows a Bookkeeping tab with empty sections (Chart of accounts, Vendors, Batches, Reconciliations, Channels).
1.3 Add at least two contacts
POST /admin/clients/<clientId>/contacts
{ "name": "Mei Lin", "email": "mei@testbk.sg", "canSubmit": true, "canApprove": false }
POST /admin/clients/<clientId>/contacts
{ "name": "Raj Kumar", "email": "raj@testbk.sg", "canSubmit": false, "canApprove": true }Bookkeeping uses the same ClientContact table as payroll. The submitter contact owns the unreconciled-items portal; the approver contact owns any future approval surfaces (currently reviewer-only — see B-13 §3).
1.4 Auth policy
PUT /admin/clients/<clientId>/auth-policy
{ "authMode": "MAGIC_LINK", "otpRequired": false, "approverPolicy": "ANY_CONTACT" }1.5 Seed the chart of accounts
Open /dashboard/clients/<clientId>/bookkeeping/chart-of-accounts (or use the API). At minimum:
| Code | Name | Type | Keywords |
|---|---|---|---|
| 1000 | Cash at Bank | ASSET | — |
| 2000 | Accounts Payable | LIABILITY | — |
| 6200 | Utilities | EXPENSE | electricity, sp group, singtel |
| 6400 | Software Subscriptions | EXPENSE | github, aws, slack, figma |
| 2200 | GST Payable | TAX_PAYABLE | iras gst |
Use the same shape as the seed (packages/db/prisma/seed.ts:1099-1146).
1.6 Seed at least one vendor master
SP Group → 6200 (aliases: sp services, singapore power)Vendor master is the priority-1 classification branch — without at least one entry every ingested document falls through to keyword matching or UNCLASSIFIED.
1.7 Verify the readiness gauge
/dashboard/clients/<clientId> should now show the Bookkeeping card as green. Required green-ticks:
- ✅ At least one CoA entry
- ✅ At least one vendor master
- ✅
bookkeepingConfig.platformset - ✅
bookkeepingConfig.baseCurrencyset - ✅
materialityThresholdSgdset
Without all five, the ingestion pipeline will refuse to draft entries — they will land in UNCLASSIFIED with a banner.
2. Verification
Database
SELECT enabled_products, bookkeeping_config, materiality_threshold_sgd
FROM clients WHERE id = '<clientId>';
-- enabled_products = '{BOOKKEEPING}'
SELECT COUNT(*) FROM chart_of_accounts_entries WHERE client_id = '<clientId>';
SELECT COUNT(*) FROM vendor_masters WHERE client_id = '<clientId>';Audit log
| Event | Details |
|---|---|
client.created | actor = admin@spade.local |
client.enabled_products.changed | from [] to [BOOKKEEPING] |
client.bookkeeping_config.set | platform = XERO etc. |
chart_of_accounts.row.created | one per CoA entry |
vendor_master.row.created | one per vendor |
3. Negative & edge cases
- Duplicate
clientCode→ 409 Conflict. enabledProducts = []after a previous enable → 400 unless force flag is set; theClientrow keeps a soft-disable.platformnot in[QUICKBOOKS, XERO, ZOHO_BOOKS, TALLY]→ 400 with the supported set.materialityThresholdSgd< 0 → 400.- CoA
accountCodecollision within client → 409 (per-client unique).
Next
Proceed to B-02 · Configure ingestion channels (recommended for the full flow) or jump to B-03 · Ingest a document — manual upload to start exercising the pipeline.