Chapter 4: Working with Shops and Users

Understanding the Shop-User Relationship

In Etsy’s data model, users and shops are distinct but related entities. A user account can have one shop, and all listings, orders, and shop settings belong to that shop. Understanding this relationship is fundamental to building effective API integrations.

Retrieving User Information

Get the Current User

After authentication, you’ll often need to know which user is connected:

# etsy_client/users.py
"""User-related API operations."""

from typing import Dict, Any, Optional
from .client import EtsyClient


class UserOperations:
    """Operations for Etsy users."""
    
    def __init__(self, client: EtsyClient):
        self.client = client
    
    def get_current_user(self) -> Dict[str, Any]:
        """
        Get the currently authenticated user.
        
        Returns:
            User data including user_id, login_name, and shop details
        """
        return self.client.get("/application/users/me")
    
    def get_user_by_id(self, user_id: int) -> Dict[str, Any]:
        """
        Get a specific user by ID.
        
        Args:
            user_id: The Etsy user ID
            
        Returns:
            User data
        """
        return self.client.get(f"/application/users/{user_id}")
    
    def get_user_shops(self, user_id: int) -> Dict[str, Any]:
        """
        Get all shops belonging to a user.
        
        Args:
            user_id: The Etsy user ID
            
        Returns:
            List of shops
        """
        return self.client.get(f"/application/users/{user_id}/shops")


# Example usage
def get_my_shop_id(client: EtsyClient) -> int:
    """
    Convenience function to get the current user's shop ID.
    
    Args:
        client: Authenticated EtsyClient
        
    Returns:
        Shop ID
        
    Raises:
        ValueError: If user has no shop
    """
    user_ops = UserOperations(client)
    user = user_ops.get_current_user()
    
    # Check if user has a shop
    if 'shop_id' not in user or not user['shop_id']:
        raise ValueError("Authenticated user does not have a shop")
    
    return user['shop_id']

User Data Structure

The user endpoint returns data like:

{
    "user_id": 123456789,
    "login_name": "YourShopName",
    "primary_email": "email@example.com",
    "create_timestamp": 1609459200,
    "shop_id": 987654321,
    "is_seller": true
}

Working with Shops

Shop Operations Module

# etsy_client/shops.py
"""Shop-related API operations."""

from typing import Dict, Any, Optional, List
from datetime import datetime
from .client import EtsyClient


class ShopOperations:
    """Operations for Etsy shops."""
    
    def __init__(self, client: EtsyClient):
        self.client = client
    
    def get_shop(self, shop_id: int) -> Dict[str, Any]:
        """
        Get detailed shop information.
        
        Args:
            shop_id: The Etsy shop ID
            
        Returns:
            Shop data including name, settings, and statistics
        """
        return self.client.get(f"/application/shops/{shop_id}")
    
    def get_shop_by_name(self, shop_name: str) -> Dict[str, Any]:
        """
        Find a shop by its name.
        
        Args:
            shop_name: The shop's display name
            
        Returns:
            Shop data
        """
        return self.client.get(f"/application/shops/{shop_name}")
    
    def update_shop(
        self,
        shop_id: int,
        title: Optional[str] = None,
        announcement: Optional[str] = None,
        sale_message: Optional[str] = None,
        digital_sale_message: Optional[str] = None,
        policy_welcome: Optional[str] = None,
        **kwargs
    ) -> Dict[str, Any]:
        """
        Update shop settings.
        
        Args:
            shop_id: The shop ID to update
            title: Shop title/tagline
            announcement: Shop announcement message
            sale_message: Message sent after physical product purchase
            digital_sale_message: Message sent after digital product purchase
            policy_welcome: Welcome message in shop policies
            **kwargs: Additional updatable fields
            
        Returns:
            Updated shop data
        """
        data = {}
        
        if title is not None:
            data['title'] = title
        if announcement is not None:
            data['announcement'] = announcement
        if sale_message is not None:
            data['sale_message'] = sale_message
        if digital_sale_message is not None:
            data['digital_sale_message'] = digital_sale_message
        if policy_welcome is not None:
            data['policy_welcome'] = policy_welcome
        
        data.update(kwargs)
        
        if not data:
            raise ValueError("No fields to update")
        
        return self.client.put(f"/application/shops/{shop_id}", json=data)
    
    def get_shop_sections(self, shop_id: int) -> Dict[str, Any]:
        """
        Get all sections (categories) in a shop.
        
        Args:
            shop_id: The shop ID
            
        Returns:
            List of shop sections
        """
        return self.client.get(f"/application/shops/{shop_id}/sections")
    
    def create_shop_section(
        self,
        shop_id: int,
        title: str
    ) -> Dict[str, Any]:
        """
        Create a new shop section.
        
        Args:
            shop_id: The shop ID
            title: Section title
            
        Returns:
            Created section data
        """
        return self.client.post(
            f"/application/shops/{shop_id}/sections",
            json={"title": title}
        )
    
    def delete_shop_section(
        self,
        shop_id: int,
        section_id: int
    ) -> Dict[str, Any]:
        """
        Delete a shop section.
        
        Args:
            shop_id: The shop ID
            section_id: The section ID to delete
            
        Returns:
            Deletion confirmation
        """
        return self.client.delete(
            f"/application/shops/{shop_id}/sections/{section_id}"
        )


