Features
Organization
Organization Settings

Organization Settings

Comprehensive guide to managing organization configuration, preferences, security settings, and administrative options.

Overview

Organization Settings is the central hub for configuring your organization's profile, preferences, team management, resource quotas, and integrations. Access requires appropriate permissions based on the Permission Matrix system (Sprint 2E21).

Key Features:

  • Organization profile and metadata management
  • Resource quota monitoring and limits
  • Default configurations for new resources
  • Notification preferences and integrations
  • Security and access control
  • Billing and subscription management
  • Audit logging and compliance tracking

Accessing Organization Settings

Navigation Path

  1. Click organization dropdown in top navigation
  2. Select Settings from the menu
  3. Or navigate directly to: /dashboard/settings

Required Permissions

Different settings sections require specific permissions:

SectionRequired PermissionScope
General Settingsorg.settings.viewOrganization
Organization Profileorg.viewOrganization
Update Settingsorg.settings.updateOrganization
Members Managementorg.members.listOrganization
Billingorg.billing.viewOrganization
Storage Configsorg.storage.listOrganization
DNS Settingsorg.dns.listOrganization
Git Connectionsorg.git.listOrganization
Delete Organizationorg.deleteOrganization

Settings Page Sections

The settings page uses tab-based navigation with permission-based access control:

  1. General - Basic organization information
  2. Members - Team member management
  3. Permissions - Custom roles and permission matrix
  4. Storage - Backup storage provider configurations
  5. DNS - Domain management provider settings
  6. Git Connections - GitHub/GitLab authentication
  7. Addon Repos - Custom Odoo addon repositories
  8. Billing - Subscription and payment management
  9. Usage - Resource quotas and consumption tracking

General Settings

Organization Profile

Basic organization information displayed and editable by admins.

Editable Fields

Organization Name

  • Display name shown throughout platform
  • Validation: 1-255 characters required
  • Updates reflected immediately in UI
  • Used in emails and notifications
name: string (required, 1-255 chars)

Organization Slug

  • URL-friendly unique identifier
  • Format: lowercase letters, numbers, and hyphens only
  • Pattern: ^[a-z0-9-]+$
  • Cannot be changed once environments are deployed
  • Used in subdomain generation (if applicable)
slug: string (unique, 1-100 chars, lowercase alphanumeric + hyphens)

Description

  • Optional text description of organization
  • Displayed on organization profile
  • Supports plain text (no markdown)
description: string | null

Website URL

  • Organization's public website
  • Optional field for external reference
  • Must be valid URL format
website: string | null (URL format)

Logo URL

  • Organization logo image URL
  • Displayed in navigation and profile
  • Recommended: 512x512px, PNG/JPG format
  • Must be publicly accessible URL
logo_url: string | null (max 512 chars)

Organization Metadata (Read-Only)

Organization ID

  • UUID identifier for API operations
  • Format: UUID v4
  • Used in all API endpoints as {org_id}

Created Date

  • Timestamp of organization creation
  • Format: ISO 8601 with timezone
  • Cannot be modified

Owner Information

  • User who created the organization
  • Displays owner's name and email
  • Owner role cannot be removed without transfer

Status

  • is_active: Boolean flag
  • Active organizations can create resources
  • Inactive organizations are soft-deleted

API: Update Organization

Endpoint: PATCH /api/v1/organizations/{org_id}

Required Permission: org.settings.update

Request Headers:

Authorization: Bearer {jwt_token}
Content-Type: application/json

Request Body:

{
  "name": "Acme Corporation",
  "description": "Leading provider of digital solutions",
  "website": "https://acme.example.com",
  "logo_url": "https://cdn.acme.com/logo.png",
  "settings": {
    "timezone": "America/New_York",
    "default_storage_provider": "cloudflare_r2",
    "notification_email": "ops@acme.com"
  }
}

Field Validation:

  • name: Required, 1-255 characters
  • description: Optional, text
  • website: Optional, valid URL format
  • logo_url: Optional, valid URL, max 512 characters
  • settings: Optional, JSON object for flexible configuration

