Files
civitai__civitai/docs/creator-studio-plan.md
T
briant 6590c38eeb docs(creator-shop): the 70/30 split is built, not pending
creator-shop.md's caveat list said the payout rate was still to be decided —
"pays out the full unitAmount, i.e. 100%, platform keeps 0" — and the proposal
doc repeated it three more times as an open question. All of it is stale:
computeCreatorShopSplit has shipped, the creator pool is floor(price * 0.7),
and payouts route on Cosmetic.createdById rather than meta.paidToUserIds,
which now survives only for legacy official items.

This cost real time: the line was read as current while checking whether model
access sales were missing a platform cut, and sent that investigation in
circles before the ledger settled it.

Also corrects the file's "pre-implementation" status header, the reuse-table
row describing paidToUserIds as the creator payout path, the plan's
built-today reference (which named a line number that has since moved), and a
stale line-number citation in owner-rollup-handoff.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 12:40:20 -06:00

39 KiB
Raw Blame History

Creator Studio (creator.civitai.com) — Implementation Plan

Status: Draft for review. Synthesised from the Creator Release 2026 HackMD, the ClickUp task (868k6zt4m), and a code-map of the current main app. Decisions + open questions for Justin are in §9.

1. What this app is

Creator Studio is a new SvelteKit spoke app at creator.civitai.com — "the single home for managing everything a creator earns from." It is the UI surface for the Creator Release 2026, which replaces the flat 25% generation compensation with creator-controlled earning: licensing fees, sellable model access (no time cap), and a Creator Shop.

Scope (from the ClickUp task), all creator-facing:

  1. Bulk licensing-fee edits across many models, with model-type default-fee suggestions.
  2. Shop management — cosmetic submission, listing order, models toggle, publish. (Deferred: built in the main app first — see below.)
  3. Earnings + analytics — license fees, tips, model-access sales, cosmetic sales; usage analytics.
  4. Model management — early/paid-access toggles.

v1 is Studio-only. It does not absorb general creator surfaces (model upload, posts, profile settings) from the main app; those stay put for now.

Access model (confirmed by Justin): any logged-in user can access Creator Studio. Specific items/actions are restricted to members, gated on the user's subscription tier. The app is not CP-gated at the door — the gate is "authenticated," and member-only capabilities (e.g. setting a licensing fee) are enforced per-action.

v1 build priority (from the designer meeting): model management and basic analytics come first; the Creator Shop is out of v1 — see the shop note below.

Licensing-fee rules (confirmed by the designer): a licensing fee is per model version; members can turn it on and adjust it; non-members cannot set one.

Creator Shop is being built in the main app by another dev (confirmed by Justin) and will be transferred into Creator Studio later. It is therefore out of scope for this app's v1 — all shop questions are deferred to that work. This resolves the earlier designer-vs-HackMD tension: shop ships (in the main app), just not here yet.

