# Pricing, Credits & Quotas

> **Phase 2 -- Not yet implemented.** This document describes the pricing, credit, rate limiting, quota, and promotion systems designed in the spec. Implementation is tracked in Phase 2 plans.
>
> **TODO:** Revisit this document when Phase 2 implementation begins to verify accuracy against final implementation.

## Purpose & Audience

This document describes the plan tiers, credit metering, rate limiting, resource quotas, and add-on system. It is intended for engineers implementing Phase 2 streams A (Plans + Credits) and D (Resource Management), and for product managers defining pricing decisions. For how these systems interact with the permission pipeline, see [platform architecture](../../architecture/platform.md).

---

## Plan Tiers

Plans use a **hybrid pricing model: per-seat base + included AI credit allocation.** Feature keys use a `module` or `module.submodule` convention. A permission like `tools.snowflake.query` is gated by plan feature `tools.snowflake` -- the resolution checks whether the permission's module prefix appears in the tenant's plan features.

Each plan is seeded as version 1. Future changes create version 2, 3, etc. Existing tenants stay on their assigned version until migrated.

|                        | **Starter** (v1)               | **Professional** (v1)         | **Business** (v1)             | **Enterprise** (v1)                  |
| ---------------------- | ------------------------------ | ----------------------------- | ----------------------------- | ------------------------------------ |
| **Target**             | Indie labels, small publishers | Mid-size labels, distributors | Large labels, multi-team orgs | Major label divisions, Sony entities |
| **Base price**         | $40/seat/month                 | $80/seat/month                | $120/seat/month               | Custom annual contract               |
| **Annual discount**    | 15%                            | 15%                           | 20%                           | Negotiated                           |
| **Seats**              | 1-10                           | 5-50                          | 25-250                        | Custom (no limit)                    |
| **Credits/seat/month** | 200                            | 1,000                         | 3,000                         | Custom committed volume              |
| **Overage policy**     | Hard cap                       | Soft overage ($0.10/credit)   | Soft overage ($0.08/credit)   | Soft overage (negotiated rate)       |
| **Billing**            | Monthly or annual              | Monthly or annual             | Annual only                   | Annual contract                      |
| **Uptime SLA**         | None (best-effort)             | 99.5% monthly                 | 99.9% monthly + SLA credits   | 99.9%+ negotiated + SLA credits      |
| **Support**            | Email (48h SLA)                | Priority email (24h SLA)      | Dedicated CSM                 | Dedicated CSM + response SLA         |

### Feature Access by Plan

| Feature Key           | Starter         | Professional        | Business               | Enterprise             |
| --------------------- | --------------- | ------------------- | ---------------------- | ---------------------- |
| `chat`                | Yes             | Yes                 | Yes                    | Yes                    |
| `tools.files`         | Yes             | Yes                 | Yes                    | Yes                    |
| `models.haiku`        | Yes             | Yes                 | Yes                    | Yes                    |
| `admin.branding`      | Yes (logo only) | Yes (logo + colors) | Yes (full white-label) | Yes (full white-label) |
| `reports`             | --              | Yes                 | Yes                    | Yes                    |
| `dashboards`          | --              | Yes                 | Yes                    | Yes                    |
| `tools.snowflake`     | --              | Yes                 | Yes                    | Yes                    |
| `tools.graphql`       | --              | Yes                 | Yes                    | Yes                    |
| `integrations.notion` | --              | Yes                 | Yes                    | Yes                    |
| `models.sonnet`       | --              | Yes                 | Yes                    | Yes                    |
| `tools.sandbox`       | --              | --                  | Yes                    | Yes                    |
| `models.opus`         | --              | --                  | Yes                    | Yes                    |
| `data.sources`        | --              | --                  | Yes                    | Yes                    |
| `data.export`         | --              | --                  | Yes                    | Yes                    |
| `reports.export`      | --              | --                  | Yes                    | Yes                    |
| `admin.search`        | --              | --                  | Yes                    | Yes                    |
| `admin.dashboard`     | --              | --                  | Yes                    | Yes                    |
| `admin.users`         | --              | --                  | --                     | Yes                    |
| `admin.audit`         | --              | --                  | --                     | Yes                    |
| `admin.settings`      | --              | --                  | --                     | Yes                    |
| `webhooks`            | --              | --                  | --                     | Yes                    |
| `ip_allowlist`        | --              | --                  | --                     | Yes                    |
| `service_accounts`    | --              | --                  | --                     | Yes                    |

