Skip to content

B-02 · Configure ingestion channels ​

SOP: SOP_AI_Bookkeeping_Automation.md §3 (Email / WhatsApp / Folder)Actors: Platform Admin (admin@spade.local). Pre-state: A bookkeeping-enabled client exists (B-01 or seeded ZENITH). Post-state: One or more ClientIngestionChannel rows enabled for the client. The folder-sync worker picks up enabled cloud-folder channels on its next tick.

The seeded ZENITH client already has two channels (WHATSAPP + GOOGLE_DRIVE) — use this flow to add, edit, or disable channels.

0. Prerequisites ​

  • A bookkeeping-enabled client.
  • API logged in as admin@spade.local.

1. Steps ​

1.1 List existing channels ​

Web: /dashboard/clients/<clientId>/ingestion-channels. The seed prints the URL.

Or via API:

http
GET /admin/clients/<clientId>/ingestion-channels

ZENITH should return two channels:

  • WHATSAPP — Zenith Ops WhatsApp — +6591234567
  • GOOGLE_DRIVE — Zenith Shared Drive — Bookkeeping — /Shared drives/Zenith/Bookkeeping/Inbox

Both enabled: true. lastPolledAt and lastError columns are empty (the seed does not run the poller).

1.2 Add a new channel ​

http
POST /admin/clients/<clientId>/ingestion-channels

{
  "channelType": "SHAREPOINT",
  "label": "Testbook SharePoint Inbox",
  "enabled": true,
  "configJson": {
    "siteId": "<sharepoint-site-id>",
    "driveId": "<drive-id>",
    "folderPath": "/Documents/Bookkeeping/Inbox"
  },
  "credentialsRef": "secretref:testbk/sharepoint"
}

Channel types currently supported: WHATSAPP, GOOGLE_DRIVE, SHAREPOINT, DROPBOX. The Add channel dialog shows the full enum.

configJson is per-type:

TypeRequired keys
WHATSAPPphoneNumberId, displayNumber
GOOGLE_DRIVEdriveId, folderPath
SHAREPOINTsiteId, driveId, folderPath
DROPBOXaccountId, folderPath

credentialsRef is a placeholder string the worker resolves against the secret manager. In dev with a mock secret manager this is informational.

1.3 Edit a channel ​

http
PUT /admin/clients/<clientId>/ingestion-channels/<channelId>

{
  "label": "Updated label",
  "enabled": false,
  "configJson": { ... }
}

Toggling enabled: false is the soft-delete: the worker skips polling for this channel. The row stays in the table for audit.

1.4 Hard-delete a channel ​

http
DELETE /admin/clients/<clientId>/ingestion-channels/<channelId>

Only allowed if the channel has not produced any BookkeepingDocument rows yet. Otherwise the row stays and the API returns 409 with the document count.

1.5 Verify the worker picks it up ​

bash
pnpm --filter @breezycorp/worker dev

bookkeeping.folder-sync runs on a configurable interval (default 5 min). On each tick it iterates enabled GOOGLE_DRIVE / SHAREPOINT / DROPBOX channels (WhatsApp is webhook-only, no polling) and:

  • Fetches new docs since lastCursor.
  • For each new file, enqueues bookkeeping.ingest-document.
  • Updates lastCursor and lastPolledAt.

Watch worker logs for bookkeeping.folder-sync.poll channelId=<id>.

2. Verification ​

Database ​

sql
SELECT id, channel_type, label, enabled, last_polled_at, last_error
  FROM client_ingestion_channels WHERE client_id = '<clientId>';

Web UI ​

/dashboard/clients/<clientId>/ingestion-channels lists every channel with toggle states and last-polled stamp.

Audit log ​

client.ingestion_channel.created  channelType=SHAREPOINT
client.ingestion_channel.updated  field=enabled
client.ingestion_channel.deleted  channelId=<id>

3. Negative & edge cases ​

  • Empty configJson → 400 Bad Request "configJson must not be empty".
  • Wrong shape for configJson (e.g. WHATSAPP missing phoneNumberId) → 400 with the missing keys.
  • Hard-delete with documents present → 409 with documentCount.
  • Multiple WHATSAPP channels for the same client — allowed, but the inbound webhook (POST /hooks/whatsapp) routes by phoneNumberId, so each must have a distinct ID.
  • Disabled channel + new file — folder-sync skips it. The webhook for WHATSAPP still accepts inbound but the handler short-circuits when the channel is disabled.

Next ​

Proceed to B-03 · Ingest a document — manual upload for the simplest happy-path or B-04 · Ingest via channels for the WhatsApp / Drive paths.

Internal use only — BreezyCorp