# WCO-K06: White-label mode for agency partners
**Area:** Widget Customization Docs
**Model:** moonshotai/kimi-k2.5
**Tokens:** 8,354
**Cost:** $0.0089
**Time:** 2424ms
**Timestamp:** 2026-02-16T13:58:25.486893

---

 I'll provide a complete white-label implementation across backend (FastAPI), frontend (vanilla JS), database, and infrastructure.

## 1. Backend Implementation

### Database Models (`app/models/white_label.py`)

```python
"""White-label configuration models for agency partners."""
from datetime import datetime
from typing import Optional
from uuid import uuid4

from sqlalchemy import (
    Column, String, DateTime, Boolean, ForeignKey, Integer, CheckConstraint,
    UniqueConstraint, Text, event
)
from sqlalchemy.dialects.postgresql import UUID, JSONB
from sqlalchemy.orm import relationship, validates

from app.db.base_class import Base
from app.core.config import settings


class WhiteLabelConfig(Base):
    """
    White-label configuration for agency partners.
    Allows complete rebranding of the Sunaiva widget.
    """
    __tablename__ = "white_label_configs"

    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
    agency_id = Column(
        UUID(as_uuid=True), 
        ForeignKey("agencies.id", ondelete="CASCADE"),
        nullable=False,
        index=True
    )
    business_id = Column(
        UUID(as_uuid=True),
        ForeignKey("businesses.id", ondelete="CASCADE"),
        nullable=True,  # Null means default config for agency
        index=True,
        unique=True
    )
    
    # Branding
    company_name = Column(String(255), nullable=False)
    logo_url = Column(String(512), nullable=True)
    logo_dark_url = Column(String(512), nullable=True)  # For dark mode
    favicon_url = Column(String(512), nullable=True)
    
    # Colors (validated hex codes)
    primary_color = Column(String(7), nullable=False, default="#2563EB")
    secondary_color = Column(String(7), nullable=True)
    accent_color = Column(String(7), nullable=True)
    background_color = Column(String(7), nullable=True, default="#FFFFFF")
    text_color = Column(String(7), nullable=True, default="#1F2937")
    
    # Typography
    font_family = Column(String(100), nullable=True)
    font_url = Column(String(512), nullable=True)  # Google Fonts or custom
    
    # Domain & Assets
    custom_domain = Column(String(255), nullable=True, unique=True)
    cdn_base_url = Column(String(512), nullable=True)
    widget_script_url = Column(String(512), nullable=True)
    
    # Support Contacts (shown in error messages)
    support_email = Column(String(255), nullable=True)
    support_phone = Column(String(50), nullable=True)
    support_hours = Column(String(100), nullable=True)
    
    # Branding Control
    remove_powered_by = Column(Boolean, default=False)
    hide_sunaiva_copyright = Column(Boolean, default=False)
    custom_copyright_text = Column(String(255), nullable=True)
    
    # Metadata
    config_status = Column(
        String(20), 
        nullable=False, 
        default="active",
        server_default="active"
    )
    
    created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
    updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
    
    # Relationships
    agency = relationship("Agency", back_populates="white_label_configs")
    business = relationship("Business", back_populates="white_label_config")
    
    __table_args__ = (
        CheckConstraint(
            "config_status IN ('active', 'inactive', 'suspended')",
            name="check_config_status"
        ),
        CheckConstraint(
            "custom_domain IS NULL OR custom_domain ~* '^[a-z0-9][-a-z0-9]*[a-z0-9]*\.[a-z]{2,}$'",
            name="check_domain_format"
        ),
        UniqueConstraint('agency_id', 'business_id', name='unique_agency_business_config'),
    )
    
    @validates('primary_color', 'secondary_color', 'accent_color', 
               'background_color', 'text_color')
    def validate_hex_color(self, key: str, value: Optional[str]) -> Optional[str]:
        """Validate hex color codes."""
        if value is None:
            return value
        if not value.startswith('#') or len(value) not in (4, 7):
            raise ValueError(f"Invalid hex color format for {key}: {value}")
        return value.upper()
    
    @validates('custom_domain')
    def validate_domain(self, key: str, value: Optional[str]) -> Optional[str]:
        """Validate custom domain format."""
        if value:
            value = value.lower().strip()
            if value.startswith(('http://', 'https://')):
                raise ValueError("Domain should not include protocol")
            if value.endswith('/'):
                value = value[:-1]
        return value
    
    def to_public_dict(self) -> dict:
        """Return public-safe configuration for widget consumption."""
        return {
            "company_name": self.company_name,
            "logo_url": self.logo_url,
            "logo_dark_url": self.logo_dark_url,
            "colors": {
                "primary": self.primary_color,
                "secondary": self.secondary_color,
                "accent": self.accent_color,
                "background": self.background_color,
                "text": self.text_color,
            },
            "typography": {
                "font_family": self.font_family,
                "font_url": self.font_url,
            },
            "support": {
                "email": self.support_email,
                "phone": self.support_phone,
                "hours": self.support_hours,
            },
            "branding": {
                "remove_powered_by": self.remove_powered_by,
                "hide_copyright": self.hide_sunaiva_copyright,
                "copyright_text": self.custom_copyright_text,
            },
            "assets": {
                "cdn_base_url": self.cdn_base_url,
            }
        }


class WhiteLabelAsset(Base):
    """Stored assets for white-label configurations (logos, fonts, etc)."""
    __tablename__ = "white_label_assets"
    
    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
    config_id = Column(
        UUID(as_uuid=True),
        ForeignKey("white_label_configs.id", ondelete="CASCADE"),
        nullable=False
    )
    
    asset_type = Column(
        String(50),
        nullable=False,
        comment="logo, logo_dark, favicon, font, custom_css, other"
    )
    file_name = Column(String(255), nullable=False)
    original_name = Column(String(255), nullable=False)
    file_path = Column(String(512), nullable=False)
    file_size = Column(Integer, nullable=False)  # bytes
    mime_type = Column(String(100), nullable=False)
    dimensions = Column(JSONB, nullable=True)  # {"width": 300, "height": 100}
    
    created_at = Column(DateTime, default=datetime.utcnow)
    
    config = relationship("WhiteLabelConfig", back_populates="assets")
    
    __table_args__ = (
        CheckConstraint(
            "asset_type IN ('logo', 'logo_dark', 'favicon', 'font', 'custom_css', 'other')",
            name="check_asset_type"
        ),
    )


# Update relationships in existing models (Agency and Business)
def configure_relationships():
    """Configure relationships after all models are loaded."""
    from app.models.agency import Agency
    from app.models.business import Business
    
    Agency.white_label_configs = relationship(
        "WhiteLabelConfig", 
        back_populates="agency",
        cascade="all, delete-orphan"
    )
    Business.white_label_config = relationship(
        "WhiteLabelConfig",
        back_populates="business",
        uselist=False,
        cascade="all, delete-orphan"
    )
    WhiteLabelConfig.assets = relationship(
        "WhiteLabelAsset",
        back_populates="config",
        cascade="all, delete-orphan"
    )
```