---

## Add-On System

Add-ons let tenants boost specific dimensions without changing plan tiers. The base plan sets features, governance, support, and baseline allocations. Add-ons handle "but I also need more X" without forcing a full upgrade.

### Add-On Categories

**Credit add-ons:** Credit Pack 500 (+500/seat/mo, $30), Credit Pack 2000 (+2,000/seat/mo, $100), Credit Pack 10000 (+10,000/seat/mo, $400). Stackable.

**Feature add-ons:** Opus Model Access ($50/mo), Sandbox Execution ($30/mo), API Access ($100/mo), Data Export ($20/mo). Non-stackable.

**Governance add-ons:** SSO & SCIM ($5/seat/mo), Audit Logging ($3/seat/mo), IP Allowlist ($2/seat/mo). Non-stackable.

**Storage add-ons:** File Storage 1GB ($10/mo), File Storage 10GB ($80/mo), Data Cache 1GB ($15/mo), Extra Chats 500 ($10/mo), Extra Dashboards ($20/mo), Extra Data Sources ($25/mo). Stackable with limits.

**Rate limit add-ons:** Double Throughput 2.0x ($50/mo), 5x Throughput 5.0x ($200/mo). Non-stackable.

**Retention add-ons:** Chat History 1 Year ($10/mo), Audit Log 2 Years ($20/mo), File Retention 1 Year ($15/mo). Non-stackable.

**Seat add-ons:** Extra Seats 5 (computed pricing), Extra Seats 25 (computed pricing). Stackable with limits.

### How Plans and Add-Ons Compose

The effective entitlement for any dimension is: **plan base + sum(active add-ons) + tenant overrides**.

```
Effective credits/seat/month:
  Plan.creditsPerSeat
  + SUM(TenantAddOn.quantity x AddOn.creditBoost) for credit add-ons
  + TenantPlan.creditOverride (if set, replaces plan base)

Effective features:
  PlanFeature[] for this plan version
  UNION AddOn.featureUnlocks for active add-ons
  UNION TenantPlan.featureOverrides (if set)

Effective quota for resource R:
  ResourceQuota at plan scope for R
  + SUM(AddOn.quotaBoosts[R]) for active add-ons
  + ResourceQuota at tenant scope (override, replaces if set)

Effective rate limits:
  RateLimit at plan scope
  x MAX(AddOn.rateLimitBoost) for active rate_limit add-ons (single multiplier)
  overridden by tenant/user-scope RateLimit if set
```

**Resolution priority:** Tenant-level overrides always take final precedence. They exist for enterprise contracts with negotiated terms. Add-ons are the self-serve path; overrides are the enterprise negotiation path.

The `GetEffectiveEntitlements` RPC returns the fully resolved view so consumers never compute composition themselves.

---

## Credit System

Credit metering is **separate from permission resolution.** The permission pipeline determines whether a user _may_ perform an action. Credit checking determines whether the tenant _has budget_ for it.

- Permission denied -> never costs credits (denial is free)
- Permission granted -> check credits -> execute if credits available
- Credit exhausted -> operation-specific behavior based on overage policy

### Credit Costs by Operation

| Operation         | Starter | Professional    | Business | Enterprise |
| ----------------- | ------- | --------------- | -------- | ---------- |
| `chat.haiku`      | 1       | 1               | 1        | 1          |
| `chat.sonnet`     | --      | 5               | 4        | 3          |
| `chat.opus`       | --      | 25 (via add-on) | 20       | 15         |
| `tools.snowflake` | --      | 5               | 4        | 3          |
| `tools.graphql`   | --      | 3               | 2        | 2          |
| `tools.sandbox`   | --      | --              | 5        | 4          |
| `tools.files`     | 3       | 3               | 2        | 2          |
| `reports.export`  | --      | 1               | 1        | 1          |
| `data.export`     | --      | --              | 2        | 1          |

**Free operations (0 credits):** `admin.dashboard.view`, `chat.view`, `reports.view`, `search.*`, all admin management operations, session management.

### Credit Flow