Response (200 OK):

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "Acme Corporation",
  "slug": "acme-corp",
  "description": "Leading provider of digital solutions",
  "website": "https://acme.example.com",
  "logo_url": "https://cdn.acme.com/logo.png",
  "is_active": true,
  "settings": {
    "timezone": "America/New_York",
    "default_storage_provider": "cloudflare_r2",
    "notification_email": "ops@acme.com"
  },
  "billing_email": "billing@acme.com",
  "member_count": 15,
  "project_count": 8,
  "created_at": "2024-01-15T10:30:00Z",
  "updated_at": "2024-12-10T14:22:00Z"
}

Error Responses:

403 Forbidden:

{
  "detail": "You don't have permission to update organization settings."
}

404 Not Found:

{
  "detail": "Organization not found"
}

409 Conflict (Slug Already Exists):

{
  "detail": "Organization with slug 'acme-corp' already exists"
}

Organization Metadata

Display Information

Member Count

  • Total active members in organization
  • Calculated from organization_members table
  • Filter: is_active = true

Project Count

  • Total projects owned by organization
  • Calculated from projects table
  • Filter: organization_id = {org_id}

Creation Date

  • ISO 8601 timestamp with timezone
  • Shown in organization profile
  • Used for billing anniversary calculations

Organization Status

Active Status

  • is_active = true: Organization operational
  • is_active = false: Soft-deleted, marked for cleanup
  • Inactive organizations cannot create resources

Billing Email

  • Email for invoices and billing notifications
  • Defaults to owner's email on creation
  • Can be updated separately from user emails

Default Settings Configuration

The settings JSONB field stores flexible organization preferences. Common configurations:

Timezone

Field: settings.timezone

Default timezone for deployments and scheduled tasks.

{
  "settings": {
    "timezone": "America/New_York"
  }
}

Supported Timezones: IANA timezone database (e.g., America/New_York, Europe/London, Asia/Tokyo)

Default Storage Provider

Field: settings.default_storage_provider

Default backup storage for new environments.

{
  "settings": {
    "default_storage_provider": "cloudflare_r2"
  }
}

Valid Values:

  • aws_s3 - Amazon S3
  • cloudflare_r2 - Cloudflare R2
  • backblaze_b2 - Backblaze B2
  • minio - MinIO (self-hosted)
  • sftp - SFTP server
  • ftp - FTP/FTPS server

References storage_configs table to fetch actual credentials.

Default Server

Field: settings.default_server_id

Default VM for new environment deployments.

{
  "settings": {
    "default_server_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
  }
}

Validation: Must reference valid vm_id in organization's servers.

Default Git Connection

Field: settings.default_git_connection_id

Default Git authentication for repository access.

{
  "settings": {
    "default_git_connection_id": "d9c73b2a-42f3-4c8e-9f3a-8b6e2c1d4e5f"
  }
}

Validation: Must reference valid OrganizationGitConnection ID.

Notification Email

Field: settings.notification_email

Email for operational notifications (deployments, alerts).

{
  "settings": {
    "notification_email": "ops@example.com"
  }
}

Separate from Billing Email: Allows different contacts for ops vs finance.


Notification Preferences

Organization-Level Notifications

Configure notification delivery channels and preferences for organization-wide events.

Notification Types

TypeDefault ChannelDescription
env_deployedIn-AppEnvironment deployment completed
env_errorBothEnvironment deployment failed
task_completedIn-AppBackground task finished
task_failedBothBackground task error
system_maintenanceEmailScheduled maintenance window
billing_warningBothApproaching quota or payment due
billing_overdueBothPayment failed or overdue

Notification Channels

  • In-App: Bell icon notifications in dashboard
  • Email: Sent to user's email address or notification email
  • Both: In-app + email delivery

User Notification Preferences

Individual users can override organization defaults via their profile settings.

Model: NotificationPreference

Configurable Settings:

interface NotificationPreference {
  task_completed: 'in_app' | 'email' | 'both'
  task_failed: 'in_app' | 'email' | 'both'
  env_deployed: 'in_app' | 'email' | 'both'
  env_error: 'in_app' | 'email' | 'both'
  system_updates: 'in_app' | 'email' | 'both'
  billing_alerts: 'in_app' | 'email' | 'both'
  security_alerts: 'in_app' | 'email' | 'both'
  email_digest: boolean  // Receive daily digest vs real-time
}

Webhook Notifications (Future Enhancement)