### Pydantic Schemas (`app/schemas/white_label.py`)

```python
"""Pydantic schemas for white-label configuration."""
from datetime import datetime
from typing import Optional, Dict, Any
from uuid import UUID

from pydantic import BaseModel, Field, validator, HttpUrl, constr
import re


class ColorValidator:
    """Mixin for hex color validation."""
    
    @validator('primary_color', 'secondary_color', 'accent_color', 
               'background_color', 'text_color', pre=True, allow_reuse=True)
    def validate_hex(cls, v: Optional[str]) -> Optional[str]:
        if v is None:
            return v
        if not re.match(r'^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$', v):
            raise ValueError('Invalid hex color format. Use #RRGGBB or #RGB')
        return v.upper()


class SupportContact(BaseModel):
    """Support contact information."""
    email: Optional[EmailStr] = None
    phone: Optional[constr(regex=r'^[\d\s\-\+\(\)]+$')] = None  # type: ignore
    hours: Optional[str] = Field(None, max_length=100)
    
    class Config:
        schema_extra = {
            "example": {
                "email": "support@tradiesrus.com.au",
                "phone": "1800 123 456",
                "hours": "Mon-Fri 8am-6pm AEST"
            }
        }


class BrandColors(BaseModel):
    """Color scheme configuration."""
    primary: str = Field(default="#2563EB", regex=r'^#[A-Fa-f0-9]{6}$')
    secondary: Optional[str] = Field(None, regex=r'^#[A-Fa-f0-9]{6}$')
    accent: Optional[str] = Field(None, regex=r'^#[A-Fa-f0-9]{6}$')
    background: Optional[str] = Field("#FFFFFF", regex=r'^#[A-Fa-f0-9]{6}$')
    text: Optional[str] = Field("#1F2937", regex=r'^#[A-Fa-f0-9]{6}$')


class TypographyConfig(BaseModel):
    """Font and typography settings."""
    font_family: Optional[str] = Field(None, max_length=100)
    font_url: Optional[HttpUrl] = None


class WhiteLabelCreate(BaseModel):
    """Create new white-label configuration."""
    agency_id: UUID
    business_id: Optional[UUID] = None
    company_name: str = Field(..., min_length=2, max_length=255)
    
    # Colors
    primary_color: str = Field(default="#2563EB", regex=r'^#[A-Fa-f0-9]{6}$')
    secondary_color: Optional[str] = Field(None, regex=r'^#[A-Fa-f0-9]{6}$')
    accent_color: Optional[str] = Field(None, regex=r'^#[A-Fa-f0-9]{6}$')
    background_color: Optional[str] = Field("#FFFFFF")
    text_color: Optional[str] = Field("#1F2937")
    
    # Typography
    font_family: Optional[str] = Field(None, max_length=100)
    font_url: Optional[HttpUrl] = None
    
    # Domain
    custom_domain: Optional[constr(regex=r'^[a-z0-9][-a-z0-9]*\.[a-z]{2,}$')] = None  # type: ignore
    
    # Support
    support_email: Optional[EmailStr] = None
    support_phone: Optional[str] = Field(None, max_length=50)
    support_hours: Optional[str] = Field(None, max_length=100)
    
    # Branding flags
    remove_powered_by: bool = False
    hide_sunaiva_copyright: bool = False
    custom_copyright_text: Optional[str] = Field(None, max_length=255)
    
    @validator('custom_domain')
    def validate_domain(cls, v: Optional[str]) -> Optional[str]:
        if v:
            return v.lower().strip()
        return v


class WhiteLabelUpdate(BaseModel):
    """Update existing white-label configuration."""
    company_name: Optional[str] = Field(None, min_length=2, max_length=255)
    primary_color: Optional[str] = Field(None, regex=r'^#[A-Fa-f0-9]{6}$')
    secondary_color: Optional[str] = Field(None, regex=r'^#[A-Fa-f0-9]{6}$')
    accent_color: Optional[str] = Field(None, regex=r'^#[A-Fa-f0-9]{6}$')
    background_color: Optional[str] = None
    text_color: Optional[str] = None
    font_family: Optional[str] = Field(None, max_length=100)
    font_url: Optional[HttpUrl] = None
    custom_domain: Optional[constr(regex=r'^[a-z0-9][-a-z0-9]*\.[a-z]{2,}$')] = None  # type: ignore
    support_email: Optional[EmailStr] = None
    support_phone: Optional[str] = Field(None, max_length=50)
    support_hours: Optional[str] = Field(None, max_length=100)
    remove_powered_by: Optional[bool] = None
    hide_sunaiva_copyright: Optional[bool] = None
    custom_copyright_text: Optional[str] = Field(None, max_length=255)
    config_status: Optional[str] = Field(None, regex=r'^(active|inactive|suspended)$')


class WhiteLabelResponse(BaseModel):
    """Full white-label configuration response."""
    id: UUID
    agency_id: UUID
    business_id: Optional[UUID]
    company_name: str
    logo_url: Optional[str]
    logo_dark_url: Optional[str]
    primary_color: str
    secondary_color: Optional[str]
    accent_color: Optional[str]
    background_color: Optional[str]
    text_color: Optional[str]
    font_family: Optional[str]
    font_url: Optional[str]
    custom_domain: Optional[str]
    support_email: Optional[str]
    support_phone: Optional[str]
    support_hours: Optional[str]
    remove_powered_by: bool
    hide_sunaiva_copyright: bool
    custom_copyright_text: Optional[str]
    config_status: str
    created_at: datetime
    updated_at: datetime
    
    class Config:
        orm_mode = True


class WhiteLabelPublic(BaseModel):
    """Public-facing white-label config for widget."""
    company_name: str
    logo_url: Optional[str]
    logo_dark_url: Optional[str]
    colors: BrandColors
    typography: TypographyConfig
    support: SupportContact
    branding: Dict[str, Any]
    assets: Dict[str, Any]
    
    class Config:
        orm_mode = True


class LogoUploadResponse(BaseModel):
    """Response after logo upload."""
    success: bool
    asset_id: UUID
    url: str
    dimensions: Dict[str, int]
    message: str
```