Cutover — a separate backend track (not this app's v1)

Retiring 25% compensation and activating licensing fees still land together on the backend, but per Justin this cutover is decoupled from Creator Studio v1: the app ships ~1 week before the cutover. Fees set during that pre-cutover week carry a special "not-yet-payable" flag so they don't pay out until the cutover flips. So comp-retirement is its own backend track, not part of this app's v1 (§8).


2. Architecture & tooling

Creator Studio is a spoke app in this monorepo, exactly like apps/moderator and apps/auth. Do not port the main app's React/Mantine components — these apps are Svelte 5 / SvelteKit / Tailwind v4.

Read these first (they are the source of truth for the wiring):

Scaffolding: use the scaffold-civitai-app skill to stand up apps/creator-studio. It cherry-picks only the packages we import and wires each one's dependency, transpile entry, env vars, and server shim (SvelteKit by default).

SvelteKit tooling notes (consolidated so we don't re-learn them):

Concern Decision
Framework SvelteKit + @sveltejs/adapter-node, Svelte 5, Vite
UI @civitai/ui (shadcn-svelte / bits-ui) — reach for shadcn-svelte before hand-building; Civitai composites (EdgeMedia, ImageGuard, masonry) get built on top of the primitives, into @civitai/ui
Styling Tailwind v4 (@tailwindcss/vite), dark-only (<html class="dark">), @import '@civitai/ui/theme.css', @source the package so classes aren't purged
Auth createSpokeGuard in hooks.server.ts; gate is any authenticated user (require: (u) => !!u). Member-only actions are enforced per-action on the tier (see §5)
Data @civitai/db/kysely (env-free, no Prisma engine) for domain reads/writes; monetization mutations run in-process via a creator-studio module (kysely + @civitai/buzz) — no main-app tRPC hop. See §5
Buzz @civitai/buzz — a server-side-only client for the buzz service (transactions + earnings reads). All buzz comms are server-side; never imported into browser code
Env app-local .env (+ committed .env.example); process.env shim in vite.config.ts

Package discipline (avoid sprawl). A thing becomes a shared @civitai/* package only when (1) ≥2 apps actually import it and (2) it's a coherent, nameable capability — not "a slice of shared logic." Otherwise it's a module inside the app, extracted to a package only when a second app needs it (rule-of-three). For v1 that means exactly one new package — @civitai/buzz — with monetization writes and analytics reads living as creator-studio modules until the full-cutover consolidation (§9).


3. Page list (v1)

Routes under apps/creator-studio/src/routes/. Grouped, minimal, one screen per job.

Route Page Purpose
/ Dashboard / overview At-a-glance earnings across all sources + headline stats; entry points to each section.
/earnings Earnings Breakdown by source: license fees, tips, model-access sales, cosmetic sales. Time-series + totals. Links to CP cash/withdrawal.
/earnings/analytics Basic analytics Model usage that drives fees (generations per resource, downloads, engagement over time). v1 priority — keep "basic" for v1; richer analytics is post-v1.
/licensing Licensing fees (bulk editor) Table of the creator's models/versions; multi-select; set/clear fee; apply model-type default suggestions (LoRA ~0.1, base ~1 buzz/image); fractional pricing. (Bulk editor may trail the per-version editor on /models§8.)
/models Model management Grouped by model (versions nested, drafts included). Per-version: full early/paid-access config, licensing-fee on/off + amount (members only), "sell access indefinitely" for CP members, and (2nd-priority) publish/schedule. v1 priority. See models.md.
/settings Payout & settings Payment config (Tipalti) status, membership/tier status, per-account default fee suggestions.
/join Membership upsell Shown to non-members (any logged-in user can reach the Studio): what the member tier unlocks + link to subscribe. (Tier vs CP gate to confirm — §5.2.)

Public/allowlisted (pre-gate): /favicon.svg, health check.

Shop pages are not in this app's v1. Shop management (/shop, /shop/items/[id]) is being built in the main app by another dev and transferred into Creator Studio later; routes reserved but not built here now.

Navigation. Desktop = sidebar, mobile = header nav — both come from @civitai/ui's shadcn sidebar + sheet + is-mobile hook (the responsive collapse is largely built in). Both navs render from a single app-local constant (apps/creator-studio/src/lib/nav.ts{ href, label, icon, memberOnly? }[]) that mirrors this table, so the two can't drift and adding a page is a one-line change. It's app-local config (one app → a module, not a package). Notes: match active state carefully for nested routes (/earnings vs /earnings/analytics); memberOnly lets both navs conditionally show/disable member-gated items off the user's tier (ties into the §5.2 gate).


4. Feature → workstream map

# Workstream Built today? v1 work
1 Retire 25% comp Live (to sunset) Post-v1 cutover track (not this app's v1): stop minting Compensation rows; remove comp UI; keep tips + license-fee payout paths
2 Creator-controlled licensing fees Mostly built Fractional pricing, member-tier gating, bulk edit (new), default suggestions
3 Sell model access (no time cap) Partially (score-capped EA) Remove time/qty caps for members; expose toggle in Studio
4 Creator Shops Being built in the main app (by another dev) Not this app's v1 — transferred into Creator Studio later; cosmetics 70/30; Shopify merch is fast-follow
5 Creator Studio Net-new This whole app

5. API / service plan

5.1 The core architectural decision — where does business logic run?

Starting point: the money ledger is already a separate service. createBuzzTransaction doesn't write a local table — it POSTs to BUZZ_ENDPOINT (buzzApiFetch('/transaction'), buzz.service.ts:383). The buzz service is a standalone .NET / ASP.NET Core Minimal API on PostgreSQL (C:\work\civitai-buzz) with ClickHouse only as a post-commit tracking sink. So the ledger is not something to extract — it's already external, and every app is a client of it.

What lives in the main app is monetization orchestration, not the ledger: validate → call the buzz service → co-write domain rows in the same flow (early-access purchase mints the buzz transaction and an EntityAccess grant + version meta), plus fee rules, stacking, split recipients, tier gating — all over ModelVersion / CustomerSubscription.

Decision (agreed with the team): server-side, in-process; Studio runs mutations itself — no main-app tRPC hop. One new package (@civitai/buzz); monetization writes are a creator-studio module, not a package yet (package discipline, §2).

@civitai/buzz        (PACKAGE — built) server-side client for the buzz service. Earns package status: the main app
                     already uses it broadly AND creator-studio needs it. Lifted out of buzz.service.ts.
                     SERVER-ONLY — no browser entry; all buzz comms stay server-side.
monetization module  (creator-studio MODULE — apps/creator-studio/src/lib/server/monetization/, NOT a package yet)
                     the creator ops: setLicensingFee, bulkSetLicensingFee, setUnlimitedAccess + member-tier gate.
                     Deps: @civitai/db (kysely) + @civitai/buzz. NO ClickHouse. NO stacking (backend handles it).

Why a module, not a package: in v1 only creator-studio writes fees (the main app keeps its own inline version), so the ops have a single caller — no cross-app sharing yet, so no package. They graduate to a package only at the consolidation (full cutover), when the main app adopts the same code (§9).

Imported only in server code — in creator-studio that's +page.server.ts, form actions, +server.ts, hooks.server.ts. The browser calls creator-studio's own server, which runs the operation in-process:

BROWSER ─► creator-studio SERVER ─► monetization module ─► @civitai/buzz ─► buzz service (.NET)
 (no buzz)   (SvelteKit load/actions)   (kysely domain writes)   (package)

Why in-process (module/package), not a service or a tRPC hop:

  • A standalone monetization service is the wrong shape: the orchestration co-writes main-app domain tables, so a service would need those tables (moving the domain, not payments) or add distributed-transaction failure modes on money code. Running in-process keeps the buzz-call ↔ domain-write co-transactionality inside each consumer.
  • @civitai/buzz earns package status (main app uses it broadly + creator-studio); the monetization ops do not yet (one caller in v1) — extract them to a package when the main app adopts them, not before.

Cost note: the eventual package extraction (at consolidation) means porting the moved functions Prisma→kysely and repointing the main app's live call sites — money-critical. But v1's creator-side writes are new, small, and single-app, so there's no extraction on the critical path (Q1 decided minimal — §9).

Earnings/analytics reads — ClickHouse, not the buzz service (Justin):

All earnings + analytics read from ClickHouse, not the buzz service — querying the buzz service for dashboards is too slow. Buzz earnings already land in ClickHouse, and aggregate tables like resourceCompensations are already daily aggregates — the right source. Design for scale from materialized views / daily records, never individual rows, and audit which mat views exist vs. need adding (§7.6). Creator Studio reads via @civitai/clickhouse (a required v1 dep). (The buzz client's report endpoints exist but are not the analytics path.)

5.2 Reuse (existing main-app endpoints/services)

Need Existing surface
Is user a paying member (by tier)? Active subscription tier: CustomerSubscriptionProduct.metadata.tier (bronze/silver/gold). ⚠️ creatorProgram.getCreatorRequirements also gates on creator score ≥40k (full CP membership) — a stricter bar; use it only if the gate is meant to be CP-membership, not tier alone. Confirm which (Justin said "tier," HackMD said "CP members" — see §9 "to confirm"). creator-program.service.ts:205
Earnings + analytics (all) ClickHouse via @civitai/clickhouse — buzz earnings + resourceCompensations aggregates live here; the buzz service is too slow for this. Work from materialized views / daily records (§7.6)
CP cash / banked / pool creatorProgram.getCash / getBanked / getCompensationPool (creator-program.router.ts)
Set a licensing fee (single) modelVersion upsert — licensingFee* fields (model-version.schema.ts:418), flag licensing-fee
Early/paid access config + purchase modelVersion.earlyAccessPurchase, earlyAccessModelVersionsOnTimeframe (model-version.router.ts)
Cosmetic purchase + 70/30 split cosmeticShop.purchaseShopItem; split via computeCreatorShopSplit (creator-shop.schema.ts) paying Cosmetic.createdByIdTransactionType.Sell (cosmetic-shop.service.ts, purchaseCosmeticShopItem)

5.3 New monetization operations (creator-studio module; extract to a package at consolidation)

These are module functions in creator-studio, not main-app tRPC procedures — creator-studio calls them from its own server. The main app keeps its own inline fee/access logic for v1; the two converge onto one shared implementation only at the consolidation (§9 "full cutover").

Operation Why
setLicensingFee / bulkSetLicensingFee Per-version fee set (member-only) + bulk edit across many versions — no bulk path exists today.
getDefaultFeeSuggestions Model-type default suggestions (LoRA ~0.1, base ~1 buzz/image).
setUnlimitedAccess Remove EA time/quantity caps for members.
fee validation (member gate + fractional) Enforce member tier + fractional bounds when setting a fee. Stacking/split is NOT here — the backend already handles it (§7.3).
member-tier gate Authorization asserted inside the module on member-only operations (and reused when the main app adopts it at consolidation).

Earnings reads are not package operations — all analytics come from ClickHouse via @civitai/clickhouse (§5.2, §7.6). Per-creator shop CRUD is out of scope (main-app shop work owns it, see §1).


6. Main-app code to REMOVE

Strategy (per decision): redirect-first. In v1 we do not delete the superseded main-app code. We (a) redirect the relevant routes to creator.civitai.com, and (b) mark each block for deletion with // TODO(creator-studio): remove after Creator Release cutover so a later phase can safely rip it out. The 25%-comp payout change is the one behavioural change that lands at cutover.

6.1 Retire 25% generation compensation (behavioural — at cutover)

File What
src/server/jobs/deliver-creator-compensation.ts:150 Stop minting Compensation-type BuzzTransaction rows at cutover; keep tip + licenseFee payout paths. This is the real sunset.
src/store/tip.store.ts:4 creatorTip: 0.25 default → remove creator-tip default.
src/components/generation_v2/FormFooter.tsx:150 Remove creatorComp && hasCreatorTip tip line from cost calc.
src/components/ImageGeneration/GenerationForm/GenerationCostPopover.tsx:108 Remove creator-tip rate UI.
src/components/Buzz/Rewards/DailyCreatorCompReward.tsx Remove the "Compensation" tab; License-Fees view moves to Studio /earnings.
src/pages/user/buzz-dashboard.tsx:77 Remove creatorComp-gated comp section; redirect creator-earnings entry to creator.civitai.com/earnings.
src/server/services/feature-flags.service.ts Retire the creatorComp flag once UI is gone.

6.2 Redirect superseded management surfaces

Main-app surface Redirect target
Buzz dashboard creator-earnings section (buzz-dashboard.tsx) creator.civitai.com/earnings
Licensing-fee editing in ModelVersionUpsertForm.tsx:800 Keep (per Justin) — inline fee editing stays as an additional surface until model management is fully deprecated from the main site. Not redirected/removed in v1; optionally add a "manage all fees" link → creator.civitai.com/licensing.
Any "creator score → early access caps" management copy (constants.ts:1708) Superseded by member "sell indefinitely"; keep score caps for non-members, add Studio link.

Every redirect/removal block gets the TODO(creator-studio) marker so the post-release cleanup is a grep away.


7. New packages, schema & cross-team work

What has to be built/changed outside Creator Studio's own UI for it to work.

7.1 Schema / data (main-app DB)

Change Detail
Fractional licensing fee ModelVersion.licensingFee is Int today (schema.full.prisma:1042, MAX_LICENSING_FEE=100). Justin confirmed fees as small as 1 buzz per 100 images = 0.01 buzz/image, so we need decimal with 0.01 precision. Migrate to numeric/decimal; settle sub-buzz amounts at the daily payout boundary (not floored per transaction) in deliver-creator-compensation.ts. Manual migration (repo does not run prisma migrate deploy).
Access "unlimited" flag Represent "available for sale indefinitely, no time/quantity cap" on the model version (extend earlyAccessConfig or add a field) so Creator Program members (per Justin) bypass scoreTimeFrameUnlock/scoreQuantityUnlock. Needs main-app support + studio integration (models.md).
Licensing-fee "active" flag (per Justin) A member's fee must auto-pause when they have no active membership — the set value is never removed, only whether it applies. The mini endpoint (src/pages/api/v1/model-versions/mini/[id].ts, where Koen resolves the fee) checks active membership on hit to decide whether the fee applies, and the flag drives whether the fee shows on the model card. Add an active/enabled flag on the version's licensing fee. The pre-cutover "not-yet-payable" flag (§1) is the same kind of gate for the launch window.

Per-creator shop schema (owner + submission/approval on CosmeticShopSection/CosmeticShopItem) is owned by the main-app shop work, not this plan.

7.2 New package + creator-studio modules

  • @civitai/buzz (package, built) — server-side client for the buzz service, lifted out of buzz.service.ts; both apps import it. Server-only, no browser entry. (The one thing that earns package status now — §2.)
  • Monetization module (creator-studio, not a package yet) — the §5.3 ops, built on @civitai/db (kysely) + @civitai/buzz, authorization inside, no ClickHouse. Lives in apps/creator-studio/src/lib/server/monetization/.
  • Analytics reads module (creator-studio, not a package) — ClickHouse reads via @civitai/clickhouse (§7.6).
  • Consolidation (post-v1, not v1) — at the full cutover, extract the monetization module into a shared package and repoint the main app's inline fee/access logic (model-version.service.ts) onto it so it exists once. Money-critical (Prisma→kysely port + dep untangling); deferred off v1's path (§9).

7.3 Fee stacking — already handled (no new work)

Per Justin, stacking is already handled on the backend — a derivative fee stacking on the base model's fee is a backend concern, not something creators do. There is nothing for creators to do here beyond setting their own licensing fee. So the earlier "must implement additive stacking + split payout" is not Creator Studio work, and it does not land in the monetization module. (Eng note: the lineage/base fee now comes from a derivative's explicit parent root (licensingSourceVersionId → its LicensingRoot), stacked on top of the creator's own per-version fee — the two are independent, so a creator can always set their own fee. The old BaseModelLicensingFee fallback that this note used to worry about no longer exists.)

7.4 Cross-team dependencies & coordination

The design has cross-team dependencies — surface these early (likely with Koen / backend):

  • ClickHouse / data — the earnings source split (compensation / licenseFee) already exists in orchestration.resourceCompensations (tips + access/cosmetic sit in other tables — see §7.6 / A5); the real add is the owner-keyed earnings rollup (§7.6). (The earlier concern about tagging the buzz-service report endpoints is moot — analytics read from ClickHouse, not the buzz service.)
  • Main-app owner — sign-off on the §7.2 consolidation refactor: repointing live, money-critical fee/access call sites onto the extracted monetization package at the cutover.
  • Buzz-service owner (C:\work\civitai-buzz, .NET) — only if v1 shows access/cosmetic-sale earnings by source (those are buzz transactions — §7.6 gap #2).

7.5 Engineering to-dos (not Justin — eng verification)

  • Earnings by source is partly available: orchestration.resourceCompensations.source = comp / licenseFee only (CH audit 2026-07-14 — no tip there; tips live in default.buzz_resource_compensation, access/cosmetic are buzz txns → A5). The remaining gaps are the owner-keyed rollup and unioning A1+A5 sources (§7.6).
  • The earlyAccessPurchase() dependency-map is now optional — with Q1 minimal and the extraction deferred to the post-v1 consolidation, it's off any critical path. Do it when scoping the consolidation.

7.6 ClickHouse analytics — materialized views

Analytics + earnings read from ClickHouse via @civitai/clickhouse (required v1 dep), from daily aggregates, never individual rows (the buzz service is too slow). Inventory from a live audit (2026-07-01):

Exists — reuse (all keyed by modelVersionId, date):

Need Table
Earnings by resource + source (comp / licenseFee — no tip here) orchestration.resourceCompensations (SummingMergeTree; +accountType, source)
Earnings mirror (comp/tip split) default.buzz_resource_compensation
Generations per resource default.daily_resource_generation_counts
Downloads default.daily_downloads, daily_downloads_unique, modelVersionUniqueDownloads
Model/version metrics default.entityMetric* suite

Needs adding — the gap is the creator/owner dimension. Every earnings aggregate is keyed by modelVersionId, never the owner's userId (the userId columns that exist — daily_user_resource, userModelDownloads — are the generator/downloader, not the creator). So "creator X's earnings" needs modelVersion → ownerUserId:

  1. Owner-keyed earnings rollup — an MV aggregating (ownerUserId, date, source) → sum(amount) off resourceCompensations (+ a modelVersion → ownerUserId dictionary in CH). Scales better than app-side WHERE modelVersionId IN (…), which balloons for prolific creators. Full spec + open decisions: owner-rollup-handoff.md. Two design decisions flagged there (CH audit 2026-07-14): (D1) the key likely needs accountType/currency so buzz-color vs cash stay separable (B8); (D2) resourceCompensations holds comp + license only — tips + access/cosmetic come from other MVs (A5), so the /earnings source filter must union A1+A5 on a shared source vocabulary. Also: amount is fractional — the MV must not FLOOR it.
  2. Per-creator access/cosmetic-sale earnings (only if v1 needs them) — those are buzz transactions, not in resourceCompensations; buzz.transactions_daily_stats is platform-wide (no account dimension). Would need a per-toAccountId daily buzz-earnings-by-type MV.

Shared read logic: the CH client (@civitai/clickhouse) is already shared by both apps. The creator-earnings query logic is new in Creator Studio and legacy in the main app (getDailyCompensationRewardByUser, being redirected). Consolidating both onto one server-side read module is a full-cutover item (§9) — worth it so both domains show identical numbers during the transition.


8. Phasing

Ordering reflects the designer steer: model management + analytics lead. The shop is not this app's job (main-app dev owns it; transfers in later). The comp⇆fee cutover is a separate backend track — Creator Studio v1 ships ~1 week before it (Justin), so comp-retirement is not a v1 item.

v1 — MVP (designer-prioritised)

  1. App shell — scaffold apps/creator-studio, auth spoke gate (any authenticated user) + member-tier action gating, @civitai/ui, dashboard.
  2. Buzz package + monetization module@civitai/buzz (client, built) + a creator-studio monetization module (creator ops: setLicensingFee / bulkSetLicensingFee / setUnlimitedAccess + member-tier gate; no stacking — backend handles it) built on @civitai/db + @civitai/buzz (§7.2). No main-app repoint in v1 — that's the consolidation (post-v1).
  3. Model management /models: per-version early/paid-access toggles, per-version licensing-fee on/off + amount (members only; non-members cannot set), "sell access indefinitely" for CP members.
  4. Analytics /earnings/analytics + /earnings: read from ClickHouse (daily aggregates / materialized views, §7.6) — not the buzz service.
  5. Licensing-fee mechanics — fractional pricing (schema migration + daily-boundary settlement) + the membership-gated "active" flag and pre-cutover "not-yet-payable" flag (§7.1). No stacking work (backend). Bulk editing / default suggestions can trail the per-version editor.

Not in v1 (separate backend track): retiring 25% comp + activating fees flips ~1 week after v1 ships (§1).

Post-v1 — fast-follow / later

  • Comp⇆fee cutover (~1 week after v1) — flip fees live + stop minting Compensation rows (§6.1); pre-cutover fees become payable.
  • Transfer the Creator Shop into Creator Studio — the main-app dev's shop (cosmetics, 70/30) moves here; Shopify merchandise trails that (blocked on Shopify token).
  • Bulk licensing-fee editor + model-type default suggestions — if not landed in v1.
  • Delete the redirect-marked main-app code (the TODO(creator-studio) sweep).
  • Broaden the Studio beyond monetization (model upload, posts, profile settings migration) — only if the "creator hub" direction is later chosen.
  • Richer analytics (cohorts, per-model funnels), cash-flow forecasting.

9. Decisions & open questions

Decided (Justin, 2026-07-01)

  • Access model. Any logged-in user can access Creator Studio; member-only items/actions are gated on the user's subscription tier. → app gate is "authenticated," not CP-only (§1, §2).
  • Timeline. Launch by end of month (~2026-07-31); Justin is working the orchestrator side with Koen.
  • Fractional pricing. Fees as small as 1 buzz per 100 images = 0.01 buzz/image. → migrate licensingFee to decimal (0.01 precision), settle at the daily payout boundary (§7.1).
  • Shop. Being built in the main app by another dev; transferred into Creator Studio later. → out of this app's v1; all shop questions deferred to that work.
  • Infra. creator.civitai.com as an auth spoke — confirmed.
  • Service topology (eng decision). Buzz ledger is already an external service. No standalone monetization service. Server-side, in-process: one new package@civitai/buzz (buzz-service client, built) — plus creator-studio modules for monetization writes (kysely + buzz; auth inside) and analytics reads (ClickHouse); Studio runs mutations itself with no main-app tRPC hop. Modules graduate to packages only at the consolidation, per the package bar (§2). Analytics read from ClickHouse (Q6 below), not the buzz service (§5.1). What remains for Justin is Q1's timeline/scope tradeoff, not the topology.
  • Fee stacking (Q2). Already handled on the backend — creators only set their fee; no stacking/split work in Creator Studio (§7.3).
  • Membership gating (Q3). Fees auto-pause when a member lapses; the set value is kept, only its application is gated. The mini endpoint checks active membership on hit; add an active flag on the version's fee for card display (§7.1).
  • Inline fee editing (Q4). Stays on the main app as an additional surface until model management is fully deprecated there (§6.2).
  • Cutover timing (Q5). The comp⇆fee cutover is a separate backend track; Creator Studio v1 ships ~1 week before it. Pre-cutover fees carry a "not-yet-payable" flag (§1, §8).
  • Analytics source (Q6). All analytics/earnings read from ClickHouse (daily aggregates / materialized views), not the buzz service (too slow). ClickHouse is a required v1 dep (§7.6).

Decided — Q1: minimal monetization scope for v1

The creator-studio monetization module ships the minimal creator-side writes onlysetLicensingFee / bulkSetLicensingFee with a Creator Program membership gate (decided 2026-07-09) and the active flag (§7.1), plus setUnlimitedAccess (same shape). These are plain ModelVersion writes — no buzz call, no domain co-write. The risky buyer-side paths (earlyAccessPurchase, cosmetic purchase) stay in the main app; they don't move for v1. It's a module, not a package (single caller in v1 — §2); with the cutover decoupled (Q5) the extraction is off the critical path, so the dependency-map (§7.5) is no longer a blocker.

Member gate — DECIDED (2026-07-09)

  • Full Creator Program membership is the single bar for ALL member-only actions (fee-set and sell-indefinitely) — not a tier-only or feature-specific split. Rationale: tier-only would let a brand-new bronze subscriber fee-gate other people's models; CP requires creator score. Resolved from the onboarding CP flag. See questions-justin-product.md B1 / pre-implementation-decisions.md B1.

Questions for Justin — review pass (2026-07-02) · ANSWERED 2026-07-09

Full responses in questions-justin-product.md (B1B11) and the reconciled pre-implementation-decisions.md. Notable changes from the assumptions below: single CP-membership gate (Q1), indefinite-sale = uncapped early access (Q2), all earnings sources in v1 (Q3), two-section analytics (Q4), notify on fee pause (Q5), bulk editing is v1 (Q9).

Consolidated product/business calls for Justin's doc review. (Eng/design-owned items — charting lib, /licensing separate-vs-mode, owner-keyed rollup, etc. — are in creator-studio/README.md.)

  1. Feature-specific gates? Confirm the two bars differ: licensing fee gates on subscription tier; indefinite-sale gates on CP membership (see the "To confirm" item above).
  2. Indefinite-sale mechanics. How does "available for sale indefinitely" actually work — a one-time purchase at a creator-set price? How does it relate to early-access pricing (replaces / stacks / separate)? New and underspecified; needs main-app support (§7.1).
  3. v1 earnings sources. Show all sources (incl. model-access + cosmetic sales — need a new buzz rollup, §7.6 gap #2), or ship comp / license / tip only in v1 and add the rest later? (earnings.md)
  4. "Basic analytics" scope. Lock the v1 metric/chart list (proposed: generations-over-time, downloads-over-time, a top-models table, a few stat tiles) so it doesn't balloon (analytics.md).
  5. Fee auto-pause → notify? When a fee silently pauses because membership lapsed (§7.1), is the creator notified (in-app / email)? A silent pause = lost income = support tickets. Do we need notifications in v1 (fee paused, payout ready)?
  6. Cutover creator comms / grandfathering. When 25% comp retires (~1 week after v1), what's the creator-facing story, and is there any grandfathering / transition for creators mid-accrual?
  7. Max licensing fee. Floor is 0.01 buzz/image (confirmed). Is the cap still 100 buzz/image (MAX_LICENSING_FEE) with fractional pricing, or does it change?
  8. Studio discoverability. How do creators find creator.civitai.com — a nav link from the main app for everyone, only users with models, or a launch announcement? (Shapes the non-member / /join experience.)
  9. Publish/schedule + bulk fee editor — v1 or fast-follow? Both are flagged "2nd priority" / "may trail" (models.md, licensing.md) — confirm which land in v1.
  10. Currency display. Buzz-only in v1, or show USD equivalents for cash earnings (dashboard / earnings)?
  11. Default fee suggestions. Confirm the values (LoRA ~0.1, base ~1 buzz/image) and which model types get one.

For the full cutover (post-v1 notes)

  • Extract the monetization module into a package and repoint the main app's own fee-set call sites onto it so there's one implementation (today ModelVersionUpsertForm → main-app service writes the fee; that stays until this consolidation). This is when the module graduates to a package (second app now imports it — §2).
  • Consolidate earnings/analytics reads — the main app's legacy getDailyCompensationRewardByUser (buzz.service.ts:1142) and Creator Studio's new ClickHouse reads should resolve to one server-side read module so civitai.com and creator.civitai.com show identical numbers during the transition (§7.6).
  • Add the owner-keyed earnings rollup in ClickHouse (§7.6).
  • Flip comp-retirement + fee activation together (the cutover, ~1 week after v1); make pre-cutover fees payable.
  • Delete the redirect-marked main-app code (the TODO(creator-studio) sweep).

Appendix — key code references

  • Comp payout: src/server/jobs/deliver-creator-compensation.ts (daily 02:00 UTC; tip|compensation|licenseFee sources)
  • Licensing fee: ModelVersion.licensingFee*, ModelVersion.licensingSourceVersionId (a derivative's explicit parent root), LicensingRoot table (root membership + isDefault; replaces the old LicensingRoot flag + BaseModelLicensingFee pointer), ModelVersionFlag.NotDerivative (non-derivative versions skip parent attribution), MAX_LICENSING_FEE=100 (model-version.schema.ts), flag licensing-fee. Fee resolution: root → own fee; explicit parent → its fee; no (baseModel, modelType) fallback.
  • Early access: earlyAccessConfig/earlyAccessEndsAt (schema.full.prisma:1035), score caps (constants.ts:1708-1738), earlyAccessPurchase() (model-version.service.ts:1481)
  • CP membership: getCreatorRequirements() (creator-program.service.ts:205), MIN_CREATOR_SCORE=40000, OnboardingSteps.CreatorProgram=16
  • Cosmetic shop: platform-owned /shop; purchaseCosmeticShopItem() split (cosmetic-shop.service.ts:619); CRUD is moderator-only (cosmetic-shop.router.ts)
  • Spoke wiring: docs/packages/new-app-integration.md, docs/auth/spoke-integration-guide.md, packages/civitai-ui/README.md, skill scaffold-civitai-app