This document reflects what the code actually enforces today, verified by reading packages/core-types/src/credit-costs.ts (the canonical CREDIT_COSTS table) and grepping every .charge()/.hold()/.adjust()/chargedCall()/creditGuard()/registerRecurringCharge() call site across the repo — not the proposal docs, not code comments. Where those disagree with reality, it's called out explicitly below. Treat docs/billing/PRICING-PROPOSAL.md and docs/billing/credit-debit-scalable-plan.md as historical drafts, not current pricing.
This revision supersedes the 2026-08-29 audit: most of that audit's §5 "inconsistencies" have since been fixed by the credit-integration-kit work and the comms/mailbox recurring-charge adoption, and this pass adds a cost-plus repricing of the AI-token-driven actions plus the remaining bug cleanup.
1. Plan credit allotments & effective ₹/credit
Source: packages/core-database/src/seed.ts:50-118. Paid-tier allotments halved 2026-08-30 (were 1,500/5,000/15,000/50,000): the old numbers gave every persona ~3× their realistic monthly usage, killing pack top-ups and upgrade pressure. Free stays 100 (acquisition hook).
| Plan | Credits/mo | Price/mo | Price/yr | Effective ₹/credit | Trial | Seats included |
|---|---|---|---|---|---|---|
free |
100 | ₹0 | — | — | 0 days | 1 |
basic |
750 | ₹399 | ₹3,990 (17% off) | ₹0.532 | 14 days | 1 |
starter |
2,500 | ₹999 | ₹9,990 | ₹0.3996 (~₹0.40) | 14 days | 5 |
pro |
7,500 | ₹2,499 | ₹24,990 | ₹0.3332 | 14 days | 10 |
business |
25,000 | ₹6,799 | ₹67,990 | ₹0.2720 | 14 days | 50 |
Every tier still beats pack rates (₹0.69–0.83/credit), so subscribing always wins over topping up. Note the knock-on: halving the allotments doubles every margin computed against credit value — the AI-chat floor in §3, derived when Business credits were ₹0.136, is now ~2× the stated figures.
Extra seats are credit-priced, not INR-priced: the extra_seat addon at 150cr/seat/month (repriced from 100cr on 2026-08-30; existing purchasers keep 100cr via the tenant_addons.price_per_unit snapshot). Earlier revisions of this table showed a per-plan "₹99–199/mo extra seat" column sourced from plans.extra_seat_cost_inr — that column is dead (never read by any code path) and is now marked as such in the schema. The seat limit is enforced at invite time (TeamService.createInvitation → checkLimit("platform.seats"), counting members + pending unexpired invites; -1 = unlimited).
The old "₹0.20/credit" anchor was the pre-halving Starter ratio; the Starter ratio is now ~₹0.40. Effective ₹/credit still gets cheaper on higher tiers, so the Business tier remains the thinnest-margin case the AI repricing below targets — but with twice the cushion it had when §3's figures were computed.
2. Credit packs (top-ups)
Source: packages/core-database/src/seed.ts:216-218. Unchanged by this pass — the pack-vs-plan price gap below is accepted as intentional (packs serve a different, no-commitment customer).
| Pack | Price | Credits | Bonus | Effective ₹/credit |
|---|---|---|---|---|
| small | ₹415 | 500 | 0% | ₹0.830 |
| medium | ₹1,660 | 2,200 | 10% | ₹0.755 |
| large | ₹4,150 | 6,000 | 20% | ₹0.692 |
3. AI-token-driven actions — repriced 2026-08-30 (cost-plus)
Why: AI_CHAT_RATES (credits per 1,000 tokens) used to reuse the exact same numerals as the raw Anthropic cost table (USD per 1,000,000 tokens) — a coincidence, not a deliberate margin. Actual margin worked out to owner-copilot chat ~1.6x-3.2x over raw cost, customer-facing chat (1.5x markup) ~2.5x-4.8x — thinnest at the Business tier, where credits are cheapest. This pass doubles the base rate table and raises the owner markup off 1.0x, bringing the floor (Business tier, owner chat) to ~4.3x (3.28x raw × 1.3 markup) and customer chat to ~4.9x (3.28x raw × 1.5 markup — the doubled base rate benefits both kinds equally; the customer markup itself didn't change).
| Action | Cost | Type | Source |
|---|---|---|---|
| Owner copilot chat turn | (inputTokens×rate.in + outputTokens×rate.out)/1000 × 1.3 markup, floor 1cr (pre-flight estimate only) |
formula | chat-cost.ts/chat-usage.ts, brain-service/routes/chat.route.ts |
| Customer-facing chat turn | same formula, ×1.5 markup | formula | chat-usage.ts, chat-engine/lib/billing.ts, brain-service/routes/chat.route.ts |
| — token rates by model tier | haiku 2cr in / 10cr out, sonnet 6/30, opus 30/150 (credits per 1k tokens) — doubled from 1/5, 3/15, 15/75 | constant | CREDIT_COSTS.AI_CHAT_RATES |
| — owner markup | 1.3x — raised from 1.0x (was bare compute-equivalent, no justification) | constant | CREDIT_COSTS.AI_CHAT_OWNER_MARKUP |
| — customer markup | 1.5x (unchanged — already adequate once the base table moved) | constant | CREDIT_COSTS.AI_CHAT_CUSTOMER_MARKUP |
| AI-generate a blog draft | 45cr — raised from 30cr alongside the chat repricing | constant | CREDIT_COSTS.BLOG_AI_GENERATE — blog-service/mcp/routes.ts |
Chat billing uses a fractional accumulator (thousandths-of-a-credit) that only settles a whole credit once the running total crosses 1000 — no per-message rounding waste. Pre-flight holds are sized to an expected reply (512 tokens, AI_CHAT_PREFLIGHT_OUTPUT_TOKENS), not the hard ceiling, so low-balance tenants aren't falsely blocked. AI_CHAT_BASE_FLOOR (1cr) applies only to that pre-flight estimate, never to the real settled charge.
Left unchanged, despite being AI-priced — already adequately margined on inspection:
| Action | Cost | Type | Source | Why unchanged |
|---|---|---|---|---|
| Embed a knowledge item | 2cr per chunk (held worst-case, captured at true chunk count) | formula | CREDIT_COSTS.AI_KNOWLEDGE_EMBED — brain-service/services/knowledge-ingest.service.ts |
Embedding is functionally free to compute; already deeply profitable — this is a value/indexing price, not cost-plus. |
| Summarize a chat session | 5cr | constant | CREDIT_COSTS.CHAT_AI_SUMMARY |
Single-shot haiku completion, already ~2.9x-5.7x margin. |
| Draft a chat reply | 5cr | constant | CREDIT_COSTS.CHAT_AI_DRAFT |
Same. |
| Draft outreach message | 5cr | constant | CREDIT_COSTS.CONTACTS_AI_OUTREACH |
Same. |
| Summarize a contact | 5cr | constant | CREDIT_COSTS.CONTACTS_AI_SUMMARY |
Same. |
| AI-generate a caption | 5cr | constant | CREDIT_COSTS.CONTENT_AI_CAPTION |
Same. |
| AI-suggest hashtags | 3cr | constant | CREDIT_COSTS.CONTENT_AI_HASHTAGS |
Same. |
| AI-generate newsletter subject | 5cr | constant | CREDIT_COSTS.NEWSLETTER_AI_SUBJECT |
Same. |
4. Infra-only actions — no cost basis to reprice against
Marginal compute cost for all of these is near-zero, so a cost-plus pass yields no signal to move them — leaving them unchanged is the correct application of that philosophy, not an oversight. Repricing these would be a separate value-based decision.
Blog (apps/blog-service)
| Action | Cost | Type | Source |
|---|---|---|---|
| Publish a post | 10cr | constant | CREDIT_COSTS.BLOG_POST_PUBLISH (REST via creditGuard, MCP via publish-billing.ts) |
Publish charge is idempotent per live-cycle (no double-charge on re-publish/re-schedule), refunded on unschedule, delete-while-scheduled, or unpublish within a 30-min grace window.
Content & social (apps/content-engine)
| Action | Cost | Type | Source |
|---|---|---|---|
| Publish a scheduled slot | 2cr per platform it fans out to (5 platforms = 10cr in 5 separate charges) | constant×N | CREDIT_COSTS.CONTENT_PUBLISH_PER_PLATFORM |
| Daily analytics rollup | 2cr per connected account per UTC day (~60cr/mo per account with posts <30 days old) | constant | CREDIT_COSTS.ANALYTICS_PER_ACCOUNT_DAILY |
Both raised 1 → 2 on 2026-08-30 (value-based pass): social distribution is the flagship action and was priced at cost; analytics is the one infra action with real third-party cost behind it (X API Basic is $200/mo). Failed publishes still refund; the daily analytics idempotency key (analytics:{connectionId}:{YYYY-MM-DD}) made the transition seamless — a day already charged at 1cr can't be re-charged at 2.
Collections (apps/collections-service)
| Action | Cost | Type | Source |
|---|---|---|---|
| First publish of an entry | 1cr, once per entry ever | constant | CREDIT_COSTS.COLLECTIONS_ENTRY_PUBLISH |
Everything else (CRUD, relations, bulk ops, the embed widget) stays free.
Forms (apps/forms-service)
| Action | Cost | Type | Source |
|---|---|---|---|
| Accepted submission | 0.25cr (1 credit per 4 submissions) | formula (accumulator) | CREDIT_COSTS.FORMS_SUBMISSION_THOUSANDTHS = 250 |
Independent of credits, forms also has a monthly submission-count cap by plan — a separate gate from the credit charge.
Newsletter & transactional email (apps/newsletter-service, apps/communication)
| Action | Cost | Type | Source |
|---|---|---|---|
| Campaign send | ceil(activeSubscribers / EMAIL_RECIPIENTS_PER_CREDIT) credits |
formula | campaign-send.ts |
| Re-permission campaign send | same formula | formula | repermission.ts |
| Test send (template preview) | 1cr flat | constant | CREDIT_COSTS.NEWSLETTER_TEST_SEND |
| Send email via MCP tool | 1cr flat | constant | CREDIT_COSTS.MCP_EMAIL_SEND |
Transactional email send (/v1/send, send_message) |
0.2cr (1 credit per 5 emails) | formula (accumulator) | CREDIT_COSTS.EMAIL_SEND_THOUSANDTHS = 200 |
EMAIL_RECIPIENTS_PER_CREDIT is now derived from EMAIL_SEND_THOUSANDTHS (1000 / EMAIL_SEND_THOUSANDTHS) in credit-costs.ts — the "5 recipients per credit" ratio used to be duplicated as independent literals in three places; that's fixed, it's a single source now.
Communication — mailbox & aliases (apps/communication) — recurring, not one-time
| Action | Cost | Type | Source |
|---|---|---|---|
| Claim a vanity email alias | 20cr now, then 20cr every 30 days for as long as it's held | constant + recurring | CREDIT_COSTS.COMMS_ALIAS_CLAIM via registerRecurringCharge (packages/core-billing/src/recurring.ts) |
| Provision a mail.vlozi.app mailbox | 20cr now, then 20cr every 30 days | constant + recurring | CREDIT_COSTS.MAILBOX_CLAIM via registerRecurringCharge |
Both are sourced from CREDIT_COSTS (no longer hardcoded local literals) and swept hourly by apps/billing-crons. A lapsed alias falls back to the shared sender address; a lapsed mailbox is rejected at send time (MAILBOX_SUSPENDED, 402) — enforced centrally in comms's dispatchSend(), the one path both features' sends funnel through. Switching alias slugs cancels the old slug's recurring charge. See docs/billing/CREDIT-STANDARDS.md §9 for the full recurring-charge standard.
Manager — addons (apps/manager) — still a separate system
| Action | Cost | Type | Source |
|---|---|---|---|
| Purchase an addon | addon.creditCostPerUnit × quantity (from the addonCatalog DB table) |
DB-driven | services/credits.ts (buyAddon) |
| Addon renewal | same per-unit rate | DB-driven | services/credits.ts (processAddonRenewals) |
debitCredits()/addCredits() are now thin delegates over the shared CreditLedger (the old parallel implementation is gone), and the lifecycle was hardened 2026-08-30: purchases are transactional + idempotency-keyed, duplicate purchases merge into one row, the purchase price is snapshotted onto tenant_addons.price_per_unit, and renewals bill that frozen snapshot (anchored to the previous renewal date) — an admin catalog price change never silently rebills an existing customer. The addon renewal engine remains deliberately separate from recurring.ts (catalog-driven pricing + rebuildEntitlements side effects are the wrong shape for the dumb resource-agnostic primitive).
Marketing-surface drift note (2026-08-30): the website pricing page (apps/website/app/pricing/PricingClient.tsx) now imports CREDIT_COSTS from @repo/core-types instead of a local copy — the class of bug where the page advertised a nonexistent flat chatbot price and understated email value 5× is closed. pricing-copy.md / landing-copy.md still state numbers in prose; re-audit them on any repricing.
5. Services that charge nothing today
- Media (
apps/media-service) — uploads/storage — free. - Mailbox app (
apps/mailbox) — the mailbox feature is billed (see §4), but the charging code lives entirely inapps/communication; the mailbox worker itself has no billing logic. - Flows (
apps/flows-service) — flow runs are not credit-metered; gated only by a plan-based flow-count cap. Individual flow nodes that call into already-billed services get metered there, not in flows-service itself. - Gateway (
apps/gateway) — pure routing/auth, no billing logic.
6. Remaining inconsistencies & risks
Fixed since the 2026-08-29 audit: alias/mailbox literals moved into CREDIT_COSTS and made recurring; RECIPIENTS_PER_CREDIT is now derived, not triplicated; the stale "0.5cr" forms comment; the dead ai.insight.generate reason (removed); the "per item" vs "per chunk" AI_KNOWLEDGE_EMBED comment.
What's still open:
- Addon pricing lives entirely outside
CreditLedger/CREDIT_COSTS(apps/manager/src/services/credits.ts's hand-rolleddebitCredits/addCredits+ theaddonCatalogDB table). Explicitly out of scope for this repricing pass and the recurring-charge adoption before it — a bigger architectural job to fold in, tracked as a known follow-up. ledger.adjust()(admin manual balance correction) has zero production call sites — only exercised in tests today.- Credit-pack price-per-credit (₹0.69-0.83) is 3-4x plan-included price-per-credit (₹0.136-0.266) — confirmed intentional (packs serve a different, no-commitment customer), not a bug.
7. Recent price-change history
- 2026-08-30 — Cost-plus repricing:
AI_CHAT_RATESdoubled across all three tiers;AI_CHAT_OWNER_MARKUPraised 1.0x → 1.3x;BLOG_AI_GENERATEraised 30cr → 45cr. Bug cleanup in the same pass: deadai.insight.generatereason removed,AI_KNOWLEDGE_EMBED's comment corrected (per-chunk not per-item),comms.claim_alias's catalogbillingtag corrected fromflatto a newrecurringBillingMetakind. - 2026-08-29/30 — Credit-integration-kit build + comms/mailbox recurring-charge adoption: alias/mailbox claims moved from hardcoded local literals to
CREDIT_COSTS, and from one-time-forever charges to a genuine 30-day recurring charge via the newrecurring.tsprimitive. - 2026-06-27 (
03712f33) — Forms submission rate cut 0.5cr → 0.25cr (1-per-2 → 1-per-4 submissions). - 2026-07-06 (
dfddbaf3) — Chat pre-flight billing behavior fix (not a price change): the hold used to reserve worst-case for every message, causing false insufficient-credits errors for low-balance tenants. Fixed to reserve based on an expected reply size (512 tokens), relying on the accumulator to true up afterward. - 2026-04-29/30 (
21d76403,4f6de255) — AI chat pricing moved from a flat per-message hold to the per-tier rate table with owner/customer markups;CONTENT_GENERATE(flat 20cr) replaced byCONTENT_PUBLISH_PER_PLATFORM(1cr × platform fan-out).