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
- Application Manager is running with the cost-governance subsystem enabled (Phase 3).
- A
ProviderAccount exists for the (tenant or agency, provider) to attach the subscription to (UC-028).
- The acting administrator holds
llm.providers.write.
- For enforcement: the subscription's
overageBehavior = Block (auto-creates a Subscription-tier cap).
- For seat-based provisioning (post-MVP): the
SeatBased subscription type is available (Phase 5).
Postconditions
Success Postconditions
- A
ProviderSubscription value object is attached to the ProviderAccount with type, included quota, overage behaviour, and renewal cadence.
- Utilization is tracked against the included quota for the current billing period.
- When
overageBehavior = Block, a Tier = Subscription BudgetCap exists referencing the subscription.
- Ledger rows during the subscription window carry
SubscriptionRateSnapshot when the effective rate differs from catalogue.
- At
nextRenewal, consumed quota resets and any exhausted Subscription-tier cap auto-un-blocks.
Failure Postconditions
- Attaching a subscription to a non-existent or wrong-owner
ProviderAccount is rejected.
- A
PrepaidCredits subscription with exhausted credits and overageBehavior = Block denies further calls until rollover or top-up.
- Mid-period
includedQuota changes do not take effect immediately; the current period stays at its original value.
- 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
- Administrator selects a
ProviderAccount and chooses a subscription type (PrepaidCredits, MonthlyCommitment, TokenQuota day-one).
- Administrator sets
monthlyFee, includedQuota (dollars?, tokens?, calls?, seats?), overageBehavior, optional overageRate, billingPeriodStart, nextRenewal, and renewalCadence.
- The default
overageBehavior is applied per type if unset: PrepaidCredits → Block, MonthlyCommitment → BilledOverage, TokenQuota → BilledOverage.
- The
ProviderSubscription value object is attached to the ProviderAccount.
- If
overageBehavior = Block, a BudgetCap with Tier = Subscription referencing the subscription is auto-created.
- 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
- Each reconciled call (UC-034) decrements the subscription's remaining included quota (dollars / tokens / calls as applicable).
- AM tracks utilization regardless of
overageBehavior — tracking is always on.
GET /api/subscriptions/{id}/utilization returns a current-period summary: utilization %, consumed vs. included quota, projected end-of-period, days remaining, overage burn.
- 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
- As calls reconcile, the subscription's remaining prepaid balance approaches zero.
- The auto-created
Tier = Subscription BudgetCap is walked by CapLatticeResolver on every reservation.
- When the next reservation would exceed remaining prepaid credits, the Subscription-tier cap has no headroom.
- The reservation is denied with
BudgetExhausted (HardStop semantics for the Block subscription).
- 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
- The rollover job resets
consumedQuota for the subscription (fresh period quota).
- If a
Tier = Subscription cap was exhausted in the prior period, it auto-un-blocks — fresh headroom restores calls.
nextRenewal advances by the renewalCadence.
- Any deferred mid-period term change (UC-035e) takes effect at this boundary.
- 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
- Administrator edits the subscription terms (e.g. raises
includedQuota.tokens).
- The change is persisted but does not take effect immediately — it is staged for the next
billingPeriodStart.
- The current period continues to track and enforce against its original
includedQuota value (avoids mid-period accounting whiplash).
- At the next rollover (UC-035d), the staged value becomes the active
includedQuota.
- 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
- A
SeatBased subscription with includedQuota.seats = 10 becomes a seat pool.
- Each seat is materialised as an
AgentIdentity with TemplateId = developer.cursor, bound to one user.
- Provisioning UX surfaces seat availability ("7 of 10 used") and blocks new provisioning when the pool is full (
overageBehavior = Block).
- 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 |