# StudyFlow Billing and Entitlements Plan

Last updated: 2026-03-19

This file turns the pricing direction into an implementation-ready billing and entitlement model.

It answers:

- what each subscription tier unlocks
- what gets limited monthly
- what gets blocked when limits are reached
- what tables and endpoints are needed
- where billing logic should live

## Product Rule

Billing should be enforced as product capabilities, not as a loose marketing promise.

That means:

- the app must know the user’s current plan
- the app must know remaining monthly allowances
- the backend must enforce limits before expensive actions run
- the UI should explain what is locked, what remains, and when usage resets

## Pre-Subscription Account State

The product should support a signed-in account with no active paid plan.

This state is important because it lets users:

- create an account
- enter the app shell
- see onboarding and pricing in context
- understand the product before paying

Recommended capabilities for a no-plan account:

- access to `/app`
- access to account/settings
- access to billing and upgrade flows
- one limited-size personal document upload
- one deterministic friendly HTML workspace generated from that document

Recommended restrictions for a no-plan account:

- no second personal document upload after the trial upload is used
- no private AI features by default
- no grounded chat on user-owned documents by default
- no study-pack generation
- no infographic generation

Operational rule:

- a no-plan account should consume at most one small deterministic transform entitlement
- that trial should not silently spill into paid AI usage
- the exact trial file-size cap is still open and should be decided before implementation
- it should function as an onboarding state, not a hidden free AI tier

## Recommended Plans

### Basic

Price:

- `$5/month`

Includes:

- friendly HTML workspace
- grounded AI chat
- authenticated dashboard and document history

Does not include:

- AI study-pack generation

Suggested monthly limits:

- `25` document uploads
- `300` grounded chat messages
- `90-day` document retention

Recommended chat model:

- `gemini-2.5-flash-lite`

### Plus

Price:

- `$9/month`

Includes:

- everything in Basic
- AI study-pack generation
- faster processing queue

Suggested monthly limits:

- `40` document uploads
- `600` grounded chat messages
- `15` study-pack credits
- `180-day` retention

Recommended models:

- grounded chat: `gemini-2.5-flash-lite`
- study-pack generation: `gemini-2.5-flash-lite` or `gemini-2.5-flash`

### Ultra

Price:

- `$12/month`

Includes:

- everything in Plus
- deeper study-pack generation
- smarter model on premium actions only
- higher priority processing
- premium infographic-style study visual generation

Suggested monthly limits:

- `50` document uploads
- `1,000` grounded chat messages
- `8` deep study-pack credits
- longer retention

Recommended models:

- grounded chat: `gemini-2.5-flash-lite` or `gemini-2.5-flash`
- deep study-pack generation: `gemini-2.5-pro`

Important constraint:

- Ultra should not use the smart model for every chat turn by default
- the expensive model should be reserved for premium actions only

## What Must Be Metered

The backend should track at least these counters:

### 1. Document uploads

Counts each successfully accepted user document.

Use for:

- Basic
- Plus
- Ultra

### 2. Grounded chat messages

Counts each user message sent to grounded chat.

Use for:

- Basic
- Plus
- Ultra

### 3. Study-pack credits

Counts each standard study-pack generation.

Use for:

- Plus

### 4. Deep study-pack credits

Counts each premium study-pack generation that uses the smarter model path.

Use for:

- Ultra

### 5. Infographic credits

Counts each generated infographic or visual study asset.

Use for:

- Ultra by default
- possibly Plus later if pricing supports it

### 5. Active retention / storage policy

Not necessarily billed as a counter, but enforced by plan.

Use for:

- document cleanup
- old workspace cleanup
- inactive conversation cleanup

## Usage Consumption Rules

Define usage consumption with one simple system:

1. entitlement check first
2. reservation second
3. consume on successful completion only
4. release reservation on failure or timeout

This prevents:

- charging for failed work
- double consumption on retries
- async race conditions that burn more than one unit

### Idempotency rule

Every metered action should have a stable idempotency key that represents one user intent.

Examples:

- no-plan trial transform: `userId:trial-transform`
- paid upload: `userId:fileHash`
- grounded chat: `conversationId:clientMessageId`
- study-pack generation: `documentVersionId:mode:workspaceVersionId`
- infographic generation: `documentVersionId:topicId:visualType`

If the same action is retried with the same idempotency key, the backend should return the existing result or existing reservation state instead of consuming usage again.

### No-plan trial transform

Consume the one trial entitlement when:

- the file passes validation
- extraction succeeds
- the deterministic workspace is successfully persisted

Do not consume it when:

- the file is rejected before processing
- extraction fails
- rendering fails before a workspace is saved

### Paid document upload quota

Consume one upload unit when:

- the file passes validation
- extraction succeeds
- a valid source document record is created

Do not consume one upload unit when:

- the file is rejected before processing
- upload transport fails
- extraction fails before a usable source document exists

### Grounded chat messages

Consume one grounded chat message when:

- entitlement checks pass
- retrieval succeeds
- the final answer is successfully generated and persisted as a conversation turn

Do not consume a message when:

- entitlement checks fail
- retrieval fails before answer generation
- model generation fails and no answer is stored

### Standard study-pack credits

Consume one standard study-pack credit when:

- the study-pack result is successfully generated and persisted

Do not consume a credit when:

- entitlement checks fail
- generation fails before persistence
- the same idempotent action is retried

### Deep study-pack credits

Consume one deep credit when:

- the deep-mode result is successfully generated and persisted

Do not:

- consume a deep credit on failed generation
- silently fall back to standard mode and consume a different credit without telling the user

### Infographic credits

Consume one infographic credit when:

- the topic has already passed infographic eligibility
- entitlement checks pass
- the final infographic asset is successfully generated and persisted

Do not consume a credit when:

- the topic is not eligible
- the user is blocked by plan or exhausted credits
- generation fails before a final asset is saved

### Reservation timeout rule

Reservations should expire automatically if the workflow never completes.

Suggested starting windows:

- chat: `2 minutes`
- study-pack: `30 minutes`
- infographic: `30 minutes`

Expired reservations should not count as consumed usage.

### User-visible promise

The product should say this plainly:

- uploads count only after a valid document is processed
- AI credits count only after successful generation
- failed attempts do not burn credits

## What Should Be Hard-Blocked

When the user is out of allowance:

- upload should be blocked if document quota is exhausted
- chat should be blocked if message quota is exhausted
- study-pack generation should be blocked if study-pack credits are exhausted
- deep study-pack generation should be blocked if deep credits are exhausted
- infographic generation should be blocked if infographic credits are exhausted

Do not silently downgrade the user into a cheaper model without saying so.

Either:

- block and explain
- or offer upgrade / add-on credits

When the user has no active paid plan:

- upload should be allowed only if the single trial upload has not been used and the file is under the trial cap
- grounded chat should remain blocked by default
- study-pack generation should be blocked
- infographic generation should be blocked

Show:

- whether the user still has the one trial transform available
- why AI features are unavailable
- which plan unlocks it
- a direct upgrade CTA

## Recommended Product Behavior

### When Basic user clicks study-pack generation

Show:

- locked feature explanation
- upgrade CTA to Plus

### When Plus user runs out of study-pack credits

Show:

- monthly credit exhausted
- next reset date
- upgrade CTA to Ultra or add-on pack later

### When Ultra user asks for deep mode without deep credits

Show:

- deep credits exhausted
- offer standard study-pack if still available

### When user asks for infographic generation without credits or plan access

Show:

- whether the feature is locked to a higher plan or simply exhausted for the current period
- reset date if exhausted
- upgrade CTA if locked

## Where Billing Logic Should Live

### Should live in:

- app/backend layer
- `studyflow-auth` or a dedicated billing module
- `studyflow-db-api` for persisted usage state

### Should not live in:

- n8n
- frontend-only checks

Reason:

- entitlements are account state
- workflows should receive already-authorized actions

## Recommended Billing Architecture

Use four layers:

### 1. Stripe subscription state

Source of truth for:

- active plan
- billing period
- cancellation state
- renewal status

### 2. Local entitlements state

Source of truth for:

- what the user can do right now
- feature flags
- limits
- reset windows

### 3. Usage ledger

Source of truth for:

- what was consumed
- when
- by which action

### 4. Enforcement layer

Source of truth for:

- allow
- deny
- upgrade prompt

## Recommended Tables

### `subscriptions`

- `id`
- `user_id`
- `provider`
- `provider_customer_id`
- `provider_subscription_id`
- `plan_code`
- `status`
- `current_period_start`
- `current_period_end`
- `cancel_at_period_end`
- `created_at`
- `updated_at`

### `plan_entitlements`

Static or semi-static mapping table.

- `id`
- `plan_code`
- `monthly_document_upload_limit`
- `monthly_grounded_chat_limit`
- `monthly_study_pack_limit`
- `monthly_deep_study_pack_limit`
- `monthly_infographic_limit`
- `retention_days`
- `priority_level`
- `chat_model`
- `study_pack_model`
- `deep_study_pack_model`
- `infographic_model`
- `created_at`
- `updated_at`

### `usage_counters`

One row per user per billing period.

- `id`
- `user_id`
- `subscription_id`
- `period_start`
- `period_end`
- `documents_used`
- `grounded_chat_messages_used`
- `study_packs_used`
- `deep_study_packs_used`
- `infographics_used`
- `updated_at`

