Inventory and Variations

Overview

Digital products often come in variations:

This chapter covers managing these variations through the API.

Understanding Etsy Variations

Etsy uses a specific structure for variations:

Listing
├── Property 1: "Size" (Letter, A4, A5)
├── Property 2: "Format" (PDF, PNG)
└── Offerings (combinations with prices)
    ├── Letter + PDF = $4.99
    ├── Letter + PNG = $4.99
    ├── A4 + PDF = $4.99
    └── ...

Each combination is called an offering with its own price and SKU.

Inventory for Digital Products

Why Track Inventory?

For digital products, you typically have unlimited stock. But there are scenarios where you might want limited “inventory”:

Setting Unlimited Inventory

For most digital products:

def set_unlimited_inventory(client, shop_id, listing_id):
    """Set high quantity for digital product."""
    client.put(
        f"/application/shops/{shop_id}/listings/{listing_id}",
        data={"quantity": 999}
    )

Creating Variations

Define Property Options

First, define the properties and their options:

VARIATION_PROPERTIES = {
    "size": {
        "property_id": 100,  # Etsy property IDs vary
        "options": ["Letter", "A4", "A5"]
    },
    "format": {
        "property_id": 200,
        "options": ["PDF", "PNG"]
    }
}

Create Listing with Variations

def create_listing_with_variations(client, shop_id, listing_data, variations):
    """Create a listing with product variations."""
    # First create the base listing
    listing = client.post(
        f"/application/shops/{shop_id}/listings",
        data=listing_data
    )
    listing_id = listing["listing_id"]
    
    # Then add variations via inventory endpoint
    inventory_data = build_inventory_payload(variations)
    
    client.put(
        f"/application/listings/{listing_id}/inventory",
        data=inventory_data
    )
    
    return listing_id

Building Inventory Payload

The inventory structure is complex. See code/variations.py for complete implementation.

def build_inventory_payload(variations, base_price):
    """Build the inventory payload for Etsy API."""
    products = []
    
    # Generate all combinations
    from itertools import product as cartesian
    option_lists = [v["options"] for v in variations.values()]
    
    for combo in cartesian(*option_lists):
        products.append({
            "sku": "-".join(combo),
            "property_values": [
                {"property_id": pid, "value": val}
                for pid, val in zip(variations.keys(), combo)
            ],
            "offerings": [{
                "price": base_price,
                "quantity": 999,
                "is_enabled": True
            }]
        })
    
    return {"products": products}

Reading Existing Variations

Get Listing Inventory

def get_listing_inventory(client, listing_id):
    """Get inventory and variations for a listing."""
    return client.get(f"/application/listings/{listing_id}/inventory")

Parse Variation Options

def get_variation_options(inventory):
    """Extract variation options from inventory data."""
    options = {}
    
    for product in inventory.get("products", []):
        for prop_value in product.get("property_values", []):
            prop_name = prop_value.get("property_name")
            value = prop_value.get("values", [None])[0]
            
            if prop_name not in options:
                options[prop_name] = set()
            options[prop_name].add(value)
    
    return {k: list(v) for k, v in options.items()}

Updating Variations

Update Prices for Specific Variation

def update_variation_price(client, listing_id, sku, new_price):
    """Update price for a specific variation (by SKU)."""
    inventory = get_listing_inventory(client, listing_id)
    
    for product in inventory["products"]:
        if product.get("sku") == sku:
            product["offerings"][0]["price"] = new_price
    
    client.put(
        f"/application/listings/{listing_id}/inventory",
        data=inventory
    )

Add New Variation Option

Adding a new option (e.g., new size) requires rebuilding the inventory:

