Skip to content

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 with enabledProducts = [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

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:

http
POST /admin/clients
Authorization: Bearer <admin-jwt>

{
  "clientCode": "TESTBK",
  "legalName": "Testbook Pte Ltd",
  "timezone": "Asia/Singapore",
  "defaultScheduleDay": 20
}

1.2 Enable bookkeeping

http
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

http
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

http
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:

CodeNameTypeKeywords
1000Cash at BankASSET
2000Accounts PayableLIABILITY
6200UtilitiesEXPENSEelectricity, sp group, singtel
6400Software SubscriptionsEXPENSEgithub, aws, slack, figma
2200GST PayableTAX_PAYABLEiras 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.platform set
  • bookkeepingConfig.baseCurrency set
  • materialityThresholdSgd set

Without all five, the ingestion pipeline will refuse to draft entries — they will land in UNCLASSIFIED with a banner.

2. Verification

Database

sql
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

EventDetails
client.createdactor = admin@spade.local
client.enabled_products.changedfrom [] to [BOOKKEEPING]
client.bookkeeping_config.setplatform = XERO etc.
chart_of_accounts.row.createdone per CoA entry
vendor_master.row.createdone per vendor

3. Negative & edge cases

  • Duplicate clientCode → 409 Conflict.
  • enabledProducts = [] after a previous enable → 400 unless force flag is set; the Client row keeps a soft-disable.
  • platform not in [QUICKBOOKS, XERO, ZOHO_BOOKS, TALLY] → 400 with the supported set.
  • materialityThresholdSgd < 0 → 400.
  • CoA accountCode collision 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.

Internal use only — BreezyCorp