```
Permission granted (step 16)
    |
    v
Rate limit check (velocity)
    |
    v
Credit check
    |
    +-- Cost is 0 (free operation) -> EXECUTE
    |
    +-- Atomic deduction (race-safe):
    |   UPDATE credit_ledger
    |     SET consumed = consumed + :cost
    |     WHERE id = :id AND consumed + :cost <= allocated
    |   IF rows_affected = 1 -> EXECUTE
    |   IF rows_affected = 0 -> allocation exhausted, check overage:
    |
    +-- Overage policies:
    |   soft_overage -> deduct without cap, EXECUTE
    |   throttle    -> degrade to Haiku if possible, else 429
    |   hard_cap    -> 429 "Credit limit reached"
    |
    +-- [Always] Write CreditEntry (async)
```

### Credit Allocation Lifecycle

- **Period start:** Compute allocation = (plan base + add-on boosts) x seat count
- **Mid-period seat change:** Added seats get prorated credits. Removed seats get no clawback.
- **Period end:** Atomic rollover -- new ledger inserted before old one finalized. Unused credits do **not** roll over.

### Credit Alerts

| Threshold           | Action                                                  |
| ------------------- | ------------------------------------------------------- |
| 80% consumed        | Email tenant admin. In-app banner.                      |
| 90% consumed        | Email admin + power_user/admin roles. Prominent banner. |
| 100% consumed       | Behavior depends on overage policy. Email regardless.   |
| 150% overage (soft) | Escalation to super admin. Flag for account review.     |

### Credit Caching

```
Cache key: credit:{tenantId}:balance   -> { allocated, consumed, overage, periodEnd }
Cache key: credit:{tenantId}:costs     -> Map<operation, credits>

TTL: Balance 5 seconds, Costs 1 hour
Invalidation: Credit consumed -> atomic increment. Plan changed -> DEL costs. Period rollover -> DEL balance.
```

---

## Rate Limiting

Rate limits control **velocity** -- how fast resources can be consumed. Credits control **budget** -- how much total. Both must pass on every AI-intensive operation.

### Resolution Hierarchy

Most specific to least specific. First match wins.

```
[1] Per-user override
[2] Per-service-account override
[3] Per-role limit (multi-role: lowest limit wins)
[4] Per-tenant override
[5] Per-plan default
[6] Global default
[7] No limit (unlimited)
```

### Default Per-User Limits (requests/minute)

| Operation              | Starter | Professional | Business | Enterprise |
| ---------------------- | ------- | ------------ | -------- | ---------- |
| `api` (all requests)   | 60      | 120          | 300      | 600        |
| `chat.*` (all AI chat) | 10      | 30           | 60       | 120        |
| `chat.opus`            | --      | --           | 10       | 30         |
| `tools.snowflake`      | --      | 10           | 20       | 60         |
| `tools.graphql`        | --      | 20           | 40       | 120        |
| `tools.sandbox`        | --      | --           | 10       | 30         |
| `tools.files`          | 5       | 10           | 20       | 60         |
| `admin.*`              | 30      | 30           | 60       | 120        |

### Default Per-Tenant Aggregate Limits (requests/minute)

| Operation   | Starter | Professional | Business | Enterprise |
| ----------- | ------- | ------------ | -------- | ---------- |
| `api`       | 200     | 1,000        | 5,000    | 20,000     |
| `chat.*`    | 30      | 200          | 1,000    | 5,000      |
| `chat.opus` | --      | --           | 100      | 500        |
| `tools.*`   | 20      | 100          | 500      | 2,000      |

### Throttle Policies

| Context              | Policy    | Rationale                                          |
| -------------------- | --------- | -------------------------------------------------- |
| Per-user `chat.*`    | `degrade` | Downgrade model automatically. Better UX.          |
| Per-user `tools.*`   | `reject`  | Discrete operations -- user can retry.             |
| Per-user `api`       | `reject`  | General throughput protection.                     |
| Per-tenant aggregate | `queue`   | Queue briefly (30s timeout) rather than rejecting. |
| Service account      | `reject`  | Automated clients handle 429 + Retry-After.        |
| Per-user `admin.*`   | `delay`   | Progressive backoff discourages brute-force.       |

### Burst Handling

The `burstMax` field enables token bucket semantics:

