logicspike/docs

Billing

Credit pricing — current state (audited 2026-08-30)

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 in apps/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:

  1. Addon pricing lives entirely outside CreditLedger/CREDIT_COSTS (apps/manager/src/services/credits.ts's hand-rolled debitCredits/addCredits + the addonCatalog DB 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.
  2. ledger.adjust() (admin manual balance correction) has zero production call sites — only exercised in tests today.
  3. 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_RATES doubled across all three tiers; AI_CHAT_OWNER_MARKUP raised 1.0x → 1.3x; BLOG_AI_GENERATE raised 30cr → 45cr. Bug cleanup in the same pass: dead ai.insight.generate reason removed, AI_KNOWLEDGE_EMBED's comment corrected (per-chunk not per-item), comms.claim_alias's catalog billing tag corrected from flat to a new recurring BillingMeta kind.
  • 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 new recurring.ts primitive.
  • 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 by CONTENT_PUBLISH_PER_PLATFORM (1cr × platform fan-out).
Billing