class ShopStatistics:
    """Analyze shop statistics and performance."""
    
    def __init__(self, shop_data: Dict[str, Any]):
        """
        Initialize with shop data from API.
        
        Args:
            shop_data: Shop data dictionary from get_shop()
        """
        self.data = shop_data
    
    @property
    def shop_id(self) -> int:
        return self.data['shop_id']
    
    @property
    def name(self) -> str:
        return self.data['shop_name']
    
    @property
    def title(self) -> str:
        return self.data.get('title', '')
    
    @property
    def currency(self) -> str:
        return self.data.get('currency_code', 'USD')
    
    @property
    def listing_count(self) -> int:
        return self.data.get('listing_active_count', 0)
    
    @property
    def sale_count(self) -> int:
        return self.data.get('transaction_sold_count', 0)
    
    @property
    def review_count(self) -> int:
        return self.data.get('review_count', 0)
    
    @property
    def average_rating(self) -> float:
        return self.data.get('review_average', 0.0)
    
    @property
    def is_vacation_mode(self) -> bool:
        return self.data.get('is_vacation', False)
    
    @property
    def created_date(self) -> datetime:
        timestamp = self.data.get('create_timestamp', 0)
        return datetime.fromtimestamp(timestamp)
    
    def summary(self) -> str:
        """Generate a text summary of shop statistics."""
        return f"""
Shop: {self.name}
Title: {self.title}
Currency: {self.currency}

Statistics:
- Active Listings: {self.listing_count}
- Total Sales: {self.sale_count}
- Reviews: {self.review_count}
- Average Rating: {self.average_rating:.2f}/5

Status:
- Vacation Mode: {'Yes' if self.is_vacation_mode else 'No'}
- Created: {self.created_date.strftime('%Y-%m-%d')}
"""

Practical Shop Management Examples

# scripts/shop_info.py
"""Display shop information and statistics."""

import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent))

from config import Config
from etsy_client import EtsyClient
from etsy_client.shops import ShopOperations, ShopStatistics
from etsy_client.users import get_my_shop_id


def display_shop_info():
    """Fetch and display comprehensive shop information."""
    
    client = EtsyClient(
        api_key=Config.ETSY_API_KEY,
        access_token=Config.ETSY_ACCESS_TOKEN
    )
    
    # Get shop ID
    shop_id = get_my_shop_id(client)
    print(f"Shop ID: {shop_id}")
    
    # Get shop details
    shop_ops = ShopOperations(client)
    shop_data = shop_ops.get_shop(shop_id)
    
    # Analyze statistics
    stats = ShopStatistics(shop_data)
    print(stats.summary())
    
    # Get sections
    sections = shop_ops.get_shop_sections(shop_id)
    
    print("\nShop Sections:")
    print("-" * 30)
    
    if sections.get('results'):
        for section in sections['results']:
            count = section.get('active_listing_count', 0)
            print(f"  - {section['title']} ({count} listings)")
    else:
        print("  No sections defined")