| Scenario       | Window | Max | Burst | Behavior                                |
| -------------- | ------ | --- | ----- | --------------------------------------- |
| Steady analyst | 60s    | 30  | --    | Max 30/min, evenly spaced               |
| Bursty analyst | 60s    | 30  | 10    | 10 rapid queries, then 30/min sustained |
| Power user     | 60s    | 60  | 20    | 20 burst, then 60/min sustained         |

### Rate Limit Headers

```
X-RateLimit-Limit: 30
X-RateLimit-Remaining: 22
X-RateLimit-Reset: 1714300800
X-RateLimit-Policy: degrade
Retry-After: 5                    (only on 429)
```

### Rate Limit vs Credit Interaction

Rate limits are checked **before** credits. This prevents burning credits on throttled requests. Degraded requests (Opus to Sonnet) consume Sonnet credits, not Opus credits.

---

## Resource Quotas

Quotas control **capacity** -- how many items can exist and how much storage they consume. This is the third dimension alongside credits (budget) and rate limits (velocity).

### Check Order

```
Permission check (can you?) -> Rate limit (how fast?) -> Credit check (budget?) -> Quota check (room?)
```

### Default Count Quotas

| Resource            | Starter    | Professional | Business     | Enterprise |
| ------------------- | ---------- | ------------ | ------------ | ---------- |
| `users`             | 10         | 50           | 250          | Custom     |
| `chats`             | 100/user   | 500/user     | 2,000/user   | Unlimited  |
| `chats.messages`    | 5,000/user | 25,000/user  | 100,000/user | Unlimited  |
| `dashboards`        | 5/tenant   | 25/tenant    | 100/tenant   | Unlimited  |
| `data_sources`      | 2/tenant   | 10/tenant    | 50/tenant    | Unlimited  |
| `reports`           | 10/tenant  | 50/tenant    | 200/tenant   | Unlimited  |
| `reports.scheduled` | 0          | 5/tenant     | 25/tenant    | Unlimited  |
| `service_accounts`  | 0          | 2/tenant     | 10/tenant    | Unlimited  |
| `webhooks`          | 0          | 2/tenant     | 10/tenant    | Unlimited  |
| `roles.custom`      | 0          | 5/tenant     | 25/tenant    | Unlimited  |
| `groups`            | 3/tenant   | 10/tenant    | 50/tenant    | Unlimited  |

### Default Storage Quotas

| Resource                | Starter       | Professional    | Business         | Enterprise |
| ----------------------- | ------------- | --------------- | ---------------- | ---------- |
| `files.storage_mb`      | 100 MB/tenant | 1,000 MB/tenant | 10,000 MB/tenant | Custom     |
| `data_sources.cache_mb` | 50 MB/tenant  | 500 MB/tenant   | 5,000 MB/tenant  | Custom     |
| `chats.attachments_mb`  | 200 MB/tenant | 2,000 MB/tenant | 20,000 MB/tenant | Custom     |

### Default Retention Policies

| Resource               | Starter | Professional | Business       | Enterprise                 |
| ---------------------- | ------- | ------------ | -------------- | -------------------------- |
| `chats`                | 90 days | 365 days     | 730 days (2yr) | Custom                     |
| `files`                | 30 days | 90 days      | 365 days       | Custom                     |
| `data_sources.cache`   | 1 day   | 7 days       | 30 days        | Custom                     |
| `audit.retention_days` | 90 days | 365 days     | 730 days       | Custom (min 365 for SOC 2) |
| `reports` (generated)  | 30 days | 90 days      | 365 days       | Custom                     |

### Quota Enforcement

Quotas are enforced **synchronously** on create/upload. Unlike credits (which can support async soft overage), a quota check that fails must prevent the resource from being created.

```
Over quota -> 409 "Quota exceeded: {resource}"
Response includes: current count, limit, and upgrade path
("Upgrade to Business for 500 chats")
```

### Quota Alerts

| Threshold                  | Action                                         |
| -------------------------- | ---------------------------------------------- |
| 80% (default `warningPct`) | Email tenant admin. In-app banner.             |
| 95%                        | Prominent warning on every create action.      |
| 100%                       | Create blocked. Clear error with upgrade path. |
| Retention approaching      | 7-day warning before auto-archive/delete.      |

---

## Plan Lifecycle Management

Plans evolve over time. The versioning system ensures tenants are never surprised by changed terms.

