Skip to content

feat(billing): expand Free plan and tighten Team chunk cap - #225

Merged
maakle merged 4 commits into
mainfrom
claude/pricing-revenue-analysis-rRSjr
May 22, 2026
Merged

feat(billing): expand Free plan and tighten Team chunk cap#225
maakle merged 4 commits into
mainfrom
claude/pricing-revenue-analysis-rRSjr

Conversation

@maakle

@maakle maakle commented May 22, 2026

Copy link
Copy Markdown
Owner

Summary

Pricing model v3 — two changes that move ARR in the same direction overall:

  • Free plan loosened: 1 → 2 connectors, 10K → 25K stored chunks. Lets people actually evaluate multi-source RAG before paying. Pure upgrade for existing Free users; UPDATE in place, no grandfathering.
  • Team chunk cap tightened: 1M → 500K stored chunks. Pushes data-heavy customers (the band where the model under-monetizes today) from Team ($499/mo) toward Business ($1,999/mo) earlier. Existing Team subscriptions grandfather onto a renamed team-legacy-2026-05-v2 row — same Stripe price, same monthly grant, same 1M cap — mirroring the rename mechanic from migration 0061. New signups land on the fresh team row at the 500K cap.

Modeling suggests ~10–12% book ARR uplift, almost entirely from new Team customers in the 500K–1M chunk band who now migrate to Business 4× earlier in their growth curve.

Files touched

  • packages/db/migrations/0070_free_expansion_team_chunk_cap.sql — new data migration (Free UPDATE + Team rename/replace)
  • packages/db/migrations/meta/_journal.json + 0069_snapshot.json — generated by pnpm db:new-data-migration
  • packages/billing/src/plan-defaults.ts — canonical fallback caps for Free (25K) and Team (500K)
  • packages/billing/test/plan-defaults.test.ts, storage-quota.test.ts — aligned to new fallback values
  • apps/web/src/components/landing/pricing-band.tsx — Free (2 connectors, 25K chunks), Team (500K chunks)
  • 14× apps/web/src/app/api/connectors/*/connect/route.ts — refresh stale (free → 1 connector) comment to (free → 2 connectors)

Test plan

  • pnpm --filter @holo/billing test — 32/32 passing
  • pnpm --filter @holo/web typecheck — clean
  • pnpm --filter @holo/db check — migration meta OK (70 entries)
  • Visual check of /pricing landing page once deployed
  • Visual check of /settings/billing plan grid (auto-pulls from DB after migrating)
  • Verify existing Team subscriptions still resolve maxStoredChunks: 1000000 after migration (they keep the legacy plan row's value)
  • Verify new signups on Team land on the 500K cap

https://claude.ai/code/session_013xTfsKeF7jh6HDrxTUtaoG


Generated by Claude Code

claude added 4 commits May 22, 2026 09:20
Pricing model v3: lift Free trial caps so multi-source RAG can actually
be evaluated before paying, and lower the Team chunk cap so data-heavy
customers feel the Business upgrade pull earlier in their growth curve.

- Free: 1 → 2 connectors, 10K → 25K stored chunks (in place; pure upgrade
  for existing Free users, no grandfathering needed).
- Team: 1M → 500K stored chunks. Existing Team subscriptions grandfather
  onto a renamed `team-legacy-2026-05-v2` row (same Stripe price, same
  monthly grant, same 1M cap), mirroring the mechanic from migration
  0061. New signups land on the fresh `team` row at the 500K cap.

Updates the canonical defaults in plan-defaults.ts, the landing page
pricing band, and the stale per-connector comment strings that named
the old "free → 1 connector" gate. Billing tests aligned to the new
fallback values.

https://claude.ai/code/session_013xTfsKeF7jh6HDrxTUtaoG
The INSERT for the new Team row used 2000000 credits — the pre-redenom
value copy-pasted from migration 0061. After 0063 + 0065's /100 + /100
redenomination, the canonical Team grant is 20,000 credits/month. Left
unfixed, every new Team subscription created after this migration would
have received 100× the advertised grant.

The renamed legacy row (`team-legacy-2026-05-v2`) is unaffected — it
keeps the existing post-redenom value of 20,000 it had before the
rename. Caught before deployment; no customer data impact.

https://claude.ai/code/session_013xTfsKeF7jh6HDrxTUtaoG
The Team → Business cliff was 4× price ($499 → $1,999), 5× credits, and
20× chunks. Customers in the 500K–2M chunk band had no proportionate
upgrade — they were forced into Business or sat at Team's storage cap.
Scale fills the gap at $999 / 50K credits / 2M chunks, turning the
upper ladder into clean 2× steps.

- Migration 0071 inserts the new `scale` plan row (public, standard
  sync; Stripe Price gets provisioned on next worker boot via
  ensureStripeProductsForPlans).
- plan-defaults.ts gains the `scale: 2_000_000` canonical fallback.
- limits.ts upgrade ladder: Team-full now suggests Scale (was Business);
  Scale-full suggests Business. Connector-cap suggestion is unchanged
  (Team/Scale/Business all have unlimited connectors).
- Landing page (pricing-band.tsx) gets a 5th card; grid breakpoints
  re-shape (md:2 → lg:3 → xl:5) so the cards don't shrink too much on
  narrow viewports.
- /settings/billing PlanGrid: SLUG_ORDER and PURCHASABLE include
  `scale`; grid breakpoints match the landing page.
- /api/stripe/checkout planSlug enum extended to accept `scale` —
  otherwise the Upgrade-to-Scale button would 400.
- Tests cover the new upgrade-suggestion edges (Team→Scale, Scale→
  Business) and the canonical defaults map.

https://claude.ai/code/session_013xTfsKeF7jh6HDrxTUtaoG
Annual billing was the highest-leverage missing piece for the v1 pricing
model. Customers can now select monthly or annual at checkout; annual
charges upfront for the year at ~15% off list, and grants 12× the
monthly credit allotment at the start of each annual period.

Annual prices (clean monthly-equivalents at exactly 15% off):
  Starter   $99/mo  →  $84/mo billed annually ($1,008/yr)
  Team     $499/mo  → $424/mo billed annually ($5,088/yr)
  Scale    $999/mo  → $849/mo billed annually ($10,188/yr)
  Business $1999/mo → $1,699/mo billed annually ($20,388/yr)

Implementation:
- Migration 0072 adds `annual_price_cents` and `stripe_annual_price_id`
  columns on `billing_plans` and seeds the annual prices above.
- `ensureStripeProductsForPlans` now provisions a second Stripe Price
  per plan with `recurring.interval: 'year'` and lookup_key
  `<slug>-annual`. Existing monthly Prices are untouched.
- Checkout API accepts `billingInterval: 'monthly' | 'annual'` (defaults
  to monthly for backwards compatibility) and routes to the right
  Stripe Price.
- Webhook handler detects annual periods by duration (> 60 days) rather
  than fetching the Stripe price — avoids an extra API round-trip per
  invoice — and grants 12 × monthlyCredits when annual. The
  `idempotencyKey` is still the invoice id, so retries collapse.
- Landing page extracts the hosted-plans grid into a client component
  with a Monthly/Annual segmented toggle (defaults to Annual to show
  the lower per-month headline). Server component handles the rest of
  the section for SSR.
- /settings/billing PlanGrid grows the same toggle; pricing in each
  card flips to "$X/mo billed annually" with the yearly total as a
  sub-caption when toggled.

Credits on annual are issued upfront, not metered monthly within the
year. Customers who commit to annual accept they could burn through 12
months of credits in month 1; top-ups remain available for overage.

https://claude.ai/code/session_013xTfsKeF7jh6HDrxTUtaoG
@maakle
maakle merged commit 4022832 into main May 22, 2026
5 checks passed
@maakle
maakle deleted the claude/pricing-revenue-analysis-rRSjr branch May 22, 2026 15:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants