API Server Operations
Operations guide for deploying and managing the WyzBooks REST API server. Covers architecture, deployment, server management, and troubleshooting.
Overview
The WyzBooks API is an Express.js HTTP server that provides REST access to the same accounting data used by the desktop Electron app. It supports real-time synchronization between multiple clients via Server-Sent Events (SSE).
- Framework — Express 4.x (must stay on 4.x; 5.x has ESM-only deps)
- Runtime — Node.js 16+
- Default port — 3141
- Bind address — 127.0.0.1 (localhost only)
- Process owner —
wyzbooksuser (never root) - Process manager — PM2 with systemd boot persistence (
pm2-wyzbooksservice) - Auth — API key via
X-API-Keyheader (required in multi-tenant mode) - Data format — Double-encoded JSON file
- Dependencies —
express,xlsx
Quick Reference
# Local development
npm run api # Start API on localhost:3141
# Deploy code changes to server
npm run deploy:api # rsync + npm install + restart
# Server management (SSH as wyzbooks user — never use root)
ssh wyzbooks@web3
pm2 status wyzbooks-api # Check if running
pm2 restart wyzbooks-api # Restart after code changes
pm2 logs wyzbooks-api # View logs
pm2 stop wyzbooks-api # Stop
pm2 start wyzbooks-api # Start
pm2 save # Save state for boot persistence
# Systemd (boot persistence — managed by pm2-wyzbooks service)
systemctl status pm2-wyzbooks
journalctl -u pm2-wyzbooks -f
Architecture
The server is deployed to /opt/wyzbooks-api/ with the following structure:
- src/api/server.js — Express app and startup
- src/api/openapi.js — OpenAPI 3.0 spec
- src/api/middleware/ — DataLayer (JSON file I/O, locking, sync versioning), ApiError class, SSE client registry
- src/api/routes/ — 18 route files: accounts, audit, bank-txns, bills, console, contacts, data, import, invoices, items, journals, messages, payments, reconcile, reports, sync, system, terms
- src/lib/ — Shared accounting functions (reports, parsers, reconciliation) and merge utilities
- .env — Environment variables (port, API key, data dir)
Data Storage
- Data directory —
/var/lib/wyzbooks/(default). Containswyzbooks-data.json(double-encoded JSON). - Config/logs —
~/.wyzbooks/containswyzbooks.ini,audit.log(1 MB rotation), andaudit-import.log(1 MB rotation). - Double encoding — The data file uses double-encoded JSON: the outer JSON object contains string values that are themselves JSON. Reading requires
JSON.parse()twice. This matches the Electron app's storage format.
Request Flow
Client Request
-> Express body parser (JSON 50MB / text 50MB)
-> API key auth (if WYZBOOKS_API_KEY set)
-> DataLayer injected as req.dl
-> Route handler
-> Response (JSON)
-> Error handler (if thrown)
Real-Time Sync (SSE)
- Connection — Clients connect via
GET /sync/streamwithX-Client-Idheader. Server broadcasts to all clients except the sender. - Reconnection — Reconnecting clients use
Last-Event-IDheader for automatic catch-up of missed events. - Heartbeat — 30-second heartbeat keeps connections alive through proxies.
- Events —
POST /sync/pushtriggersdata-changedevents.POST /messages/sendtriggerstext-messageevents.
Environment Variables
| Variable | Default | Description |
|---|---|---|
WYZBOOKS_API_PORT |
3141 |
Listen port |
WYZBOOKS_API_KEY |
(none) | API key for auth. If set, all /api/* requests require X-API-Key header |
WYZBOOKS_DATA_DIR |
~/Library/Application Support/WyzBooks/data (macOS) |
Data file directory |
HOME |
system default | Used for ~/.wyzbooks/ config and audit logs |
On the server, these are typically set in /opt/wyzbooks-api/.env (loaded by systemd EnvironmentFile or pm2 ecosystem file).
First-Time Server Setup
# 1. Deploy files to server
./scripts/cloud-sync/deploy.sh user@myserver.com
# 2. SSH in and run setup
ssh user@myserver.com
cd /opt/wyzbooks-api
sudo bash scripts/cloud-sync/setup-server.sh \
--api-key "$(openssl rand -hex 32)" \
--api-port 3141 \
--data-dir /var/lib/wyzbooks \
--user wyzbooks
The setup script installs Node.js 18+ if missing, creates a wyzbooks system user (no-login), creates the data directory with correct ownership, writes the .env file with API key and port, runs npm install --omit=dev, configures systemd or pm2, and optionally configures Apache reverse proxy.
Deploying Code Changes
npm run deploy:api
This single command rsyncs src/api/, src/lib/, and package.json to the server, runs npm install --omit=dev on the server, and restarts the API via pm2 restart wyzbooks-api.
When to deploy: After any change to files under src/api/, src/lib/, or package.json. Changes to src/app.jsx, src/main.js, src/preload.js, or src/cloud-sync.js are client-side only and do not require a server deploy.
Server Management
- Start (pm2) —
pm2 start src/api/server.js --name wyzbooks-api - Start (systemd) —
sudo systemctl start wyzbooks-api - Stop —
pm2 stop wyzbooks-apiorsudo systemctl stop wyzbooks-api - Restart —
pm2 restart wyzbooks-apiorsudo systemctl restart wyzbooks-api. A restart is required after deploying code changes — Node.js loads modules once at startup. - Logs (pm2) —
pm2 logs wyzbooks-apiorpm2 logs wyzbooks-api --lines 200 - Logs (systemd) —
sudo journalctl -u wyzbooks-api -f(follow) orsudo journalctl -u wyzbooks-api --since today
Health Check
curl http://127.0.0.1:3141/api/v1/system/health
Returns { ok: true, data: { version, collections: { accounts, contacts, ... } } }.
Check connected clients:
curl -H "X-API-Key: YOUR_KEY" http://127.0.0.1:3141/api/v1/sync/status
Returns { ok: true, data: { syncVersion, connectedClients, clientIds: [...] } }.
Data Layer
- File locking — Atomic
mkdir(NAS-safe). Lock path:{dataFile}.lock/directory. 10 retries, 50ms between attempts. Stale locks older than 10 seconds are auto-removed. HTTP 423DATA_LOCKEDif lock cannot be acquired. - Sync versioning —
_syncVersionis a monotonic counter incremented on each push._revis a per-record revision number._deletionsis an array of tombstones (max 1000). Conflict detection returns HTTP 409 if client'sbaseVersion< server's_syncVersion. - Audit logging — Data changes:
~/.wyzbooks/audit.log. Import operations:~/.wyzbooks/audit-import.log. Both rotate at 1 MB. Format:[ISO-timestamp] ACTION entity detail.
Apache Reverse Proxy
The API binds to localhost only. Apache provides TLS termination and external access.
# Required modules
sudo a2enmod proxy proxy_http headers
# Add to existing vhost
<Location /api/>
ProxyPass http://127.0.0.1:3141/api/
ProxyPassReverse http://127.0.0.1:3141/api/
SetEnv proxy-sendchunked 1
SetEnv proxy-initial-not-pooled 1
RequestHeader set X-Forwarded-Proto "https"
</Location>
Critical SSE settings: proxy-sendchunked and proxy-initial-not-pooled must be set, and ProxyTimeout should be 3600 (default 60s is too short for SSE). Without these, SSE connections buffer and fail.
Security
- Localhost only — The API never accepts connections from external IPs directly.
- API key auth — When
WYZBOOKS_API_KEYis set, all requests requireX-API-Keyheader. Without it, any local process can access the API. - TLS — Handled by Apache/Nginx, not by the API server.
- Stateless — No session state. Every request is independently authenticated.
- Key storage — Plain text in
.envfile,wyzbooks.ini, and app preferences. Treat as a password.
Multi-Tenant Mode & Admin Console
The API server supports multi-tenant isolation. When ~/.wyzbooks/tenants.json exists, each API key maps to a separate tenant with its own data directory.
- Admin Console — Navigate to
/consoleon your server (e.g.https://wyzbooks.com/console). Login with your admin key (Enter key submits). The console supports light and dark themes (toggle in header), configurable auto-refresh interval, and show/hide password toggle on the login form. - Tenant Detail — Click a tenant name to expand a detail panel showing: sync version, data checksum, deletion count, collection record counts, the 30 most recent change stack entries with version badges, and connected SSE clients with IP address and User-Agent.
- Client Management — The tenant detail panel shows all connected SSE clients. Each client displays its client ID, IP address, and User-Agent string. A Disconnect button lets you forcibly close a specific client's SSE connection.
- Key Management — Change the admin key from the console header menu. Set or regenerate individual tenant API keys from the tenant detail panel. Logout button in the header.
- Purge Stack — Resets a tenant's sync version to 0, strips all
_revstamps, and clears deletions. After purging, the next client to connect should use Push First or Pull First to re-establish a baseline. - Tenant CRUD — Add, update, and delete tenants. Regenerate API keys. Migrate legacy data from the default data directory to a tenant's directory.
wbctl — CLI Management Tool
wbctl is a Perl CLI tool for managing the WyzBooks API server from the command line. It works locally via HTTP or remotely via SSH.
# Install (copy to a directory in your PATH)
cp scripts/cloud-sync/wbctl /usr/local/bin/
cp scripts/cloud-sync/wbctl.conf.sample ~/.wbctl.conf
# Configure
export WYZBOOKS_URL=https://wyzbooks.com
export WYZBOOKS_ADMIN_KEY=your-admin-key
export WYZBOOKS_SSH=user@myserver.com
- Server control —
wbctl start,stop,restart,status,logs— manage the API process via pm2 or systemd over SSH. - Health & sync —
wbctl health,sync,changes,clients— check server health, sync status, recent changes, and connected SSE clients. - Tenant management —
wbctl tenants,tenant-add,tenant-update,tenant-delete,tenant-migrate— full CRUD for multi-tenant deployments. - Data operations —
wbctl export,purge,stats,audit— export tenant data, purge sync stack, view collection stats, and read audit logs. - Key management —
wbctl set-key,regen-key,admin-key— change tenant API keys or the admin key. - Deployment —
wbctl deploy,setup— rsync code to server or run initial server setup.
Run wbctl help for the full command list, or wbctl <command> --help for usage details. Configuration can be stored in ~/.wbctl.conf or set via environment variables.
Troubleshooting
- API won't start — Check if port is in use (
lsof -i :3141), verify Node.js 18+ (node --version), check dependencies (cd /opt/wyzbooks-api && npm ls), and verify.envfile. - SSE connections drop — Verify Apache has
proxy-sendchunkedandproxy-initial-not-pooledset. VerifyProxyTimeout 3600. Checkpm2 logsfor reconnection messages. - Data file locked — Stale locks auto-recover after 10 seconds. Manual recovery:
rmdir /var/lib/wyzbooks/wyzbooks-data.json.lock - Permission errors — Fix ownership:
sudo chown -R wyzbooks:wyzbooks /var/lib/wyzbooksand/opt/wyzbooks-api. For audit logs:sudo mkdir -p ~wyzbooks/.wyzbooks && sudo chown wyzbooks:wyzbooks ~wyzbooks/.wyzbooks
Dependencies
| Package | Version | Purpose |
|---|---|---|
express |
^4.22.1 | HTTP framework (must stay on 4.x) |
xlsx |
^0.18.5 | Excel export for reports |
No other runtime dependencies. Node.js built-in modules (http, https, fs, path, crypto, os) are used for everything else.