Skip to content

Multi-tenant and white label

The owner planning to sell through several agencies, and the developer who will have to make it possible.

  • Today one installation = one bot, one event look, one sales group, one budget. Nothing in the database says which event or agency a guest belongs to.
  • Route A — one installation per tenant. Works with the existing deploy scripts, needs about a day of small code clean-ups, and scales to a handful of agencies.
  • Route B — true multi-tenancy. An event_id/tenant_id through the whole data model, per-tenant branding, budgets, bots and admin access. A large change; only worth it past roughly ten active tenants or when self-service is needed.
  • Start with Route A. Its clean-ups are also the first steps of Route B.

Verified in the code at the commit below.

Area Where What is fixed
Kiosk look bot/kiosk/brand/, bot/kiosk/index.html The palette, background art, fonts and the header logo of one specific event’s brand book are committed files; the logo mark-up is in index.html. Re-branding is a code change and a deploy.
Footer logos KIOSK_LOGOS in bot/api.py Two fixed file slots in bot/kiosk/static/; one slot’s file name and alt text carry a partner’s name. Files must be committed to be deployed.
Kiosk theme KIOSK_THEME One of two themes of the same brand book.
Bot identity TELEGRAM_BOT_TOKEN, TELEGRAM_BOT_USERNAME One bot per installation. Its username is part of every pack name (…_by_<bot_username>), so guests see it.
Pack title pack_title in bot/services/telegram_stickers.py The Russian phrase for “AI sticker pack” plus the guest’s username; not configurable.
Guest texts bot/texts.py, TEXTS in bot/kiosk/kiosk.js Russian only. The kiosk has a ?lang= hook, but only ru exists; the bot has no language switch.
Outfit catalogue and art style bot/catalog.py, ART_STYLE Global to the installation.
Event label EVENT_NAME Used only as a prefix in admin alerts. Sessions and jobs carry no event identifier.
Budget and quota BUDGET_USD, IMAGE_RPD_LIMIT, one provider key One counter for everything.
Pack limit users table keyed by Telegram user id “One pack per account” is global: a guest who got a pack at one event is refused at the next one on the same installation, until an admin grants an attempt or the account is reset.
Admin panel ADMIN_PANEL_USER / ADMIN_PANEL_PASSWORD A single shared login that sees every guest.
Alerts ADMIN_CHAT_ID One chat.
Leads LEADS_CHAT_ID, the stickers_ prefix, card title One sales group; the product name and prefix are constants in code.
Landing page separate repository One brand, one footer credit switch (SITE.partner.enabled), one leads endpoint.
Domain deploy/caddy/stickers.caddy, BASE_URL The API domain is written in the Caddy site file.

Each agency (or each large event) gets its own copy: a server or a directory with its own port, its own bot, its own .env, its own database and its own kiosk branding.

flowchart LR
  subgraph T1["Installation: agency A"]
    B1["Bot A"] --- D1[("DB A")]
    K1["Kiosk in brand A"]
  end
  subgraph T2["Installation: agency B"]
    B2["Bot B"] --- D2[("DB B")]
    K2["Kiosk in brand B"]
  end
  REPO["One code repository"] --> T1
  REPO --> T2
  P1["Provider project A"] --> T1
  P2["Provider project B"] --> T2

What already works: the deploy scripts set up a fresh server end to end; every secret and switch is an environment variable; data, backups and logs are per installation; isolation between tenants is total.

What to clean up first (small changes):

# Change Why
1 Move the kiosk brand out of the repository tree: load palette, logo and background from a per-installation folder outside bot/kiosk/ Re-branding without a commit and without mixing tenants’ art in one repository
2 Rename the logo slots to neutral names and read alt texts from configuration A partner’s name is in a file name today
3 Make the pack title and the product name on lead cards configurable White label
4 Parameterise the domain in the Caddy site file and the deploy scripts Today it is a literal
5 Decide the multi-event rule for the pack limit: reset per event, or keep it global Otherwise returning guests are refused
6 Add the missing languages to the bot and the kiosk The landing page already sells in three
7 Document a “new tenant” checklist: bot, provider project and key, domain record, .env, branding, rehearsal Repeatability

Costs of this route: a server (or a slice of one) per tenant; a bot and a provider project per tenant; upgrades are rolled out installation by installation; no combined statistics; each tenant’s daily image quota is separate — which is an advantage.

When it stops scaling: roughly ten installations, or the first agency that wants to create events by itself.

One system serving many tenants and events.

Layer Change
Data model tenants and events tables; event_id on sessions, jobs, api_usage, audit_log; the pack limit scoped per event; tenant_id on leads
Kiosk Event resolved from the address (/kiosk/<event>/); branding, texts, outfit list, theme and timeouts loaded per event
Bot Either one bot per tenant (several tokens, webhooks) or one shared bot with the event carried in the deep-link token. Pack names expose the bot username, so a white-label tenant will want its own bot.
Generation Per-event art style, outfit catalogue, mode flags and cost cap; per-tenant provider keys and quota counters
Money Budget, spend and alerts per event; a per-event money ceiling (missing today even for one tenant)
Admin panel User accounts with roles and tenant scoping instead of one shared password; per-event dashboards
Leads A leads service that knows products and tenants — see Playbook: new product
Privacy Per-tenant retention settings, export and deletion; a clear statement of who is the data controller for each event
Infrastructure The same prerequisites as the largest scaling scenario: PostgreSQL, separate workers, shared file storage — see Scaling scenarios

This is a rewrite of the data layer and the admin panel. It should not start before there is demand that Route A cannot serve.

What to give an agency partner, in either route

Section titled “What to give an agency partner, in either route”
  • A station in their client’s colours and logos (kiosk screen only — outfits stay from the catalogue).
  • A bot under a name agreed with them.
  • Access to the admin panel for the event’s operator.
  • The privacy statement: consent in the bot, photos deleted within 72 hours, one pack per account.
  • Not included without further work: their own landing page and their own requests group.

Legal and commercial terms between the studio and an agency are outside the repositories.


Last verified: 2026-11-13 against ai-sticker-pack@b715418 and ai-stickers-landing@e350ff5.