Listings are the core of your Etsy shop. This chapter covers:
A digital product listing includes:
{
"listing_id": 1234567890,
"title": "Monthly Planner Printable PDF",
"description": "A beautiful monthly planner...",
"price": {"amount": 499, "divisor": 100, "currency_code": "USD"},
"quantity": 999,
"tags": ["planner", "printable", "pdf", "monthly"],
"state": "active",
"is_digital": true,
"file_data": "digital",
"who_made": "i_did",
"when_made": "2020_2025",
"taxonomy_id": 2078,
"views": 1250,
"num_favorers": 45
}
Key fields for digital products:
is_digital: Must be truefile_data: Set to "digital" to enable file uploadsquantity: Often set high (999) since digital = unlimiteddef get_all_listings(client, shop_id, state="active"):
"""Retrieve all listings with pagination."""
listings = []
offset = 0
limit = 100 # Max per request
while True:
response = client.get(
f"/application/shops/{shop_id}/listings",
params={"state": state, "limit": limit, "offset": offset}
)
listings.extend(response["results"])
if len(response["results"]) < limit:
break
offset += limit
return listings
listing = client.get(f"/application/listings/{listing_id}")
# Get only digital listings
listings = get_all_listings(client, shop_id)
digital_only = [l for l in listings if l.get("is_digital")]
To create a digital listing, you need these minimum fields:
| Field | Description | Example |
|---|---|---|
title |
Product title (max 140 chars) | “Budget Planner Printable” |
description |
Full description | “This printable planner…” |
price |
Price in currency subunits | 499 (= $4.99) |
quantity |
Stock quantity | 999 |
who_made |
Who made it | “i_did” |
when_made |
When made | “2020_2025” |
taxonomy_id |
Etsy category | 2078 |
is_digital |
Digital flag | true |
def create_digital_listing(client, shop_id, title, description, price_cents, tags):
data = {
"title": title,
"description": description,
"price": price_cents,
"quantity": 999,
"who_made": "i_did",
"when_made": "2020_2025",
"is_digital": True,
"taxonomy_id": 2078, # Art & Collectibles > Prints > Digital
"tags": tags[:13] # Max 13 tags
}
return client.post(f"/application/shops/{shop_id}/listings", data=data)
Etsy uses taxonomy IDs to categorize products. Find the right one:
# Get all taxonomies
taxonomies = client.get("/application/seller-taxonomy/nodes")
# Search for relevant categories
for tax in taxonomies["results"]:
if "digital" in tax["name"].lower() or "print" in tax["name"].lower():
print(f"{tax['id']}: {tax['name']}")
Common digital product taxonomies:
2078: Art & Collectibles > Prints > Digital Prints67: Craft Supplies > Patterns & How To# Update just the price
client.put(
f"/application/shops/{shop_id}/listings/{listing_id}",
data={"price": 599} # $5.99
)
updates = {
"title": "Updated Title Here",
"description": "New improved description...",
"tags": ["new", "tags", "here"],
"price": 699
}
client.put(f"/application/shops/{shop_id}/listings/{listing_id}", data=updates)
The real power of the API is bulk operations. See code/listings.py for complete implementations.
def apply_sale_discount(client, shop_id, discount_percent):
"""Apply a percentage discount to all listings."""
listings = get_all_listings(client, shop_id)
for listing in listings:
current_price = listing["price"]["amount"]
new_price = int(current_price * (1 - discount_percent / 100))
client.put(
f"/application/shops/{shop_id}/listings/{listing['listing_id']}",
data={"price": new_price}
)
print(f"Updated {listing['title']}: ${current_price/100} → ${new_price/100}")
def add_tags_to_listings(client, shop_id, listing_ids, new_tags):
"""Add tags to specific listings (max 13 tags total)."""
for listing_id in listing_ids:
listing = client.get(f"/application/listings/{listing_id}")
current_tags = listing.get("tags", [])
# Combine and limit to 13
combined = list(set(current_tags + new_tags))[:13]
client.put(
f"/application/shops/{shop_id}/listings/{listing_id}",
data={"tags": combined}
)
def add_seasonal_tags(client, shop_id, season):
"""Add seasonal tags to relevant listings."""
seasonal_tags = {
"christmas": ["christmas", "holiday", "gift", "xmas"],
"valentines": ["valentine", "love", "romantic", "february"],
"backtoschool": ["school", "student", "teacher", "academic"]
}
tags = seasonal_tags.get(season, [])
listings = get_all_listings(client, shop_id)
for listing in listings:
current_tags = listing.get("tags", [])
updated = list(set(current_tags + tags))[:13]
client.put(
f"/application/shops/{shop_id}/listings/{listing['listing_id']}",
data={"tags": updated}
)
Listings can have different states:
| State | Description |
|---|---|
active |
Visible and purchasable |
inactive |
Hidden from shop |
draft |
Not yet published |
expired |
Listing period ended |
sold_out |
No inventory (rare for digital) |
# Move to inactive
client.put(
f"/application/shops/{shop_id}/listings/{listing_id}",
data={"state": "inactive"}
)
# Make active again
client.put(
f"/application/shops/{shop_id}/listings/{listing_id}",
data={"state": "active"}
)
# Permanently delete (cannot be undone!)
client.delete(f"/application/shops/{shop_id}/listings/{listing_id}")
⚠️ Warning: Deletion is permanent. Consider deactivating instead.
def optimize_title(base_title, keywords):
"""Create SEO-optimized title (max 140 chars)."""
parts = [base_title] + keywords
title = " | ".join(parts)
return title[:140] # Truncate if too long
DESCRIPTION_TEMPLATE = """
{main_description}
📁 WHAT'S INCLUDED:
{file_list}
📋 HOW TO USE:
1. Purchase and download
2. Open with {software}
3. Print at home or local print shop
💡 PLEASE NOTE:
- This is a DIGITAL download, no physical item shipped
- Colors may vary based on monitor and printer settings
- For personal use only
❓ QUESTIONS?
Message me anytime!
"""
def generate_tags(product_type, style, occasion=None):
"""Generate relevant tags for a listing."""
tags = [product_type, f"{product_type} printable", f"digital {product_type}"]
tags.extend([style, f"{style} {product_type}"])
if occasion:
tags.extend([occasion, f"{occasion} {product_type}"])
# Always include these
tags.extend(["instant download", "printable", "digital download"])
return list(set(tags))[:13] # Unique, max 13
When doing bulk operations, respect rate limits:
import time
def bulk_update_with_delay(client, shop_id, updates, delay=0.5):
"""Update multiple listings with delay between calls."""
results = []
for listing_id, data in updates.items():
try:
client.put(
f"/application/shops/{shop_id}/listings/{listing_id}",
data=data
)
results.append({"listing_id": listing_id, "status": "success"})
except Exception as e:
results.append({"listing_id": listing_id, "status": "error", "error": str(e)})
time.sleep(delay) # Prevent rate limiting
return results
Creating a listing is just the first step. For digital products, you need to upload the actual files customers will download. That’s covered in the next chapter.
| ← Previous: Shop Management | Next: Digital Files → |