Use Case 035: Provider Subscriptions & Utilization

Overview

Property Value
Use Case ID UC-035
Use Case Name Provider Subscriptions & Utilization
Module Agent Identity — Cost Governance
Priority Medium
Status ✅ Implemented
Version 1.0
Last Updated June 15, 2026

Implementation status (agent-identity release, June 2026). Implemented: ProviderSubscription (1:1 with a provider account), utilization tracking, an auto-created Subscription-tier BudgetCap for blocking quotas, staged mid-period quota changes (applied on rollover), and a daily Hangfire rollover (subscription-rollover). Admin UI at /subscriptions, API at /api/subscriptions.

Description

This use case describes provider subscriptions — Enterprise contracts, prepaid wallets, and (post-MVP) seat-based products — attached to a ProviderAccount as an optional ProviderSubscription value object, plus the utilization tracking that surrounds them, in the Application Manager agent-identity release. Application Manager always tracks utilization against a subscription's included quota; it enforces only when a contract has a hard ceiling. When overageBehavior = Block, AM auto-creates a BudgetCap with Tier = Subscription referencing the subscription, and the CapLatticeResolver walks it like any other cap (UC-032).

Because the effective per-token rate inside a subscription window can differ from the catalogue rate, the LlmCallLedger snapshots SubscriptionRateSnapshot for accurate audit reconstruction (UC-034). A Hangfire period-rollover job at each nextRenewal resets consumed quota, auto-un-blocks any exhausted Subscription-tier cap, and applies deferred mid-period term changes at the new boundary. Day-one subscription types are PrepaidCredits, MonthlyCommitment, and TokenQuota (Phase 3); SeatBased and Hybrid are post-MVP (Phase 5). The SDK master design lives in riptide-sdk/docs/plans/AGENT-IDENTITY-ARCHITECTURE.md; the Application Manager plan in docs/internal/agent-identity-plan.md (Section 3, §3.6 subscriptions and §3.9 lifecycle edge cases, §10 decision 22).

Actors

Actor Description Role
Cost Administrator Admin who attaches and edits subscriptions (llm.providers.write) Primary
LLM Gateway (LlmDispatchService) Reconciles spend, snapshotting subscription rates Supporting
CapLatticeResolver Walks the auto-created Subscription-tier cap (UC-032) Supporting
Subscription Rollover Job Hangfire job at nextRenewal that resets quota Supporting
System Application Manager platform Supporting
LLM Provider External provider holding the contractual relationship External

Preconditions

  1. Application Manager is running with the cost-governance subsystem enabled (Phase 3).
  2. A ProviderAccount exists for the (tenant or agency, provider) to attach the subscription to (UC-028).
  3. The acting administrator holds llm.providers.write.
  4. For enforcement: the subscription's overageBehavior = Block (auto-creates a Subscription-tier cap).
  5. For seat-based provisioning (post-MVP): the SeatBased subscription type is available (Phase 5).

Postconditions

Success Postconditions

  1. A ProviderSubscription value object is attached to the ProviderAccount with type, included quota, overage behaviour, and renewal cadence.
  2. Utilization is tracked against the included quota for the current billing period.
  3. When overageBehavior = Block, a Tier = Subscription BudgetCap exists referencing the subscription.
  4. Ledger rows during the subscription window carry SubscriptionRateSnapshot when the effective rate differs from catalogue.
  5. At nextRenewal, consumed quota resets and any exhausted Subscription-tier cap auto-un-blocks.

Failure Postconditions

  1. Attaching a subscription to a non-existent or wrong-owner ProviderAccount is rejected.
  2. A PrepaidCredits subscription with exhausted credits and overageBehavior = Block denies further calls until rollover or top-up.
  3. Mid-period includedQuota changes do not take effect immediately; the current period stays at its original value.
  4. Seat-based provisioning blocks when the seat pool is full (post-MVP).

Primary Flow Sequence

sequenceDiagram
    participant Admin as Cost Administrator
    participant SC as SubscriptionsController
    participant DB as IdentityDbContext
    participant Cap as BudgetCap (Subscription tier)
    participant Audit as AuditLog

    Admin->>SC: POST /api/subscriptions (PrepaidCredits, Block)
    SC->>DB: attach ProviderSubscription to ProviderAccount
    alt overageBehavior == Block
        SC->>Cap: auto-create Tier=Subscription cap (SubscriptionId)
    end
    SC->>Audit: record subscription create (before/after)
    SC-->>Admin: subscription summary

    Note over DB,Cap: during the billing window
    DB->>DB: each reconcile decrements includedQuota
    DB->>Cap: CapLatticeResolver walks the Subscription cap

UC-035a: Attach a Subscription to a Provider Account

Triggers

  • Administrator calls POST /api/subscriptions or uses the SubscriptionsController admin UI.

Basic Flow

  1. Administrator selects a ProviderAccount and chooses a subscription type (PrepaidCredits, MonthlyCommitment, TokenQuota day-one).
  2. Administrator sets monthlyFee, includedQuota (dollars?, tokens?, calls?, seats?), overageBehavior, optional overageRate, billingPeriodStart, nextRenewal, and renewalCadence.
  3. The default overageBehavior is applied per type if unset: PrepaidCredits → Block, MonthlyCommitment → BilledOverage, TokenQuota → BilledOverage.
  4. The ProviderSubscription value object is attached to the ProviderAccount.
  5. If overageBehavior = Block, a BudgetCap with Tier = Subscription referencing the subscription is auto-created.
  6. The change is recorded in AuditLog with before/after values.

Alternative Flows

  • A1: Wrong-owner provider account — Rejected; subscription scope must match the account's OwnedBy.
  • A2: Type defaults overridden — Admin may set a non-default overageBehavior (e.g. MonthlyCommitment with Block) — a Subscription-tier cap is then created.
  • A3: Post-MVP type selected (SeatBased / Hybrid) — Available only in Phase 5; rejected/hidden before then.

UC-035b: Track Utilization

Triggers

  • Calls dispatch through the gateway against the ProviderAccount; an admin views GET /api/subscriptions/{id}/utilization.

Basic Flow

  1. Each reconciled call (UC-034) decrements the subscription's remaining included quota (dollars / tokens / calls as applicable).
  2. AM tracks utilization regardless of overageBehavior — tracking is always on.
  3. GET /api/subscriptions/{id}/utilization returns a current-period summary: utilization %, consumed vs. included quota, projected end-of-period, days remaining, overage burn.
  4. The dashboard By Subscription view (detail in UC-036) reads this utilization data.

Alternative Flows

  • A1: BilledOverage — Once included quota is exhausted, calls continue; overage is tracked at overageRate and surfaced, but not gated.
  • A2: Unlimited — Utilization is tracked for visibility; never gated.
  • A3: Pricing differs from catalogue — The effective subscription rate is snapshotted as SubscriptionRateSnapshot on each ledger row (UC-034); two rate-tier rows over the same model in one month are normal.

UC-035c: Block on Prepaid Exhaustion (Auto Subscription-Tier Cap)

Triggers

  • A PrepaidCredits subscription (overageBehavior = Block) reaches its included quota.

Basic Flow

  1. As calls reconcile, the subscription's remaining prepaid balance approaches zero.
  2. The auto-created Tier = Subscription BudgetCap is walked by CapLatticeResolver on every reservation.
  3. When the next reservation would exceed remaining prepaid credits, the Subscription-tier cap has no headroom.
  4. The reservation is denied with BudgetExhausted (HardStop semantics for the Block subscription).
  5. Further calls against that provider account HardStop until the period rolls over or credits are topped up.

Alternative Flows

  • A1: ApprovalRequired on the Subscription cap — If the auto-created cap is configured ApprovalRequired, the reservation parks as PendingApproval (UC-032d) instead of denying.
  • A2: Top-up mid-period — An admin updates the prepaid balance; per A2 of UC-035e, included-quota increases take effect at the next boundary, but a deliberate top-up may be applied immediately if modeled as a separate credit (configuration-dependent).
sequenceDiagram
    participant GW as LLM Gateway
    participant CLR as CapLatticeResolver
    participant Cap as Subscription-tier Cap
    participant DB as IdentityDbContext

    GW->>CLR: ReserveSpend(amount)
    CLR->>Cap: remaining prepaid >= amount ?
    alt prepaid exhausted (Block)
        Cap-->>CLR: no headroom
        CLR-->>GW: BudgetExhausted (deny)
    else headroom
        Cap-->>CLR: OK
        CLR->>DB: reservation Held
        CLR-->>GW: reservationId
    end

UC-035d: Period Rollover & Auto-Un-Block

Triggers

  • The Hangfire subscription rollover job fires at nextRenewal.

Basic Flow

  1. The rollover job resets consumedQuota for the subscription (fresh period quota).
  2. If a Tier = Subscription cap was exhausted in the prior period, it auto-un-blocks — fresh headroom restores calls.
  3. nextRenewal advances by the renewalCadence.
  4. Any deferred mid-period term change (UC-035e) takes effect at this boundary.
  5. The rollover is recorded in AuditLog.

Alternative Flows

  • A1: BilledOverage subscription — Overage counters reset; the running overage tally for the prior period is closed and reported.
  • A2: Rollover job missed/delayed — On the next run, the boundary is computed from nextRenewal; quota still resets for the correct period.

UC-035e: Mid-Period Term Change Deferral

Triggers

  • An admin updates includedQuota (or other contract terms) mid-period via PUT /api/subscriptions/{id}.

Basic Flow

  1. Administrator edits the subscription terms (e.g. raises includedQuota.tokens).
  2. The change is persisted but does not take effect immediately — it is staged for the next billingPeriodStart.
  3. The current period continues to track and enforce against its original includedQuota value (avoids mid-period accounting whiplash).
  4. At the next rollover (UC-035d), the staged value becomes the active includedQuota.
  5. The edit is recorded in AuditLog with before/after values and the effective date.

Alternative Flows

  • A1: Reducing quota mid-period — Same deferral; the reduction applies next period, not retroactively.
  • A2: Changing overageBehavior to Block mid-period — A Subscription-tier cap is created, but its limit reflects the next-period quota; enforcement aligns at the boundary.

UC-035f: Seat-Based Pool (Post-MVP, Phase 5)

Post-MVP. Described for completeness; not delivered in the Phase 3 cost-governance plane.

Triggers

  • A SeatBased subscription (e.g. a "Cursor Business / 10 seats" contract) is attached, and developer agents are provisioned against it.

Basic Flow

  1. A SeatBased subscription with includedQuota.seats = 10 becomes a seat pool.
  2. Each seat is materialised as an AgentIdentity with TemplateId = developer.cursor, bound to one user.
  3. Provisioning UX surfaces seat availability ("7 of 10 used") and blocks new provisioning when the pool is full (overageBehavior = Block).
  4. Seats return to the pool when an agent is disabled or its owner leaves the org.

Alternative Flows

  • A1: Pool full — New seat-bound agent provisioning is blocked until a seat is freed.
  • A2: Owner leaves the org — The bound agent is disabled and its seat returns to the pool.
  • A3: Hybrid contract — overageBehavior is per contract; enforcement follows the negotiated terms (post-MVP).

API Endpoints

Method Path Auth Purpose
GET /api/subscriptions X-Api-Key + llm.providers.write List subscriptions
POST /api/subscriptions X-Api-Key + llm.providers.write Attach a subscription to a provider account
PUT /api/subscriptions/{id} X-Api-Key + llm.providers.write Update subscription terms (deferred to next period)
GET /api/subscriptions/{id}/utilization X-Api-Key + cost.insights.view.tenant Current-period utilization summary
// POST /api/subscriptions
{
  "providerAccountId": "9f2a14c0-5e33-7b21-bc44-0d2a77aa9011",
  "type": "PrepaidCredits",
  "monthlyFee": 0.00,
  "includedQuota": { "dollars": 500.00 },
  "overageBehavior": "Block",
  "overageRate": null,
  "billingPeriodStart": "2026-06-01T00:00:00Z",
  "nextRenewal": "2026-07-01T00:00:00Z",
  "renewalCadence": "Monthly"
}

// 201 Created
{
  "subscriptionId": "a1c4d2e0-8b77-7f31-9a22-0e3b55cc44dd",
  "providerAccountId": "9f2a14c0-5e33-7b21-bc44-0d2a77aa9011",
  "type": "PrepaidCredits",
  "overageBehavior": "Block",
  "includedQuota": { "dollars": 500.00 },
  "consumedQuota": { "dollars": 0.00 },
  "utilizationPercent": 0.0,
  "subscriptionCapId": "b73d2f10-9a4c-7e2f-bb55-0c3a1de98a77",
  "nextRenewal": "2026-07-01T00:00:00Z",
  "createdAt": "2026-06-09T14:20:11Z"
}

Business Rules

Rule Description
BR-1 A ProviderSubscription is an optional value object on ProviderAccount; multiple accounts per (tenant, provider) may each carry one.
BR-2 AM tracks utilization always; it enforces only when overageBehavior = Block.
BR-3 When overageBehavior = Block, a Tier = Subscription BudgetCap referencing the subscription is auto-created; CapLatticeResolver walks it like any other cap.
BR-4 Day-one types are PrepaidCredits (default Block), MonthlyCommitment (default BilledOverage), TokenQuota (default BilledOverage).
BR-5 SeatBased and Hybrid are post-MVP (Phase 5).
BR-6 Ledger rows during a subscription window carry SubscriptionRateSnapshot when the effective rate differs from catalogue (UC-034).
BR-7 At nextRenewal, the rollover job resets consumedQuota and auto-un-blocks any exhausted Subscription-tier cap.
BR-8 Mid-period includedQuota (and term) changes take effect at the next billingPeriodStart, not immediately.
BR-9 The current period always tracks and enforces against its original includedQuota value.
BR-10 Seat-based subscriptions form a pool; each seat is an AgentIdentity (TemplateId = developer.cursor) bound to one user; provisioning blocks when full (post-MVP).
BR-11 Seats return to the pool on agent disable or owner departure (post-MVP).
BR-12 Every subscription create/edit and rollover is recorded in AuditLog with before/after values.

Data Requirements

ProviderSubscription (value object on ProviderAccount)

Field Type Constraints
Type enum PrepaidCredits, MonthlyCommitment, TokenQuota, SeatBased, Hybrid (*post-MVP)
MonthlyFee decimal ≥ 0
IncludedQuota.Dollars decimal? Nullable; dollar quota
IncludedQuota.Tokens long? Nullable; token quota
IncludedQuota.Calls long? Nullable; call quota
IncludedQuota.Seats int? Nullable; seat pool size (post-MVP)
OverageBehavior enum Block, BilledOverage, Unlimited
OverageRate decimal? Required when BilledOverage; per-unit overage price
BillingPeriodStart DateTimeOffset Current period start
NextRenewal DateTimeOffset Next rollover boundary
RenewalCadence enum e.g. Monthly, Annual
ConsumedQuota json Running consumed dollars/tokens/calls/seats for the period
StagedQuota json? Nullable; deferred term change pending next boundary

Security Considerations

  • Authentication: Subscription CRUD requires X-Api-Key; admin UI uses cookie auth. Utilization reads require X-Api-Key plus a cost-insights capability.
  • Authorization / capabilities: llm.providers.write for attach/edit; cost.insights.view.tenant / cost.insights.view.agency for utilization reads.
  • Data protection: The subscription value object holds contract terms (fees, quotas), not provider API keys — keys live on ProviderAccount, encrypted via the ProviderAccountCredentials Data Protection purpose string (UC-028).
  • Audit (actor chain): Subscription create/edit, rollover, and auto-created Subscription-tier caps are recorded in AuditLog with before/after values and the acting principal.
  • Fail-closed: Prepaid Block subscriptions deny on exhaustion via the auto-created cap rather than silently allowing overage.

Testing Scenarios

ID Scenario Expected Result
T-1 Attach PrepaidCredits subscription (default Block) Subscription attached; Subscription-tier cap auto-created
T-2 Attach MonthlyCommitment (default BilledOverage) Attached; no Block cap created
T-3 Attach TokenQuota (default BilledOverage) Attached; utilization tracked; no Block cap
T-4 Attach MonthlyCommitment with overridden Block Subscription-tier cap auto-created
T-5 Attach to wrong-owner provider account Rejected
T-6 Select SeatBased/Hybrid before Phase 5 Rejected/hidden (post-MVP)
T-7 Utilization summary mid-period Returns %, consumed/included, projected end-of-period, days remaining, overage burn
T-8 Utilization tracked under Unlimited Tracked; never gated
T-9 Subscription rate differs from catalogue SubscriptionRateSnapshot on each ledger row (UC-034)
T-10 Two rate-tier rows over same model in one month Both valid; subscription window vs. catalogue
T-11 Prepaid credits exhausted, Block Next reservation denied with BudgetExhausted
T-12 Prepaid Subscription cap set to ApprovalRequired Reservation parks PendingApproval instead of denying
T-13 BilledOverage quota exhausted Calls continue; overage tracked at overageRate; not gated
T-14 Rollover at nextRenewal consumedQuota reset; nextRenewal advances by cadence
T-15 Exhausted Subscription cap at rollover Auto-un-blocks; calls resume
T-16 Rollover job missed then runs late Quota resets for the correct period from nextRenewal
T-17 Mid-period includedQuota raise Staged; current period unchanged; applies next boundary
T-18 Mid-period includedQuota reduction Deferred to next period; not retroactive
T-19 Change overageBehavior to Block mid-period Cap created; enforcement aligns at next boundary
T-20 Seat pool with seats used Utilization shows "X of N used" (post-MVP)
T-21 Seat pool full, new provisioning Blocked until a seat frees (post-MVP)
T-22 Owner leaves org with seat-bound agent Agent disabled; seat returns to pool (post-MVP)
T-23 Subscription edit audited AuditLog row with before/after and effective date
  • UC-028: LLM Provider Account & Deployment Management — the ProviderAccount a subscription attaches to.
  • UC-032: Budget Cap Lattice & Enforcement — the Tier = Subscription cap walked by CapLatticeResolver.
  • UC-034: Spend Reservation & Reconciliation — SubscriptionRateSnapshot on the ledger.
  • UC-036: Cost Insights Dashboard — the By Subscription view detail.

Revision History

Version Date Author Notes
1.0 June 9, 2026 Platform Architecture Team Initial draft