### Service Layer (`app/services/white_label.py`)

```python
"""White-label business logic and image processing."""
import io
import imghdr
from typing import Tuple, Optional, BinaryIO
from uuid import UUID

from PIL import Image
from fastapi import UploadFile, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, update

from app.models.white_label import WhiteLabelConfig, WhiteLabelAsset
from app.schemas.white_label import WhiteLabelCreate, WhiteLabelUpdate
from app.core.config import settings
from app.services.storage import StorageService


class WhiteLabelService:
    """Service for managing white-label configurations."""
    
    ALLOWED_IMAGE_TYPES = {'png', 'jpg', 'jpeg', 'svg', 'webp'}
    MAX_LOGO_SIZE = 5 * 1024 * 1024  # 5MB
    MIN_DIMENSIONS = (50, 50)
    MAX_DIMENSIONS = (2000, 2000)
    RECOMMENDED_DIMENSIONS = (300, 100)  # width, height
    
    def __init__(self, db: AsyncSession, storage: StorageService):
        self.db = db
        self.storage = storage
    
    async def get_config_by_business(
        self, 
        business_id: UUID
    ) -> Optional[WhiteLabelConfig]:
        """Get white-label config for specific business."""
        result = await self.db.execute(
            select(WhiteLabelConfig).where(
                WhiteLabelConfig.business_id == business_id,
                WhiteLabelConfig.config_status == "active"
            )
        )
        return result.scalar_one_or_none()
    
    async def get_config_by_domain(
        self, 
        domain: str
    ) -> Optional[WhiteLabelConfig]:
        """Get config by custom domain."""
        result = await self.db.execute(
            select(WhiteLabelConfig).where(
                WhiteLabelConfig.custom_domain == domain.lower(),
                WhiteLabelConfig.config_status == "active"
            )
        )
        return result.scalar_one_or_none()
    
    async def create_config(
        self, 
        data: WhiteLabelCreate
    ) -> WhiteLabelConfig:
        """Create new white-label configuration."""
        # Check for existing config
        existing = await self.db.execute(
            select(WhiteLabelConfig).where(
                WhiteLabelConfig.business_id == data.business_id
            )
        )
        if existing.scalar_one_or_none():
            raise HTTPException(
                status_code=400,
                detail="White-label config already exists for this business"
            )
        
        config = WhiteLabelConfig(**data.dict())
        self.db.add(config)
        await self.db.commit()
        await self.db.refresh(config)
        return config
    
    async def update_config(
        self, 
        config_id: UUID, 
        data: WhiteLabelUpdate
    ) -> WhiteLabelConfig:
        """Update existing configuration."""
        result = await self.db.execute(
            select(WhiteLabelConfig).where(WhiteLabelConfig.id == config_id)
        )
        config = result.scalar_one_or_none()
        
        if not config:
            raise HTTPException(status_code=404, detail="Configuration not found")
        
        update_data = data.dict(exclude_unset=True)
        for field, value in update_data.items():
            setattr(config, field, value)
        
        await self.db.commit()
        await self.db.refresh(config)
        return config
    
    async def validate_and_process_logo(
        self, 
        file: UploadFile,
        is_dark_mode: bool = False
    ) -> Tuple[BinaryIO, dict]:
        """
        Validate image file and return processed buffer with metadata.
        
        Raises:
            HTTPException: If validation fails
        """
        # Check file size
        content = await file.read()
        if len(content) > self.MAX_LOGO_SIZE:
            raise HTTPException(
                status_code=400,
                detail=f"File too large. Max size: {self.MAX_LOGO_SIZE / 1024 / 1024}MB"
            )
        
        # Detect image type
        image_type = imghdr.what(None, content)
        if image_type not in self.ALLOWED_IMAGE_TYPES and not file.filename.endswith('.svg'):
            raise HTTPException(
                status_code=400,
                detail=f"Invalid file type. Allowed: {', '.join(self.ALLOWED_IMAGE_TYPES)}"
            )
        
        # Process SVG differently
        if file.filename.endswith('.svg') or image_type == 'svg':
            return io.BytesIO(content), {
                "type": "svg",
                "width": None,
                "height": None
            }
        
        # Process raster images with PIL
        try:
            img = Image.open(io.BytesIO(content))
            
            # Check dimensions
            width, height = img.size
            if width < self.MIN_DIMENSIONS[0] or height < self.MIN_DIMENSIONS[1]:
                raise HTTPException(
                    status_code=400,
                    detail=f"Image too small. Minimum: {self.MIN_DIMENSIONS[0]}x{self.MIN_DIMENSIONS[1]}"
                )
            
            if width > self.MAX_DIMENSIONS[0] or height > self.MAX_DIMENSIONS[1]:
                # Resize if too large
                img.thumbnail(self.MAX_DIMENSIONS, Image.Resampling.LANCZOS)
                width, height = img.size
            
            # Convert to RGB if necessary (remove alpha for JPG compatibility)
            if img.mode in ('RGBA', 'LA', 'P'):
                background = Image.new('RGB', img.size, (255, 255, 255))
                if img.mode == 'P':
                    img = img.convert('RGBA')
                if img.mode in ('RGBA', 'LA'):
                    background.paste(img, mask=img.split()[-1])
                    img = background
                else:
                    img = img.convert('RGB')
            
            # Save to buffer
            output = io.BytesIO()
            img.save(output, format='PNG' if image_type == 'png' else 'JPEG', quality=95)
            output.seek(0)
            
            return output, {
                "type": "raster",
                "width": width,
                "height": height,
                "original_format": image_type
            }
            
        except Exception as e:
            raise HTTPException(
                status_code=400,
                detail=f"Invalid image file: {str(e)}"
            )
    
    async def upload_logo(
        self,
        config_id: UUID,
        file: UploadFile,
        asset_type: str = "logo"  # "logo" or "logo_dark"
    ) -> WhiteLabelAsset:
        """Upload and process logo for white-label config."""
        if asset_type not in ("logo", "logo_dark"):
            raise HTTPException(status_code=400, detail="Invalid asset type")
        
        # Validate config exists
        result = await self.db.execute(
            select(WhiteLabelConfig).where(WhiteLabelConfig.id == config_id)
        )
        config = result.scalar_one_or_none()
        if not config:
            raise HTTPException(status_code=404, detail="Configuration not found")
        
        # Process image
        processed_buffer, metadata = await self.validate_and_process_logo(
            file, 
            is_dark_mode=(asset_type == "logo_dark")
        )
        
        # Generate storage path
        file_ext = "svg" if metadata["type"] == "svg" else "png"
        storage_path = f"white-label/{config_id}/{asset_type}.{file_ext}"
        
        # Upload to storage
        content_type = "image/svg+xml" if file_ext == "svg" else "image/png"
        public_url = await self.storage.upload_file(
            file_obj=processed_buffer,
            destination=storage_path,
            content_type=content_type,
            public=True
        )
        
        # Create asset record
        asset = WhiteLabelAsset(
            config_id=config_id,
            asset_type=asset_type,
            file_name=f"{asset_type}.{file_ext}",
            original_name=file.filename,
            file_path=storage_path,
            file_size=processed_buffer.getbuffer().nbytes,
            mime_type=content_type,
            dimensions=metadata if metadata["type"] != "svg" else None
        )
        
        self.db.add(asset)
        
        # Update config with URL
        if asset_type == "logo":
            config.logo_url = public_url
        else:
            config.logo_dark_url = public_url
        
        await self.db.commit()
        await self.db.refresh(asset)
        
        return asset, public_url, metadata
    
    async def delete_config(self, config_id: UUID) -> None:
        """Delete configuration and all associated assets."""
        result = await self.db.execute(
            select(WhiteLabelConfig).where(WhiteLabelConfig.id == config_id)
        )
        config = result.scalar_one_or_none()
        
        if not config:
            raise HTTPException(status_code=404, detail="Configuration not found")
        
        # Delete assets from storage
        for asset in config.assets:
            await self.storage.delete_file(asset.file_path)
        
        await self.db.delete(config)
        await self.db.commit()
```