### Version Lifecycle

```
  draft -> active -> deprecated -> sunset -> archived

  draft:      Config and testing
  active:     Open for new subscriptions (immutable from this point)
  deprecated: Existing tenants stay; no new subscriptions
  sunset:     Hard deadline set (>= 90 days future)
  archived:   No tenants remain; historical record
```

### Migration Types

| Type               | How                                            | When Used                        |
| ------------------ | ---------------------------------------------- | -------------------------------- |
| Self-service       | Tenant admin clicks "Upgrade" in admin UI      | Most common                      |
| Super admin manual | `SetTenantPlan` RPC with `migrationNote`       | Enterprise with negotiated terms |
| Automatic (sunset) | Remaining tenants auto-migrated on sunset date | Catch-all                        |
| Bulk migration     | Super admin batch RPC, optional filters        | Accelerating migration           |

### Grandfathering

Enterprise customers with negotiated terms use `TenantPlan` override fields:

| Override                | Use Case                                              |
| ----------------------- | ----------------------------------------------------- |
| `creditOverride`        | Negotiated credits different from plan default        |
| `priceOverrideCents`    | Legacy pricing locked in                              |
| `overagePolicyOverride` | Custom overage terms                                  |
| `featureOverrides`      | Features granted outside normal plan scope            |
| `referenceId`           | ExternalReference (SOW, MSA) authorizing the override |
| `notes`                 | Free-text explanation for auditors                    |

Every override should cite its authorizing document via `referenceId` for audit traceability. Overrides survive plan migration unless explicitly cleared.

---

## Promotions

Promotions handle temporary offers, targeted incentives, and referral programs. They integrate with the plan and add-on system to grant time-limited rewards.

### Promotion Types

| Type       | Use Case                   | Example                                                  |
| ---------- | -------------------------- | -------------------------------------------------------- |
| `campaign` | Time-limited public offer  | "Holiday Sale: 30% off Professional for 3 months"        |
| `targeted` | Specific tenant incentive  | "Win-back: Free Credit Pack 500 for 2 months"            |
| `referral` | Two-sided acquisition      | "Refer a label: they get 25% off, you get 1,000 credits" |
| `trial`    | New tenant acquisition     | "Extended trial: 30 extra days on Business"              |
| `loyalty`  | Retention for tenure/usage | "6-month anniversary: free Opus Access for 1 month"      |

### Promotion Lifecycle

```
  draft -> scheduled -> active -> paused -> ended -> archived
                         |                    ^
                         +--------------------+
                         (can end directly from active)
```

### Redemption Flow

1. Tenant admin enters code (or clicks promo link)
2. `ValidatePromotionCode` RPC checks: code exists, not exhausted/expired, promotion active, tenant matches targeting, limits not exceeded
3. Tenant admin confirms
4. `RedeemPromotion` RPC applies reward: plan discount, add-on activation, bonus credits, trial extension, or upgrade discount
5. `PromotionRedemption` record created. Effective entitlements cache invalidated.

### Referral Program

Referral promotions are two-sided. The system auto-generates referral codes per tenant.

```
Referrer (existing)              Referee (new)
    |                                |
    v                                |
GetReferralCode                      |
    |  Returns "REF-WARNER-A3F2"     |
    |                                |
    +---- shares code ------------> Signs up, enters code
                                     |
                                     v
                              RedeemPromotion
                              +-------+-------+
                              v               v
                        Referee gets:    Referrer gets:
                        25% off for      1,000 bonus
                        3 months         credits
```

**Referral safeguards:** Referee must be a new tenant (created within 30 days). Referrer rewards clawed back if referee churns within 90 days. One code per tenant. Self-referral blocked.

### Revocation

| Action              | Effect                                                                     |
| ------------------- | -------------------------------------------------------------------------- |
| Revoke a code       | Code unusable. Existing redemptions unaffected.                            |
| Revoke a redemption | Reward clawed back (add-on cancelled, credits deducted, discount removed). |
| End a promotion     | No new codes. Existing active redemptions run to expiration.               |
| Referral clawback   | If referee churns within window, referrer's reward auto-revoked.           |

Promotions flow through the existing entitlement system -- `GetEffectiveEntitlements` automatically includes promotion-granted benefits. Consumers do not need to know whether an entitlement came from a purchase or a promotion.
