Architecture of a Multi-Tenant Private ChatGPT SaaS on LibreChat: Silo Tenants, Seat Billing and Token Caps
This post is AI-assisted. ChatGPT Business costs $20 per user per month on annual billing and $25 on monthly billing. Claude Team's standard seat is the same $20 and $25. Microsoft 365 Copilot Business is $25.20 on mont
This post is AI-assisted.
ChatGPT Business costs $20 per user per month on annual billing and $25 on monthly billing. Claude Team's standard seat is the same $20 and $25. Microsoft 365 Copilot Business is $25.20 on monthly billing, on top of a Microsoft 365 license. That is a price umbrella, and a focused product can live under it: a private, branded company ChatGPT, connected to each customer's own tools, sold at $12 a seat.
The open-source core already exists. LibreChat (MIT, about 46.6K GitHub stars) gives you the chat UI, agents, skills, MCP, multi-user auth, an admin panel and token balances. Shopify runs its internal AI on it, with thousands of custom agents and more than 30 internal MCP servers, according to ClickHouse's announcement when it acquired the project in November 2025.
What LibreChat does not give you is the SaaS around it: signup, provisioning, isolation between customers, seat billing and cost control. This post is the architecture for that layer.
Requirements
Functional
- A customer signs up, picks a plan and gets
acme.yourapp.comwithin minutes - Their staff sign in with the customer's own identity provider (Google Workspace, Microsoft Entra ID, Okta)
- Claude as the model, MCP connectors to the customer's tools, shared agents per customer
- Billing per seat, updated automatically as people join and leave
Non-functional
- Hard isolation of chats, files and connector tokens between customers
- Bounded model spend per user and per customer
- Upgrades that can be rolled to one customer first
- Target cost per seat of about $3 to keep a 75% gross margin at $12
Silo, not pool
LibreChat is designed as one deployment per organization: one librechat.yaml, one .env, one user base, one admin panel. You could try to bolt tenant IDs onto every collection, but you would be fighting the codebase on every upgrade.
So the data plane is one stack per customer (the silo model), and the shared parts live in a small control plane you write yourself.
| Concern | Silo (one stack per tenant) | Pool (shared stack) |
|---|---|---|
| Data isolation | Separate database per tenant | Tenant ID on every query |
| Per-tenant config, branding, MCP servers | Native: separate yaml and env | Needs a custom config layer |
| Noisy neighbor | Contained | Shared blast radius |
| Upgrades | Canary per tenant | All at once |
| Infra cost per tenant | Higher | Lower |
| Fit with upstream LibreChat | Clean | Fork pressure |
Start silo. When you have hundreds of small tenants, pack several tenant containers per host, but keep separate databases.
The architecture
Control plane (yours)
+================================================+
| Signup + plan Tenant registry (Postgres) |
| Stripe webhooks Provisioner Seat sync job |
| Usage + alerts Upgrade orchestrator |
+================================================+
| creates / updates
v
Data plane: one stack per tenant (acme.yourapp.com)
+================================================+
| LibreChat API + web (pinned image tag) |
| MongoDB database: acme (users, chats, balance) |
| Meilisearch index (chat search) |
| RAG API + pgvector (file search) |
| MCP servers for this tenant's tools |
+================================================+
| |
Anthropic API Customer IdP (OpenID)
(one Workspace per tenant, Customer tools via MCP
own key + spend limit) (per-user OAuth)
Three decisions do most of the work:
- One Anthropic Workspace per tenant. The Anthropic Console lets you group API keys into workspaces, with a monthly spend limit and rate limits per workspace. Each tenant stack gets its own workspace-scoped key. Spend reporting per customer comes for free, and a runaway tenant hits its own ceiling, not yours.
-
OpenID per tenant. Each tenant's
.envpoints at the customer's identity provider. People leave the company, they lose access. No passwords for you to store. -
Per-user OAuth for MCP. LibreChat supports
stdio,sse,websocketandstreamable-httpMCP servers, and withrequiresOAuth: trueeach person connects their own account. The assistant can only see what that person can see in the underlying tool.
The tenant config template
The provisioner renders this per tenant. The values that change are the tenant name, the spend cap and the MCP servers.
version: 1.3.18
interface:
customWelcome: 'Welcome to Acme AI. Ask about your clients, files and projects.'
balance:
enabled: true
startBalance: 6000000 # 1,000,000 credits = $1, so $6 of tokens
autoRefillEnabled: true
refillIntervalValue: 30
refillIntervalUnit: 'days'
refillAmount: 6000000
refillMode: 'reset' # replace the allowance each period, no rollover
display: 'currency'
mcpServers:
documents:
type: streamable-http
url: https://mcp.acme.yourapp.com/documents/mcp
requiresOAuth: true
timeout: 60000
And the branding in the tenant .env:
APP_TITLE="Acme AI"
CUSTOM_FOOTER="Acme AI. Private assistant for Acme staff."
ALLOW_REGISTRATION=false
ALLOW_SOCIAL_LOGIN=true
OPENID_ISSUER=https://login.acme-idp.example.com
OPENID_CLIENT_ID=from-the-customer
OPENID_BUTTON_LABEL="Sign in with Acme"
ANTHROPIC_API_KEY=key-from-the-acme-workspace
The refillMode: 'reset' line matters for a seat business. With the older add behavior, unused allowance rolls over and a quiet user can bank a large balance. Reset caps every person at the same monthly ceiling. Check that every replica runs a version that supports reset before you switch, as the example config warns.
Provisioning flow
// control-plane/provision.ts (sketch)
export async function provisionTenant(t: Tenant) {
const ws = await createAnthropicWorkspace(t.slug, { monthlyLimitUsd: t.seats * 6 });
await createMongoDatabase(t.slug);
await writeConfig(t.slug, renderYaml(t), renderEnv(t, ws.apiKey));
await deployStack(t.slug, { image: `librechat:${PINNED_TAG}` }); // ECS service or compose on a host
await createDnsRecord(`${t.slug}.yourapp.com`);
await waitForHealthy(`https://${t.slug}.yourapp.com/health`);
await registry.markActive(t.id);
}
createAnthropicWorkspace is a placeholder for however you manage workspaces and keys; whether you do it by API or once by hand in the Console, keep the key in a secrets manager and inject it at deploy time.
Seat billing that follows reality
Bill on seats provisioned, sync once a day, and let Stripe prorate.
// control-plane/seat-sync.ts
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function syncSeats(t: Tenant) {
const users = await tenantDb(t.slug).collection("users").countDocuments({});
const seats = Math.max(users, t.minimumSeats);
if (seats !== t.billedSeats) {
await stripe.subscriptionItems.update(t.stripeSeatItemId, {
quantity: seats,
proration_behavior: "create_prorations",
});
await registry.setBilledSeats(t.id, seats);
}
}
Stripe Billing costs 0.7% of billing volume on pay as you go, plus the standard US card fee of 2.9% plus 30 cents. On a $600 monthly invoice for 50 seats that is about $0.44 per seat.
Cost control in three fences
- Per user: LibreChat balances, as configured above. When a user's allowance is spent, their requests stop until the next reset.
- Per tenant: the Anthropic Workspace spend limit, set to roughly seats times the per-user cap.
- Per model: route by task. Claude Haiku 5.5 is $0.10 per million input tokens and $0.50 per million output tokens for prompts up to 100K tokens. Claude Sonnet 5.5 is $2 and $10. Make Haiku the default for quick questions and send agents and long documents to Sonnet.
The model mix is the biggest lever in the whole business. With 300 messages per seat per month, 4,000 input and 600 output tokens per message:
| Mix | Token cost per seat per month |
|---|---|
| 100% Sonnet 5.5 | $4.20 |
| 30% Sonnet, 70% Haiku | $1.41 |
| 100% Haiku 5.5 | $0.21 |
Add about $1.20 per seat for a $60 monthly hosting budget on a 50-seat stack (the t4g.medium that runs the app lists at about $24.53 a month on demand in us-east-1) and the $0.44 payments line. The routed mix lands at about $3.05 per seat, a 75% gross margin at $12. Prompt caching, which these numbers ignore, only improves it.
Upgrades without fear
- Pin the LibreChat image tag per tenant in the registry.
- Keep one internal tenant on the newest tag, then one friendly customer, then everyone.
- Render configs from templates so a new config key is one change, not 50.
- Run a smoke test after each tenant upgrade: login, one Haiku message, one Sonnet message, one MCP tool call.
What fails first
- MCP OAuth tokens expire or get revoked. Make reconnecting a one-click path for users.
- Long conversations grow context with every turn. Nudge people to start a new chat per task.
- Slow MCP servers make the whole answer slow. Set per-server timeouts and alert on them.
- Seat disputes. Show every customer admin the exact list of users being billed.
Build it with us
Axionry is an AI product development studio. We build private company assistants and multi-tenant AI products like this one, with Claude, MCP connectors, SSO and spend controls, and we ship them in weeks. The product page is at axionry.com/build/private-chatgpt, and you can book a call to talk through your version.
Sources
- ChatGPT Business pricing: https://openai.com/business/pricing/
- Claude Team pricing update (Jan 2026): https://claude.com/it/blog/claude-team-updates
- Microsoft 365 Copilot Business pricing: https://www.microsoft.com/en-us/microsoft-365/copilot/business
- ClickHouse acquires LibreChat, Shopify figures (4 Nov 2025): https://www.businesswire.com/news/home/20251104505230/en
- LibreChat repo: https://github.com/danny-avila/LibreChat
- LibreChat MCP server config: https://www.librechat.ai/docs/configuration/librechat_yaml/object_structure/mcp_servers
- LibreChat balance config: https://www.librechat.ai/docs/configuration/librechat_yaml/object_structure/balance
- Anthropic API pricing: https://platform.claude.com/docs/en/about-claude/pricing
- Anthropic Console workspaces: https://claude.com/resources/articles/workspaces
- Stripe Billing pricing: https://stripe.com/billing/pricing
- AWS t4g.medium price (Holori): https://calculator.holori.com/aws/ec2/t4g.medium/us-east-1
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.