def add_variation_option(client, listing_id, property_name, new_option, base_price):
    """Add a new option to an existing variation."""
    inventory = get_listing_inventory(client, listing_id)
    
    # Get existing products as template
    template = inventory["products"][0] if inventory["products"] else None
    if not template:
        return None
    
    # Create new product entry for new option
    new_product = {
        "sku": f"{new_option}-variant",
        "property_values": [
            {"property_id": pv["property_id"], "value": new_option}
            if pv["property_name"] == property_name 
            else pv
            for pv in template["property_values"]
        ],
        "offerings": [{
            "price": base_price,
            "quantity": 999,
            "is_enabled": True
        }]
    }
    
    inventory["products"].append(new_product)
    
    client.put(
        f"/application/listings/{listing_id}/inventory",
        data=inventory
    )

Common Variation Patterns for Digital Products

Format Variations

FORMAT_VARIATIONS = {
    "format": {
        "options": ["PDF", "PNG", "JPG", "SVG"],
        "prices": {"PDF": 499, "PNG": 499, "JPG": 399, "SVG": 599}
    }
}

Size Variations

SIZE_VARIATIONS = {
    "size": {
        "options": ["Letter (8.5x11)", "A4", "A5", "5x7"],
        "prices": {"Letter (8.5x11)": 499, "A4": 499, "A5": 399, "5x7": 299}
    }
}

Bundle Variations

BUNDLE_VARIATIONS = {
    "bundle": {
        "options": ["Single", "Pack of 3", "Pack of 10", "Full Collection"],
        "prices": {"Single": 499, "Pack of 3": 999, "Pack of 10": 2499, "Full Collection": 4999}
    }
}

Managing Files per Variation

A key challenge: Etsy attaches files to the listing, not to individual variations. All customers get all files regardless of which variation they purchase.

Workarounds

Option 1: Include all files

Upload all formats/sizes. Customers choose what they need.
Files: planner-letter.pdf, planner-a4.pdf, planner-a5.pdf

Option 2: Separate listings

Create separate listings for each major variation.
Listing 1: Planner - Letter Size
Listing 2: Planner - A4 Size

Option 3: ZIP bundles

Create ZIP files for each variation combo.
Upload: letter-pdf.zip, a4-pdf.zip, letter-png.zip, etc.

Bulk Variation Updates

Update Prices Across All Variations

def bulk_update_variation_prices(client, shop_id, price_multiplier):
    """Apply price change to all variations across all listings."""
    listings = get_all_listings(client, shop_id)
    
    for listing in listings:
        inventory = get_listing_inventory(client, listing["listing_id"])
        
        for product in inventory.get("products", []):
            for offering in product.get("offerings", []):
                old_price = offering["price"]
                offering["price"] = int(old_price * price_multiplier)
        
        client.put(
            f"/application/listings/{listing['listing_id']}/inventory",
            data=inventory
        )

Enable/Disable Variations

def toggle_variation(client, listing_id, sku, enabled):
    """Enable or disable a specific variation."""
    inventory = get_listing_inventory(client, listing_id)
    
    for product in inventory["products"]:
        if product.get("sku") == sku:
            product["offerings"][0]["is_enabled"] = enabled
    
    client.put(
        f"/application/listings/{listing_id}/inventory",
        data=inventory
    )

SKU Management

SKUs help you track variations:

def generate_sku(product_name, variation_combo):
    """Generate consistent SKU from product and variations."""
    base = product_name.lower().replace(" ", "-")[:10]
    var_code = "-".join(v[:3].upper() for v in variation_combo)
    return f"{base}-{var_code}"

# Example: "planner-LET-PDF" for Letter size PDF

Best Practices

  1. Keep variations simple: Too many options confuse customers
  2. Use clear option names: “Letter (8.5x11)” not just “Letter”
  3. Price variations logically: Larger bundles = better value
  4. Include all files: When possible, give customers everything
  5. Use SKUs consistently: Makes tracking and automation easier

What’s Next

With listings, files, and variations managed, the next chapter covers extracting analytics and insights from your sales data.


← Previous: Orders & Fulfillment Next: Analytics →

← Back to Table of Contents