### API Endpoints (`app/api/v1/white_label.py`)

```python
"""White-label API endpoints."""
from typing import Optional
from uuid import UUID

from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, status
from sqlalchemy.ext.asyncio import AsyncSession

from app.api.deps import get_db, get_current_agency_user, require_agency_access
from app.schemas.white_label import (
    WhiteLabelCreate, WhiteLabelUpdate, WhiteLabelResponse, 
    WhiteLabelPublic, LogoUploadResponse
)
from app.services.white_label import WhiteLabelService
from app.services.storage import get_storage_service
from app.core.cache import get_cache, CacheInterface

router = APIRouter(prefix="/white-label", tags=["white-label"])


@router.post("/", response_model=WhiteLabelResponse, status_code=status.HTTP_201_CREATED)
async def create_white_label(
    data: WhiteLabelCreate,
    db: AsyncSession = Depends(get_db),
    current_user: dict = Depends(require_agency_access),
):
    """
    Create new white-label configuration.
    Agency users can create configs for their own agency.
    """
    if str(current_user["agency_id"]) != str(data.agency_id):
        raise HTTPException(
            status_code=403,
            detail="Cannot create config for different agency"
        )
    
    service = WhiteLabelService(db, get_storage_service())
    return await service.create_config(data)


@router.get("/business/{business_id}", response_model=WhiteLabelPublic)
async def get_business_white_label(
    business_id: UUID,
    db: AsyncSession = Depends(get_db),
    cache: CacheInterface = Depends(get_cache)
):
    """
    Public endpoint: Get white-label config for specific business.
    Used by widget to load branding.
    """
    cache_key = f"wl:business:{business_id}"
    
    # Try cache first
    cached = await cache.get(cache_key)
    if cached:
        return cached
    
    service = WhiteLabelService(db, get_storage_service())
    config = await service.get_config_by_business(business_id)
    
    if not config:
        raise HTTPException(
            status_code=404,
            detail="No white-label configuration found"
        )
    
    result = config.to_public_dict()
    
    # Cache for 1 hour (white-label configs don't change often)
    await cache.set(cache_key, result, ttl=3600)
    
    return result


@router.get("/domain/{domain:path}", response_model=WhiteLabelPublic)
async def get_domain_white_label(
    domain: str,
    db: AsyncSession = Depends(get_db),
    cache: CacheInterface = Depends(get_cache)
):
    """
    Get white-label config by custom domain.
    Used for CORS validation and domain-based widget loading.
    """
    cache_key = f"wl:domain:{domain.lower()}"
    
    cached = await cache.get(cache_key)
    if cached:
        return cached
    
    service = WhiteLabelService(db, get_storage_service())
    config = await service.get_config_by_domain(domain)
    
    if not config:
        raise HTTPException(status_code=404, detail="Domain not configured")
    
    result = config.to_public_dict()
    await cache.set(cache_key, result, ttl=3600)
    
    return result


@router.put("/{config_id}", response_model=WhiteLabelResponse)
async def update_white_label(
    config_id: UUID,
    data: WhiteLabelUpdate,
    db: AsyncSession = Depends(get_db),
    current_user: dict = Depends(require_agency_access),
    cache: CacheInterface = Depends(get_cache)
):
    """Update white-label configuration."""
    service = WhiteLabelService(db, get_storage_service())
    
    # Verify ownership
    # ... validation logic here ...
    
    config = await service.update_config(config_id, data)
    
    # Invalidate cache
    if config.business_id:
        await cache.delete(f"wl:business:{config.business_id}")
    if config.custom_domain:
        await cache.delete(f"wl:domain:{config.custom_domain}")
    
    return config


@router.post("/{config_id}/logo", response_model=LogoUploadResponse)
async def upload_logo(
    config_id: UUID,
    file: UploadFile = File(...),
    is_dark_mode: bool = False,
    db: AsyncSession = Depends(get_db),
    current_user: dict = Depends(require_agency_access),
    cache: CacheInterface = Depends(get_cache)
):
    """
    Upload logo for white-label configuration.
    Validates image dimensions and format.
    """
    service = WhiteLabelService(db, get_storage_service())
    asset_type = "logo_dark" if is_dark_mode else "logo"
    
    try:
        asset, url, metadata = await service.upload_logo(
            config_id, file, asset_type
        )
        
        # Invalidate cache
        result = await db.execute(
            select(WhiteLabelConfig).where(WhiteLabelConfig.id == config_id)
        )
        config = result.scalar_one()
        if config.business_id:
            await cache.delete(f"wl:business:{config.business_id}")
        
        return LogoUploadResponse(
            success=True,
            asset_id=asset.id,
            url=url,
            dimensions=metadata if metadata["type"] != "svg" else {},
            message=f"Logo uploaded successfully. Recommended size: 300x100px"
        )
        
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(
            status_code=500,
            detail=f"Upload failed: {str(e)}"
        )


@router.delete("/{config_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_white_label(
    config_id: UUID,
    db: AsyncSession = Depends(get_db),
    current_user: dict = Depends(require_agency_access)
):
    """Delete white-label configuration and all assets."""
    service = WhiteLabelService(db, get_storage_service())
    await service.delete_config(config_id)
    return None
```