def update_digital_message(message: str):
    """Update the message sent to customers after digital purchases."""
    
    client = EtsyClient(
        api_key=Config.ETSY_API_KEY,
        access_token=Config.ETSY_ACCESS_TOKEN
    )
    
    shop_id = get_my_shop_id(client)
    shop_ops = ShopOperations(client)
    
    result = shop_ops.update_shop(
        shop_id,
        digital_sale_message=message
    )
    
    print("✓ Digital sale message updated!")
    print(f"  New message: {result.get('digital_sale_message', '')[:100]}...")


if __name__ == "__main__":
    display_shop_info()

Managing Shop Sections for Digital Products

Sections help organize your digital products. Here’s how to manage them programmatically:

# scripts/manage_sections.py
"""Manage shop sections for digital product organization."""

import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent))

from config import Config
from etsy_client import EtsyClient
from etsy_client.shops import ShopOperations
from etsy_client.users import get_my_shop_id


class SectionManager:
    """Manage shop sections for digital products."""
    
    # Common digital product categories
    DIGITAL_SECTIONS = [
        "Digital Planners",
        "Printable Wall Art",
        "SVG Cut Files",
        "Social Media Templates",
        "Resume Templates",
        "Canva Templates",
        "Digital Stickers",
        "Spreadsheet Templates",
        "Notion Templates",
        "Lightroom Presets"
    ]
    
    def __init__(self, client: EtsyClient, shop_id: int):
        self.client = client
        self.shop_id = shop_id
        self.shop_ops = ShopOperations(client)
    
    def get_sections(self) -> dict:
        """Get current sections with listing counts."""
        return self.shop_ops.get_shop_sections(self.shop_id)
    
    def section_exists(self, title: str) -> bool:
        """Check if a section with given title exists."""
        sections = self.get_sections()
        
        for section in sections.get('results', []):
            if section['title'].lower() == title.lower():
                return True
        
        return False
    
    def create_section(self, title: str) -> dict:
        """Create a section if it doesn't exist."""
        if self.section_exists(title):
            print(f"Section '{title}' already exists")
            return None
        
        result = self.shop_ops.create_shop_section(self.shop_id, title)
        print(f"✓ Created section: {title}")
        return result
    
    def setup_digital_sections(self, categories: list = None):
        """Set up standard sections for digital products."""
        categories = categories or self.DIGITAL_SECTIONS
        
        print("Setting up digital product sections...")
        print("-" * 40)
        
        created = 0
        for category in categories:
            if not self.section_exists(category):
                self.create_section(category)
                created += 1
            else:
                print(f"  Exists: {category}")
        
        print(f"\nCreated {created} new sections")
    
    def audit_sections(self):
        """Audit sections and report empty ones."""
        sections = self.get_sections()
        
        print("\nSection Audit")
        print("-" * 40)
        
        empty_sections = []
        active_sections = []
        
        for section in sections.get('results', []):
            title = section['title']
            count = section.get('active_listing_count', 0)
            
            if count == 0:
                empty_sections.append(title)
            else:
                active_sections.append((title, count))
        
        print("\nActive Sections:")
        for title, count in sorted(active_sections, key=lambda x: -x[1]):
            print(f"  {title}: {count} listings")
        
        if empty_sections:
            print("\nEmpty Sections (consider removing):")
            for title in empty_sections:
                print(f"  - {title}")


def main():
    """Demo section management."""
    client = EtsyClient(
        api_key=Config.ETSY_API_KEY,
        access_token=Config.ETSY_ACCESS_TOKEN
    )
    
    shop_id = get_my_shop_id(client)
    manager = SectionManager(client, shop_id)
    
    # Show current sections
    print("Current Sections:")
    sections = manager.get_sections()
    for section in sections.get('results', []):
        print(f"  - {section['title']}")
    
    # Audit sections
    manager.audit_sections()


if __name__ == "__main__":
    main()

Shop Settings for Digital Products

Digital product shops have specific settings worth configuring:

# etsy_client/shop_settings.py
"""Shop settings optimized for digital products."""

