For digital products, “fulfillment” is automatic—Etsy delivers the files immediately after purchase. But you still need to:
This chapter covers accessing and managing order data via the API.
Etsy uses a hierarchy:
Receipt (Order)
└── Transactions (Line Items)
├── Transaction 1: Product A
└── Transaction 2: Product B
def get_recent_orders(client, shop_id, limit=25):
"""Get the most recent orders."""
response = client.get(
f"/application/shops/{shop_id}/receipts",
params={"limit": limit}
)
return response["results"]
from datetime import datetime, timedelta
def get_orders_in_range(client, shop_id, start_date, end_date):
"""Get orders within a specific date range."""
# Convert to Unix timestamps
min_created = int(start_date.timestamp())
max_created = int(end_date.timestamp())
orders = []
offset = 0
while True:
response = client.get(
f"/application/shops/{shop_id}/receipts",
params={
"min_created": min_created,
"max_created": max_created,
"limit": 100,
"offset": offset
}
)
orders.extend(response["results"])
if len(response["results"]) < 100:
break
offset += 100
return orders
def get_order(client, shop_id, receipt_id):
"""Get details for a specific order."""
return client.get(f"/application/shops/{shop_id}/receipts/{receipt_id}")
A receipt contains comprehensive order information:
{
"receipt_id": 1234567890,
"receipt_type": 0,
"status": "paid",
"buyer_user_id": 98765432,
"buyer_email": "customer@email.com",
"name": "Customer Name",
"total_price": {"amount": 1498, "divisor": 100, "currency_code": "USD"},
"subtotal": {"amount": 1400, "divisor": 100, "currency_code": "USD"},
"total_tax_cost": {"amount": 98, "divisor": 100, "currency_code": "USD"},
"create_timestamp": 1699900000,
"is_paid": true,
"transactions": [...]
}
Each transaction represents one item purchased:
def get_order_items(client, shop_id, receipt_id):
"""Get individual items from an order."""
order = get_order(client, shop_id, receipt_id)
items = []
for transaction in order.get("transactions", []):
items.append({
"transaction_id": transaction["transaction_id"],
"listing_id": transaction["listing_id"],
"title": transaction["title"],
"quantity": transaction["quantity"],
"price": transaction["price"]["amount"] / transaction["price"]["divisor"]
})
return items
Digital products are automatically marked as fulfilled when payment completes. You don’t need to “ship” them:
def is_digital_order(order):
"""Check if an order contains only digital items."""
for transaction in order.get("transactions", []):
if not transaction.get("is_digital", False):
return False
return True
Unfortunately, the API doesn’t expose whether customers have downloaded their files. This is a limitation you’ll need to work around (e.g., ask in messages if they need help).
def get_orders_by_listing(client, shop_id, listing_id):
"""Get all orders for a specific listing."""
all_orders = get_recent_orders(client, shop_id, limit=100)
matching = []
for order in all_orders:
for transaction in order.get("transactions", []):
if transaction["listing_id"] == listing_id:
matching.append(order)
break
return matching
def get_orders_by_status(client, shop_id, status):
"""Get orders with a specific status."""
response = client.get(
f"/application/shops/{shop_id}/receipts",
params={"was_paid": status == "paid"}
)
return response["results"]
See code/orders.py for complete implementation.
def daily_sales_summary(client, shop_id, date=None):
"""Generate a sales summary for a specific day."""
if date is None:
date = datetime.now()
start = datetime(date.year, date.month, date.day)
end = start + timedelta(days=1)
orders = get_orders_in_range(client, shop_id, start, end)
total_revenue = sum(
o["total_price"]["amount"] / o["total_price"]["divisor"]
for o in orders
)
return {
"date": date.strftime("%Y-%m-%d"),
"order_count": len(orders),
"total_revenue": total_revenue,
"average_order": total_revenue / len(orders) if orders else 0
}
def product_sales_report(client, shop_id, days=30):
"""See which products sold in the last N days."""
end_date = datetime.now()
start_date = end_date - timedelta(days=days)
orders = get_orders_in_range(client, shop_id, start_date, end_date)
product_sales = {}
for order in orders:
for transaction in order.get("transactions", []):
listing_id = transaction["listing_id"]
if listing_id not in product_sales:
product_sales[listing_id] = {
"title": transaction["title"],
"quantity": 0,
"revenue": 0
}
product_sales[listing_id]["quantity"] += transaction["quantity"]
product_sales[listing_id]["revenue"] += (
transaction["price"]["amount"] / transaction["price"]["divisor"]
)
# Sort by revenue
return sorted(
product_sales.values(),
key=lambda x: x["revenue"],
reverse=True
)
When a customer contacts you, find their orders:
def find_customer_orders(client, shop_id, buyer_email=None, buyer_user_id=None):
"""Find orders from a specific customer."""
all_orders = get_recent_orders(client, shop_id, limit=100)
matching = []
for order in all_orders:
if buyer_email and order.get("buyer_email") == buyer_email:
matching.append(order)
elif buyer_user_id and order.get("buyer_user_id") == buyer_user_id:
matching.append(order)
return matching
For digital products, common issues include:
Export orders for accounting or analysis:
import csv
from pathlib import Path
def export_orders_csv(orders, filename="orders.csv"):
"""Export orders to CSV file."""
fieldnames = ["receipt_id", "date", "customer", "total", "items"]
with open(filename, "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
for order in orders:
writer.writerow({
"receipt_id": order["receipt_id"],
"date": datetime.fromtimestamp(order["create_timestamp"]).isoformat(),
"customer": order.get("name", "N/A"),
"total": order["total_price"]["amount"] / order["total_price"]["divisor"],
"items": len(order.get("transactions", []))
})
return Path(filename).absolute()
While the API doesn’t have push notifications, you can poll for new orders:
import time
def monitor_new_orders(client, shop_id, check_interval=300):
"""Poll for new orders every N seconds."""
last_check = datetime.now()
while True:
orders = get_orders_in_range(
client, shop_id,
last_check,
datetime.now()
)
for order in orders:
print(f"New order: {order['receipt_id']} - ${order['total_price']['amount']/100}")
# Could trigger email, Slack notification, etc.
last_check = datetime.now()
time.sleep(check_interval)
For production, consider using Etsy’s webhooks (limited) or a proper job scheduler.
Digital products often come in variations (PDF vs PNG, A4 vs Letter). The next chapter covers managing product variations and inventory.
| ← Previous: Digital Files | Next: Inventory & Variations → |