### Middleware for Custom Domains (`app/middleware/white_label.py`)

```python
"""Middleware to handle custom domain white-label routing."""
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import Response, JSONResponse

from app.core.cache import get_cache
from app.db.session import async_session


class WhiteLabelDomainMiddleware(BaseHTTPMiddleware):
    """
    Middleware to inject white-label context for custom domains.
    Adds X-White-Label headers for downstream processing.
    """
    
    async def dispatch(self, request: Request, call_next):
        host = request.headers.get("host", "").lower().split(":")[0]
        
        # Skip for API domains and localhost
        if host in ["api.sunaiva.com", "localhost", "127.0.0.1"]:
            return await call_next(request)
        
        # Check if this is a custom white-label domain
        cache = get_cache()
        cache_key = f"wl:domain:{host}"
        
        config = await cache.get(cache_key)
        
        if not config:
            # Query database
            async with async_session() as db:
                from app.services.white_label import WhiteLabelService
                from app.services.storage import get_storage_service
                
                service = WhiteLabelService(db, get_storage_service())
                db_config = await service.get_config_by_domain(host)
                
                if db_config:
                    config = db_config.to_public_dict()
                    await cache.set(cache_key, config, ttl=3600)
        
        if config:
            # Inject into request state
            request.state.white_label = config
            request.state.is_white_label = True
            
            # Add headers for CORS and identification
            response = await call_next(request)
            response.headers["X-White-Label"] = "true"
            response.headers["X-Company-Name"] = config["company_name"]
            
            return response
        
        return await call_next(request)
```