### `usage_events`

Append-only ledger for auditability.

- `id`
- `user_id`
- `subscription_id`
- `event_type`
- `document_id`
- `conversation_id`
- `run_id`
- `units`
- `metadata_json`
- `created_at`

Event type examples:

- `document_uploaded`
- `grounded_chat_message`
- `study_pack_generated`
- `deep_study_pack_generated`
- `infographic_generated`

## Recommended Backend Endpoints

### Auth / account layer

#### `GET /billing/me`

Returns:

- active plan
- billing status
- renewal / reset date

Example:

```json
{
  "plan": "plus",
  "status": "active",
  "currentPeriodEnd": "2026-04-19T00:00:00Z",
  "cancelAtPeriodEnd": false
}
```

#### `GET /billing/entitlements`

Returns:

- current plan capabilities
- remaining monthly allowances

Example:

```json
{
  "plan": "plus",
  "limits": {
    "documents": 40,
    "groundedChatMessages": 600,
    "studyPacks": 15,
    "deepStudyPacks": 0
  },
  "used": {
    "documents": 8,
    "groundedChatMessages": 122,
    "studyPacks": 3,
    "deepStudyPacks": 0
  },
  "remaining": {
    "documents": 32,
    "groundedChatMessages": 478,
    "studyPacks": 12,
    "deepStudyPacks": 0
  },
  "currentPeriodEnd": "2026-04-19T00:00:00Z"
}
```

#### `POST /billing/webhooks/stripe`

Purpose:

- receive Stripe events
- update local subscription state

### Usage enforcement layer

#### `POST /entitlements/check`

Purpose:

- check whether an action is allowed before expensive processing starts

Input:

```json
{
  "userId": "user_123",
  "action": "grounded_chat_message"
}
```

Output:

```json
{
  "allowed": true,
  "plan": "basic",
  "remaining": 122
}
```

### Usage accounting

#### `POST /usage/record`

Purpose:

- record successful usage after an action completes

Input:

```json
{
  "userId": "user_123",
  "action": "study_pack_generated",
  "documentId": "doc_456",
  "runId": "doc_456-1710864000000",
  "units": 1
}
```

## Where To Enforce Limits In The Product

### Before upload

Check:

- document upload quota

### Before grounded chat call

Check:

- grounded chat quota

### Before standard study-pack generation

Check:

- plan allows study packs
- study-pack credits remain

### Before deep study-pack generation

Check:

- plan allows deep mode
- deep credits remain

### Before infographic generation

Check:

- plan allows infographic generation
- infographic credits remain

## n8n Boundary

`n8n` should receive only already-authorized actions.

That means:

- app/backend checks entitlement first
- then app/backend triggers workflow
- then successful completion records usage

Do not make `n8n` the primary billing-enforcement engine.

At most, `n8n` can help write usage events after a successful workflow completes.

## Recommended Reset Model

Use subscription-period resets, not calendar-month resets.

Reason:

- aligns with Stripe billing periods
- easier to explain
- easier to audit

Each user’s quota resets on their own billing renewal date.

## Upgrade / Downgrade Rules

### Upgrade

Should apply immediately or at least within the current period for:

- unlocked features
- higher limits

### Downgrade

Should apply at next billing period by default.

Important edge case:

- if a user has more stored documents than the lower plan allows, do not delete data immediately
- instead:
  - freeze new uploads
  - keep read access
  - prompt upgrade or cleanup

## Best Initial Implementation

For MVP billing, implement:

1. Stripe subscriptions
2. local subscription mirror table
3. usage counters
4. usage event ledger
5. entitlement check endpoint
6. entitlements summary endpoint
7. feature gating in the app UI

That is enough to support:

- Basic
- Plus
- Ultra

without overbuilding.

## Concrete Initial Mapping

### Basic

- `documents/month = 25`
- `grounded_chat_messages/month = 300`
- `study_packs/month = 0`
- `deep_study_packs/month = 0`

### Plus

- `documents/month = 40`
- `grounded_chat_messages/month = 600`
- `study_packs/month = 15`
- `deep_study_packs/month = 0`

### Ultra

- `documents/month = 50`
- `grounded_chat_messages/month = 1000`
- `study_packs/month = 15`
- `deep_study_packs/month = 8`
- `infographics/month = 5`

This is a starting recommendation, not a forever contract.

## Next Best Step

The next implementation artifact should be:

- a Stripe + auth + entitlements integration plan

That should define:

- Stripe products and prices
- webhook events to handle
- subscription status mapping
- auth-session access to current entitlements
- protected UI states for locked features
