Use Case 028: LLM Provider Account & Deployment Management

Overview

Property Value
Use Case ID UC-028
Use Case Name LLM Provider Account & Deployment Management
Module Agent Identity — LLM Gateway
Priority High
Status ✅ Implemented
Version 1.0
Last Updated June 15, 2026

Implementation status (agent-identity release, June 2026). Implemented: provider accounts, the model catalog (ModelCatalogEntry), and model deployments (ModelDeployment), administered from the AI Gateway group (/ai-gateway/provider-accounts, /ai-gateway/models, /ai-gateway/deployments). Several names below differ from the shipped code: the account field is ApiKeySecretName (plus the KeySource/EncryptedApiKey additions in the next note), not Credential; the account uses Enabled (bool), not a Status enum, and TenantId scoping rather than an OwnedBy enum. Gaps vs. this spec: pricing is a single per-model rate (InputCostPer1KTokens / OutputCostPer1KTokens) with no PricingVersion / PricingAuthority / override layering; deployment health-endpoint tracking (UC-028e) is not implemented; provider subscriptions live in a separate ProviderSubscription entity (UC-035).

Description

This use case describes how administrators and platform stewards register and govern the LLM provider surface that the Application Manager (AM) gateway dispatches against: provider accounts (the credentialed relationship with Anthropic, OpenAI, AWS Bedrock, Azure OpenAI, Google, OpenRouter), the model catalog (per-model pricing, context windows, capabilities), and model deployments (the concrete, classification-ceilinged endpoints that task-type model chains resolve to). AM holds every provider credential; agents never see them.

This is part of the agent-identity release. The LLM Gateway subsystem absorbs the RAF-SVC-LLM specification — that service no longer ships standalone, and its model-registry / provider-account / deployment responsibilities become AM's gateway design. See the SDK master design riptide-sdk/docs/plans/AGENT-IDENTITY-ARCHITECTURE.md and the AM plan docs/internal/agent-identity-plan.md (§2).

Implementation note — API-key storage. This document is the original design spec; some field and command names below differ from the shipped code. In particular, a provider account does not always store the key itself. Each account carries a Key source that selects where its API key lives: Environment variable (the default — the account stores only the secret/env-var name and the key is read from the environment or configuration at call time, never persisted by AM), Database (the key is stored in the Identity database encrypted at rest via ASP.NET Core Data Protection under the purpose LlmProviderKeys), or Vault (the account stores a secret name resolved from AM's configured Azure Key Vault). Accounts created before this option default to Environment variable. For the administrator-facing workflow — including the guided setup wizard and live connectivity check — see AI Gateway & Model Governance.

Actors

Actor Description Role
Administrator / Platform Steward Holds llm.providers.write / llm.deployments.write / llm.gateway.config capabilities; manages accounts, models, deployments Primary
AM LLM Gateway The AM subsystem that stores accounts/models/deployments and resolves them at dispatch time Supporting
LLM Providers Anthropic, OpenAI, AWS Bedrock, Azure OpenAI, Google, OpenRouter (and local vLLM/RayServe endpoints) External
AuditLog AM's append-only audit surface; records every config change with before/after values and acting principal Supporting
AI Agent Downstream consumer whose AllowedTaskTypes chains resolve to the deployments registered here (read-only with respect to this UC) Supporting

Preconditions

  1. AM is running; the Identity database has the LLM Gateway tables (LlmProvider seed, LlmModelCatalog, ProviderAccount, ModelDeployment) applied via EF migration.
  2. The administrator is authenticated to the AM admin UI and holds the relevant capability (llm.providers.write, llm.deployments.write, or llm.gateway.config).
  3. The LlmProvider enum has been seeded (Anthropic, OpenAI, AwsBedrock, AzureOpenAi, Google, OpenRouter).
  4. Bundled LlmModelCatalog rows (shipped with AM) are present from the seed migration.
  5. For tenant-scoped operations, the tenant exists and has a Top-tier budget cap (see UC-032).

Postconditions

Success Postconditions

  1. A ProviderAccount is persisted with its credential encrypted under the ProviderAccountCredentials Data Protection purpose; the plaintext is never stored or returned.
  2. LlmModelCatalog rows (bundled or tenant override) exist with a PricingVersion and EffectiveFrom, queryable by PricingAuthority.
  3. ModelDeployment rows exist, each pointing at a ProviderAccount and LlmModelCatalog model, with a maxClassification ceiling and (optionally) a health-check endpoint.
  4. Every create / rotate / disable / override / deployment edit writes an AuditLog row with before/after values and the acting principal.
  5. Deployments are resolvable by the task-type routing layer (UC-029) and chargeable by the cost-governance layer (UC-032/UC-034).

Failure Postconditions

  1. No ProviderAccount persisted if the credential fails to encrypt or a required field is missing.
  2. Credential rotation that fails leaves the prior credential intact (rotation is atomic).
  3. A ModelDeployment referencing an unknown (ProviderId, ModelId) catalog row is rejected (fail-closed).
  4. A tenant pricing override with an EffectiveFrom in the past relative to an existing version, or a malformed price, is rejected; no row written.
  5. Error surfaced to the administrator via the admin UI; no partial state.

Primary Flow Sequence

sequenceDiagram
    participant Admin
    participant Web as ProviderAccountsController
    participant MediatR
    participant Protector as ProviderAccountCredentialProtector
    participant Repo as IProviderAccountRepository
    participant Audit as AuditLog
    Admin->>Web: Submit Create Provider Account (provider, name, ownedBy, credential)
    Web->>MediatR: Send CreateProviderAccountCommand
    MediatR->>MediatR: Validate (provider enum, ownedBy, name, credential vs authMethod)
    MediatR->>Protector: Protect(credential) [purpose=ProviderAccountCredentials]
    Protector-->>MediatR: Encrypted credential blob
    MediatR->>Repo: Add(ProviderAccount{status=Active})
    Repo-->>MediatR: Persisted (Id)
    MediatR->>Audit: Write CreateProviderAccount (no plaintext)
    MediatR-->>Web: ProviderAccountId
    Web-->>Admin: Redirect to account detail (credential masked, write-only)

UC-028a: Create a Provider Account

Triggers

  • Administrator navigates to LLM Gateway > Provider Accounts > New and submits the form.

Basic Flow

  1. Administrator selects a provider from the LlmProvider enum and an ownedBy value (Tenant = tenant supplies and pays the provider directly; Agency = agency operates a shared account and rebills).
  2. Administrator enters a display name, optional tenant scope (tenantId nullable for agency-owned accounts), and the provider API key/credential.
  3. For local authMethod=none deployments, the credential field may be left blank (nullable credential).
  4. Administrator optionally attaches a Subscription value object (Monthly Commitment / Token Quota / Prepaid Credits — detail in UC-035).
  5. CreateProviderAccountCommand validates the provider, ownership, name, and that a credential is present unless authMethod=none.
  6. The credential is encrypted via ProviderAccountCredentialProtector (Data Protection purpose ProviderAccountCredentials).
  7. The ProviderAccount is persisted with status = Active; multiple accounts per (tenant, provider) are allowed.
  8. An AuditLog row is written (no plaintext credential).
  9. Administrator is redirected to the account detail page; the credential field renders as write-only (masked, never echoed).

Alternative Flows

  • A1: Missing credential for cloud account — validation rejects when authMethod requires a credential but none supplied; form redisplays with error.
  • A2: Duplicate display name within (tenant, provider) — allowed (multi-account first-class), but a warning is surfaced.
  • A3: Tenant lacks a Top-tier cap — agency-owned accounts proceed; tenant-owned creation surfaces a reminder that agent provisioning will block without cap headroom (UC-032).

UC-028b: Rotate or Disable a Provider Credential

Triggers

  • Administrator clicks Rotate Credential or Disable on a provider account detail page.

Basic Flow (Rotate)

  1. Administrator enters the new credential into the write-only field and confirms.
  2. RotateProviderAccountCredentialCommand encrypts the new value via ProviderAccountCredentialProtector.
  3. The new encrypted blob atomically replaces the old; the prior credential is overwritten only after the new one is successfully protected and persisted.
  4. An AuditLog row records the rotation (acting principal, timestamp; never the secret value).
  5. In-flight dispatches complete against the configuration they started with; new dispatches use the rotated credential.

Basic Flow (Disable)

  1. Administrator clicks Disable; AM prompts for confirmation.
  2. DisableProviderAccountCommand sets status = Disabled.
  3. Deployments backed by the disabled account become non-viable in the resolution cascade (UC-029); affected dispatches fall through to the next chain entry or return a terminal failure.
  4. An AuditLog row is written.

Alternative Flows

  • A1: Rotation fails mid-write — the prior credential remains valid; no partial state; error surfaced.
  • A2: Disable an account with active deployments — allowed; the admin is warned which deployments lose their backing account.
sequenceDiagram
    participant Admin
    participant Web as ProviderAccountsController
    participant MediatR
    participant Protector as ProviderAccountCredentialProtector
    participant Repo as IProviderAccountRepository
    participant Audit as AuditLog
    Admin->>Web: Submit Rotate Credential (new secret)
    Web->>MediatR: Send RotateProviderAccountCredentialCommand
    MediatR->>Protector: Protect(newSecret)
    Protector-->>MediatR: New encrypted blob
    MediatR->>Repo: Update credential (atomic swap)
    alt Persist succeeds
        Repo-->>MediatR: OK
        MediatR->>Audit: Write RotateCredential (no plaintext)
        MediatR-->>Web: Success
        Web-->>Admin: Toast: credential rotated
    else Persist fails
        Repo-->>MediatR: Error
        MediatR-->>Web: Failure (prior credential intact)
        Web-->>Admin: Error; rotation not applied
    end

UC-028c: Register or Override Model Catalog Pricing

Triggers

  • Administrator navigates to LLM Gateway > Models and registers a new model or adds a tenant pricing override.

Basic Flow

  1. Administrator views the catalog; the PricingAuthority filter toggles between External (pulled from a provider's published pricing) and Internal (set by IT for local-infrastructure chargeback).
  2. To register a model, the administrator supplies (ProviderId, ModelId), input/output/cache-read/cache-write prices, contextWindow, maxOutputTokens, supportsStructuredOutput, and optional tokenizerRef.
  3. RegisterModelCommand assigns a PricingVersion and EffectiveFrom, then persists the catalog row.
  4. To override pricing for a tenant, the administrator opens the override editor; OverrideModelPricingCommand (tenant-scoped) writes a tenant override row layered over the bundled row, with a new PricingVersion and EffectiveFrom.
  5. Two pricing rows over the same model in the same period are normal once subscriptions are in play (the active subscription rate is snapshotted on the ledger — UC-034/UC-035).
  6. An AuditLog row records the registration/override.

Alternative Flows

  • A1: Unknown model referenced at dispatch — if a deployment or task chain references a (ProviderId, ModelId) with no catalog row, the call is rejected (fail-closed) — never priced at $0 or guessed.
  • A2: Override EffectiveFrom in the past — rejected to preserve version monotonicity; the admin must use a future or current effective date.
  • A3: Malformed price (negative / non-numeric) — validation rejects; no row written.

UC-028d: Create or Update a Model Deployment

Triggers

  • Administrator navigates to LLM Gateway > Deployments > New (or edits an existing deployment).

Basic Flow

  1. Administrator selects a catalog model (ProviderId, ModelId) and a backing ProviderAccount.
  2. Administrator sets target (Cloud or Local), endpoint, authMethod, and maxClassification (the classification ceiling this deployment may handle).
  3. Administrator optionally sets a healthCheckEndpoint.
  4. CreateModelDeploymentCommand validates that the referenced catalog model exists (fail-closed if not) and that the provider account is Active.
  5. The ModelDeployment is persisted with healthStatus = Unknown and lastHealthCheck = null until the first health probe.
  6. The deployment becomes a resolution target for task-type chains (UC-029) and a charge point for model-tier caps (UC-032).
  7. An AuditLog row is written.
  8. UpdateModelDeploymentCommand follows the same flow for edits, recording before/after values.

Alternative Flows

  • A1: Catalog model missing — rejected (fail-closed); the admin is directed to register the model first (UC-028c).
  • A2: Backing account disabled — rejected; the admin must re-enable the account or pick another.
  • A3: maxClassification lower than a task type's classificationFloor — allowed at create time, but the deployment will simply be filtered out for those task types at resolution (UC-029b).

UC-028e: Deployment Health Tracking

Triggers

  • AM's health probe runs against a deployment's healthCheckEndpoint, or GET /api/llm/health is called (sub-1s SLA, UC-030).

Basic Flow

  1. AM probes the deployment's healthCheckEndpoint.
  2. UpdateDeploymentHealthCommand records the result, setting healthStatus (Healthy, Degraded, Unhealthy) and lastHealthCheck timestamp.
  3. The resolution cascade (UC-029b) skips Unhealthy deployments and prefers Healthy ones.
  4. The admin UI (ModelDeploymentsController) surfaces the current healthStatus and lastHealthCheck.

Alternative Flows

  • A1: No healthCheckEndpoint configured — healthStatus stays Unknown; the deployment is treated as eligible but unverified.
  • A2: Probe times out — healthStatus = Unhealthy; deployment excluded from resolution until a subsequent successful probe.

API Endpoints

Provider-account, model, and deployment administration is performed through the AM admin UI controllers, which dispatch CQRS commands/queries via MediatR. The read endpoints below are exposed on the API project (X-Api-Key) for SDK and gateway consumption.

Method Path Auth Purpose
GET /api/llm/models X-Api-Key Model registry view (catalog rows, pricing, capabilities)
POST /api/llm/admin/providers X-Api-Key + llm.providers.write Create provider account (credential write-only)
POST /api/llm/admin/providers/{id}/rotate X-Api-Key + llm.providers.write Rotate provider credential
POST /api/llm/admin/providers/{id}/disable X-Api-Key + llm.providers.write Disable provider account
POST /api/llm/admin/models X-Api-Key + llm.gateway.config Register a model catalog row
POST /api/llm/admin/models/{id}/override-pricing X-Api-Key + llm.gateway.config Tenant-scoped pricing override
POST /api/llm/admin/deployments X-Api-Key + llm.deployments.write Create model deployment
PUT /api/llm/admin/deployments/{id} X-Api-Key + llm.deployments.write Update model deployment
GET /api/llm/admin/deployments X-Api-Key List deployments with health status

Representative Request / Response

// POST /api/llm/admin/providers
// Request
{
  "provider": "Anthropic",
  "name": "Acme Tenant — Anthropic Prod",
  "ownedBy": "Tenant",
  "tenantId": "8f1c9e2a-4b6d-4a11-9c3e-2d7f5a8b1c20",
  "authMethod": "ApiKey",
  "credential": "sk-ant-REDACTED-PLAINTEXT-ONLY-IN-REQUEST",
  "subscription": {
    "type": "MonthlyCommitment",
    "monthlyFee": 5000.0,
    "overageBehavior": "BilledOverage"
  }
}

// Response 201 Created
{
  "id": "c4a7b210-9d3f-4e58-bf21-6a0c1e9d4477",
  "provider": "Anthropic",
  "name": "Acme Tenant — Anthropic Prod",
  "ownedBy": "Tenant",
  "tenantId": "8f1c9e2a-4b6d-4a11-9c3e-2d7f5a8b1c20",
  "status": "Active",
  "credentialMasked": "sk-ant-••••••••••••4477",
  "subscriptionAttached": true,
  "createdAt": "2026-06-09T14:32:11Z"
}

Business Rules

Rule Description
BR-1 An account's Key source determines where its API key lives: EnvironmentVariable (default) and Vault store only a secret name and resolve the key at call time (AM never persists it); Database stores the key encrypted at rest via IDataProtector (purpose LlmProviderKeys). In no mode is a plaintext key returned to the UI/API or written to logs or the audit trail.
BR-2 The credential purpose string is distinct from AgentCredentials so a scoped key compromise cannot cross secret classes.
BR-3 Multiple ProviderAccounts per (tenant, provider) are first-class; no uniqueness constraint on that pair.
BR-4 ownedBy = Tenant accounts are tenant-paid; ownedBy = Agency accounts are agency-operated and rebilled.
BR-5 An unknown (ProviderId, ModelId) referenced at dispatch is rejected (fail-closed) — never priced or guessed.
BR-6 Model catalog rows are versioned (PricingVersion + EffectiveFrom); tenant override rows layer over bundled rows.
BR-7 PricingAuthority = External means provider-published pricing; Internal means IT-set local-infrastructure chargeback.
BR-8 A ModelDeployment must reference an existing catalog model and an Active provider account.
BR-9 Unhealthy deployments are excluded from the resolution cascade until a subsequent successful health probe.
BR-10 Credential rotation is atomic — a failed rotation leaves the prior credential intact.
BR-11 Every create / rotate / disable / override / deployment edit writes an AuditLog row with before/after values and acting principal.

Data Requirements

LlmProvider (seeded enum)

Field Type Constraints
Value enum One of Anthropic, OpenAI, AwsBedrock, AzureOpenAi, Google, OpenRouter (Google is one provider; Vertex vs. Gemini split at model level)

LlmModelCatalog

Field Type Constraints
Id Guid (UUIDv7) Primary key
ProviderId enum (LlmProvider) Part of logical key (ProviderId, ModelId)
ModelId string Max 200 chars; part of logical key
InputPrice decimal Per-token (or per-million) input price; ≥ 0
OutputPrice decimal Per-token output price; ≥ 0
CacheReadPrice decimal Cache-read price; ≥ 0
CacheWritePrice decimal Cache-write price; ≥ 0
PricingVersion int / string Monotonic per model
EffectiveFrom DateTimeOffset Not in the past relative to an existing version
PricingAuthority enum External or Internal
ContextWindow int > 0
MaxOutputTokens int > 0
SupportsStructuredOutput bool —
TokenizerRef string? Optional; used for local models
TenantId Guid? Null = bundled row; non-null = tenant override

ProviderAccount

Field Type Constraints
Id Guid (UUIDv7) Primary key
TenantId Guid? Null for agency-owned accounts
OwnedBy enum Tenant or Agency
Provider enum (LlmProvider) Required
Name string Max 200 chars, required
KeySource enum EnvironmentVariable (default), Database, or Vault — selects where the API key lives (see the implementation note above)
Credential string For EnvironmentVariable / Vault: the secret name to resolve (no key stored by AM). For Database: the key itself, encrypted at rest via Data Protection (purpose LlmProviderKeys). Nullable when authMethod = none.
Subscription value object? Optional (see UC-035)
Status enum Active, Disabled
CreatedAt DateTimeOffset Set on create

ModelDeployment

Field Type Constraints
Id Guid (UUIDv7) Primary key
ModelId string References a catalog (ProviderId, ModelId)
ProviderAccountId Guid FK to ProviderAccount (must be Active)
Target enum Cloud or Local
Endpoint string Max 500 chars
AuthMethod string e.g. ApiKey, Sigv4, none
MaxClassification enum/int Classification ceiling this deployment may handle
HealthCheckEndpoint string? Optional
HealthStatus enum Unknown, Healthy, Degraded, Unhealthy
LastHealthCheck DateTimeOffset? Null until first probe

Security Considerations

  • Authentication. Admin UI access uses AM cookie auth; API/gateway access uses X-Api-Key. No anonymous access to provider, model, or deployment management.
  • Authorization / capabilities. Gated by llm.providers.write, llm.deployments.write, and llm.gateway.config capabilities composed into admin roles via the existing Role / Capability / RoleCapabilityMapping model.
  • Data protection. Provider credentials encrypted via IDataProtector purpose ProviderAccountCredentials, distinct from AgentCredentials. The credential field is write-only in the UI and API — never echoed, masked on display.
  • Audit. Every create, rotate, disable, pricing override, and deployment edit is folded into AuditLog with before/after values and acting principal. Plaintext credentials are never logged.
  • PII handling. Provider account and deployment metadata carry no end-user PII; PII enforcement happens at dispatch time (UC-030). Pricing and chargeback data are tenant-scoped and not cross-tenant readable.
  • Fail-closed. Unknown models and disabled/unhealthy backing infrastructure are excluded from dispatch rather than defaulted — no silent fallback to an unpriced or unverified path.

Testing Scenarios

ID Scenario Expected Result
T-1 Create a cloud provider account with a valid credential Account persisted Active; credential encrypted under ProviderAccountCredentials; audit row written; credential never returned in plaintext
T-2 Create a local deployment account with authMethod=none and no credential Accepted; nullable credential allowed
T-3 Create a cloud account with authMethod=ApiKey and no credential Rejected by validation (UC-028a A1)
T-4 Create two accounts for the same (tenant, provider) Both persisted (multi-account first-class, BR-3)
T-5 Rotate a credential successfully New blob replaces old atomically; audit row written; in-flight dispatches unaffected
T-6 Rotation fails at persist Prior credential intact; no partial state; error surfaced (UC-028b A1)
T-7 Disable an account backing active deployments Status Disabled; deployments become non-viable; admin warned; audit row written
T-8 Register a new catalog model with all prices and capabilities Row persisted with PricingVersion + EffectiveFrom; audit row written
T-9 Add a tenant pricing override layered over a bundled row Override row written tenant-scoped; both visible under their PricingAuthority
T-10 Override with EffectiveFrom in the past Rejected (UC-028c A2)
T-11 Register a model with a negative input price Rejected (UC-028c A3)
T-12 Create a deployment referencing an unregistered (ProviderId, ModelId) Rejected fail-closed (BR-5, UC-028d A1)
T-13 Dispatch resolves to a deployment whose catalog row was deleted Call rejected fail-closed; never priced at $0 (BR-5)
T-14 Create a deployment on a disabled provider account Rejected (UC-028d A2)
T-15 Filter the model catalog by PricingAuthority = Internal Only IT-set local-chargeback rows returned
T-16 Health probe succeeds against a deployment healthStatus = Healthy, lastHealthCheck updated; deployment preferred in cascade
T-17 Health probe times out healthStatus = Unhealthy; deployment excluded from cascade (UC-028e A2)
T-18 Deployment with no healthCheckEndpoint healthStatus = Unknown; treated as eligible but unverified (UC-028e A1)
T-19 User without llm.providers.write attempts to create an account 403 / access denied; no row written
T-20 Credential value appears in any API response, log, or audit row Never present — masked everywhere (security, BR-1)
T-21 GET /api/llm/models returns catalog Pricing, context window, capabilities present; tenant overrides reflected for the calling tenant
T-22 Attach a MonthlyCommitment subscription on account create Subscription value object stored; surfaced to UC-035 utilization tracking
T-23 Boundary: model with contextWindow = 0 Rejected by validation (must be > 0)
T-24 maxClassification below a task type's classificationFloor Deployment created but filtered out for that task type at resolution (UC-029b)
  • UC-029: Task-Type Routing & Agent Provisioning — task-type chains resolve to the ModelDeployments registered here.
  • UC-030: LLM Request Dispatch — the dispatch lifecycle that consumes provider accounts, models, and deployments.
  • UC-035: Provider Subscriptions & Utilization — the Subscription value object attached to ProviderAccount.
  • UC-032: Budget Cap Lattice — model-tier caps point at ModelDeployment rows.

Revision History

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