Account Management Service
Account Management Service
Executive Summary
The Account Management Service is a centralized platform for managing the complete account hierarchy in the Augment-It ecosystem. It handles user profiles, team creation and membership, organization management, billing and payment processing, resource quotas, and access control for AI models and external services. Built as a consolidation of user accounts, team accounts, organization accounts, privilege gates, payment orchestration, and supported models services, it provides a unified interface for all account-related operations while maintaining clear separation from authentication concerns.
Background & Motivation
Problem Statement
The Augment-It platform requires a complex multi-tenant architecture where individual users can belong to teams, teams can belong to organizations, and billing/resource access flows through this hierarchy. The original design proposed multiple separate services:
- userAccountService: User profile management
- teamAccountService: Team creation and membership
- orgAccountService: Organization management
- privilageGatesService: Resource access control
- paymentOrchestratorService: Billing and payment processing
- teamSupportedModelsService: Team-level AI model access
- orgSupportedModelsService: Organization-level AI model access
This creates several architectural problems:
- Service Proliferation: Too many microservices for closely related functionality
- Data Consistency Issues: Distributed transactions across multiple services for simple operations
- Complex Inter-Service Communication: Basic operations requiring multiple service calls
- Maintenance Overhead: Managing and deploying 7+ services for related concerns
- Performance Issues: Network latency for authorization decisions requiring multiple lookups
- Development Complexity: Coordinating changes across multiple service boundaries
Why This Solution
- Single Domain Boundary: User, team, org, billing, and resource access are all part of the same business domain
- Data Consistency: Single database and transaction boundary for related operations
- Performance: Fast authorization decisions with single service calls
- Simplified Architecture: Easier to understand, test, and maintain
- Clear Integration: Well-defined interface with User Authorization Service
- Scalable Design: Can be split later if genuine scaling needs arise
Goals & Non-Goals
Goals
- Unified Account Hierarchy: Manage users, teams, and organizations in a single cohesive model
- Integrated Billing: Handle payment methods, billing, and usage tracking within the account context
- Resource Access Control: Determine what AI models and services users can access based on their account hierarchy
- Team Collaboration: Enable team creation, invitations, and membership management
- Organization Management: Support enterprise customers with multi-team organizations
- Quota Management: Track and enforce usage limits across the account hierarchy
- Payment Processing: Handle credit cards, billing cycles, and payment orchestration
- Usage Analytics: Provide detailed usage reporting for billing and optimization
Non-Goals
- Authentication: Handled by User Authorization Service
- Session Management: Not this service's responsibility
- AI Model Execution: Only manages access, not execution
- Data Processing: Business logic handled by individual services
- User Interface: Backend service only
Technical Design
High-Level Architecture
graph TD
A[Microfrontends] --> B[Account Management Service]
C[User Authorization Service] --> B
D[API Connector Service] --> B
B --> E[Account Manager]
B --> F[Team Manager]
B --> G[Organization Manager]
B --> H[Billing Manager]
B --> I[Resource Manager]
B --> J[Quota Manager]
E --> K[Profile Service]
E --> L[Preferences Service]
F --> M[Team Repository]
F --> N[Invitation Service]
F --> O[Membership Manager]
G --> P[Org Repository]
G --> Q[Hierarchy Manager]
H --> R[Payment Processor]
H --> S[Invoice Manager]
H --> T[Subscription Manager]
I --> U[Model Access Manager]
I --> V[Service Access Manager]
I --> W[API Key Manager]
J --> X[Usage Tracker]
J --> Y[Quota Enforcer]
J --> Z[Analytics Engine]
subgraph "Data Layer"
AA[PostgreSQL - Accounts]
BB[Redis - Cache]
CC[Vault - API Keys]
end
subgraph "External Services"
DD[Stripe|Payment Gateway]
EE[Email Service]
FF[AI Model Providers]
end
M --> AA
P --> AA
K --> AA
R --> DD
N --> EE
U --> CC
B --> BB
subgraph "Integration Points"
GG[User Auth Service]
HH[API Connector Service]
II[Parser Services]
JJ[Shell App]
end
GG --> |"User validation"| B
B --> |"Resource access"| HH
B --> |"Quota checks"| II
B --> |"Account data"| JJ
Service Relationships
With User Authorization Service
- Clear Boundary: Auth service handles authentication/authorization, Account service handles account data
- User Validation: Account service validates user existence with Auth service
- Permission Context: Account service provides team/org context for authorization decisions
sequenceDiagram
participant Client
participant AuthService as User Authorization Service
participant AccountService as Account Management Service
participant Database
Client->>AuthService: Login request
AuthService->>Database: Validate credentials
AuthService-->>Client: JWT token with user ID
Client->>AccountService: Get user teams (with JWT)
AccountService->>AuthService: Validate token
AuthService-->>AccountService: User ID + permissions
AccountService->>Database: Fetch user teams
AccountService-->>Client: Team list
Client->>AccountService: Check model access
AccountService->>Database: Get user/team/org hierarchy
AccountService-->>Client: Available models + quotas
With API Connector Service
- Resource Validation: Account service determines which AI models/APIs user can access
- API Key Management: Account service provides appropriate API keys for requests
- Usage Tracking: Account service tracks API usage for billing
sequenceDiagram
participant Client
participant APIConnector as API Connector Service
participant AccountMgmt as Account Management Service
participant AIProvider as AI Model Provider
Client->>APIConnector: Make AI request
APIConnector->>AccountMgmt: Check model access + quotas
AccountMgmt-->>APIConnector: Access granted + API key
APIConnector->>AIProvider: Forward request with API key
AIProvider-->>APIConnector: Response
APIConnector->>AccountMgmt: Track usage
AccountMgmt-->>APIConnector: Usage recorded
APIConnector-->>Client: Response
Core Components
1. Account Manager
Responsibility: User profile and account lifecycle management
Features:
- User profile creation and updates
- Account preferences and settings
- Profile picture and metadata management
- Account linking across teams and organizations
2. Team Manager
Responsibility: Team creation, membership, and collaboration
Features:
- Team creation and configuration
- Member invitation and management
- Role assignment within teams
- Team-level resource allocation
3. Organization Manager
Responsibility: Enterprise organization management
Features:
- Organization creation and hierarchy
- Multi-team organization structure
- Organization-level policies and settings
- Enterprise billing and compliance
4. Billing Manager
Responsibility: Payment processing and billing orchestration
Features:
- Payment method management
- Subscription and billing cycle handling
- Invoice generation and delivery
- Usage-based billing calculations
5. Resource Manager
Responsibility: AI model and service access control
Features:
- Model access configuration per team/org
- API key management and rotation
- Service endpoint provisioning
- Access policy enforcement
6. Quota Manager
Responsibility: Usage tracking and quota enforcement
Features:
- Real-time usage monitoring
- Quota enforcement and overage handling
- Usage analytics and reporting
- Cost optimization recommendations
7. Provisioned Services Manager
Responsibility: Manage all external service integrations across the account hierarchy
Features:
- Multi-category service provisioning (AI, Data, Integration, Web Crawlers)
- API key and credential management per service type
- Service-specific quota and cost controls
- Hierarchical service inheritance (User → Team → Organization)
- Service health monitoring and failover configuration
Provisioned Services Architecture
The Account Management Service handles comprehensive service provisioning across the entire Augment-It ecosystem. Instead of just "supported models", the system manages Provisioned Services - all external APIs and services that users, teams, and organizations need access to for their data augmentation workflows.
Service Categories
graph TB
subgraph "Provisioned Services Ecosystem"
A[Account Management Service]
subgraph "AI Model APIs"
B1[OpenAI]
B2[Anthropic]
B3[Groq]
B4[Google Gemini]
B5[Cohere]
B6[Hugging Face]
end
subgraph "Data Store APIs"
C1[NocoDB]
C2[Airtable]
C3[Databricks]
C4[PostgreSQL]
C5[MongoDB]
C6[Supabase]
end
subgraph "Integration APIs"
D1[Webhook Services]
D2[Zapier]
D3[Make.com]
D4[Slack]
D5[Discord]
D6[Email Services]
end
subgraph "AI-Powered Web Crawlers"
E1[Intelligent Web Scraper]
E2[Content Extractor]
E3[Data Harvester]
E4[Social Media Crawler]
E5[News Aggregator]
end
subgraph "Specialized Services"
F1[Document Parsers]
F2[Image Processing]
F3[Audio Transcription]
F4[Video Analysis]
F5[Translation Services]
end
end
A --> B1
A --> B2
A --> C1
A --> C2
A --> D1
A --> D2
A --> E1
A --> E2
A --> F1
A --> F2
Hierarchical Service Inheritance
Services are provisioned and inherited through the account hierarchy:
graph TD
A[Organization] --> B[Team A]
A --> C[Team B]
B --> D[User 1]
B --> E[User 2]
C --> F[User 3]
C --> G[User 4]
subgraph "Service Inheritance Flow"
H[Org Provisioned Services]
I[Team Provisioned Services]
J[User Access Resolution]
end
A -.-> H
B -.-> I
C -.-> I
H --> I
I --> J
D -.-> J
E -.-> J
F -.-> J
G -.-> J
subgraph "Access Resolution Logic"
K["1. Check Team Services"]
L["2. Inherit Org Services"]
M["3. Apply User Quotas"]
N["4. Validate Permissions"]
end
J --> K --> L --> M --> N
Service Provisioning Workflow
sequenceDiagram
participant Admin as Team/Org Admin
participant AccountMgmt as Account Management Service
participant Vault as HashiCorp Vault
participant Provider as External Service Provider
participant User as Team Member
Note over Admin, User: Service Provisioning Flow
Admin->>AccountMgmt: Add new service (OpenAI)
AccountMgmt->>AccountMgmt: Validate admin permissions
Admin->>AccountMgmt: Provide API key & configuration
AccountMgmt->>Vault: Store encrypted API key
Vault-->>AccountMgmt: Return key reference ID
AccountMgmt->>Provider: Test connection
Provider-->>AccountMgmt: Connection successful
AccountMgmt->>AccountMgmt: Save service configuration
AccountMgmt-->>Admin: Service provisioned successfully
Note over Admin, User: User Access Flow
User->>AccountMgmt: Request OpenAI access
AccountMgmt->>AccountMgmt: Check user hierarchy
AccountMgmt->>AccountMgmt: Validate quotas & permissions
AccountMgmt->>Vault: Retrieve API key
Vault-->>AccountMgmt: Return decrypted key
AccountMgmt-->>User: Access granted with quotas
Note over Admin, User: Usage Tracking
User->>Provider: Make API request (via API Connector)
Provider-->>User: API response
User->>AccountMgmt: Report usage metrics
AccountMgmt->>AccountMgmt: Update quotas & billing
AccountMgmt->>AccountMgmt: Check quota limits
alt Quota Exceeded
AccountMgmt-->>User: Quota exceeded warning
AccountMgmt->>Admin: Quota alert notification
end
Service Configuration Data Model
typescript
interface ProvisionedService {
id: string;
accountId: string; // Team or Organization ID
accountType: 'team' | 'organization';
category: ServiceCategory;
provider: ServiceProvider;
name: string;
description?: string;
configuration: ServiceConfiguration;
credentials: ServiceCredentials;
quotas: ServiceQuotas;
costLimits: CostLimits;
enabled: boolean;
healthStatus: ServiceHealthStatus;
createdAt: Date;
updatedAt: Date;
createdBy: string;
}
type ServiceCategory =
| 'ai_models'
| 'data_stores'
| 'integrations'
| 'web_crawlers'
| 'specialized';
type ServiceProvider =
// AI Models
| 'openai' | 'anthropic' | 'groq' | 'google' | 'cohere' | 'huggingface'
// Data Stores
| 'nocodb' | 'airtable' | 'databricks' | 'postgresql' | 'mongodb' | 'supabase'
// Integrations
| 'zapier' | 'make' | 'slack' | 'discord' | 'sendgrid' | 'twilio'
// Web Crawlers
| 'scrapfly' | 'brightdata' | 'proxycrawl' | 'scraperapi'
// Specialized
| 'aws_textract' | 'google_vision' | 'azure_cognitive' | 'deepgram';
interface ServiceConfiguration {
baseUrl?: string;
region?: string;
version?: string;
features: ServiceFeature[];
parameters: Record<string, any>;
retryPolicy: RetryPolicy;
timeoutMs: number;
}
interface ServiceFeature {
name: string;
enabled: boolean;
config?: Record<string, any>;
quotas?: FeatureQuotas;
}
interface ServiceCredentials {
keyId: string; // Reference to encrypted key in Vault
keyType: 'api_key' | 'oauth2' | 'jwt' | 'basic_auth' | 'custom';
additionalSecrets?: Record<string, string>; // Additional encrypted values
rotationPolicy?: KeyRotationPolicy;
}
interface ServiceQuotas {
requestsPerMinute?: number;
requestsPerHour?: number;
requestsPerDay?: number;
requestsPerMonth?: number;
dataPerDay?: number; // in MB
concurrentRequests?: number;
customQuotas?: Record<string, number>;
}
interface CostLimits {
dailyLimit?: number;
monthlyLimit?: number;
perRequestLimit?: number;
currency: string;
alertThresholds: AlertThreshold[];
}
interface AlertThreshold {
percentage: number; // e.g., 80 for 80% of limit
recipients: string[]; // email addresses
actions: AlertAction[];
}
type AlertAction = 'email' | 'slack' | 'webhook' | 'disable_service';
interface ServiceHealthStatus {
isHealthy: boolean;
lastChecked: Date;
responseTime: number;
uptime: number; // percentage
errorRate: number; // percentage
lastError?: string;
} Service Access Resolution Engine
typescript
class ServiceAccessManager {
constructor(
private accountRepo: AccountRepository,
private vaultService: VaultService,
private quotaManager: QuotaManager
) {}
public async getUserAvailableServices(userId: string): Promise<AvailableServices> {
const hierarchy = await this.getAccountHierarchy(userId);
const allServices: ProvisionedService[] = [];
// Collect services from all levels in hierarchy
for (const level of hierarchy) {
const services = await this.getAccountServices(level.id, level.type);
allServices.push(...services);
}
// Deduplicate and resolve conflicts (team overrides org, etc.)
const resolvedServices = this.resolveServiceConflicts(allServices);
// Apply user-specific quotas and permissions
const availableServices: AvailableServices = {
aiModels: [],
dataStores: [],
integrations: [],
webCrawlers: [],
specialized: []
};
for (const service of resolvedServices) {
if (service.enabled) {
const userAccess = await this.calculateUserAccess(userId, service);
const serviceInfo: ServiceInfo = {
id: service.id,
provider: service.provider,
name: service.name,
features: service.configuration.features,
quotas: userAccess.quotas,
costLimits: userAccess.costLimits,
healthStatus: service.healthStatus
};
// Categorize service
switch (service.category) {
case 'ai_models':
availableServices.aiModels.push(serviceInfo);
break;
case 'data_stores':
availableServices.dataStores.push(serviceInfo);
break;
case 'integrations':
availableServices.integrations.push(serviceInfo);
break;
case 'web_crawlers':
availableServices.webCrawlers.push(serviceInfo);
break;
case 'specialized':
availableServices.specialized.push(serviceInfo);
break;
}
}
}
return availableServices;
}
public async checkServiceAccess(
userId: string,
provider: ServiceProvider,
feature?: string
): Promise<ServiceAccessResult> {
const hierarchy = await this.getAccountHierarchy(userId);
// Find service in hierarchy (team services override org services)
for (const level of hierarchy) {
const service = await this.findServiceInAccount(level.id, level.type, provider);
if (service && service.enabled) {
// Check feature access if specified
if (feature) {
const featureConfig = service.configuration.features.find(f => f.name === feature);
if (!featureConfig || !featureConfig.enabled) {
continue; // Try next level in hierarchy
}
}
// Check quotas
const quotaStatus = await this.quotaManager.checkServiceQuota(
userId,
service.id,
feature
);
if (!quotaStatus.withinLimits && quotaStatus.hardLimit) {
return {
hasAccess: false,
reason: 'Quota exceeded',
quotaStatus
};
}
return {
hasAccess: true,
serviceId: service.id,
accountLevel: level.type,
quotaStatus,
costLimits: service.costLimits
};
}
}
return {
hasAccess: false,
reason: `No access configured for ${provider}${feature ? ` feature: ${feature}` : ''}`
};
}
public async getServiceCredentials(
userId: string,
provider: ServiceProvider
): Promise<ServiceCredentials> {
const accessResult = await this.checkServiceAccess(userId, provider);
if (!accessResult.hasAccess) {
throw new Error(`Access denied for ${provider}`);
}
const service = await this.accountRepo.getProvisionedService(accessResult.serviceId);
// Decrypt credentials from vault
const apiKey = await this.vaultService.getSecret(service.credentials.keyId);
const additionalSecrets: Record<string, string> = {};
if (service.credentials.additionalSecrets) {
for (const [key, secretId] of Object.entries(service.credentials.additionalSecrets)) {
additionalSecrets[key] = await this.vaultService.getSecret(secretId);
}
}
return {
apiKey,
keyType: service.credentials.keyType,
additionalSecrets,
serviceConfig: service.configuration
};
}
private resolveServiceConflicts(services: ProvisionedService[]): ProvisionedService[] {
const serviceMap = new Map<string, ProvisionedService>();
// Sort by precedence: team services override org services
const sortedServices = services.sort((a, b) => {
if (a.provider !== b.provider) return 0;
// Team level takes precedence over org level
if (a.accountType === 'team' && b.accountType === 'organization') return -1;
if (a.accountType === 'organization' && b.accountType === 'team') return 1;
// More recent services take precedence
return b.updatedAt.getTime() - a.updatedAt.getTime();
});
for (const service of sortedServices) {
const key = `${service.provider}`;
if (!serviceMap.has(key)) {
serviceMap.set(key, service);
}
}
return Array.from(serviceMap.values());
}
private async calculateUserAccess(
userId: string,
service: ProvisionedService
): Promise<UserServiceAccess> {
// Get user's role in the account that provides this service
const userRole = await this.getUserRoleInAccount(
userId,
service.accountId,
service.accountType
);
// Apply role-based quota multipliers
const quotaMultiplier = this.getQuotaMultiplier(userRole);
const adjustedQuotas = this.applyQuotaMultiplier(service.quotas, quotaMultiplier);
return {
quotas: adjustedQuotas,
costLimits: service.costLimits,
permissions: this.getRolePermissions(userRole)
};
}
private getQuotaMultiplier(role: TeamRole): number {
const multipliers: Record<TeamRole, number> = {
'owner': 1.0,
'admin': 1.0,
'member': 0.8,
'viewer': 0.5
};
return multipliers[role] || 0.5;
}
}
interface AvailableServices {
aiModels: ServiceInfo[];
dataStores: ServiceInfo[];
integrations: ServiceInfo[];
webCrawlers: ServiceInfo[];
specialized: ServiceInfo[];
}
interface ServiceInfo {
id: string;
provider: ServiceProvider;
name: string;
features: ServiceFeature[];
quotas: ServiceQuotas;
costLimits: CostLimits;
healthStatus: ServiceHealthStatus;
}
interface ServiceAccessResult {
hasAccess: boolean;
serviceId?: string;
accountLevel?: 'team' | 'organization';
quotaStatus?: QuotaStatus;
costLimits?: CostLimits;
reason?: string;
}
interface UserServiceAccess {
quotas: ServiceQuotas;
costLimits: CostLimits;
permissions: string[];
} Service Management Interface
typescript
interface ProvisionedServicesAPI {
// Service Provisioning
provisionService(
accountId: string,
accountType: 'team' | 'organization',
serviceData: CreateProvisionedServiceRequest
): Promise<ProvisionedService>;
updateProvisionedService(
serviceId: string,
updates: Partial<ProvisionedService>
): Promise<ProvisionedService>;
removeProvisionedService(serviceId: string): Promise<void>;
// Service Discovery
getAvailableServices(userId: string): Promise<AvailableServices>;
getProvisionedServices(
accountId: string,
accountType: 'team' | 'organization'
): Promise<ProvisionedService[]>;
// Access Control
checkServiceAccess(
userId: string,
provider: ServiceProvider,
feature?: string
): Promise<ServiceAccessResult>;
getServiceCredentials(
userId: string,
provider: ServiceProvider
): Promise<ServiceCredentials>;
// Service Health
checkServiceHealth(serviceId: string): Promise<ServiceHealthStatus>;
getServiceHealthHistory(
serviceId: string,
period: DateRange
): Promise<ServiceHealthMetric[]>;
// Usage and Quotas
trackServiceUsage(
userId: string,
serviceId: string,
usage: ServiceUsageData
): Promise<void>;
getServiceUsage(
accountId: string,
accountType: 'team' | 'organization',
period: DateRange
): Promise<ServiceUsageMetrics>;
updateServiceQuotas(
serviceId: string,
quotas: ServiceQuotas
): Promise<void>;
// Cost Management
getServiceCosts(
accountId: string,
accountType: 'team' | 'organization',
period: DateRange
): Promise<ServiceCostBreakdown>;
// Credential Management
rotateServiceCredentials(serviceId: string): Promise<void>;
testServiceConnection(serviceId: string): Promise<ServiceConnectionTest>;
} API Specifications
Core Data Models
typescript
interface User {
id: string;
email: string;
profile: UserProfile;
preferences: UserPreferences;
teamMemberships: TeamMembership[];
createdAt: Date;
updatedAt: Date;
}
interface UserProfile {
firstName?: string;
lastName?: string;
displayName?: string;
avatar?: string;
bio?: string;
timezone?: string;
locale?: string;
phoneNumber?: string;
company?: string;
jobTitle?: string;
}
interface UserPreferences {
notifications: NotificationSettings;
privacy: PrivacySettings;
ui: UIPreferences;
integrations: IntegrationSettings;
}
interface Team {
id: string;
name: string;
description?: string;
organizationId?: string;
avatar?: string;
settings: TeamSettings;
members: TeamMembership[];
modelAccess: ModelAccess[];
billingInfo: BillingInfo;
quotas: QuotaSettings;
createdAt: Date;
updatedAt: Date;
}
interface Organization {
id: string;
name: string;
description?: string;
domain?: string; // for email-based auto-joining
avatar?: string;
settings: OrganizationSettings;
teams: Team[];
paymentMethods: PaymentMethod[];
billingInfo: BillingInfo;
modelAccess: ModelAccess[];
quotas: QuotaSettings;
compliance: ComplianceSettings;
createdAt: Date;
updatedAt: Date;
}
interface TeamMembership {
id: string;
userId: string;
teamId: string;
role: TeamRole;
permissions: TeamPermission[];
joinedAt: Date;
invitedBy?: string;
}
type TeamRole = 'owner' | 'admin' | 'member' | 'viewer';
interface TeamPermission {
resource: string;
actions: string[];
conditions?: PolicyCondition[];
}
interface ModelAccess {
provider: AIProvider;
models: ModelConfiguration[];
apiKeyId: string; // Reference to encrypted key in Vault
enabled: boolean;
quotas: ModelQuotas;
costLimits: CostLimits;
createdAt: Date;
}
type AIProvider = 'openai' | 'anthropic' | 'groq' | 'google' | 'cohere' | 'huggingface';
interface ModelConfiguration {
name: string;
enabled: boolean;
parameters?: ModelParameters;
costPerToken?: number;
quotas?: ModelQuotas;
}
interface ModelQuotas {
requestsPerMinute?: number;
requestsPerDay?: number;
tokensPerMinute?: number;
tokensPerDay?: number;
maxCostPerDay?: number;
}
interface BillingInfo {
customerId: string; // Stripe customer ID
subscriptionId?: string;
planType: PlanType;
billingCycle: BillingCycle;
currentUsage: UsageMetrics;
invoices: Invoice[];
paymentHistory: PaymentRecord[];
}
type PlanType = 'free' | 'starter' | 'professional' | 'enterprise';
type BillingCycle = 'monthly' | 'yearly';
interface PaymentMethod {
id: string;
type: 'card' | 'bank' | 'paypal';
details: PaymentMethodDetails;
isDefault: boolean;
createdAt: Date;
}
interface UsageMetrics {
period: {
start: Date;
end: Date;
};
aiModelUsage: ModelUsage[];
totalCost: number;
requestCount: number;
tokenCount: number;
}
interface ModelUsage {
provider: AIProvider;
model: string;
requestCount: number;
tokenCount: number;
cost: number;
} Main Service Interface
typescript
interface AccountManagementService {
// User Account Management
createUserAccount(userData: CreateUserRequest): Promise<User>;
updateUserProfile(userId: string, profile: Partial<UserProfile>): Promise<User>;
updateUserPreferences(userId: string, preferences: Partial<UserPreferences>): Promise<User>;
getUserAccount(userId: string): Promise<User>;
deleteUserAccount(userId: string): Promise<void>;
// Team Management
createTeam(creatorId: string, teamData: CreateTeamRequest): Promise<Team>;
updateTeam(teamId: string, updates: Partial<Team>): Promise<Team>;
deleteTeam(teamId: string): Promise<void>;
getTeam(teamId: string): Promise<Team>;
getUserTeams(userId: string): Promise<Team[]>;
// Team Membership
inviteUserToTeam(teamId: string, email: string, role: TeamRole, invitedBy: string): Promise<TeamInvitation>;
acceptTeamInvitation(invitationToken: string): Promise<TeamMembership>;
rejectTeamInvitation(invitationToken: string): Promise<void>;
updateTeamMembership(membershipId: string, updates: Partial<TeamMembership>): Promise<TeamMembership>;
removeTeamMember(teamId: string, userId: string): Promise<void>;
leaveTeam(teamId: string, userId: string): Promise<void>;
// Organization Management
createOrganization(creatorId: string, orgData: CreateOrganizationRequest): Promise<Organization>;
updateOrganization(orgId: string, updates: Partial<Organization>): Promise<Organization>;
deleteOrganization(orgId: string): Promise<void>;
getOrganization(orgId: string): Promise<Organization>;
addTeamToOrganization(orgId: string, teamId: string): Promise<void>;
removeTeamFromOrganization(orgId: string, teamId: string): Promise<void>;
// Resource Access Management
addModelAccess(accountId: string, accountType: 'team' | 'org', modelAccess: ModelAccess): Promise<void>;
updateModelAccess(accessId: string, updates: Partial<ModelAccess>): Promise<ModelAccess>;
removeModelAccess(accessId: string): Promise<void>;
getUserAvailableModels(userId: string): Promise<AvailableModels>;
checkModelAccess(userId: string, provider: AIProvider, model: string): Promise<ModelAccessResult>;
// API Key Management
addAPIKey(accountId: string, accountType: 'team' | 'org', provider: AIProvider, apiKey: string): Promise<string>;
rotateAPIKey(keyId: string, newKey: string): Promise<void>;
removeAPIKey(keyId: string): Promise<void>;
getAPIKeyForUser(userId: string, provider: AIProvider): Promise<string>;
// Billing and Payment
addPaymentMethod(accountId: string, accountType: 'team' | 'org', paymentMethod: PaymentMethodData): Promise<PaymentMethod>;
updatePaymentMethod(paymentMethodId: string, updates: Partial<PaymentMethodData>): Promise<PaymentMethod>;
removePaymentMethod(paymentMethodId: string): Promise<void>;
setDefaultPaymentMethod(paymentMethodId: string): Promise<void>;
getPaymentMethods(accountId: string, accountType: 'team' | 'org'): Promise<PaymentMethod[]>;
// Subscription Management
createSubscription(accountId: string, accountType: 'team' | 'org', planType: PlanType): Promise<Subscription>;
updateSubscription(subscriptionId: string, changes: SubscriptionUpdate): Promise<Subscription>;
cancelSubscription(subscriptionId: string): Promise<void>;
// Usage Tracking and Quotas
trackModelUsage(userId: string, provider: AIProvider, model: string, usage: UsageData): Promise<void>;
getUserUsage(userId: string, period: DateRange): Promise<UsageMetrics>;
getTeamUsage(teamId: string, period: DateRange): Promise<UsageMetrics>;
getOrganizationUsage(orgId: string, period: DateRange): Promise<UsageMetrics>;
checkQuota(userId: string, provider: AIProvider, model: string): Promise<QuotaStatus>;
updateQuotas(accountId: string, accountType: 'team' | 'org', quotas: QuotaSettings): Promise<void>;
// Billing and Invoicing
generateInvoice(accountId: string, accountType: 'team' | 'org', period: DateRange): Promise<Invoice>;
getInvoices(accountId: string, accountType: 'team' | 'org'): Promise<Invoice[]>;
payInvoice(invoiceId: string): Promise<PaymentResult>;
// Analytics and Reporting
getUsageAnalytics(accountId: string, accountType: 'team' | 'org', period: DateRange): Promise<UsageAnalytics>;
getCostAnalytics(accountId: string, accountType: 'team' | 'org', period: DateRange): Promise<CostAnalytics>;
getPerformanceMetrics(accountId: string, accountType: 'team' | 'org'): Promise<PerformanceMetrics>;
// Administrative
getAccountHierarchy(userId: string): Promise<AccountHierarchy>;
transferTeamOwnership(teamId: string, newOwnerId: string): Promise<void>;
transferOrganizationOwnership(orgId: string, newOwnerId: string): Promise<void>;
suspendAccount(accountId: string, accountType: 'user' | 'team' | 'org', reason: string): Promise<void>;
reactivateAccount(accountId: string, accountType: 'user' | 'team' | 'org'): Promise<void>;
} Team Invitation Implementation
typescript
class TeamInvitationManager {
constructor(
private emailService: EmailService,
private teamRepository: TeamRepository,
private userRepository: UserRepository
) {}
public async inviteUserToTeam(
teamId: string,
email: string,
role: TeamRole,
invitedBy: string
): Promise<TeamInvitation> {
// Validate team exists and inviter has permission
const team = await this.teamRepository.findById(teamId);
if (!team) {
throw new Error('Team not found');
}
const inviterMembership = team.members.find(m => m.userId === invitedBy);
if (!inviterMembership || !this.canInviteMembers(inviterMembership.role)) {
throw new Error('Insufficient permissions to invite members');
}
// Check if user already exists
const existingUser = await this.userRepository.findByEmail(email);
// Generate invitation token
const invitationToken = this.generateInvitationToken();
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days
const invitation: TeamInvitation = {
id: uuidv4(),
teamId,
email,
role,
invitedBy,
token: invitationToken,
status: 'pending',
expiresAt,
createdAt: new Date()
};
// Store invitation
await this.teamRepository.saveInvitation(invitation);
// Send invitation email
await this.sendInvitationEmail(invitation, team, existingUser);
return invitation;
}
public async acceptInvitation(token: string): Promise<TeamMembership> {
const invitation = await this.teamRepository.findInvitationByToken(token);
if (!invitation || invitation.status !== 'pending') {
throw new Error('Invalid or expired invitation');
}
if (invitation.expiresAt < new Date()) {
throw new Error('Invitation has expired');
}
// Get or create user
let user = await this.userRepository.findByEmail(invitation.email);
if (!user) {
// Create minimal user record - they'll complete registration later
user = await this.userRepository.create({
email: invitation.email,
profile: { displayName: invitation.email.split('@')[0] },
status: 'pending_verification'
});
}
// Create team membership
const membership: TeamMembership = {
id: uuidv4(),
userId: user.id,
teamId: invitation.teamId,
role: invitation.role,
permissions: this.getDefaultPermissions(invitation.role),
joinedAt: new Date(),
invitedBy: invitation.invitedBy
};
await this.teamRepository.addMember(membership);
// Update invitation status
await this.teamRepository.updateInvitation(invitation.id, {
status: 'accepted',
acceptedAt: new Date()
});
return membership;
}
private async sendInvitationEmail(
invitation: TeamInvitation,
team: Team,
existingUser?: User
): Promise<void> {
const inviterUser = await this.userRepository.findById(invitation.invitedBy);
const acceptUrl = `${process.env.APP_URL}/invite/${invitation.token}`;
await this.emailService.sendTemplate(invitation.email, 'team-invitation', {
teamName: team.name,
inviterName: inviterUser?.profile.displayName || inviterUser?.email,
role: invitation.role,
acceptUrl,
isExistingUser: !!existingUser,
expiresAt: invitation.expiresAt
});
}
private canInviteMembers(role: TeamRole): boolean {
return ['owner', 'admin'].includes(role);
}
private generateInvitationToken(): string {
return crypto.randomBytes(32).toString('hex');
}
private getDefaultPermissions(role: TeamRole): TeamPermission[] {
const basePermissions: Record<TeamRole, TeamPermission[]> = {
owner: [
{ resource: '*', actions: ['*'] }
],
admin: [
{ resource: 'team', actions: ['read', 'update'] },
{ resource: 'members', actions: ['read', 'invite', 'remove'] },
{ resource: 'models', actions: ['read', 'use'] },
{ resource: 'billing', actions: ['read'] }
],
member: [
{ resource: 'team', actions: ['read'] },
{ resource: 'members', actions: ['read'] },
{ resource: 'models', actions: ['read', 'use'] }
],
viewer: [
{ resource: 'team', actions: ['read'] },
{ resource: 'models', actions: ['read'] }
]
};
return basePermissions[role] || [];
}
} Resource Access Control Implementation
typescript
class ResourceAccessManager {
constructor(
private accountRepo: AccountRepository,
private vaultService: VaultService
) {}
public async checkModelAccess(
userId: string,
provider: AIProvider,
model: string
): Promise<ModelAccessResult> {
// Get user's account hierarchy
const hierarchy = await this.getAccountHierarchy(userId);
// Check access at each level (user -> team -> org)
for (const level of hierarchy) {
const access = await this.checkLevelAccess(level, provider, model);
if (access.hasAccess) {
// Check quotas
const quotaStatus = await this.checkQuotas(userId, level, provider, model);
return {
hasAccess: true,
level: level.type,
accountId: level.id,
apiKeyId: access.apiKeyId,
quotas: access.quotas,
quotaStatus,
costLimits: access.costLimits
};
}
}
return {
hasAccess: false,
reason: 'No access configured for this model'
};
}
public async getAPIKeyForUser(userId: string, provider: AIProvider): Promise<string> {
const hierarchy = await this.getAccountHierarchy(userId);
for (const level of hierarchy) {
const modelAccess = await this.getLevelModelAccess(level.id, level.type, provider);
if (modelAccess && modelAccess.enabled && modelAccess.apiKeyId) {
// Decrypt API key from vault
return await this.vaultService.getSecret(modelAccess.apiKeyId);
}
}
throw new Error(`No API key configured for ${provider}`);
}
public async trackUsage(
userId: string,
provider: AIProvider,
model: string,
usage: UsageData
): Promise<void> {
const hierarchy = await this.getAccountHierarchy(userId);
const accessResult = await this.checkModelAccess(userId, provider, model);
if (!accessResult.hasAccess) {
throw new Error('Access denied for usage tracking');
}
// Track usage at the account level that provides access
const usageRecord: UsageRecord = {
id: uuidv4(),
userId,
accountId: accessResult.accountId,
accountType: accessResult.level,
provider,
model,
requestCount: usage.requestCount,
tokenCount: usage.tokenCount,
cost: usage.cost,
metadata: usage.metadata,
timestamp: new Date()
};
await this.accountRepo.saveUsageRecord(usageRecord);
// Update real-time usage counters
await this.updateUsageCounters(accessResult.accountId, accessResult.level, usage);
// Check if usage exceeds any limits
await this.checkAndEnforceQuotas(userId, accessResult.accountId, accessResult.level, provider, model);
}
private async getAccountHierarchy(userId: string): Promise<AccountLevel[]> {
const user = await this.accountRepo.getUserWithHierarchy(userId);
const hierarchy: AccountLevel[] = [];
// Start with user's teams
for (const membership of user.teamMemberships) {
const team = await this.accountRepo.getTeam(membership.teamId);
hierarchy.push({
type: 'team',
id: team.id,
name: team.name,
permissions: membership.permissions
});
// Add organization if team belongs to one
if (team.organizationId) {
const org = await this.accountRepo.getOrganization(team.organizationId);
hierarchy.push({
type: 'organization',
id: org.id,
name: org.name,
permissions: [] // Org permissions inherited through team
});
}
}
// Remove duplicates and sort by precedence (org -> team -> user)
return this.deduplicateAndSort(hierarchy);
}
private async checkLevelAccess(
level: AccountLevel,
provider: AIProvider,
model: string
): Promise<LevelAccessResult> {
const modelAccess = await this.getLevelModelAccess(level.id, level.type, provider);
if (!modelAccess || !modelAccess.enabled) {
return { hasAccess: false };
}
// Check if specific model is enabled
const modelConfig = modelAccess.models.find(m => m.name === model);
if (!modelConfig || !modelConfig.enabled) {
return { hasAccess: false };
}
return {
hasAccess: true,
apiKeyId: modelAccess.apiKeyId,
quotas: modelConfig.quotas || modelAccess.quotas,
costLimits: modelAccess.costLimits
};
}
private async checkQuotas(
userId: string,
level: AccountLevel,
provider: AIProvider,
model: string
): Promise<QuotaStatus> {
const currentUsage = await this.getCurrentUsage(level.id, level.type, provider, model);
const quotas = await this.getQuotas(level.id, level.type, provider, model);
const status: QuotaStatus = {
withinLimits: true,
usage: currentUsage,
limits: quotas,
warnings: []
};
// Check various quota limits
if (quotas.requestsPerMinute && currentUsage.requestsPerMinute >= quotas.requestsPerMinute) {
status.withinLimits = false;
status.warnings.push('Requests per minute limit exceeded');
}
if (quotas.requestsPerDay && currentUsage.requestsPerDay >= quotas.requestsPerDay) {
status.withinLimits = false;
status.warnings.push('Daily request limit exceeded');
}
if (quotas.maxCostPerDay && currentUsage.costPerDay >= quotas.maxCostPerDay) {
status.withinLimits = false;
status.warnings.push('Daily cost limit exceeded');
}
return status;
}
}
interface ModelAccessResult {
hasAccess: boolean;
level?: 'team' | 'organization';
accountId?: string;
apiKeyId?: string;
quotas?: ModelQuotas;
quotaStatus?: QuotaStatus;
costLimits?: CostLimits;
reason?: string;
}
interface AccountLevel {
type: 'team' | 'organization';
id: string;
name: string;
permissions: TeamPermission[];
}
interface QuotaStatus {
withinLimits: boolean;
usage: CurrentUsage;
limits: ModelQuotas;
warnings: string[];
} Billing and Payment Integration
typescript
class BillingManager {
constructor(
private stripeService: StripeService,
private accountRepo: AccountRepository,
private emailService: EmailService
) {}
public async addPaymentMethod(
accountId: string,
accountType: 'team' | 'org',
paymentMethodData: PaymentMethodData
): Promise<PaymentMethod> {
// Get or create Stripe customer
const account = await this.getAccount(accountId, accountType);
let customerId = account.billingInfo?.customerId;
if (!customerId) {
const customer = await this.stripeService.customers.create({
email: account.billingEmail,
name: account.name,
metadata: {
accountId,
accountType
}
});
customerId = customer.id;
// Update account with customer ID
await this.updateBillingInfo(accountId, accountType, { customerId });
}
// Create payment method in Stripe
const stripePaymentMethod = await this.stripeService.paymentMethods.create({
type: paymentMethodData.type,
...paymentMethodData.details
});
// Attach to customer
await this.stripeService.paymentMethods.attach(stripePaymentMethod.id, {
customer: customerId
});
const paymentMethod: PaymentMethod = {
id: uuidv4(),
stripePaymentMethodId: stripePaymentMethod.id,
type: paymentMethodData.type,
details: this.extractPaymentMethodDetails(stripePaymentMethod),
isDefault: false,
createdAt: new Date()
};
await this.accountRepo.savePaymentMethod(accountId, accountType, paymentMethod);
return paymentMethod;
}
public async processUsageBilling(
accountId: string,
accountType: 'team' | 'org',
period: DateRange
): Promise<Invoice> {
const usage = await this.calculateUsageForPeriod(accountId, accountType, period);
const account = await this.getAccount(accountId, accountType);
if (usage.totalCost === 0) {
// No usage, no invoice needed
return null;
}
// Create invoice items in Stripe
const invoiceItems = await Promise.all(
usage.aiModelUsage.map(modelUsage =>
this.stripeService.invoiceItems.create({
customer: account.billingInfo.customerId,
amount: Math.round(modelUsage.cost * 100), // Convert to cents
currency: 'usd',
description: `${modelUsage.provider} ${modelUsage.model} - ${modelUsage.requestCount} requests, ${modelUsage.tokenCount} tokens`,
period: {
start: Math.floor(period.start.getTime() / 1000),
end: Math.floor(period.end.getTime() / 1000)
}
})
)
);
// Create invoice
const stripeInvoice = await this.stripeService.invoices.create({
customer: account.billingInfo.customerId,
collection_method: 'charge_automatically',
auto_advance: true
});
// Finalize and pay invoice
const finalizedInvoice = await this.stripeService.invoices.finalizeInvoice(stripeInvoice.id);
const invoice: Invoice = {
id: uuidv4(),
stripeInvoiceId: finalizedInvoice.id,
accountId,
accountType,
period,
items: usage.aiModelUsage.map(modelUsage => ({
description: `${modelUsage.provider} ${modelUsage.model}`,
quantity: modelUsage.requestCount,
unitPrice: modelUsage.cost / modelUsage.requestCount,
totalPrice: modelUsage.cost
})),
subtotal: usage.totalCost,
tax: finalizedInvoice.tax || 0,
total: finalizedInvoice.total / 100,
status: finalizedInvoice.status as InvoiceStatus,
paidAt: finalizedInvoice.status_transitions?.paid_at ? new Date(finalizedInvoice.status_transitions.paid_at * 1000) : null,
createdAt: new Date()
};
await this.accountRepo.saveInvoice(invoice);
// Send invoice email
await this.sendInvoiceEmail(account, invoice);
return invoice;
}
public async handleWebhook(event: Stripe.Event): Promise<void> {
switch (event.type) {
case 'invoice.payment_succeeded':
await this.handleInvoicePaymentSuccess(event.data.object as Stripe.Invoice);
break;
case 'invoice.payment_failed':
await this.handleInvoicePaymentFailure(event.data.object as Stripe.Invoice);
break;
case 'customer.subscription.updated':
await this.handleSubscriptionUpdate(event.data.object as Stripe.Subscription);
break;
default:
console.log(`Unhandled webhook event type: ${event.type}`);
}
}
private async handleInvoicePaymentSuccess(invoice: Stripe.Invoice): Promise<void> {
const ourInvoice = await this.accountRepo.findInvoiceByStripeId(invoice.id);
if (ourInvoice) {
await this.accountRepo.updateInvoice(ourInvoice.id, {
status: 'paid',
paidAt: new Date()
});
// Send payment confirmation email
const account = await this.getAccount(ourInvoice.accountId, ourInvoice.accountType);
await this.emailService.sendTemplate(account.billingEmail, 'payment-confirmation', {
invoiceId: ourInvoice.id,
amount: ourInvoice.total,
paidAt: new Date()
});
}
}
private async calculateUsageForPeriod(
accountId: string,
accountType: 'team' | 'org',
period: DateRange
): Promise<UsageMetrics> {
const usageRecords = await this.accountRepo.getUsageRecords(accountId, accountType, period);
const modelUsageMap = new Map<string, ModelUsage>();
let totalCost = 0;
let totalRequests = 0;
let totalTokens = 0;
for (const record of usageRecords) {
const key = `${record.provider}-${record.model}`;
const existing = modelUsageMap.get(key) || {
provider: record.provider,
model: record.model,
requestCount: 0,
tokenCount: 0,
cost: 0
};
existing.requestCount += record.requestCount;
existing.tokenCount += record.tokenCount;
existing.cost += record.cost;
modelUsageMap.set(key, existing);
totalCost += record.cost;
totalRequests += record.requestCount;
totalTokens += record.tokenCount;
}
return {
period,
aiModelUsage: Array.from(modelUsageMap.values()),
totalCost,
requestCount: totalRequests,
tokenCount: totalTokens
};
}
} Security Considerations
- API Key Security
- Store encrypted API keys in HashiCorp Vault or AWS Secrets Manager
- Implement key rotation policies
- Audit all key access and usage
- Never log API keys in plain text
- Payment Security
- PCI compliance through Stripe integration
- Encrypt sensitive billing information
- Implement fraud detection
- Secure webhook validation
- Access Control
- Validate user permissions for all operations
- Implement role-based access at team/org levels
- Audit all access control changes
- Rate limiting on sensitive operations
- Data Protection
- Encrypt sensitive data at rest
- Implement data retention policies
- Support GDPR data deletion
- Secure inter-service communication
Performance Optimization
- Caching Strategy
- Cache user hierarchies and permissions in Redis
- Cache quota status for fast checks
- Implement cache warming for hot paths
- Use cache tags for efficient invalidation
- Database Optimization
- Proper indexing on lookup fields
- Connection pooling
- Read replicas for analytics queries
- Partitioning for usage data
- Quota Checking
- Use Redis counters for real-time quota tracking
- Implement sliding window algorithms
- Cache quota configurations
- Batch usage updates
- Billing Performance
- Asynchronous invoice generation
- Batch payment processing
- Efficient usage aggregation queries
- Background job processing
Implementation Plan
Phase 1: Core Account Management (Weeks 1-3)
- User Profile Management
- User account creation and updates
- Profile management and preferences
- Basic team creation and membership
- Database Schema
- User, team, organization tables
- Membership and invitation tables
- Basic audit logging
Phase 2: Team and Organization Management (Weeks 4-5)
- Team Features
- Team invitation system
- Role-based permissions
- Team settings and configuration
- Organization Features
- Organization creation
- Multi-team hierarchies
- Organization-level settings
Phase 3: Resource Access Control (Weeks 6-7)
- Model Access Management
- AI model access configuration
- API key management and encryption
- Resource access validation
- Quota System
- Usage tracking and quotas
- Real-time quota enforcement
- Usage analytics
Phase 4: Billing and Payment (Weeks 8-9)
- Payment Integration
- Stripe integration
- Payment method management
- Subscription handling
- Usage-Based Billing
- Usage calculation and invoicing
- Automated billing cycles
- Payment webhooks
Phase 5: Advanced Features (Week 10)
- Analytics and Reporting
- Usage dashboards
- Cost optimization recommendations
- Performance metrics
- Administration
- Account suspension/reactivation
- Ownership transfers
- Compliance features
Dependencies
- Internal: User Authorization Service, API Connector Service, Email Service
- External: Stripe/Payment Gateway, HashiCorp Vault, SMS Service
- Development: TypeScript 5+, PostgreSQL, Redis, Jest for testing
Testing Strategy
- Unit Tests
- Account management logic
- Resource access control
- Quota calculations
- Billing computations
- Integration Tests
- Payment processing flows
- Team invitation workflows
- API key management
- Usage tracking accuracy
- End-to-End Tests
- Complete user onboarding
- Team collaboration scenarios
- Billing cycle testing
- Resource access validation
Alternatives Considered
Multiple Specialized Services
- Approach: Separate services for users, teams, orgs, billing, quotas
- Pros: Clear service boundaries, technology flexibility
- Cons: Complex coordination, data consistency issues, performance overhead
- Decision: Single service provides better consistency and performance
Embedded Account Logic
- Approach: Include account logic in each consuming service
- Pros: No network calls, simpler deployment
- Cons: Code duplication, inconsistent implementations, hard to maintain
- Decision: Centralized service ensures consistency
Third-Party Account Management
- Approach: Use services like Auth0 Organizations or AWS Organizations
- Pros: Managed solution, proven scalability
- Cons: Limited customization, vendor lock-in, cost scaling
- Decision: Custom solution provides better integration with AI model management
Open Questions
- Billing Granularity: Should we bill at request level or batch usage daily/hourly?
- Quota Enforcement: How strict should quota enforcement be - hard stops or warnings?
- API Key Sharing: Should teams be able to share API keys across multiple services?
- Organization Hierarchy: Do we need support for nested organizations (enterprise feature)?
- Data Retention: How long should we retain detailed usage data for analytics?
- Multi-Currency: Should we support billing in multiple currencies for global customers?
Appendix
Glossary
- Account Hierarchy: The user → team → organization structure for access control and billing
- Model Access: Configuration defining which AI models a user/team/org can use
- Quota: Usage limits applied at various levels to control costs and usage
- Resource Access Control: System for determining what services a user can access
- Usage-Based Billing: Billing model based on actual AI model usage rather than fixed subscriptions
- Team Invitation: Process for adding users to teams via email invitation
- API Key Management: Secure storage and rotation of third-party service API keys
- Billing Orchestration: Coordination of payment processing, invoicing, and subscription management
References
Revision History
- v0.1.0 (2025-08-12): Complete comprehensive specification consolidating account management, billing, and resource access control