from typing import Dict, Any, Optional
from .client import EtsyClient
from .shops import ShopOperations


class DigitalShopSettings:
    """Configure shop settings for digital product sales."""
    
    def __init__(self, client: EtsyClient, shop_id: int):
        self.client = client
        self.shop_id = shop_id
        self.shop_ops = ShopOperations(client)
    
    def configure_digital_welcome_message(self) -> Dict[str, Any]:
        """
        Set up an optimized welcome message for digital downloads.
        
        Returns:
            Updated shop data
        """
        message = """Thank you for your purchase! 🎉

📥 HOW TO DOWNLOAD:
1. Go to your Etsy account
2. Click "Purchases and reviews"
3. Click "Download Files" next to your order

💡 TIPS:
- Downloads are available immediately after purchase
- You can download files up to 5 times
- Files never expire from your account

📱 MOBILE USERS:
For the best experience, download on a computer or use a file manager app on mobile.

❓ NEED HELP?
If you have any issues downloading, please message me and I'll help you right away!

Thank you for supporting my shop! ❤️"""
        
        return self.shop_ops.update_shop(
            self.shop_id,
            digital_sale_message=message
        )
    
    def get_policy_settings(self) -> Dict[str, Any]:
        """Get current shop policy settings."""
        shop = self.shop_ops.get_shop(self.shop_id)
        
        return {
            'policy_welcome': shop.get('policy_welcome', ''),
            'policy_payment': shop.get('policy_payment', ''),
            'policy_shipping': shop.get('policy_shipping', ''),
            'policy_refunds': shop.get('policy_refunds', ''),
            'policy_additional': shop.get('policy_additional', ''),
            'policy_privacy': shop.get('policy_privacy', ''),
        }
    
    def configure_digital_policies(
        self,
        custom_refund_policy: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        Configure policies appropriate for digital products.
        
        Args:
            custom_refund_policy: Override the default refund policy
            
        Returns:
            Updated shop data
        """
        default_refund = """Due to the digital nature of our products, all sales are final. 
We cannot offer refunds once files have been downloaded.

However, we stand behind our products! If you experience technical issues:
1. Contact us with details about the problem
2. Include screenshots if possible
3. We'll work to resolve the issue promptly

If files are corrupted or significantly different from the listing description, 
we'll either provide working files or issue a refund at our discretion."""
        
        updates = {
            'policy_welcome': """Welcome to our digital products shop! 
All items are instant downloads - no physical products will be shipped.""",
            'policy_refunds': custom_refund_policy or default_refund,
        }
        
        return self.shop_ops.update_shop(self.shop_id, **updates)


def setup_digital_shop(client: EtsyClient, shop_id: int):
    """
    One-time setup for a digital products shop.
    
    Args:
        client: Authenticated EtsyClient
        shop_id: Shop to configure
    """
    settings = DigitalShopSettings(client, shop_id)
    
    print("Configuring digital shop settings...")
    print("-" * 40)
    
    # Set up welcome message
    print("Setting digital sale message...")
    settings.configure_digital_welcome_message()
    print("✓ Digital sale message configured")
    
    # Configure policies
    print("Setting digital product policies...")
    settings.configure_digital_policies()
    print("✓ Policies configured")
    
    print("\n✓ Shop configured for digital products!")

Monitoring Shop Health

# etsy_client/shop_health.py
"""Monitor shop health and performance metrics."""

from typing import Dict, Any, List
from datetime import datetime, timedelta
from .client import EtsyClient
from .shops import ShopOperations


class ShopHealthMonitor:
    """Monitor and report on shop health metrics."""
    
    def __init__(self, client: EtsyClient, shop_id: int):
        self.client = client
        self.shop_id = shop_id
        self.shop_ops = ShopOperations(client)
    
    def get_health_report(self) -> Dict[str, Any]:
        """
        Generate a comprehensive health report.
        
        Returns:
            Dictionary with health metrics and recommendations
        """
        shop = self.shop_ops.get_shop(self.shop_id)
        
        # Collect metrics
        metrics = {
            'shop_name': shop['shop_name'],
            'active_listings': shop.get('listing_active_count', 0),
            'total_sales': shop.get('transaction_sold_count', 0),
            'review_count': shop.get('review_count', 0),
            'average_rating': shop.get('review_average', 0),
            'favorites': shop.get('num_favorers', 0),
            'vacation_mode': shop.get('is_vacation', False),
        }
        
        # Calculate health scores
        issues = []
        recommendations = []
        
        # Check listing count
        if metrics['active_listings'] < 10:
            issues.append("Low listing count")
            recommendations.append(
                "Add more listings to increase visibility. "
                "Shops with 50+ listings tend to perform better."
            )
        
        # Check reviews
        if metrics['review_count'] > 0:
            if metrics['average_rating'] < 4.5:
                issues.append("Below-average rating")
                recommendations.append(
                    "Review recent negative feedback and address common issues."
                )
        else:
            recommendations.append(
                "Encourage customers to leave reviews after purchase."
            )
        
        # Check vacation mode
        if metrics['vacation_mode']:
            issues.append("Vacation mode is ON")
            recommendations.append(
                "Your shop is not visible to buyers while in vacation mode."
            )
        
        # Calculate overall health
        health_score = 100
        health_score -= len(issues) * 10
        
        if metrics['active_listings'] >= 50:
            health_score += 10
        if metrics['average_rating'] >= 4.8:
            health_score += 10
        
        health_score = max(0, min(100, health_score))
        
        return {
            'metrics': metrics,
            'health_score': health_score,
            'issues': issues,
            'recommendations': recommendations,
            'generated_at': datetime.now().isoformat()
        }
    
    def print_report(self):
        """Print a formatted health report."""
        report = self.get_health_report()
        
        print("\n" + "=" * 50)
        print(f"Shop Health Report: {report['metrics']['shop_name']}")
        print("=" * 50)
        
        print(f"\n📊 Health Score: {report['health_score']}/100")
        
        print("\n📈 Metrics:")
        metrics = report['metrics']
        print(f"  Active Listings: {metrics['active_listings']}")
        print(f"  Total Sales: {metrics['total_sales']}")
        print(f"  Reviews: {metrics['review_count']}")
        print(f"  Average Rating: {metrics['average_rating']:.2f}/5")
        print(f"  Favorites: {metrics['favorites']}")
        
        if report['issues']:
            print("\n⚠️  Issues Found:")
            for issue in report['issues']:
                print(f"  - {issue}")
        
        if report['recommendations']:
            print("\n💡 Recommendations:")
            for i, rec in enumerate(report['recommendations'], 1):
                print(f"  {i}. {rec}")
        
        print(f"\nGenerated: {report['generated_at']}")

Integrating Shop Operations

Update the main client to include shop operations:

# etsy_client/client.py - Add to existing EtsyClient class

class EtsyClient:
    # ... existing code ...
    
    @property
    def shops(self):
        """Access shop operations."""
        from .shops import ShopOperations
        if not hasattr(self, '_shop_ops'):
            self._shop_ops = ShopOperations(self)
        return self._shop_ops
    
    @property
    def users(self):
        """Access user operations."""
        from .users import UserOperations
        if not hasattr(self, '_user_ops'):
            self._user_ops = UserOperations(self)
        return self._user_ops
    
    def get_my_shop(self) -> Dict[str, Any]:
        """Convenience method to get the authenticated user's shop."""
        user = self.get_me()
        if 'shop_id' not in user:
            raise ValueError("User does not have a shop")
        return self.shops.get_shop(user['shop_id'])

Key Takeaways

  1. Users own shops: Always get the user first to find their shop ID
  2. Shop sections organize products: Use them to categorize digital products
  3. Configure for digital sales: Set appropriate messages and policies
  4. Monitor shop health: Regular health checks catch issues early
  5. Use convenience methods: Build helper functions for common tasks

Moving Forward

With shop and user management in place, you’re ready to work with the core of your digital products business: listings. The next chapter covers creating, updating, and managing listings through the API.


← Chapter 3: Authentication and Authorization Table of Contents Chapter 5: Managing Listings →