Four layers. One platform that controls two of them.
On a Shopify, WooCommerce, or headless store, the merchant owns every layer of the agent-readiness stack — they can ship JSON-LD edits into theme files, publish a custom Storefront API endpoint, run a merchant-side MCP server pointed at their own data, and host a /.well-known/ucp profile that any agent on the open web can hit. Etsy is structurally different. The merchant cannot edit listing-page HTML — Etsy generates the Schema.org JSON-LD. The merchant cannot replace the checkout — Etsy Payments is mandatory. What the merchant CAN do is feed Etsy higher-quality listing data so the auto-generated structured data is richer, build a merchant MCP server on top of the Etsy Open API v3 so agents that are not natively integrated with Etsy can still discover the shop's inventory, and then deep-link buyers into Etsy's own checkout flow.
This page is the honest playbook for that constrained-but-real surface. Layer 1 (Structured Data) is partially solved — Etsy auto-emits roughly 8 of the 20 MVP fields, and the seller's job is to fill the inputs that drive the remaining fields they can influence. Layer 2 (API Endpoint) is fully solved — the Etsy Open API v3 is an OAuth 2.0 PKCE-protected REST API with ~50 endpoints. Layer 3 (MCP) is fully solvable at the merchant level — a Python FastMCP server with five tools, deployed to Fly.io or run locally, gives Claude/Cursor a clean read path into the seller's shop. Layer 4 (UCP) is the unsolved problem at the individual-seller level — Etsy owns checkout, the seller cannot expose initiate_checkout or calculate_total, and the only honest workarounds are (a) deep-link agents into Etsy's hosted cart, (b) rely on Etsy's platform-level ACP/UCP partnerships, or (c) wait for Etsy to publish a seller-level checkout API.
What each layer looks like inside the Etsy box.
Structured Data
Etsy auto-emits Product, Offer, AggregateOffer, AggregateRating, and BreadcrumbList Schema.org JSON-LD on every listing — roughly 8/20 MVP fields without seller effort. The seller's job is to fill the inputs that drive the rest: GTIN/UPC, attributes, descriptions ≥150 words, return policy completeness, and shipping profiles. No custom JSON-LD injection is possible.
API Endpoint
Etsy Open API v3 at https://openapi.etsy.com/v3/ with OAuth 2.0 PKCE — ~50 endpoints covering listings, inventory, receipts, shipping profiles, and shop data. Rate limits ~10,000/day and ~10/sec (re-verify before launch). No calculate_total or initiate_checkout endpoint exists for sellers.
MCP Server
A merchant-side FastMCP Python server with five tools: list_my_etsy_listings, get_etsy_listing, search_my_shop, check_etsy_inventory, and get_etsy_checkout_link. Deploys to Fly.io in ~30 minutes. Wraps the Etsy Open API v3 and exposes it to Claude Desktop and Cursor.
UCP — Honest
At the individual-seller level, UCP is the unsolved layer. Etsy owns checkout — the seller cannot expose initiate_checkout. The honest paths are: (a) MCP discovery + deep-link to etsy.com/cart/add, (b) ride Etsy's platform-level ACP (Sept 2025) and UCP (Jan 2026) partnerships, (c) wait for Etsy to publish a seller-level checkout API.
On Shopify, the seller is the merchant of record and controls every layer — Etsy is the merchant of record on Etsy. That single fact is why Layer 1 is partial, Layer 4 is unsolved at the seller level, and the seller's leverage is concentrated in Layers 2 and 3. Read this page as a guide to maximizing what the seller actually controls, not as a pretense that Etsy is configurable like an owned store.
What Etsy emits, what the seller controls, and the ~8/20 MVP gap.
Every Etsy listing page renders Schema.org JSON-LD that Etsy generates server-side from the listing record. The seller cannot inject custom JSON-LD, edit the markup, or override Etsy's mapping logic. What the seller CAN do is fill the listing fields that feed Etsy's generator — and the difference between a half-filled listing and a fully filled listing is the difference between an agent retrieving a usable Product object and an agent retrieving a stub with no GTIN, no attributes, and no shipping signal.
What Etsy auto-emits today
- Product — name, description, image, brand (shop name), sku (listing_id), url.
- Offer — price, priceCurrency, availability, itemCondition, seller (shop), priceValidUntil.
- AggregateOffer — when the listing has variations, lowPrice / highPrice / offerCount.
- AggregateRating — ratingValue, reviewCount, sourced from shop-level reviews.
- BreadcrumbList — Home › Category › Subcategory › Listing.
The 20-field MVP mapping — what's covered, what's missing
| MVP Field | Etsy Auto-Emits? | Seller Input That Drives It |
|---|---|---|
| name | Yes | Listing title (140 char max, front-load keywords) |
| description | Yes | Description body — first 160 chars matter most for agent retrieval |
| image | Yes | Up to 10 listing photos + 1 video |
| brand | Yes (shop name) | Shop name — cannot vary per listing |
| sku | Yes (listing_id) | Auto-assigned, plus optional per-variation SKU |
| gtin / gtin13 / gtin14 | Partial | GTIN/UPC field on listing — frequently empty, agents penalize |
| mpn | No | Not exposed in Etsy listing schema |
| price / priceCurrency | Yes | Listing price + shop currency |
| availability | Yes | Inventory quantity per variation |
| itemCondition | Yes | Listing condition attribute (New, Used, etc.) |
| aggregateRating | Yes (shop-level) | Shop reviews — not per-listing review aggregation |
| review (individual) | No | Reviews exist but not emitted as individual Review nodes |
| shippingDetails | Partial | Shipping profile — destination + cost not always in JSON-LD |
| hasMerchantReturnPolicy | Partial | Return policy field — required for agent trust signals |
| color / size / material | Partial | Variations / attributes — frequently free-text instead of structured |
| weight / dimensions | No | Used for shipping calc but not emitted in JSON-LD |
| category | Yes (BreadcrumbList) | Taxonomy selection at listing creation |
| offers.seller | Yes | Shop name (always Etsy-mediated, not direct merchant) |
| productID | Yes (listing_id) | Auto-assigned |
| url | Yes | Auto-generated canonical etsy.com URL |
Net result: roughly 8 of 20 MVP fields are fully covered by Etsy's auto-emission, another ~7 are partially covered (depend entirely on seller input quality), and ~5 are not exposed at all. The seller's only lever is input quality.
Inputs the seller actually controls
- Title — 140 chars, front-load the head keyword, include one long-tail modifier, avoid emoji.
- Description — first 160 chars are the agent retrieval snippet; aim for ≥150 words total with materials, dimensions, use cases, and care.
- Attributes — fill every applicable structured attribute (color, material, occasion, holiday, recipient) — these feed Etsy's filters AND its JSON-LD.
- GTIN/UPC — populate when available; many sellers leave blank, which is a Layer 1 own-goal.
- Variations — use the Variations & Inventory tool for color/size/material, with per-variation price + SKU + photo.
- Return policy — set a written return policy at the shop level; "no returns" still emits a structured signal agents can reason about.
- Shipping profile — bind every listing to a shipping profile with at least origin country and processing time.
Etsy Pattern (the $15/month re-verify before launch standalone-site product) gives the seller a separate yoursite.com storefront and waives Etsy's 6.5% transaction fee, but it does NOT give the seller control over the JSON-LD on the underlying Etsy listing page. Pattern is a separate Schema.org surface — useful for SEO and brand, but irrelevant to closing the Etsy-listing structured data gap.
The Etsy Open API v3 — what's there, what's not, and how to wire it.
The Etsy Open API v3 is the seller's only structured read/write path into Etsy. It is an OAuth 2.0 PKCE-protected REST API at https://openapi.etsy.com/v3/ with roughly 50 endpoints covering listings, inventory, receipts, shop data, shipping profiles, and reviews. Two endpoints that exist on Shopify and would matter for agent-driven checkout — calculate_total and initiate_checkout — DO NOT EXIST in the Etsy Open API at the seller level. Checkout is owned by Etsy and exposed only through the platform-level ACP integration with OpenAI.
The basics
- Base URL:
https://openapi.etsy.com/v3/ - Auth: OAuth 2.0 with PKCE (Proof Key for Code Exchange) — required for all endpoints except a small ping set.
- Required headers:
Authorization: Bearer {access_token}ANDx-api-key: {keystring}on every request. - Rate limits: ~10,000 requests/day and ~10 requests/second per app (re-verify before launch).
- Response format: JSON. All resources are paginated with
limit+offset(maxlimit=100). - Token lifetime: access tokens expire in 1 hour; refresh tokens last 90 days and rotate on each use.
OAuth scopes you actually need
| Scope | What it grants | Needed for MCP? |
|---|---|---|
listings_r | Read active, inactive, draft, and sold-out listings | Yes — core |
listings_w | Create and update listings, manage inventory + variations | Optional |
listings_d | Delete listings | No — keep minimal |
transactions_r | Read receipts (orders) and transactions | Yes if surfacing orders |
transactions_w | Update tracking, mark shipped | Optional |
shops_r | Read shop info, sections, policies | Yes — core |
shops_w | Update shop info and sections | No |
profile_r | Read user profile | No |
Minimal MCP scope set: listings_r shops_r transactions_r. Request only what you use — Etsy reviews scope requests during app approval.
The 8 canonical operations — mapped to Etsy v3
| Canonical Operation | Etsy v3 Route | Method |
|---|---|---|
| list_products | /application/shops/{shop_id}/listings/active | GET |
| get_product | /application/listings/{listing_id} | GET |
| search_products | /application/shops/{shop_id}/listings/active?keywords=... | GET |
| get_inventory | /application/listings/{listing_id}/inventory | GET |
| get_variations | /application/listings/{listing_id}/inventory (same as inventory) | GET |
| list_orders | /application/shops/{shop_id}/receipts | GET |
| calculate_total | NOT AVAILABLE — Etsy owns checkout math | — |
| initiate_checkout | NOT AVAILABLE — deep-link to etsy.com/cart/add instead | — |
curl — list active listings for a shop
curl -X GET "https://openapi.etsy.com/v3/application/shops/{SHOP_ID}/listings/active?limit=25&offset=0" \
-H "Authorization: Bearer {ACCESS_TOKEN}" \
-H "x-api-key: {KEYSTRING}"
curl — get a single listing with images and inventory
curl -X GET "https://openapi.etsy.com/v3/application/listings/{LISTING_ID}?includes=Images,Inventory,Shop" \
-H "Authorization: Bearer {ACCESS_TOKEN}" \
-H "x-api-key: {KEYSTRING}"
curl — get inventory (variations) for a listing
curl -X GET "https://openapi.etsy.com/v3/application/listings/{LISTING_ID}/inventory" \
-H "Authorization: Bearer {ACCESS_TOKEN}" \
-H "x-api-key: {KEYSTRING}"
curl — list recent receipts (orders)
curl -X GET "https://openapi.etsy.com/v3/application/shops/{SHOP_ID}/receipts?limit=25&was_paid=true" \
-H "Authorization: Bearer {ACCESS_TOKEN}" \
-H "x-api-key: {KEYSTRING}"
The cart-add deep-link pattern
Because there is no seller-level initiate_checkout, the honest Layer-2-to-checkout bridge is Etsy's own cart-add URL (re-verify before launch):
https://www.etsy.com/cart/add?listing_id={LISTING_ID}&quantity={N}
An agent that retrieves a listing via the MCP server can hand the buyer this URL — the buyer clicks, lands in their logged-in Etsy cart, and completes checkout in Etsy's flow. The agent does NOT calculate tax, shipping, or total; Etsy does that on the cart/checkout page. This is the only seller-controllable path to agent-initiated purchase today.
The OAuth 2.0 PKCE flow — once, then forget
Etsy requires PKCE — there is no client-credentials grant for seller-scoped access. The dance runs once per shop, then the refresh token persists. The five steps:
- Generate a code verifier and code challenge. 43–128 random URL-safe characters for the verifier; SHA-256 + base64url-encode it for the challenge.
- Redirect the seller to Etsy's authorize URL with
response_type=code,client_id={keystring},redirect_uri,scope,state,code_challenge, andcode_challenge_method=S256. - Receive the redirect callback with
?code={auth_code}&state={state}. Validatestatematches what you sent. - Exchange the code for tokens by POSTing to
https://api.etsy.com/v3/public/oauth/tokenwithgrant_type=authorization_code,client_id,redirect_uri,code, andcode_verifier. The response includesaccess_token,refresh_token, andexpires_in(3600 seconds). - Persist the refresh token. Refresh rotates on every use, so after each refresh you must overwrite the stored token with the new one or you will lock yourself out within 24 hours.
Pagination — limit + offset, never cursor
Every Etsy v3 listing/receipt collection endpoint uses limit + offset pagination with a max limit of 100. The response includes count (total available) at the top level. For a 500-listing shop, that's 5 pages at limit=100 — but each page is a separate rate-limited request. For shops with active inventory updates, prefer updated_at_min filtering on the receipts endpoint over re-fetching pages from offset 0 every cycle.
Error responses — what to retry, what to surface
| Status | Meaning | MCP server response |
|---|---|---|
| 200 | OK | Return data + data_as_of timestamp |
| 400 | Bad request — usually missing required param | Surface as tool error; agent should re-ask user for input |
| 401 | Access token expired or invalid | Refresh token, retry once; if still 401, force re-auth |
| 403 | Insufficient scope or shop not authorized | Surface honestly — do not retry |
| 404 | Listing or shop not found | Return empty result with explanation |
| 409 | Conflict (e.g. listing already in this state) | Surface to agent |
| 429 | Rate limit hit | Honor Retry-After header; do not silently retry forever |
| 5xx | Etsy server error | Retry once with backoff; surface if it persists |
10,000/day sounds generous until an MCP server with 5 tools, 3 concurrent users, and a 500-listing shop tries to refresh a full catalog hourly — that's 60,000 requests/day. Build the MCP server with per-listing caching (1-hour TTL is reasonable), batch the listing-by-listing inventory calls, and surface "data as of {timestamp}" in every response. Rate limits — re-verify before launch.
Print the 4-layer checklist before you touch a listing.
A one-page PDF: every input field that drives Etsy's auto-emitted JSON-LD, every OAuth scope you actually need, the 5 MCP tools to ship first, and the 3 honest workarounds for Layer 4. Drop your email — it arrives in under a minute.
A merchant-side FastMCP server — 5 tools, ~250 lines of Python, deployable in 30 minutes.
The MCP layer is where individual Etsy sellers have the most leverage. Etsy's platform-level integrations (ChatGPT ACP, Google UCP) cover agents that are NATIVELY integrated with Etsy — but every other agent (a custom Claude project, a Cursor workflow, an internal LangGraph pipeline) has no way to discover the seller's shop unless the seller exposes one. A merchant-side MCP server is that exposure layer. It wraps the Etsy Open API v3, handles OAuth refresh, and presents five clean tools to any MCP-compatible client.
Architecture
- Runtime: Python 3.11, FastMCP (the official MCP Python SDK).
- Storage: a single JSON file (
tokens.json) for the OAuth refresh token, or Fly.io's persistent volume. - HTTP client:
httpx— async, with built-in retry on 429 (rate limit) and 401 (expired token → refresh + retry). - Deployment: local for testing (Claude Desktop reads stdio), Fly.io for shared/remote use (HTTP/SSE transport).
- Scopes:
listings_r shops_r transactions_r— minimum viable.
The five MCP tools
- list_my_etsy_listings(state="active", limit=25) — returns active/draft/inactive/sold-out listings with title, price, listing_id, url, primary image.
- get_etsy_listing(listing_id) — full listing detail: description, all images, attributes, materials, return policy, shipping profile summary.
- search_my_shop(keywords, limit=25) — keyword search within the seller's own shop.
- check_etsy_inventory(listing_id) — variations + per-variation price, quantity, SKU.
- get_etsy_checkout_link(listing_id, quantity=1) — returns the deep-link cart-add URL; the only honest checkout handoff a seller-level tool can produce.
server.py — full source
# server.py — Merchant Etsy MCP Server (FastMCP)
# Wraps Etsy Open API v3. Five tools. OAuth 2.0 PKCE refresh handled.
import os
import json
import time
from pathlib import Path
from typing import Optional
import httpx
from mcp.server.fastmcp import FastMCP
ETSY_BASE = "https://openapi.etsy.com/v3"
KEYSTRING = os.environ["ETSY_KEYSTRING"]
SHARED_SECRET = os.environ["ETSY_SHARED_SECRET"]
SHOP_ID = os.environ["ETSY_SHOP_ID"]
TOKEN_PATH = Path(os.environ.get("ETSY_TOKEN_PATH", "tokens.json"))
mcp = FastMCP("etsy-merchant")
def _load_tokens() -> dict:
return json.loads(TOKEN_PATH.read_text())
def _save_tokens(tokens: dict) -> None:
TOKEN_PATH.write_text(json.dumps(tokens, indent=2))
async def _refresh_access_token() -> str:
tokens = _load_tokens()
async with httpx.AsyncClient(timeout=20) as client:
r = await client.post(
"https://api.etsy.com/v3/public/oauth/token",
data={
"grant_type": "refresh_token",
"client_id": KEYSTRING,
"refresh_token": tokens["refresh_token"],
},
)
r.raise_for_status()
new = r.json()
tokens["access_token"] = new["access_token"]
tokens["refresh_token"] = new["refresh_token"]
tokens["expires_at"] = int(time.time()) + int(new["expires_in"]) - 60
_save_tokens(tokens)
return tokens["access_token"]
async def _access_token() -> str:
tokens = _load_tokens()
if int(time.time()) >= tokens.get("expires_at", 0):
return await _refresh_access_token()
return tokens["access_token"]
async def _get(path: str, params: Optional[dict] = None) -> dict:
token = await _access_token()
headers = {"Authorization": f"Bearer {token}", "x-api-key": KEYSTRING}
async with httpx.AsyncClient(timeout=30) as client:
r = await client.get(f"{ETSY_BASE}{path}", headers=headers, params=params or {})
if r.status_code == 401:
token = await _refresh_access_token()
headers["Authorization"] = f"Bearer {token}"
r = await client.get(f"{ETSY_BASE}{path}", headers=headers, params=params or {})
if r.status_code == 429:
# Honest rate-limit surfacing — caller decides whether to retry.
return {"error": "rate_limited", "retry_after": r.headers.get("Retry-After")}
r.raise_for_status()
return r.json()
def _listing_summary(L: dict) -> dict:
return {
"listing_id": L["listing_id"],
"title": L["title"],
"price": f"{L['price']['amount'] / L['price']['divisor']:.2f} {L['price']['currency_code']}",
"url": L["url"],
"state": L["state"],
"quantity": L.get("quantity"),
"tags": L.get("tags", [])[:10],
}
@mcp.tool()
async def list_my_etsy_listings(state: str = "active", limit: int = 25) -> dict:
"""List the seller's Etsy listings. state: active|draft|inactive|sold_out. limit max 100."""
data = await _get(
f"/application/shops/{SHOP_ID}/listings/{state}",
{"limit": min(limit, 100), "offset": 0},
)
return {
"count": data.get("count", 0),
"results": [_listing_summary(L) for L in data.get("results", [])],
"data_as_of": int(time.time()),
}
@mcp.tool()
async def get_etsy_listing(listing_id: int) -> dict:
"""Full detail for a single listing, including images and shipping profile."""
data = await _get(
f"/application/listings/{listing_id}",
{"includes": "Images,Shop,User,ShippingProfile"},
)
return {
"listing_id": data["listing_id"],
"title": data["title"],
"description": data["description"],
"price": f"{data['price']['amount'] / data['price']['divisor']:.2f} {data['price']['currency_code']}",
"url": data["url"],
"images": [img["url_fullxfull"] for img in data.get("images", [])],
"materials": data.get("materials", []),
"tags": data.get("tags", []),
"shipping_profile_id": data.get("shipping_profile_id"),
"return_policy_id": data.get("return_policy_id"),
"processing_min": data.get("processing_min"),
"processing_max": data.get("processing_max"),
"data_as_of": int(time.time()),
}
@mcp.tool()
async def search_my_shop(keywords: str, limit: int = 25) -> dict:
"""Keyword search restricted to the seller's own shop."""
data = await _get(
f"/application/shops/{SHOP_ID}/listings/active",
{"keywords": keywords, "limit": min(limit, 100), "offset": 0},
)
return {
"query": keywords,
"count": data.get("count", 0),
"results": [_listing_summary(L) for L in data.get("results", [])],
"data_as_of": int(time.time()),
}
@mcp.tool()
async def check_etsy_inventory(listing_id: int) -> dict:
"""Variations + per-variation price, quantity, SKU for a listing."""
data = await _get(f"/application/listings/{listing_id}/inventory")
products = []
for p in data.get("products", []):
offerings = p.get("offerings", [])
offering = offerings[0] if offerings else {}
price = offering.get("price", {})
products.append({
"sku": p.get("sku"),
"property_values": [
{"name": pv.get("property_name"), "value": (pv.get("values") or [None])[0]}
for pv in p.get("property_values", [])
],
"quantity": offering.get("quantity"),
"price": (
f"{price['amount'] / price['divisor']:.2f} {price['currency_code']}"
if price else None
),
"is_enabled": offering.get("is_enabled"),
})
return {"listing_id": listing_id, "products": products, "data_as_of": int(time.time())}
@mcp.tool()
async def get_etsy_checkout_link(listing_id: int, quantity: int = 1) -> dict:
"""Honest checkout handoff — deep-link the buyer into Etsy's own cart.
No seller-level initiate_checkout endpoint exists; this is the only path."""
url = f"https://www.etsy.com/cart/add?listing_id={listing_id}&quantity={max(1, quantity)}"
return {
"checkout_url": url,
"note": "Etsy owns checkout. Tax, shipping, and total are calculated on the Etsy cart page.",
}
if __name__ == "__main__":
mcp.run()
Claude Desktop config (claude_desktop_config.json)
{
"mcpServers": {
"etsy-merchant": {
"command": "python",
"args": ["/absolute/path/to/server.py"],
"env": {
"ETSY_KEYSTRING": "your_keystring_here",
"ETSY_SHARED_SECRET": "your_shared_secret_here",
"ETSY_SHOP_ID": "12345678",
"ETSY_TOKEN_PATH": "/absolute/path/to/tokens.json"
}
}
}
}
Fly.io deployment — fly.toml
app = "etsy-merchant-mcp"
primary_region = "iad"
[build]
[http_service]
internal_port = 8000
force_https = true
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 0
[mounts]
source = "etsy_tokens"
destination = "/data"
[env]
ETSY_TOKEN_PATH = "/data/tokens.json"
Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY server.py .
CMD ["python", "server.py"]
Listing optimization for agent retrieval
An MCP server is only as useful as the data it returns. Before deploying, do a one-time listing pass focused on agent legibility:
- Front-load the title — agents see the first 60 characters most clearly; put the head noun + primary modifier there.
- First-paragraph discipline — the first 160 characters of the description become the snippet most agents quote; lead with what the product IS, not the shop story.
- Fill GTIN — every listing with a manufacturer UPC should have it populated; agents penalize missing GTINs in product comparisons.
- Structured attributes over free text — pick from Etsy's dropdowns wherever possible; free-text material/color fields don't make it into the JSON-LD cleanly.
- Variations as inventory, not as listings — one listing with size/color variations beats five near-duplicate listings for agent dedup logic.
- Return policy in writing — even a strict "no returns" is a structured signal; blank policies make agents downgrade trust.
Why individual Etsy sellers cannot wire UCP — and the three honest workarounds.
The Universal Commerce Protocol (UCP) is the in-development open standard for agent-initiated checkout — a /.well-known/ucp profile plus calculate_total and initiate_checkout endpoints that any agent on the open web can hit to take a buyer from intent to confirmed order without leaving the agent surface. On Shopify, WooCommerce, BigCommerce, and headless, the merchant can implement this themselves — they are the merchant of record, they own the checkout flow, they control the domain. On Etsy, the seller is none of those things. Etsy is the merchant of record. Etsy Payments is mandatory. The seller cannot host a /.well-known/ file on etsy.com, cannot expose a checkout endpoint, and cannot route an agent's initiate_checkout call to anything other than Etsy's own cart.
Why the seller cannot implement UCP directly
- Domain control — UCP discovery is rooted at
https://{merchant-domain}/.well-known/ucp. The seller's listings live onetsy.com/shop/{seller}, not a seller-controlled domain. (Pattern gives a separateyourshop.comdomain but Pattern's checkout is a separate Etsy-controlled flow — it doesn't expose UCP either.) - Checkout ownership — even with a hypothetical seller-hosted UCP endpoint, the seller cannot fulfill
initiate_checkoutbecause Etsy Payments is the only legal payment path on the platform. - Tax + shipping math —
calculate_totalrequires running the seller's tax engine, shipping calculator, and discount rules. On Etsy, all three live inside Etsy's checkout — no API surfaces them to the seller. - Inventory write authority — UCP assumes the merchant can decrement inventory in real time at
initiate_checkout. Etsy decrements inventory at its own checkout completion event, not on a seller-side trigger.
Workaround A — MCP discovery + deep-link handoff
This is the only end-to-end agent purchase path an individual seller can ship today. The merchant MCP server exposes the catalog (Layer 3). The agent retrieves a listing, decides it's a match, and calls get_etsy_checkout_link(listing_id, quantity). The agent presents the buyer with the cart-add URL. The buyer clicks, lands in their logged-in Etsy cart, and finishes checkout in Etsy's flow. The agent does NOT see the order confirmation directly — it sees a handoff event. For most agent surfaces (Claude, Cursor, Comet, custom LangGraph), this is a tolerable UX. For pure-voice or pure-autonomous agents, it is not — those will route to the platform-level integrations in Workaround B instead.
Workaround B — Ride Etsy's platform-level ACP and UCP partnerships
Etsy was the launch partner for ChatGPT instant checkout via the Agentic Commerce Protocol (ACP) in September 2025. That means a ChatGPT user can ask for an Etsy product, see Etsy listings inline, and complete checkout without leaving ChatGPT. The seller does nothing — Etsy negotiated the partnership at the platform level, and every active listing is automatically included (re-verify before launch). Etsy is also part of Google's UCP partnership announced in January 2026; the rollout scope and timeline for Google's agent-driven shopping experience are still developing.
Cost structure to model: ChatGPT ACP adds an OpenAI fee of approximately 4% on top of Etsy's existing fees (re-verify before launch). Stacked with the 6.5% Etsy transaction fee, 3% + $0.25 payment processing, and potentially 12–15% Offsite Ads, an ACP order can carry a 13–14%+ effective fee load before shipping and material costs. Price your listings for that reality if ACP traffic becomes a material share of orders.
Workaround C — Wait for (or lobby for) a seller-level Etsy checkout API
Etsy could, in principle, expose a seller-scoped calculate_total + initiate_checkout that bridges to Etsy Payments. There is no public roadmap commitment to this as of June 2026, and the platform-level ACP/UCP partnerships reduce Etsy's incentive to build it — Etsy captures more value when it negotiates with one OpenAI or one Google than when it gives every seller a UCP endpoint to wire into arbitrary agents. The honest read: this is the slowest path and the one with the least seller leverage. Note it on the roadmap, do not block on it.
At the individual-seller level on Etsy, Layer 4 is unsolved and likely to remain so. The seller's best Layer 4 posture is: (1) ship the MCP server so non-Etsy-integrated agents can at least DISCOVER the shop, (2) accept Etsy's platform partnerships as the de-facto Layer 4 for ChatGPT and Google traffic, (3) model the stacked fee load when pricing. Do not pretend you can wire initiate_checkout from a Pattern site, a custom domain, or a third-party widget — none of those route to Etsy Payments.
What Etsy makes easy — and what it makes structurally hard.
Easy on Etsy (vs. running your own store)
| What's Easy | Why |
|---|---|
| Buyer trust | Etsy brand, 100M+ active buyers, established reviews ecosystem — agents reasoning about purchase risk see Etsy as a low-risk surface. |
| Working checkout out of the box | Etsy Payments handles cards, Apple Pay, Google Pay, klarna, currency conversion. Seller does zero integration work. |
| Tax, fraud, and chargebacks handled | Etsy calculates and remits sales tax in many jurisdictions, runs fraud screening, and is the merchant of record for chargebacks. |
| Auto enrollment in platform agent partnerships | ChatGPT ACP (Sept 2025), Google UCP (announced Jan 2026) — every active listing is included with no seller configuration. |
| Auto-emitted structured data baseline | ~8/20 MVP Schema.org fields populated without seller effort. |
Hard (or impossible) on Etsy
| What's Hard | Why |
|---|---|
| Custom JSON-LD or schema control | No template access. The seller cannot add Review, FAQ, HowTo, or extended Product properties beyond what Etsy auto-emits. |
| Seller-level checkout API | No calculate_total or initiate_checkout endpoint exists. Etsy Payments is the only legal path. |
| UCP at the seller level | No domain control, no checkout authority — workaround is MCP + cart-add deep-link. |
| 6.5% transaction fee stack | 6.5% Etsy + 3%+$0.25 processing + 12–15% Offsite Ads (when applicable) + 4% ACP (when triggered) re-verify before launch. |
| Brand-owned customer relationship | Etsy owns the buyer email and post-purchase channel. The seller cannot directly email buyers outside Etsy's messaging system. |
| Cross-listing or multi-channel inventory sync | Possible via the API but rate limits make real-time sync hard above a few hundred listings. |
How an Etsy seller actually rolls out agent-readiness — in narrative form.
One. Start with an audit, not code. Open a spreadsheet, list every active listing, and score each on five inputs: title quality, description ≥150 words, GTIN populated, structured attributes filled, return policy + shipping profile bound. Anything below 4/5 is a Layer 1 own-goal — fix the inputs before you build anything. This week's work is in Etsy's listing editor, not a terminal.
Two. Register an Etsy developer app at https://www.etsy.com/developers. Request the minimum scope set (listings_r shops_r transactions_r) — Etsy's app review notices over-broad scope requests. Implement the OAuth 2.0 PKCE dance once, store the refresh token in a JSON file, and you're done with auth forever. This is the Layer 2 prerequisite.
Three. Deploy the merchant MCP server. The full Python source is in §5 above — five tools, one HTTP client with refresh-token handling, ~250 lines. Start locally with Claude Desktop reading stdio. Once the five tools return live data for your shop, deploy to Fly.io for shared use. Budget 30 minutes for local, 60 minutes for Fly.io.
Four. Wire the agent surfaces you actually use. Claude Desktop is the easy first one. Cursor reads the same MCP config. For every surface, run four test prompts (see §9) and confirm the agent returns live data with citations back to etsy.com URLs.
Five. Accept Layer 4 as it is. Wire get_etsy_checkout_link as the handoff path for non-Etsy-integrated agents. For ChatGPT and Google traffic, trust Etsy's platform-level integrations and watch your analytics for ACP-attributed orders. Price for the stacked fee load (re-verify before launch), and revisit quarterly — Etsy's platform partnerships are the variable to watch, not seller-level UCP, which is unlikely to ship soon.
Four prompts that prove the five tools return live data.
After editing claude_desktop_config.json and restarting Claude Desktop, open a new chat and run these prompts in order. Each one exercises a different tool and a different failure mode.
Prompt 1 — Discovery (list_my_etsy_listings)
List my 10 most recent active Etsy listings. For each, show
the title, price, and the listing URL. Cite the tool you used.
Expected: Claude calls list_my_etsy_listings, returns 10 rows with live data, and cites the tool name. If you see "I don't have access to your Etsy shop," your MCP server isn't loaded — check the config path and restart Claude Desktop.
Prompt 2 — Keyword search (search_my_shop)
Search my Etsy shop for "planner" and return the top 5 matches
with title, price, and URL. Include the data_as_of timestamp.
Expected: Claude calls search_my_shop with keywords="planner". The timestamp surfaces because the tool returns it explicitly — this is the cache-freshness signal agents should always quote.
Prompt 3 — Inventory + variations (check_etsy_inventory)
For my best-selling listing, show me every variation with its
SKU, price, and current quantity. Flag any variation that is
disabled or out of stock.
Expected: Claude first calls list_my_etsy_listings to find the listing, then check_etsy_inventory with the listing_id. The tool's is_enabled and quantity fields drive the flag logic.
Prompt 4 — Checkout handoff (get_etsy_checkout_link)
If a buyer wanted to purchase 2 units of listing {your_id},
give me the exact URL I'd send them. Explain what happens when
they click it.
Expected: Claude calls get_etsy_checkout_link(listing_id, 2), returns the cart-add URL, and quotes the tool's note that "Etsy owns checkout. Tax, shipping, and total are calculated on the Etsy cart page." This is the honest Layer 4 handoff in action.
Eight things sellers try that will not work — or will quietly cost money.
- Trying to inject custom JSON-LD into a listing. Etsy strips or ignores anything in the description that looks like a
<script>tag. The only path is filling the structured fields Etsy already maps. - Assuming a third-party checkout widget can bypass Etsy Payments. Etsy's terms of service prohibit driving transactions off-platform. Listings that try to route to Stripe/PayPal links get suspended.
- Neglecting Etsy Pattern as a "free" Layer 1 win. Pattern is a separate site ($15/mo, re-verify before launch) — useful for brand + SEO + waived transaction fee on Pattern orders, but irrelevant to Etsy listing JSON-LD.
- Requesting every OAuth scope "just in case." Etsy reviews scope requests during app approval; over-broad scopes slow approval and increase the blast radius of a leaked refresh token. Start with
listings_r shops_r transactions_r. - Leading descriptions with shop-announcement copy. "Welcome to our family-owned shop!" steals the first 160 characters that agents quote as the snippet. Lead with what the product IS, then the story.
- Ignoring the Variations & Inventory API. Treating each color as a separate listing inflates the catalog, dilutes agent dedup logic, and burns rate-limit headroom on near-duplicates.
- Building an MCP server with no rate-limit headroom. ~10,000/day re-verify sounds like a lot until per-listing inventory calls hit it. Cache aggressively (1-hour TTL), batch where possible, and surface
data_as_ofin every response. - Treating Etsy's ChatGPT ACP / Google UCP partnerships as configurable. They are not seller-configurable surfaces. The seller's only lever is listing data quality — which the partnerships read from automatically.
What a $40 Etsy item actually nets the seller across four traffic mixes.
Etsy's fee structure is layered, and the seller's net per order depends on which channel drove the order. The worksheet below models a single $40 item with $6 shipping (so a $46 transaction base) across four channels. Every figure is re-verify before launch — fee schedules change and ACP/UCP economics are still settling.
| Fee Line | Direct (Etsy organic) | Offsite Ads | ChatGPT ACP | ACP + Offsite |
|---|---|---|---|---|
| Listing fee (amortized per sale) | $0.20 | $0.20 | $0.20 | $0.20 |
| Transaction fee (6.5% of $46) | $2.99 | $2.99 | $2.99 | $2.99 |
| Payment processing (3% + $0.25) | $1.63 | $1.63 | $1.63 | $1.63 |
| Offsite Ads (12–15% of $46) | $0.00 | $5.52 | $0.00 | $5.52 |
| ACP fee (~4% of $46) | $0.00 | $0.00 | $1.84 | $1.84 |
| Total fees | $4.82 | $10.34 | $6.66 | $12.18 |
| Net to seller (pre-COGS, pre-shipping label) | $41.18 | $35.66 | $39.34 | $33.82 |
| Effective fee % of $46 | 10.5% | 22.5% | 14.5% | 26.5% |
Two operator takeaways. First, ACP traffic is structurally more expensive than direct Etsy traffic but cheaper than Offsite Ads — if your shop is currently dominated by Offsite Ads spend, ACP-driven discovery may be a net improvement. Second, if you are forced into Offsite Ads at the 12% mandatory rate AND get high ACP traffic, the stacked load is severe — model a price increase explicitly tagged for that mix before agent-driven sales become a material share of revenue.
Orders that originate on a Pattern site (your yourshop.com domain, $15/month re-verify) waive the 6.5% Etsy transaction fee — payment processing still applies. For brand-savvy sellers driving direct traffic, Pattern can flip the net economics in their favor. Pattern does not, however, affect the JSON-LD on the underlying Etsy listing pages, and it does not enroll Pattern listings in ACP/UCP.
Concrete completion criteria so you know when to stop.
Structured Data — done when
Every active listing scores 4/5 on the audit rubric, every listing with a manufacturer UPC has GTIN populated, every shop policy page (Returns, Shipping, About) is filled, and a quarterly reminder exists to re-audit the bottom 10% of listings by view count.
API — done when
An Etsy developer app is registered, OAuth 2.0 PKCE returns a valid refresh token, the refresh-rotation logic is tested by deliberately expiring an access token, and a curl call against /application/shops/{shop_id}/listings/active returns live data with the minimum scope set.
MCP — done when
All five tools (list, get, search, inventory, checkout-link) return live data in Claude Desktop, the server is deployed to Fly.io with secrets in fly secrets, the refresh-token rotation survives a deliberate token expiry, and the data_as_of timestamp appears in every response.
UCP — done when
You have written down (in plain language) why seller-level UCP cannot be implemented on Etsy, your get_etsy_checkout_link tool returns a valid cart-add URL that opens the correct listing in an incognito browser, and your pricing reflects the stacked ACP fee load. A quarterly reminder watches Etsy seller comms for platform-level UCP updates.
The questions Etsy sellers actually ask about agent-readiness.
Can I add my own Schema.org JSON-LD to my Etsy listing pages?
No. Etsy generates the listing-page HTML and the Schema.org JSON-LD server-side from the listing record — sellers cannot inject custom JSON-LD, edit the markup, or override Etsy's mapping logic. The only way to improve a listing's structured data is to fill the inputs Etsy already maps: title, description, attributes, GTIN/UPC, variations, shipping profile, and return policy. If full schema control matters more than Etsy's buyer traffic, the answer is to run a separate Shopify or WooCommerce store where you own the template.
What OAuth scopes do I need to build the merchant Etsy MCP server?
For the five-tool MCP server in this guide, the minimum viable scope set is listings_r shops_r transactions_r. Add listings_w only if you plan to update listings programmatically, and transactions_w only if the MCP server marks orders as shipped. Etsy reviews scope requests during app approval and notices over-broad requests, so request only what you actually use — and rotate the refresh token immediately if it leaks, because Etsy's refresh tokens carry the same scope set as the access tokens they mint.
Can an AI agent actually buy something from my Etsy shop today?
Yes, but through two distinct paths, neither of which is a seller-implemented UCP endpoint. Path one: ChatGPT users can complete Etsy purchases inline via the Agentic Commerce Protocol partnership Etsy launched in September 2025 — every active listing is auto-included with no seller configuration. Path two: for non-ChatGPT agents (Claude, Cursor, custom LangGraph pipelines), the seller's merchant MCP server can return a deep-link cart-add URL via get_etsy_checkout_link, and the buyer completes checkout in Etsy's hosted flow. There is no seller-level initiate_checkout API.
Does Etsy Pattern give me structured data control?
No — Pattern gives you a separate yourshop.com standalone storefront with its own template, its own Schema.org surface, and a waived 6.5% Etsy transaction fee on Pattern orders ($15/month, re-verify before launch). Pattern is genuinely useful for brand SEO, owning the buyer email channel, and reducing fee load on direct traffic. It is NOT a lever on the JSON-LD that renders on your etsy.com/listing/{id} pages — those remain Etsy-controlled. Treat Pattern as a parallel surface, not a fix for the Etsy-listing structured data gap.
What are the exact fees I need to account for when pricing for agent-driven sales?
As of June 2026, US sellers face (every figure here — re-verify before launch): a $0.20 listing fee per listing per four months, a 6.5% transaction fee on item price plus shipping plus gift wrap, a payment processing fee of approximately 3% + $0.25 per order via Etsy Payments, an Offsite Ads fee of 15% (sellers under $10K/year, optional) or 12% (sellers over $10K/year, mandatory), and an additional ~4% OpenAI fee on ChatGPT ACP-driven orders. Stacked on an ACP order with Offsite Ads, the effective fee load can reach 13–14%+ before materials and shipping costs.
Will the Etsy Open API handle a full catalog sync for my MCP server?
For shops under a few hundred listings, yes — comfortably within the ~10,000 requests/day and ~10 requests/second rate limits (re-verify before launch). For larger catalogs, you need caching discipline: a 1-hour TTL per listing, batched inventory calls only when a listing is actually requested, and a hard cap on background sync jobs. The MCP server in this guide surfaces a data_as_of timestamp on every response so the calling agent can reason about freshness. If you need real-time stock guarantees, the honest answer is that the cart-add deep-link is the right handoff — Etsy will tell the buyer at cart time if something is unavailable.
How do Etsy's platform-level agent integrations affect individual sellers?
Etsy's ChatGPT ACP integration (live September 2025) and Google UCP partnership (announced January 2026) are negotiated at the platform level — every active Etsy listing is included automatically, the seller does no configuration, and the seller cannot opt out without delisting. The benefits are buyer reach inside agent surfaces the seller could not access alone; the costs are an additional fee layer on ACP-attributed orders (approximately 4% OpenAI fee, re-verify), reduced visibility into the buyer journey, and zero ability to differentiate listing presentation between agent and human traffic. Price for the fee load and trust Etsy's negotiation outcomes — there is no seller-side lever.
Is it worth building a merchant MCP server if Etsy already has ChatGPT and Google integrations?
Yes — because ChatGPT and Google's partnerships cover only the agents NATIVELY integrated with Etsy. Every other agent surface (Claude Desktop, Cursor, custom Claude projects, internal LangGraph pipelines, agent-first commerce experiments) has no way to discover your shop unless you expose one. A merchant MCP server is approximately 250 lines of Python and deploys to Fly.io in under an hour. It future-proofs you against the next agent surface, gives you a clean read path for your own internal automations, and produces the honest cart-add handoff for any agent that needs to direct a buyer to checkout.
The five steps, in order, with what "done" looks like for each.
Step 1 — Audit and complete all listing fields (GTIN, attributes, descriptions ≥150 words, return + shipping policies)
Export your active listings to a spreadsheet. Score every listing on five inputs: title quality (head keyword in first 60 chars), description length (≥150 words with materials, dimensions, use cases), GTIN/UPC populated where the product has one, structured attributes filled from Etsy's dropdowns rather than left blank, and binding to a complete shipping profile + written return policy. Fix anything below 4/5. Done = every active listing scores 4/5 or higher and the spreadsheet has no blank GTIN cells for products that ship with a manufacturer UPC.
Step 2 — Register an Etsy developer app and get API credentials (OAuth 2.0 PKCE)
Go to https://www.etsy.com/developers, create an app, and request the minimum scope set listings_r shops_r transactions_r. Etsy will issue a keystring and shared secret. Implement the OAuth 2.0 PKCE flow once — generate a code verifier and code challenge, redirect the user to Etsy's authorize endpoint, exchange the returned code for access + refresh tokens, and persist the refresh token to a JSON file (or a Fly.io persistent volume). Done = you can run a curl call against /application/shops/{shop_id}/listings/active and receive a 200 response with live listings.
Step 3 — Build and deploy the merchant MCP server (Python, FastMCP)
Copy the server.py from §5 above. Install dependencies (mcp, httpx). Set the four environment variables (ETSY_KEYSTRING, ETSY_SHARED_SECRET, ETSY_SHOP_ID, ETSY_TOKEN_PATH). Place the refresh token JSON file at the configured path. Run the server locally to verify all five tools respond with live data. Deploy to Fly.io for shared/remote use using the included fly.toml and Dockerfile. Done = the MCP server is running, the five tools all return live data, and the refresh-token mechanism survives an expired access token without manual intervention.
Step 4 — Configure Claude Desktop and verify the 5 MCP tools return live data
Edit claude_desktop_config.json with the etsy-merchant server entry from §5. Restart Claude Desktop. Run the four test prompts from §9 in order: discovery, keyword search, inventory + variations, and checkout handoff. For each prompt, verify Claude calls the expected tool, returns live data from your shop, and quotes the data_as_of timestamp where applicable. Done = all four prompts return live data with no manual intervention, and the cart-add URL from prompt 4 successfully adds the listing when clicked.
Step 5 — Monitor Etsy's platform-level agent integrations (ChatGPT ACP, Google UCP) and adjust
Add Etsy's ChatGPT ACP integration (live September 2025) and Google UCP partnership (announced January 2026) to your quarterly review checklist. Watch Etsy seller communications and your sales analytics for ACP-attributed orders. If ACP traffic becomes a material share of orders, recalculate the effective fee load (~13–14% stacked, re-verify before launch) and adjust list pricing. Re-test the four MCP prompts quarterly to catch any Etsy API schema changes. Done = a recurring calendar reminder exists, pricing has been recalculated to absorb the stacked agent-channel fees, and the MCP server has been tested within the last 90 days.
Where to read next.
Shopify (Sibling Spoke)
The same 4-layer model on a platform where the seller owns every layer — full schema control, custom checkout, seller-implemented UCP endpoint. Read this next if you also sell on Shopify.
UCP — The Standard Itself
The Universal Commerce Protocol deep-dive: /.well-known/ucp, calculate_total, initiate_checkout, and why Etsy sellers can't implement it directly.
The AgentMall Roadmap
The full 30-day hub — every spoke, every layer, the platform comparison matrix, and the order to read them in.
The window for honest Etsy agent-readiness is now.
Etsy's platform-level ACP and UCP partnerships are still new enough that most Etsy sellers have done nothing to optimize the inputs those integrations read from. The sellers who audit their listings, deploy a merchant MCP server, and price for the stacked fee load this quarter will be over-represented in agent-driven search results when ChatGPT, Google, Claude, and Cursor traffic compounds. The work is bounded — ~30 days, ~250 lines of Python, no infrastructure beyond Fly.io's free tier. Start with the audit, ship the MCP server, accept Layer 4 as it is.
Back to top of the playbook →