Membership stops governing how much a creator may charge and starts governing how often they may put a new price on something — article 33749, plus the decisions recorded in docs/licensing-fee-revamp-questions.md. - Price caps deleted. The licensing fee ceiling is a flat 100/generation for every creator (500 on a video model); paid access has no ceiling at all. Every clamp goes with them, including the cap-tier Redis cache — getViewerMonetization, the purchase path and the orchestrator-facing mini endpoint no longer resolve a subscription tier at all. - Membership instead grants a monthly allowance of NEW prices: free 3, bronze 10, silver 25, gold unlimited, counted in a new PricingSlot ledger keyed like PaidAccess. One slot per entity, spent on first pricing, never returned. - A creator score of 10,000 is required to apply a price to something that has none. Moderators are not exempt from the floor or the allowance; they remain exempt from the fee ceiling. - Both rules gate only the unpriced → priced transition. Editing, lowering or clearing a price that is already set is always free, at any score — which grandfathers everything priced before these rules existed, and is why the cutover needs no backfill. Creator Studio enforces both rules itself: its fee and gate writes are direct SQL that never reaches the service layer, so a rule not applied there is a rule the spoke can bypass. The rules themselves live in @civitai/buzz so the two implementations cannot drift on a threshold or a message. Also reverts the unshipped lapse-grace work: it mitigated lapse-repricing, and a lapse no longer changes any price. MIGRATION — 20260821120000_pricing_slot is applied by hand, per environment, and must land BEFORE this code deploys: until the table exists every first-time pricing write throws on a missing relation, and the Creator Studio models page 500s outright. Apply it under a short lock_timeout — the inline owner FK takes SHARE ROW EXCLUSIVE on "User". See the migration header. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
7.7 KiB
Blue Buzz for early / paid access
Status: proposal, nothing implemented. Ask: let a creator opt in to accepting Blue Buzz for early/paid access purchases.
What this is and isn't
@dev: "Blue and yellow prices would be the same number. Really, we're asking if the creator is open to receiving generation credit (blue buzz). They should still be able to accept green/yellow as they normally would."
So this is one price, an extra accepted currency — not dual pricing, and not a replacement. Green/yellow behaviour is untouched; blue is additive and opt-in.
The trade a creator is accepting: Blue Buzz is not bankable. buzzTypeConfig gives green and yellow
bankable: true; blue has neither bankable nor purchasable. It is earned/granted credit — rewards mint
blue by default (base.reward.ts:110). A creator opting in is choosing to be paid in generation credits they
cannot withdraw. The UI has to say that plainly or it generates support tickets.
The one line that matters
buzz.service.ts:1106:
input.toAccountType = input.toAccountType ?? 'yellow'; // Default to bank if not provided
The destination is yellow regardless of what the buyer spent. Wire blue into paid access without changing this and you have built a free-credit-to-cash conversion: farm blue from rewards, spend it on a cooperating creator's version, creator withdraws yellow. Self-purchase is blocked, two accounts defeat that.
So: when the buyer pays blue, the creator receives blue. toAccountType is settable per call — the ??
is only a default — so this is one argument, not a service change.
What already works
| Piece | State |
|---|---|
| Charging a chosen currency | earlyAccessPurchase already passes fromAccountTypes: [buzzType] |
| Per-call destination | createMultiAccountBuzzTransaction accepts toAccountType |
| Client currency UI | BuzzTransactionButton takes accountTypes and renders a distribution |
| Creator Program exclusion | Earnings queries filter toAccountType IN ('yellow', 'green') — blue is already excluded (creator-program.service.ts:74, used at 120/317/354/889/894) |
The block is a single explicit guard at model-version.service.ts:1908:
if (buzzType === 'blue')
throw throwBadRequestError('You cannot use Blue Buzz for early access purchases.');
Today it is belt-and-braces — the controller passes getAllowedAccountTypes(ctx.features)[0], which resolves
to green or yellow by domain, so blue is never offered in the first place.
Decided: the buyer picks one currency
@dev: "users can select to pay with blue or green on civitai.com, and blue or yellow on civitai.red."
So the choice is blue OR the domain currency — never a blend, never a fallback. Confirmed against the
code: getAllowedAccountTypes is purely domain-driven (isGreen ? 'green' : 'yellow', and isGreen is the
green color domain), and earlyAccessPurchase already passes a one-element fromAccountTypes. A
purchase is single-currency today; blue just adds a second option to choose from.
| Domain | Buyer may pay with |
|---|---|
| civitai.com (green) | blue or green |
| civitai.red (red) | blue or yellow |
This keeps the loophole shut by construction: one source colour in, the same colour out
(toAccountType), one transaction. No split payout, no partial conversion.
Two traps this rules out, both worth stating because the obvious implementation hits them:
- Don't use
getAllowedAccountTypeson the charge path.getAllowedAccountTypes(features, ['blue'])returns['blue', 'green'], and spend drains in array order — every buyer would silently spend blue first, with no opt-in and no choice. It stays the right helper for deciding what the client may offer. BuzzTransactionButtondistributes, it doesn't choose. It takesaccountTypes, fetches balances and computes a spend distribution across them (getBuzzTypeDistribution) — pass it['blue','green']and it drains rather than asks. The purchase modal therefore needs two buttons, each with a one-elementaccountTypes("Pay with Blue Buzz" / "Pay with Green Buzz"), which reuses the component's per-currency balance and insufficient-funds handling unchanged. Rendering one button per accepted currency is the whole UI change.
Storage
PaidAccess.terms, as acceptsBlueBuzz?: boolean alongside download / generation.
Chosen for read cost: terms is already loaded by earlyAccessPurchase and already reaches the client via
useModelVersionPermission, so the flag is free on both sides. A creator-level column would need a new
lookup and a new cache on the purchase path. Nothing rewrites terms on read since the price caps
were removed, and gatePrices only reads the two grants — no change.
@ai: Per-version storage, but the stance is catalog-wide ("am I open to generation credit"), so expressing it per-version means editing every gate. Seed it from a creator-level default in Creator Studio settings, with bulk apply — exactly the field shape the bulk grid is being designed for. Storage stays per-version; the default is UX.
Work
- Terms + write path.
acceptsBlueBuzzonModelVersionTerms;buildModelVersionTermstakes it; checkbox in both editors (onsite form + Creator Studio drawer) with explicit "not withdrawable" copy. - Purchase input. Add
buzzTypetomodelVersionEarlyAccessPurchase(currently{ modelVersionId, type }). Re-validate server-side against the terms — never trust the client's choice. - Charge + payout. Drop the guard; pass
toAccountType: 'blue'when the buyer paid blue. - Refund. Return to the same account type the buyer spent.
- Client. Offer blue in the purchase modal only when the terms allow it, showing the blue balance.
- Accounting sweep — see below.
Accounting
- Creator Program — already correct. Earnings filter on
toAccountType IN ('yellow','green'), so blue income is excluded without further work. Worth an explicit test so it stays that way. - Donation goals.
earlyAccessPurchaseattaches a donation record to the version's EA goal. A blue purchase advancing a goal creators read as cash is probably wrong — decide whether blue counts, and if not, skip the donation write for blue purchases. - Creator Studio earnings page. Must separate blue from bankable, or it overstates income for exactly the creators who opted in.
Risks
- The loophole is the whole risk. Any path where blue enters and yellow leaves converts free credit into
withdrawable currency. Worth a regression test asserting
toAccountType === fromAccountTypefor the purchase, not just a careful review. - Creator expectation. "Accept Blue Buzz" reads as "get paid" unless the UI says otherwise.
- Blue is plentiful. It is granted by rewards and bundled with Stripe purchases, so opting in likely shifts real purchase volume from bankable to non-bankable currency. That is the creator's call to make, but the earnings surface should make the split visible before and after.