## 2. Frontend Implementation

### Widget White-Label Loader (`widget/src/white-label.js`)

```javascript
/**
 * White-label configuration manager for Sunaiva Widget
 * Handles dynamic branding, custom assets, and error message customization
 */

class WhiteLabelManager {
  constructor(config = {}) {
    this.config = {
      apiBaseUrl: config.apiBaseUrl || 'https://api.sunaiva.com',
      businessId: config.businessId,
      customDomain: config.customDomain || window.location.hostname,
      cacheDuration: 3600000, // 1 hour
      ...config
    };
    
    this.branding = null;
    this.cacheKey = `sunaiva_wl_${this.config.businessId || this.config.customDomain}`;
    this.styleElement = null;
  }

  /**
   * Initialize white-label configuration
   * @returns {Promise<Object>} Branding configuration
   */
  async initialize() {
    // Check localStorage cache first
    const cached = this.getCachedConfig();
    if (cached && !this.isCacheExpired(cached)) {
      this.branding = cached.data;
      this.applyBranding();
      return this.branding;
    }

    try {
      // Fetch from API
      const endpoint = this.config.businessId 
        ? `/v1/white-label/business/${this.config.businessId}`
        : `/v1/white-label/domain/${this.config.customDomain}`;
      
      const response = await fetch(`${this.config.apiBaseUrl}${endpoint}`, {
        headers: {
          'Accept': 'application/json',
        },
        credentials: 'omit' // Public endpoint
      });

      if (!response.ok) {
        if (response.status === 404) {
          console.log('No white-label config found, using defaults');
          return null;
        }
        throw new Error(`Failed to load white-label config: ${response.status}`);
      }

      this.branding = await response.json();
      this.cacheConfig(this.branding);
      this.applyBranding();
      
      return this.branding;
      
    } catch (error) {
      console.error('White-label initialization failed:', error);
      // Try to use stale cache if available
      if (cached) {
        this.branding = cached.data;
        this.applyBranding();
        return this.branding;
      }
      return null;
    }
  }

  /**
   * Apply branding to widget
   */
  applyBranding() {
    if (!this.branding) return;

    // Inject CSS custom properties
    this.injectStyles();
    
    // Update meta tags if needed
    this.updateMetaTags();
    
    // Dispatch event for other components
    window.dispatchEvent(new CustomEvent('sunaiva:branding:loaded', {
      detail: this.branding
    }));
  }

  /**
   * Inject CSS custom properties into shadow DOM or document
   */
  injectStyles() {
    const colors = this.branding.colors || {};
    const typography = this.branding.typography || {};
    
    const css = `
      :host, .sunaiva-widget-root {
        --sunaiva-primary: ${colors.primary || '#2563EB'};
        --sunaiva-primary-rgb: ${this.hexToRgb(colors.primary) || '37, 99, 235'};
        --sunaiva-secondary: ${colors.secondary || colors.primary || '#2563EB'};
        --sunaiva-accent: ${colors.accent || '#10B981'};
        --s