Planned support for webhook-based notifications to external systems (Slack, Discord, MS Teams).


Usage & Resource Quotas

Quota System Overview

Organizations have hard limits on entity counts and resource allocation based on their subscription plan.

Enforced By: QuotaService (backend/services/quota_service.py)

Entity Limits

Max Projects

  • Maximum projects organization can create
  • Default: 10 (Free plan)
  • Field: max_projects

Max Environments

  • Maximum environments across all projects
  • Default: 30 (Free plan)
  • Field: max_environments

Max Servers

  • Maximum BYOS servers organization can add
  • Default: 5 (Free plan)
  • Field: max_servers

Max Members

  • Maximum team members (active memberships)
  • Default: 20 (Free plan)
  • Field: max_members

Per-Environment Resource Limits

Max CPU Per Environment

  • Maximum CPU cores for single environment
  • Default: 2.0 cores
  • Field: max_cpu_per_env (NUMERIC(4,2))

Max RAM Per Environment

  • Maximum RAM allocation for single environment
  • Default: 4096 MB (4 GB)
  • Field: max_ram_per_env_mb (INTEGER)

Max Disk Per Environment

  • Maximum disk allocation for single environment
  • Default: 20 GB
  • Field: max_disk_per_env_gb (INTEGER)

Total Organization Quotas

Total CPU Quota

  • Sum of all environment CPU allocations cannot exceed
  • Default: 20.0 cores
  • Field: total_cpu_quota (NUMERIC(6,2))

Total RAM Quota

  • Sum of all environment RAM cannot exceed
  • Default: 40960 MB (40 GB)
  • Field: total_ram_quota_mb (INTEGER)

Total Disk Quota

  • Sum of all environment disk cannot exceed
  • Default: 200 GB
  • Field: total_disk_quota_gb (INTEGER)

Quota Calculation

Active Environments Only: Quota service counts only is_active = true environments. When destroying environments, always set is_active = false to release quota.

Replica Resource Accounting (Sprint 2E40): PostgreSQL read replicas count as:

  • 30% of primary CPU/RAM (e.g., primary=2 cores → replica=0.6 cores)
  • 100% of primary disk (e.g., primary=20GB → replica=20GB)

API: GET /api/v1/organizations/{org_id}/quota

Response:

{
  "projects_used": 8,
  "projects_limit": 10,
  "environments_used": 24,
  "environments_limit": 30,
  "servers_used": 3,
  "servers_limit": 5,
  "members_used": 15,
  "members_limit": 20,
  "cpu_used": 14.8,
  "cpu_limit": 20.0,
  "ram_used_mb": 32768,
  "ram_limit_mb": 40960,
  "disk_used_gb": 145,
  "disk_limit_gb": 200
}

Quota Warnings

Warning Thresholds:

  • 80% utilization: Yellow warning indicator
  • 95% utilization: Orange alert indicator
  • 100% utilization: Red - creation blocked

Billing & Subscription

Current Plan Information

Subscription Model: Subscription (backend/models/billing.py)

Base Plans:

  • Free: $0/month - 1 BYOS server or 1 small environment included
  • Starter: $29/month - 3 servers or 3 environments
  • Professional: $79/month - 10 servers or 10 environments
  • Business: $199/month - 25 servers or 25 environments
  • Enterprise: Custom pricing - unlimited

Billing Intervals:

  • Monthly: Charged monthly
  • Annual: 20% discount (charged annually)

Stripe Integration

Fields:

  • stripe_customer_id: Stripe customer reference
  • stripe_subscription_id: Active subscription ID
  • stripe_price_id: Base plan price ID

Subscription Status

Lifecycle States:

StatusDescriptionServices
trial14-day trial (Professional features)Running
activePaid and operationalRunning
past_duePayment failed, grace periodRunning
suspendedAll payment retries failedStopped
pausedUser-initiated pauseStopped
cancellingScheduled to cancel at period endRunning
expiredCancelled, 30-day data retentionStopped
archivedData deleted, account closedN/A

Billing Cycle

Current Period:

  • current_period_start: Period start timestamp
  • current_period_end: Period end timestamp

Cancellation:

  • cancel_at_period_end: Flag for end-of-period cancellation
  • cancelled_at: When cancellation was requested
  • cancel_at: Scheduled cancellation date

Viewing Billing Details

Navigation: Settings → Billing tab

Required Permission: org.billing.view

API: GET /api/v1/organizations/{org_id}/subscription

Response:

{
  "id": "sub_1234567890",
  "base_plan": "professional",
  "status": "active",
  "billing_interval": "monthly",
  "monthly_price": 79.00,
  "current_period_start": "2024-12-01T00:00:00Z",
  "current_period_end": "2024-12-31T23:59:59Z",
  "cancel_at_period_end": false,
  "stripe_customer_id": "cus_ABC123XYZ",
  "stripe_subscription_id": "sub_DEF456UVW"
}

Upgrading/Downgrading Plans

Process:

  1. Navigate to Billing → Change Plan
  2. Select new plan
  3. Review pro-rated charges
  4. Confirm change
  5. Redirect to Stripe Checkout (for upgrades)
  6. Downgrade scheduled for period end

Immediate Upgrades: Applied instantly with pro-rated charges

Scheduled Downgrades: Applied at current period end to avoid refund complexity


Danger Zone

Transfer Organization Ownership

API: POST /api/v1/organizations/{org_id}/transfer-ownership

Required Permission: org.delete (OWNER role only)

Process:

  1. Select new owner from organization members
  2. Confirm with type-to-confirm pattern
  3. New owner receives email notification
  4. Original owner becomes ADMIN role
  5. New owner assumes OWNER role

Request Body:

{
  "new_owner_user_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "confirmation": "transfer ownership"
}

Validation:

  • New owner must be active organization member
  • Cannot transfer to yourself
  • Must type exact confirmation phrase

Delete Organization

API: DELETE /api/v1/organizations/{org_id}

Required Permission: org.delete (OWNER role only)

Safety Checks:

  1. Active environments prevent deletion
  2. Active subscriptions must be cancelled first
  3. Type-to-confirm: Organization slug
  4. Confirmation modal with impact summary

Soft Delete:

  • Sets is_active = false
  • Resources remain in database for 30 days
  • Can be recovered by support within retention period
  • After 30 days: Hard delete via cleanup job

Process:

  1. Check for active resources
  2. Display impact summary:
    • X projects will be deleted
    • Y environments will be destroyed
    • Z members will lose access
  3. Type organization slug to confirm
  4. Execute deletion

Request (No body required):

DELETE /api/v1/organizations/{org_id}
Authorization: Bearer {jwt_token}

Response (200 OK):

{
  "message": "Organization deleted successfully"
}

Error: Active Resources (400 Bad Request):

{
  "detail": "Cannot delete organization with 5 active environments. Please destroy all environments first."
}

Error: Not Owner (403 Forbidden):

{
  "detail": "Only the organization owner can delete the organization."
}

Archive Organization (Future)

Planned feature for temporarily disabling organization without deletion.


Organization API Keys (Future Enhancement)

Planned feature for automation and CI/CD integration.

Features:

  • Generate API keys for programmatic access
  • Scope keys to specific permissions
  • Set expiration dates
  • Rotate keys without downtime
  • Revoke compromised keys instantly
  • Audit log all API key usage

Use Cases:

  • CI/CD pipelines (GitHub Actions, GitLab CI)
  • Infrastructure as Code (Terraform, Ansible)
  • Custom integrations and webhooks
  • Monitoring and alerting systems

Audit Log

Organization Activity Tracking

All organization-level actions are logged to audit_logs table for compliance and debugging.

Model: AuditLog (backend/models/audit.py)

Logged Actions:

Action CodeDescription
org_createdOrganization created
org_updatedOrganization settings modified
org_deletedOrganization deactivated
org_member_addedMember invited/added
org_member_removedMember removed
org_member_role_changedMember role updated

Audit Log Entry Structure

interface AuditLog {
  id: string                 // UUID
  timestamp: string          // ISO 8601
  user_id: string            // Actor UUID
  user_email: string         // Actor email (denormalized)
  action: AuditAction        // Enum value
  organization_id: string    // Organization context
  resource_type: string      // 'organization', 'member', etc.
  resource_id: string        // Affected resource UUID
  resource_name: string      // Human-readable name
  details: object            // JSON with old/new values
  ip_address: string         // Client IP (IPv4/IPv6)
  user_agent: string         // Client user agent
  request_id: string         // Request trace ID
}

