Listings are the core of any Etsy business. For digital products, listing management through the API enables powerful capabilities: bulk creation, automated updates, dynamic pricing, and efficient inventory management. This chapter covers everything you need to programmatically manage your digital product listings.
A digital product listing contains several key components:
# Example listing structure for a digital product
listing_structure = {
"listing_id": 123456789,
"title": "Digital Planner 2026 | GoodNotes Planner | iPad Planner",
"description": "Complete digital planner for your productivity needs...",
"price": {"amount": 999, "divisor": 100, "currency_code": "USD"},
"quantity": 999, # High for digital (essentially unlimited)
"state": "active", # active, inactive, draft, expired
"is_digital": True,
"taxonomy_id": 1234, # Category
"tags": ["digital planner", "goodnotes", "ipad planner"],
"materials": [],
"shipping_profile_id": None, # Not needed for digital
"shop_section_id": 12345,
"who_made": "i_did",
"when_made": "2020_2026",
"is_supply": False,
"processing_min": None,
"processing_max": None,
}
# etsy_client/listings.py
"""Listing management operations for Etsy API."""
from typing import Dict, Any, Optional, List
from enum import Enum
from dataclasses import dataclass
from .client import EtsyClient
from .exceptions import ValidationError
class ListingState(Enum):
"""Possible states for a listing."""
ACTIVE = "active"
INACTIVE = "inactive"
DRAFT = "draft"
EXPIRED = "expired"
SOLD_OUT = "sold_out"
class WhoMade(Enum):
"""Who made the item."""
I_DID = "i_did"
COLLECTIVE = "collective"
SOMEONE_ELSE = "someone_else"
class WhenMade(Enum):
"""When the item was made."""
MADE_TO_ORDER = "made_to_order"
Y_2020_2026 = "2020_2026"
Y_2010_2019 = "2010_2019"
Y_2000_2009 = "2000_2009"
BEFORE_2000 = "before_2000"
@dataclass
class ListingData:
"""Data class for creating/updating listings."""
title: str
description: str
price: float
quantity: int = 999
taxonomy_id: int = None
tags: List[str] = None
materials: List[str] = None
shop_section_id: int = None
who_made: str = "i_did"
when_made: str = "2020_2026"
is_supply: bool = False
is_digital: bool = True
def to_dict(self) -> Dict[str, Any]:
"""Convert to API-compatible dictionary."""
data = {
"title": self.title,
"description": self.description,
"price": self.price,
"quantity": self.quantity,
"who_made": self.who_made,
"when_made": self.when_made,
"is_supply": self.is_supply,
}
if self.taxonomy_id:
data["taxonomy_id"] = self.taxonomy_id
if self.tags:
data["tags"] = self.tags[:13] # Max 13 tags
if self.materials:
data["materials"] = self.materials[:13] # Max 13 materials
if self.shop_section_id:
data["shop_section_id"] = self.shop_section_id
return data
class ListingOperations:
"""Operations for managing Etsy listings."""
def __init__(self, client: EtsyClient):
self.client = client
# ==================== READ OPERATIONS ====================
def get_listing(self, listing_id: int) -> Dict[str, Any]:
"""
Get a single listing by ID.
Args:
listing_id: The listing ID
Returns:
Listing data
"""
return self.client.get(f"/application/listings/{listing_id}")
def get_listings_by_shop(
self,
shop_id: int,
state: str = "active",
limit: int = 100,
offset: int = 0
) -> Dict[str, Any]:
"""
Get all listings for a shop.
Args:
shop_id: The shop ID
state: Filter by state (active, inactive, draft, expired)
limit: Maximum results per page (max 100)
offset: Pagination offset
Returns:
Dictionary with 'results' list and pagination info
"""
return self.client.get(
f"/application/shops/{shop_id}/listings",
params={
"state": state,
"limit": limit,
"offset": offset
}
)
def get_all_listings(
self,
shop_id: int,
state: str = "active"
) -> List[Dict[str, Any]]:
"""
Get ALL listings for a shop, handling pagination.
Args:
shop_id: The shop ID
state: Filter by state
Returns:
List of all listings
"""
all_listings = []
offset = 0
limit = 100
while True:
response = self.get_listings_by_shop(
shop_id, state, limit, offset
)
listings = response.get('results', [])
all_listings.extend(listings)
if len(listings) < limit:
break
offset += limit
return all_listings
def search_listings(
self,
shop_id: int,
keywords: str,
limit: int = 25
) -> Dict[str, Any]:
"""
Search listings by keywords.
Args:
shop_id: The shop ID
keywords: Search keywords
limit: Maximum results
Returns:
Search results
"""
return self.client.get(
f"/application/shops/{shop_id}/listings",
params={
"keywords": keywords,
"limit": limit
}
)
# ==================== CREATE OPERATIONS ====================
def create_listing(
self,
shop_id: int,
listing_data: ListingData,
state: str = "draft"
) -> Dict[str, Any]:
"""
Create a new listing.
Args:
shop_id: The shop ID
listing_data: ListingData object with listing details
state: Initial state (draft recommended for review)
Returns:
Created listing data
"""
data = listing_data.to_dict()
data["state"] = state
# Validate required fields for digital products
if not data.get("taxonomy_id"):
raise ValidationError("taxonomy_id is required")
return self.client.post(
f"/application/shops/{shop_id}/listings",
json=data
)
def create_digital_listing(
self,
shop_id: int,
title: str,
description: str,
price: float,
taxonomy_id: int,
tags: List[str] = None,
section_id: int = None,
state: str = "draft"
) -> Dict[str, Any]:
"""
Simplified method to create a digital product listing.
Args:
shop_id: The shop ID
title: Listing title (max 140 chars)
description: Listing description
price: Price in shop currency
taxonomy_id: Category ID
tags: List of tags (max 13)
section_id: Shop section ID
state: Initial state
Returns:
Created listing data
"""
listing = ListingData(
title=title[:140],
description=description,
price=price,
taxonomy_id=taxonomy_id,
tags=tags,
shop_section_id=section_id,
is_digital=True
)
return self.create_listing(shop_id, listing, state)
# ==================== UPDATE OPERATIONS ====================
def update_listing(
self,
listing_id: int,
**kwargs
) -> Dict[str, Any]:
"""
Update a listing.
Args:
listing_id: The listing ID
**kwargs: Fields to update
Returns:
Updated listing data
"""
# Filter out None values
data = {k: v for k, v in kwargs.items() if v is not None}
if not data:
raise ValidationError("No fields to update")
return self.client.patch(
f"/application/listings/{listing_id}",
json=data
)
def update_price(
self,
listing_id: int,
new_price: float
) -> Dict[str, Any]:
"""
Update only the price of a listing.
Args:
listing_id: The listing ID
new_price: New price value
Returns:
Updated listing data
"""
return self.update_listing(listing_id, price=new_price)
def update_quantity(
self,
listing_id: int,
quantity: int
) -> Dict[str, Any]:
"""
Update the quantity of a listing.
Args:
listing_id: The listing ID
quantity: New quantity
Returns:
Updated listing data
"""
return self.update_listing(listing_id, quantity=quantity)
def activate_listing(self, listing_id: int) -> Dict[str, Any]:
"""Activate a draft or inactive listing."""
return self.update_listing(listing_id, state="active")
def deactivate_listing(self, listing_id: int) -> Dict[str, Any]:
"""Deactivate an active listing."""
return self.update_listing(listing_id, state="inactive")
# ==================== DELETE OPERATIONS ====================
def delete_listing(self, listing_id: int) -> Dict[str, Any]:
"""
Delete a listing.
Args:
listing_id: The listing ID
Returns:
Deletion confirmation
"""
return self.client.delete(f"/application/listings/{listing_id}")
# ==================== BULK OPERATIONS ====================
def bulk_update_prices(
self,
listings: List[Dict[str, Any]],
price_modifier: float
) -> List[Dict[str, Any]]:
"""
Update prices for multiple listings.
Args:
listings: List of listings (must have listing_id and price)
price_modifier: Multiplier for prices (e.g., 0.8 for 20% off)
Returns:
List of update results
"""
results = []
for listing in listings:
listing_id = listing['listing_id']
current_price = listing['price']['amount'] / listing['price']['divisor']
new_price = round(current_price * price_modifier, 2)
try:
result = self.update_price(listing_id, new_price)
results.append({
'listing_id': listing_id,
'success': True,
'old_price': current_price,
'new_price': new_price
})
except Exception as e:
results.append({
'listing_id': listing_id,
'success': False,
'error': str(e)
})
return results
def bulk_update_tags(
self,
listing_ids: List[int],
tags: List[str]
) -> List[Dict[str, Any]]:
"""
Update tags for multiple listings.
Args:
listing_ids: List of listing IDs
tags: Tags to apply to all listings
Returns:
List of update results
"""
results = []
for listing_id in listing_ids:
try:
result = self.update_listing(listing_id, tags=tags[:13])
results.append({
'listing_id': listing_id,
'success': True
})
except Exception as e:
results.append({
'listing_id': listing_id,
'success': False,
'error': str(e)
})
return results
Etsy uses taxonomy IDs to categorize listings. Here’s how to find the right one:
# etsy_client/taxonomy.py
"""Taxonomy (category) operations for Etsy API."""
from typing import Dict, Any, List, Optional
from .client import EtsyClient
class TaxonomyOperations:
"""Operations for working with Etsy taxonomies/categories."""
# Common digital product taxonomy IDs
COMMON_DIGITAL_TAXONOMIES = {
"Digital Prints": 66,
"Digital Planners": 1063,
"SVG Files": 2078,
"Clip Art": 68,
"Digital Paper": 69,
"Graphic Design Resources": 2079,
"Social Media Templates": 2080,
"Resume Templates": 2081,
"Invitations & Announcements": 67,
"Patterns & How To": 70,
}
def __init__(self, client: EtsyClient):
self.client = client
def get_buyer_taxonomy(self) -> Dict[str, Any]:
"""
Get the complete buyer taxonomy tree.
Returns:
Taxonomy tree structure
"""
return self.client.get("/application/buyer-taxonomy/nodes")
def get_seller_taxonomy(self) -> Dict[str, Any]:
"""
Get the seller taxonomy for listing creation.
Returns:
Seller taxonomy tree
"""
return self.client.get("/application/seller-taxonomy/nodes")
def search_taxonomy(
self,
query: str,
taxonomy_data: Optional[Dict] = None
) -> List[Dict[str, Any]]:
"""
Search for taxonomy nodes by name.
Args:
query: Search query
taxonomy_data: Pre-fetched taxonomy (optional)
Returns:
List of matching taxonomy nodes
"""
if taxonomy_data is None:
taxonomy_data = self.get_seller_taxonomy()
results = []
query_lower = query.lower()
def search_node(node: Dict, path: List[str] = None):
path = path or []
current_path = path + [node['name']]
if query_lower in node['name'].lower():
results.append({
'id': node['id'],
'name': node['name'],
'path': ' > '.join(current_path),
'level': len(current_path)
})
for child in node.get('children', []):
search_node(child, current_path)
for node in taxonomy_data.get('results', []):
search_node(node)
return results
def get_taxonomy_properties(
self,
taxonomy_id: int
) -> Dict[str, Any]:
"""
Get properties available for a taxonomy.
Args:
taxonomy_id: The taxonomy ID
Returns:
Available properties for the category
"""
return self.client.get(
f"/application/seller-taxonomy/nodes/{taxonomy_id}/properties"
)
def find_digital_taxonomy(client: EtsyClient, product_type: str) -> int:
"""
Helper to find the best taxonomy ID for a digital product type.
Args:
client: EtsyClient instance
product_type: Description of the product type
Returns:
Best matching taxonomy ID
"""
taxonomy_ops = TaxonomyOperations(client)
# Check common taxonomies first
product_lower = product_type.lower()
for name, tax_id in taxonomy_ops.COMMON_DIGITAL_TAXONOMIES.items():
if any(word in product_lower for word in name.lower().split()):
return tax_id
# Search taxonomy tree
results = taxonomy_ops.search_taxonomy(product_type)
if results:
# Return the most specific (deepest) match
results.sort(key=lambda x: x['level'], reverse=True)
return results[0]['id']
# Default to Digital Prints
return 66
# Example usage
def explore_taxonomies(client: EtsyClient):
"""Print available digital product taxonomies."""
tax_ops = TaxonomyOperations(client)
print("Searching for digital product categories...")
search_terms = [
"digital",
"printable",
"template",
"svg",
"download"
]
all_results = []
for term in search_terms:
results = tax_ops.search_taxonomy(term)
all_results.extend(results)
# Deduplicate
seen = set()
unique = []
for r in all_results:
if r['id'] not in seen:
seen.add(r['id'])
unique.append(r)
print(f"\nFound {len(unique)} relevant categories:\n")
for r in sorted(unique, key=lambda x: x['path']):
print(f"ID: {r['id']:6} | {r['path']}")
Here’s a complete example of creating a digital product listing:
# scripts/create_listing.py
"""Create a digital product listing."""
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.listings import ListingOperations, ListingData
from etsy_client.users import get_my_shop_id
def create_digital_planner_listing():
"""Example: Create a digital planner listing."""
client = EtsyClient(
api_key=Config.ETSY_API_KEY,
access_token=Config.ETSY_ACCESS_TOKEN
)
shop_id = get_my_shop_id(client)
listing_ops = ListingOperations(client)
# Prepare listing data
listing = ListingData(
title="2026 Digital Planner | GoodNotes Planner | iPad Planner | Dated Daily Weekly Monthly",
description="""📱 THE ULTIMATE DIGITAL PLANNER FOR 2026 📱
Transform your planning with this comprehensive digital planner designed for GoodNotes, Notability, and other PDF annotation apps.
✨ WHAT'S INCLUDED:
• Dated pages for all of 2026
• Monthly overview spreads
• Weekly horizontal layouts
• Daily planning pages
• Goal setting worksheets
• Habit trackers
• Notes sections
• Hyperlinked tabs for easy navigation
📋 FEATURES:
• Optimized for iPad and tablets
• Hyperlinked navigation
• Clickable tabs
• Clean, minimal design
• Portrait orientation
• Compatible with Apple Pencil & stylus
💻 COMPATIBLE WITH:
• GoodNotes 5 & 6
• Notability
• Noteshelf
• PDF Expert
• Any PDF annotation app
📥 INSTANT DOWNLOAD:
You'll receive your files immediately after purchase. No physical product will be shipped.
❓ QUESTIONS?
Message me anytime! I typically respond within 24 hours.
Thank you for visiting my shop! ❤️
""",
price=9.99,
quantity=999,
taxonomy_id=1063, # Digital Planners category
tags=[
"digital planner",
"goodnotes planner",
"ipad planner",
"2026 planner",
"digital planner 2026",
"notability planner",
"daily planner",
"weekly planner",
"monthly planner",
"tablet planner",
"instant download",
"pdf planner",
"hyperlinked planner"
],
who_made="i_did",
when_made="2020_2026",
is_digital=True
)
# Create as draft first
print("Creating listing as draft...")
result = listing_ops.create_listing(
shop_id=shop_id,
listing_data=listing,
state="draft"
)
listing_id = result['listing_id']
print(f"✓ Listing created with ID: {listing_id}")
print(f" Title: {result['title']}")
print(f" State: {result['state']}")
print(f" Price: ${result['price']['amount'] / result['price']['divisor']:.2f}")
print("\nNext steps:")
print("1. Add listing images via the API or Etsy interface")
print("2. Upload digital files for download")
print("3. Activate the listing when ready")
return result
if __name__ == "__main__":
create_digital_planner_listing()
# scripts/bulk_operations.py
"""Bulk listing operations for digital products."""
import sys
import csv
from pathlib import Path
from typing import List, Dict
sys.path.insert(0, str(Path(__file__).parent.parent))
from config import Config
from etsy_client import EtsyClient
from etsy_client.listings import ListingOperations
from etsy_client.users import get_my_shop_id
class BulkListingManager:
"""Manage bulk listing operations."""
def __init__(self, client: EtsyClient, shop_id: int):
self.client = client
self.shop_id = shop_id
self.listing_ops = ListingOperations(client)
def export_listings_to_csv(
self,
output_file: str,
state: str = "active"
):
"""
Export all listings to CSV for review/editing.
Args:
output_file: Path to output CSV file
state: Filter by listing state
"""
listings = self.listing_ops.get_all_listings(self.shop_id, state)
if not listings:
print("No listings found")
return
# Prepare CSV data
fieldnames = [
'listing_id', 'title', 'price', 'quantity',
'views', 'favorites', 'tags', 'state'
]
with open(output_file, 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
for listing in listings:
price = listing['price']['amount'] / listing['price']['divisor']
writer.writerow({
'listing_id': listing['listing_id'],
'title': listing['title'],
'price': price,
'quantity': listing['quantity'],
'views': listing.get('views', 0),
'favorites': listing.get('num_favorers', 0),
'tags': ', '.join(listing.get('tags', [])),
'state': listing['state']
})
print(f"✓ Exported {len(listings)} listings to {output_file}")
def apply_sale(
self,
discount_percent: float,
min_price: float = 0.20
) -> Dict[str, int]:
"""
Apply a sale discount to all active listings.
Args:
discount_percent: Discount percentage (e.g., 20 for 20% off)
min_price: Minimum price after discount
Returns:
Summary of updates
"""
listings = self.listing_ops.get_all_listings(self.shop_id, "active")
multiplier = 1 - (discount_percent / 100)
updated = 0
errors = 0
print(f"Applying {discount_percent}% discount to {len(listings)} listings...")
for listing in listings:
listing_id = listing['listing_id']
current_price = listing['price']['amount'] / listing['price']['divisor']
new_price = max(round(current_price * multiplier, 2), min_price)
try:
self.listing_ops.update_price(listing_id, new_price)
print(f" {listing_id}: ${current_price:.2f} → ${new_price:.2f}")
updated += 1
except Exception as e:
print(f" {listing_id}: Error - {e}")
errors += 1
return {"updated": updated, "errors": errors}
def end_sale(self, original_prices: Dict[int, float]) -> Dict[str, int]:
"""
Restore original prices after a sale.
Args:
original_prices: Dict mapping listing_id to original price
Returns:
Summary of updates
"""
updated = 0
errors = 0
print(f"Restoring original prices for {len(original_prices)} listings...")
for listing_id, original_price in original_prices.items():
try:
self.listing_ops.update_price(listing_id, original_price)
print(f" {listing_id}: restored to ${original_price:.2f}")
updated += 1
except Exception as e:
print(f" {listing_id}: Error - {e}")
errors += 1
return {"updated": updated, "errors": errors}
def deactivate_low_performers(
self,
min_views: int = 100,
min_favorites: int = 5,
days_active: int = 90
) -> List[int]:
"""
Deactivate listings with poor performance.
Args:
min_views: Minimum views threshold
min_favorites: Minimum favorites threshold
days_active: Days since creation to consider
Returns:
List of deactivated listing IDs
"""
import time
listings = self.listing_ops.get_all_listings(self.shop_id, "active")
cutoff_time = time.time() - (days_active * 24 * 60 * 60)
deactivated = []
for listing in listings:
created = listing.get('created_timestamp', time.time())
# Skip recent listings
if created > cutoff_time:
continue
views = listing.get('views', 0)
favorites = listing.get('num_favorers', 0)
if views < min_views and favorites < min_favorites:
try:
self.listing_ops.deactivate_listing(listing['listing_id'])
deactivated.append(listing['listing_id'])
print(f" Deactivated: {listing['title'][:50]}...")
except Exception as e:
print(f" Error deactivating {listing['listing_id']}: {e}")
print(f"\n✓ Deactivated {len(deactivated)} low-performing listings")
return deactivated
def refresh_stale_listings(
self,
days_without_update: int = 30
) -> List[int]:
"""
Touch listings that haven't been updated recently.
Updating listings can improve their search ranking.
Args:
days_without_update: Days since last update threshold
Returns:
List of refreshed listing IDs
"""
import time
listings = self.listing_ops.get_all_listings(self.shop_id, "active")
cutoff_time = time.time() - (days_without_update * 24 * 60 * 60)
refreshed = []
for listing in listings:
updated = listing.get('updated_timestamp', listing.get('created_timestamp', 0))
if updated < cutoff_time:
try:
# Touch by updating quantity (no actual change)
self.listing_ops.update_quantity(
listing['listing_id'],
listing['quantity']
)
refreshed.append(listing['listing_id'])
except Exception as e:
print(f" Error refreshing {listing['listing_id']}: {e}")
print(f"✓ Refreshed {len(refreshed)} stale listings")
return refreshed
def main():
"""Demo bulk operations."""
client = EtsyClient(
api_key=Config.ETSY_API_KEY,
access_token=Config.ETSY_ACCESS_TOKEN
)
shop_id = get_my_shop_id(client)
manager = BulkListingManager(client, shop_id)
# Export current listings
manager.export_listings_to_csv("data/listings_export.csv")
if __name__ == "__main__":
main()
# etsy_client/images.py
"""Listing image management."""
from typing import Dict, Any, List, BinaryIO
from pathlib import Path
from .client import EtsyClient
class ImageOperations:
"""Operations for managing listing images."""
# Etsy image requirements
MAX_IMAGES = 10
MIN_SIZE = 2000 # pixels on longest side
MAX_FILE_SIZE = 10 * 1024 * 1024 # 10MB
ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/gif']
def __init__(self, client: EtsyClient):
self.client = client
def get_listing_images(
self,
listing_id: int
) -> Dict[str, Any]:
"""
Get all images for a listing.
Args:
listing_id: The listing ID
Returns:
List of image data
"""
return self.client.get(f"/application/listings/{listing_id}/images")
def upload_listing_image(
self,
shop_id: int,
listing_id: int,
image_path: str,
rank: int = 1,
overwrite: bool = False,
is_watermarked: bool = False,
alt_text: str = ""
) -> Dict[str, Any]:
"""
Upload an image to a listing.
Args:
shop_id: The shop ID
listing_id: The listing ID
image_path: Path to the image file
rank: Image position (1-10)
overwrite: Replace existing image at rank
is_watermarked: Whether image has watermark
alt_text: Alt text for accessibility
Returns:
Uploaded image data
"""
path = Path(image_path)
if not path.exists():
raise FileNotFoundError(f"Image not found: {image_path}")
# Check file size
if path.stat().st_size > self.MAX_FILE_SIZE:
raise ValueError(f"Image too large. Max size: {self.MAX_FILE_SIZE / 1024 / 1024}MB")
with open(path, 'rb') as f:
files = {
'image': (path.name, f, 'image/jpeg')
}
data = {
'rank': rank,
'overwrite': overwrite,
'is_watermarked': is_watermarked,
}
if alt_text:
data['alt_text'] = alt_text
return self.client.post(
f"/application/shops/{shop_id}/listings/{listing_id}/images",
files=files,
data=data
)
def delete_listing_image(
self,
listing_id: int,
image_id: int
) -> Dict[str, Any]:
"""
Delete an image from a listing.
Args:
listing_id: The listing ID
image_id: The image ID
Returns:
Deletion confirmation
"""
return self.client.delete(
f"/application/listings/{listing_id}/images/{image_id}"
)
def reorder_listing_images(
self,
shop_id: int,
listing_id: int,
image_ids: List[int]
) -> Dict[str, Any]:
"""
Reorder images on a listing.
Args:
shop_id: The shop ID
listing_id: The listing ID
image_ids: Image IDs in desired order
Returns:
Updated images
"""
# Update rank for each image
for rank, image_id in enumerate(image_ids, 1):
# Note: This might require multiple API calls
# Etsy's API may have a batch endpoint
pass
return self.get_listing_images(listing_id)
def upload_multiple_images(
self,
shop_id: int,
listing_id: int,
image_paths: List[str]
) -> List[Dict[str, Any]]:
"""
Upload multiple images to a listing.
Args:
shop_id: The shop ID
listing_id: The listing ID
image_paths: List of image file paths
Returns:
List of upload results
"""
if len(image_paths) > self.MAX_IMAGES:
raise ValueError(f"Too many images. Max: {self.MAX_IMAGES}")
results = []
for rank, path in enumerate(image_paths, 1):
try:
result = self.upload_listing_image(
shop_id, listing_id, path, rank=rank
)
results.append({
'path': path,
'success': True,
'image_id': result.get('listing_image_id')
})
except Exception as e:
results.append({
'path': path,
'success': False,
'error': str(e)
})
return results
With listing management in place, you’re ready to tackle the unique aspect of digital products: file management and digital downloads. The next chapter covers uploading and managing the actual files customers will download.
| ← Chapter 4: Working with Shops and Users | Table of Contents | Chapter 6: Digital Downloads and File Management → |