The multi-tenant model
Every client — including us — is a "tenant". A tenant is defined by a single block in /opt/portal/clients.json:
"acme-builders": {
"name": "Acme Builders",
"brand": { "brand_name": "Acme", "tagline": "Built for life." },
"cadence": 5, # posts kept in flight
"channels": ["bluesky","linkedin"], # their connected networks
"require_approval": true, # review before posting
"template_module": "portal_templates_wt",
"listings": "/opt/portal/listings/acme-builders.json"
}Golden ruleNever point a demo/test tenant at a real client's channels. Test tenants use the "dry" channel, which logs a post without sending it anywhere.
Client onboarding — step by step
Add a new client, start to finish
In plain EnglishA client is one entry in a config file plus a file of their verified facts. This walkthrough takes a fictional client “Acme Builders” from nothing to live, safely (a dry run first, then real posting).
Do it — step by step
- Discovery. Agree what they want posted, how often, on which networks, and whether they want to approve each post or run hands-off.
- Connect their accounts. Have them authorize their networks through the hub — follow Playbook 1 once per network. Their tokens are stored under their own account.
- Create their facts file on the hub — the verified things their posts can draw from (products, listings, services, wins). Model it on an existing one:
ssh root@64.227.29.192 nano /opt/portal/listings/acme-builders.json
- Add their tenant block to the config:
ssh root@64.227.29.192 nano /opt/portal/clients.json
Add an entry like:"acme-builders": {
"name": "Acme Builders",
"brand": { "brand_name": "Acme", "tagline": "Built for life." },
"cadence": 5,
"channels": ["dry"], # dry first — see step 5
"require_approval": true,
"template_module": "portal_templates_wt",
"listings": "/opt/portal/listings/acme-builders.json"
} - Dry run. With channels set to
["dry"], generate and review the drafts — nothing is sent anywhere:ssh root@64.227.29.192 python3 /opt/portal/generate.py
Then read them with Playbook 9. - Go live. When the drafts look right, change
"channels" to their real networks (e.g. ["bluesky","linkedin"]) and regenerate. With require_approval: true, posts now wait in their queue. - Give them their approval link (next section) — or approve on their behalf.
Under the hood (for the technical reader)
Always dry-run first. The ["dry"] channel logs a post without sending it — the safe way to prove a new client's output before anything reaches their real audience.
The approval portal
For clients who want a say, each tenant has a private, phone-friendly approval queue. The client opens a secure magic link — no password — and taps Approve, Edit, or Skip on each drafted post. Only approved posts publish.
- Links are signed and time-limited (they expire).
- Skips can capture a reason, which feeds back into better drafting.
- Held posts (missing a verified fact) show up clearly and cannot be published until resolved.
This is the screen that sells the service: the client sees a tidy stream of on-brand posts and approves a week in two minutes.
Create a client's approval link
In plain EnglishEach client gets their own private, expiring link — no login. You generate it once and send it to them.
Do it — step by step
- Generate a 30-day link for the client (use their tenant name):
ssh root@64.227.29.192 "python3 -c \"import sys;sys.path.insert(0,'/opt/portal');import portal_db as db;print('https://postiz.wholetech.com/portal/app/?c=acme-builders&t='+db.make_token('acme-builders',30))\"" - It prints the full link. Send it to the client (or open it yourself to approve for them).
- When it expires, run the command again for a fresh one.
Under the hood (for the technical reader)
Tokens are HMAC-signed with the server secret and carry an expiry — they can't be forged or reused after they lapse.
Pricing the service
Because generation is free and the only variable cost is X (pennies), margins are strong. Indicative managed-service tiers:
*Estimates for planning, not a rate card. Set final pricing per engagement.
White-label
The approval portal and reports can be served under a partner's branding. Client-facing pages carry the agency's name; the WholeTech engine stays behind the scenes. This is how a partner (for example, an agency serving home builders) resells the system as their own product.