Example Audit Entry

Action: Organization settings updated

{
  "id": "log_abc123",
  "timestamp": "2024-12-10T14:22:13Z",
  "user_id": "user_xyz789",
  "user_email": "admin@acme.com",
  "action": "org_updated",
  "organization_id": "org_acme_corp",
  "resource_type": "organization",
  "resource_id": "org_acme_corp",
  "resource_name": "Acme Corporation",
  "details": {
    "changed_fields": ["name", "description"],
    "old_values": {
      "name": "Acme Corp",
      "description": null
    },
    "new_values": {
      "name": "Acme Corporation",
      "description": "Leading provider of digital solutions"
    }
  },
  "ip_address": "203.0.113.42",
  "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)...",
  "request_id": "req_def456ghi"
}

Viewing Audit Logs

API: GET /api/v1/audit-logs?organization_id={org_id}

Query Parameters:

  • organization_id: Filter by organization
  • user_id: Filter by actor
  • action: Filter by action type
  • resource_type: Filter by resource
  • start_date: Date range start
  • end_date: Date range end
  • limit: Results per page (default: 50)
  • offset: Pagination offset

Response:

{
  "items": [
    {...audit_log_entry...}
  ],
  "total": 247,
  "limit": 50,
  "offset": 0
}

Permissions

Permission Matrix System (Sprint 2E21)

Organization settings leverage the Permission Matrix for fine-grained access control.

Key Permissions:

Permission CodeResourceActionDescription
org.viewOrganizationViewView organization details
org.settings.viewSettingsViewView organization settings
org.settings.updateSettingsUpdateModify organization settings
org.deleteOrganizationDeleteDelete organization (OWNER only)
org.members.listMembersListView member list
org.members.inviteMembersInviteAdd new members
org.members.removeMembersRemoveRemove members
org.members.update_roleMembersUpdate RoleChange member roles
org.storage.listStorageListView storage configs
org.storage.createStorageCreateAdd storage config
org.dns.listDNSListView DNS configs
org.git.listGitListView Git connections
org.billing.viewBillingViewView billing details

System Roles

Pre-defined roles with permission sets:

RolePermission LevelUse Case
org_ownerFull accessOrganization creator
org_adminAll except deleteAdministrator
org_memberStandard accessRegular developer
project_adminProject-scopedProject manager
project_memberRead + deployProject contributor

Custom Roles

Organizations can create custom roles with specific permission combinations.

API: POST /api/v1/organizations/{org_id}/roles

Required Permission: org.permissions.manage


Best Practices

Organization Naming

Naming Conventions:

  • Use full company/team name (e.g., "Acme Corporation" not "Acme")
  • Avoid abbreviations unless universally recognized
  • Match legal entity name for billing alignment

Slug Best Practices:

  • Keep short and memorable (e.g., "acme" not "acme-corporation-inc")
  • Use hyphens for multi-word organizations
  • Cannot contain spaces or special characters
  • Plan carefully - slug change impacts deployed environments

Security Recommendations

Access Control:

  • Limit OWNER role to 1-2 trusted individuals
  • Use ADMIN role for day-to-day administration
  • Grant least privilege - use DEVELOPER for regular team members
  • Review member list quarterly for stale accounts

Settings Protection:

  • Enable 2FA for all OWNER and ADMIN users
  • Use separate billing email from personal accounts
  • Store logo images on CDN with proper access controls
  • Audit settings changes via audit log

Quota Management

Monitoring:

  • Set up alerts at 80% quota utilization
  • Review usage monthly to right-size plan
  • Destroy unused environments to free quota
  • Plan replica deployments (count 30% CPU/RAM)

Optimization:

  • Use BYOS servers for cost efficiency at scale
  • Consolidate small environments when possible
  • Archive inactive projects to free entity limits
  • Upgrade plan before hitting hard limits

Backup Before Dangerous Operations

Pre-deletion Checklist:

  1. Export audit logs for compliance
  2. Backup project configurations
  3. Download critical environment data
  4. Document active integrations
  5. Notify team members of pending deletion
  6. Cancel subscriptions to avoid charges
  7. Verify no active deployments

Troubleshooting

Cannot Update Settings

Issue: "You don't have permission to update organization settings."

Causes:

  • Missing org.settings.update permission
  • Not a member of organization
  • Organization is inactive

Solution:

  1. Check permission via GET /api/v1/users/me/abilities?organization_id={org_id}
  2. Verify organization membership
  3. Contact organization OWNER/ADMIN for permission grant

Organization Deletion Blocked

Issue: "Cannot delete organization with active resources."

Causes:

  • Active environments still deployed
  • Active subscription not cancelled
  • Pending invoices unpaid

Solution:

  1. Navigate to Projects → Environments
  2. Destroy all active environments
  3. Wait 2-5 minutes for full cleanup
  4. Cancel subscription via Billing tab
  5. Retry deletion

Slug Already Taken

Issue: "Organization with slug 'acme' already exists"

Causes:

  • Another organization using same slug
  • Previously deleted organization in retention period

Solution:

  1. Choose different slug (e.g., "acme-corp", "acme-inc")
  2. Contact support if legitimate conflict
  3. Wait 30 days if you own deleted organization

Quota Warning Not Updating

Issue: Usage shows 80% but can still create resources

Causes:

  • Quota calculation cron job runs every 5 minutes
  • Recent environment destruction not reflected yet
  • Cache not invalidated

Solution:

  1. Wait 5 minutes for quota recalculation
  2. Hard refresh browser (Ctrl+Shift+R)
  3. Check is_active = false on destroyed environments
  4. Contact support if persistent

Missing Billing Tab

Issue: Billing tab not visible in settings

Causes:

  • Missing org.billing.view permission
  • Not authenticated properly
  • Organization on legacy billing system

Solution:

  1. Verify JWT token not expired
  2. Check abilities: useAbilities({ organizationId })
  3. Confirm organization has stripe_customer_id
  4. Contact support for legacy account migration

Settings Not Saving

Issue: Changes lost after page refresh

Causes:

  • API request failing silently
  • Browser localStorage corruption
  • Concurrent updates from another session

Solution:

  1. Open browser DevTools → Network tab
  2. Check for 400/403/500 errors on PATCH request
  3. Clear browser cache and cookies
  4. Disable browser extensions (ad blockers)
  5. Check audit log for conflicting changes

Related Documentation


API Reference Summary

Organization Endpoints

MethodEndpointDescriptionPermission
GET/api/v1/organizationsList organizationsorg.view
POST/api/v1/organizationsCreate organizationAuthenticated
GET/api/v1/organizations/{org_id}Get organizationorg.view
PATCH/api/v1/organizations/{org_id}Update settingsorg.settings.update
DELETE/api/v1/organizations/{org_id}Delete organizationorg.delete
GET/api/v1/organizations/{org_id}/membersList membersorg.members.list
POST/api/v1/organizations/{org_id}/membersAdd memberorg.members.invite
PATCH/api/v1/organizations/{org_id}/members/{user_id}Update memberorg.members.update_role
DELETE/api/v1/organizations/{org_id}/members/{user_id}Remove memberorg.members.remove
GET/api/v1/organizations/{org_id}/quotaGet quota usageorg.view
POST/api/v1/organizations/{org_id}/transfer-ownershipTransfer ownershiporg.delete

Request/Response Models

OrganizationUpdate Schema:

interface OrganizationUpdate {
  name?: string                  // 1-255 chars
  description?: string | null
  website?: string | null        // URL format
  logo_url?: string | null       // Max 512 chars
  settings?: Record<string, any> // Flexible JSON
&#125;

OrganizationResponse Schema:

interface OrganizationResponse &#123;
  id: string                     // UUID
  name: string
  slug: string
  description: string | null
  website: string | null
  logo_url: string | null
  is_active: boolean
  settings: Record<string, any>
  billing_email: string | null
  member_count: number | null
  project_count: number | null
  created_at: string             // ISO 8601
  updated_at: string             // ISO 8601
&#125;

Last Updated: December 11, 2024 - Sprint 2E41 (Documentation Initiative)

Backend Reference Files:

  • /backend/models/organization.py - Organization model definition
  • /backend/schemas/organization.py - Pydantic schemas
  • /backend/api/v1/routes/organizations.py - API endpoints
  • /backend/services/quota_service.py - Quota enforcement
  • /backend/models/billing.py - Subscription and billing