This final chapter covers common problems you’ll encounter and how to solve them:
Symptoms: 401 error mentioning invalid API key
Solutions:
.env matches the developer portal# Debug: Print sanitized key
print(f"API Key (first 8 chars): {Config.API_KEY[:8]}...")
print(f"API Key length: {len(Config.API_KEY)}")
Symptoms: 401 error after working previously
Solutions:
tokens.json and re-authenticatedef force_reauthenticate():
"""Delete tokens and start fresh."""
from pathlib import Path
token_file = Path("tokens.json")
if token_file.exists():
token_file.unlink()
print("Tokens cleared. Run authentication again.")
Symptoms: 403 error for certain operations
Solutions:
listings_w for creating/updating listingslistings_d for deleting listingstransactions_r for reading orders# Verify your token's scopes
import jwt
token = "your_access_token"
decoded = jwt.decode(token, options={"verify_signature": False})
print(f"Token scopes: {decoded.get('scope', 'No scopes found')}")
Possible causes:
Debug approach:
def verify_listing_ownership(client, shop_id, listing_id):
"""Check if listing belongs to your shop."""
try:
listings = get_all_listings(client, shop_id)
ids = [l["listing_id"] for l in listings]
if listing_id in ids:
print(f"✅ Listing {listing_id} found in shop")
else:
print(f"❌ Listing {listing_id} NOT in shop")
print(f" Shop has {len(ids)} listings")
except Exception as e:
print(f"Error: {e}")
Common causes:
Debug approach:
def validate_listing_data(data):
"""Validate listing data before submission."""
errors = []
# Title length
if "title" in data and len(data["title"]) > 140:
errors.append(f"Title too long: {len(data['title'])}/140 chars")
# Tags count
if "tags" in data and len(data["tags"]) > 13:
errors.append(f"Too many tags: {len(data['tags'])}/13")
# Price validation
if "price" in data:
if not isinstance(data["price"], int):
errors.append("Price must be integer (cents)")
elif data["price"] < 20: # $0.20 minimum
errors.append("Price below minimum ($0.20)")
return errors
Symptoms: Requests fail with 429 after many calls
Solutions:
def adaptive_rate_limiter(response):
"""Adjust delay based on rate limit headers."""
remaining = int(response.headers.get("X-RateLimit-Remaining", 100))
limit = int(response.headers.get("X-RateLimit-Limit", 100))
usage_percent = (limit - remaining) / limit * 100
if usage_percent > 80:
return 2.0 # Slow down
elif usage_percent > 50:
return 0.5
else:
return 0.1 # Normal speed
Causes:
Debug:
def diagnose_file_issue(client, shop_id, listing_id):
"""Diagnose file upload issues."""
# Check listing is digital
listing = client.get(f"/application/listings/{listing_id}")
if not listing.get("is_digital"):
print("❌ Listing is not marked as digital!")
print(" Set is_digital=true when creating listing")
return
# Check files
files = list_digital_files(client, shop_id, listing_id)
if not files:
print("❌ No files attached to listing")
else:
print(f"✅ {len(files)} files attached:")
for f in files:
print(f" - {f['filename']} ({f['filesize']})")
Not an API issue - but common question. Direct customers to:
If you updated a file but customer got old version:
Symptom: API returns different count than Etsy dashboard
Causes:
def accurate_listing_count(client, shop_id):
"""Get accurate count by fetching all listings."""
all_listings = get_all_listings(client, shop_id)
by_state = {}
for listing in all_listings:
state = listing["state"]
by_state[state] = by_state.get(state, 0) + 1
print(f"Total listings: {len(all_listings)}")
for state, count in by_state.items():
print(f" {state}: {count}")
Causes:
def parse_price(price_obj):
"""Correctly parse Etsy price object."""
amount = price_obj["amount"]
divisor = price_obj["divisor"]
currency = price_obj["currency_code"]
actual_price = amount / divisor
return f"{currency} {actual_price:.2f}"
Causes:
Solutions:
# Bad: Fetching one at a time
for listing_id in listing_ids:
listing = client.get(f"/application/listings/{listing_id}")
# Better: Fetch in batches where possible
listings = get_all_listings(client, shop_id)
listings_dict = {l["listing_id"]: l for l in listings}
For shops with thousands of listings:
def process_listings_in_batches(client, shop_id, processor, batch_size=100):
"""Process listings in memory-efficient batches."""
offset = 0
while True:
response = client.get(
f"/application/shops/{shop_id}/listings",
params={"limit": batch_size, "offset": offset}
)
batch = response["results"]
if not batch:
break
for listing in batch:
processor(listing)
offset += batch_size
# Optional: Clear memory
del batch
class DebugClient:
"""Client wrapper that logs all requests."""
def __init__(self, client):
self.client = client
self.log = []
def get(self, endpoint, **kwargs):
start = time.time()
try:
response = self.client.get(endpoint, **kwargs)
self._log_request("GET", endpoint, time.time() - start, "success")
return response
except Exception as e:
self._log_request("GET", endpoint, time.time() - start, str(e))
raise
def _log_request(self, method, endpoint, duration, status):
self.log.append({
"timestamp": datetime.now().isoformat(),
"method": method,
"endpoint": endpoint,
"duration_ms": int(duration * 1000),
"status": status
})
def print_log(self):
for entry in self.log:
print(f"{entry['timestamp']} | {entry['method']} {entry['endpoint']} | "
f"{entry['duration_ms']}ms | {entry['status']}")
def api_health_check(client, shop_id):
"""Run diagnostic checks on API connectivity."""
checks = []
# 1. Basic connectivity
try:
client.get("/application/openapi-ping")
checks.append(("API Connectivity", "✅ OK"))
except Exception as e:
checks.append(("API Connectivity", f"❌ {e}"))
# 2. Authentication
try:
client.get("/application/shops")
checks.append(("Authentication", "✅ OK"))
except Exception as e:
checks.append(("Authentication", f"❌ {e}"))
# 3. Shop access
try:
client.get(f"/application/shops/{shop_id}")
checks.append(("Shop Access", "✅ OK"))
except Exception as e:
checks.append(("Shop Access", f"❌ {e}"))
# 4. Listings access
try:
response = client.get(f"/application/shops/{shop_id}/listings",
params={"limit": 1})
checks.append(("Listings Access", f"✅ OK ({response.get('count', 0)} total)"))
except Exception as e:
checks.append(("Listings Access", f"❌ {e}"))
# Print results
print("\n🔍 API Health Check")
print("=" * 40)
for check, status in checks:
print(f"{check}: {status}")
etsy-apiContact Etsy developer support when:
Most issues fall into these categories:
The key to troubleshooting is good logging. Always log: