Platform Spoke · Etsy
Spoke · Make Your Platform Agent-Ready

Etsy Listings Agents Can Actually Buy — Closing the Structured Data Gap.

Etsy is structurally different from Shopify, Woo, BigCommerce, and headless: Etsy controls the listing page HTML, and Etsy controls the checkout. The merchant has no Liquid template to extend, no REST API to publish, no /.well-known/ucp profile to host. What the merchant DOES control is listing data quality (titles, descriptions, attributes, GTIN/UPC, return policy completeness), an OAuth-protected Etsy Open API v3 path for catalog discovery, and a merchant-side MCP server they can run on Claude or Cursor for non-Etsy-integrated agents. The four agent-readiness layers — Structured Data, API Endpoint, MCP, UCP — are the spine; on Etsy, Layer 4 is the unsolved problem at the individual-seller level. At the platform level it is a different story: Etsy was the launch partner for ChatGPT instant checkout via the Agentic Commerce Protocol (ACP, live since September 2025), and Etsy is co-developing the Universal Commerce Protocol (UCP) with Google as announced in January 2026 — individual sellers benefit automatically with no configuration.

4
Layers — Layer 4 is Honest
~8/20
MVP Fields Etsy Auto-Emits
v3
Etsy Open API (OAuth 2.0 PKCE)
Sept 2025
ChatGPT ACP — Etsy Launch Partner
The Honest Frame

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.

The Four Layers — On Etsy

What each layer looks like inside the Etsy box.

1

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.

2

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.

3

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.

4

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.

The asymmetry that defines this whole page

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.

Layer 1 of 4 — Structured Data

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 FieldEtsy Auto-Emits?Seller Input That Drives It
nameYesListing title (140 char max, front-load keywords)
descriptionYesDescription body — first 160 chars matter most for agent retrieval
imageYesUp to 10 listing photos + 1 video
brandYes (shop name)Shop name — cannot vary per listing
skuYes (listing_id)Auto-assigned, plus optional per-variation SKU
gtin / gtin13 / gtin14PartialGTIN/UPC field on listing — frequently empty, agents penalize
mpnNoNot exposed in Etsy listing schema
price / priceCurrencyYesListing price + shop currency
availabilityYesInventory quantity per variation
itemConditionYesListing condition attribute (New, Used, etc.)
aggregateRatingYes (shop-level)Shop reviews — not per-listing review aggregation
review (individual)NoReviews exist but not emitted as individual Review nodes
shippingDetailsPartialShipping profile — destination + cost not always in JSON-LD
hasMerchantReturnPolicyPartialReturn policy field — required for agent trust signals
color / size / materialPartialVariations / attributes — frequently free-text instead of structured
weight / dimensionsNoUsed for shipping calc but not emitted in JSON-LD
categoryYes (BreadcrumbList)Taxonomy selection at listing creation
offers.sellerYesShop name (always Etsy-mediated, not direct merchant)
productIDYes (listing_id)Auto-assigned
urlYesAuto-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.
Pattern is not a Layer 1 lever

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.

Layer 2 of 4 — API Endpoint

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} AND x-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 (max limit=100).
  • Token lifetime: access tokens expire in 1 hour; refresh tokens last 90 days and rotate on each use.

OAuth scopes you actually need

ScopeWhat it grantsNeeded for MCP?
listings_rRead active, inactive, draft, and sold-out listingsYes — core
listings_wCreate and update listings, manage inventory + variationsOptional
listings_dDelete listingsNo — keep minimal
transactions_rRead receipts (orders) and transactionsYes if surfacing orders
transactions_wUpdate tracking, mark shippedOptional
shops_rRead shop info, sections, policiesYes — core
shops_wUpdate shop info and sectionsNo
profile_rRead user profileNo

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 OperationEtsy v3 RouteMethod
list_products/application/shops/{shop_id}/listings/activeGET
get_product/application/listings/{listing_id}GET
search_products/application/shops/{shop_id}/listings/active?keywords=...GET
get_inventory/application/listings/{listing_id}/inventoryGET
get_variations/application/listings/{listing_id}/inventory (same as inventory)GET
list_orders/application/shops/{shop_id}/receiptsGET
calculate_totalNOT AVAILABLE — Etsy owns checkout math
initiate_checkoutNOT 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:

  1. Generate a code verifier and code challenge. 43–128 random URL-safe characters for the verifier; SHA-256 + base64url-encode it for the challenge.
  2. Redirect the seller to Etsy's authorize URL with response_type=code, client_id={keystring}, redirect_uri, scope, state, code_challenge, and code_challenge_method=S256.
  3. Receive the redirect callback with ?code={auth_code}&state={state}. Validate state matches what you sent.
  4. Exchange the code for tokens by POSTing to https://api.etsy.com/v3/public/oauth/token with grant_type=authorization_code, client_id, redirect_uri, code, and code_verifier. The response includes access_token, refresh_token, and expires_in (3600 seconds).
  5. 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

StatusMeaningMCP server response
200OKReturn data + data_as_of timestamp
400Bad request — usually missing required paramSurface as tool error; agent should re-ask user for input
401Access token expired or invalidRefresh token, retry once; if still 401, force re-auth
403Insufficient scope or shop not authorizedSurface honestly — do not retry
404Listing or shop not foundReturn empty result with explanation
409Conflict (e.g. listing already in this state)Surface to agent
429Rate limit hitHonor Retry-After header; do not silently retry forever
5xxEtsy server errorRetry once with backoff; surface if it persists
Rate-limit reality

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.

Free — The Etsy Agent-Readiness Checklist

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.

Layer 3 of 4 — MCP Server

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

  1. list_my_etsy_listings(state="active", limit=25) — returns active/draft/inactive/sold-out listings with title, price, listing_id, url, primary image.
  2. get_etsy_listing(listing_id) — full listing detail: description, all images, attributes, materials, return policy, shipping profile summary.
  3. search_my_shop(keywords, limit=25) — keyword search within the seller's own shop.
  4. check_etsy_inventory(listing_id) — variations + per-variation price, quantity, SKU.
  5. 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.
Layer 4 of 4 — UCP (Honest)

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 on etsy.com/shop/{seller}, not a seller-controlled domain. (Pattern gives a separate yourshop.com domain 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_checkout because Etsy Payments is the only legal payment path on the platform.
  • Tax + shipping mathcalculate_total requires 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.

The Layer 4 honest summary

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.

The Honest Trade-Off Table

What Etsy makes easy — and what it makes structurally hard.

Easy on Etsy (vs. running your own store)

What's EasyWhy
Buyer trustEtsy brand, 100M+ active buyers, established reviews ecosystem — agents reasoning about purchase risk see Etsy as a low-risk surface.
Working checkout out of the boxEtsy Payments handles cards, Apple Pay, Google Pay, klarna, currency conversion. Seller does zero integration work.
Tax, fraud, and chargebacks handledEtsy calculates and remits sales tax in many jurisdictions, runs fraud screening, and is the merchant of record for chargebacks.
Auto enrollment in platform agent partnershipsChatGPT 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 HardWhy
Custom JSON-LD or schema controlNo template access. The seller cannot add Review, FAQ, HowTo, or extended Product properties beyond what Etsy auto-emits.
Seller-level checkout APINo calculate_total or initiate_checkout endpoint exists. Etsy Payments is the only legal path.
UCP at the seller levelNo domain control, no checkout authority — workaround is MCP + cart-add deep-link.
6.5% transaction fee stack6.5% Etsy + 3%+$0.25 processing + 12–15% Offsite Ads (when applicable) + 4% ACP (when triggered) re-verify before launch.
Brand-owned customer relationshipEtsy 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 syncPossible via the API but rate limits make real-time sync hard above a few hundred listings.
The 5-Step Operator Playbook

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.

Verify Your MCP Server in Claude Desktop

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.

Mistakes That Sound Reasonable

Eight things sellers try that will not work — or will quietly cost money.

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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_of in every response.
  8. 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.
Pricing Worksheet — The Stacked Fee Load

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 LineDirect (Etsy organic)Offsite AdsChatGPT ACPACP + 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 $4610.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.

Pattern is the wildcard

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.

What "Done" Looks Like — Per Layer

Concrete completion criteria so you know when to stop.

L1

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.

L2

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.

L3

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.

L4

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.

FAQ — Eight Honest Answers

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.

Step-by-Step — 30-Day Implementation

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.

The Window

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 →

One AgentMall note per week.

Platform-specific weekend builds, real failure modes from operator logs, and the next spoke the morning it ships. No fluff. Cancel any time.