Etsy

Etsy Open API v3: OAuth 2.0 with PKCE plus x-api-key on every call. Reads receipts, listings, inventory and ledger entries, writes price and stock. No webhooks.

Etsy is the global marketplace for handmade, vintage and craft supply goods, and increasingly for small batch design led products that are neither. For a seller database it sits in an unusual position: the API is genuinely open and self-serve, the data model is clean, and the object names are Etsy's own rather than the industry's. An order is a receipt, an order line is a transaction, a variant is a product inside a listing, and settlement detail lives in a payment account ledger. Once that vocabulary is mapped the connector is one of the simpler ones in this catalogue. The two things that will shape the implementation are the absence of any webhook mechanism, which makes everything a poll, and a daily call allowance that is shared across every shop the application serves.

At a glance

What it is

Etsy Inc. is a US company running a marketplace of roughly 5 to 6 million active sellers and around 90 million active buyers, with gross merchandise sales in the region of 12 to 13 billion US dollars a year. It also owns Depop and, until its 2024 wind down, owned Elo7 in Brazil. Etsy charges a listing fee of 0.20 US dollars per listing for four months, a transaction fee on the sale, and payment processing fees, plus optional Etsy Ads and an Offsite Ads fee that is compulsory above a revenue threshold.

The marketplace is policy constrained in a way the others are not. Sellers must make, design or curate what they sell, so pure reselling is not allowed, and production partners must be declared. That keeps the average seller small: a large share of shops are single person operations with a few dozen listings, which changes what a connector is optimising for. The common case is not a hundred thousand SKUs, it is a few hundred, with heavy variant structure, personalisation and long processing times.

Etsy is a strong channel for Indian sellers. It has no marketplace in India in the sense of a domestic storefront, but Indian sellers list into the global marketplace and ship cross border, particularly in jewellery, textiles, home and craft supplies. The seller's shop currency, the buyer's currency and the payment currency can all differ, which is why the money objects in this API carry an explicit currency code and why a naive currency assumption will corrupt revenue figures.

API access

  1. Sign in with an Etsy account and register an app at the developer portal. This is immediate.
  2. The app is issued a keystring and a shared secret. The keystring doubles as the OAuth client_id.
  3. Set one or more redirect URIs on the app. Etsy matches them exactly.
  4. A new app starts on personal or development limits. Commercial distribution, and raised call limits, require submitting the app for review, describing what it does and how it uses seller data.

Only one version is in force. Open API v3 reached general release in early 2022 and the older v2 API was shut off shortly afterwards; anything on the internet referencing openapi.etsy.com/v2 is dead. Within v3, Etsy ships additive changes and announces removals on the developer changelog rather than cutting a new version.

Base URL: https://api.etsy.com/v3/. Every application endpoint sits under /v3/application/, which is a path segment that is easy to leave out and produces a confusing 404.

Etsy does not ship a first party SDK. It publishes the OpenAPI specification, which generates a client in any language, and that is the recommended route. There is a getting-started example repository and community libraries in Python, PHP, Node and Ruby.

Authentication

Two credentials travel on a scoped request and both are required. Missing either produces an authentication error that does not say which one is missing.

  • x-api-key, carrying the app key. The quickstart read on 2026-09-21 states that this header "requires your keystring and shared secret combined in format: keystring:sharedsecret". Many working implementations send the keystring alone. Try the documented combined form first and treat this as the one place on this page where the documentation and common practice may disagree.
  • Authorization: Bearer {access_token} for anything scoped, which is every seller endpoint.

Step 1: build the PKCE pair

PKCE is mandatory on Etsy, not optional. Generate a code verifier, which the documentation describes as "a high-entropy random string consisting of between 43 and 128 characters" drawn from the unreserved URI character set. The code challenge is the base64 URL encoded SHA-256 hash of the verifier, and code_challenge_method is always S256. Store the verifier against the state value; it is needed at the exchange step and cannot be recomputed from the challenge.

Step 2: send the seller to the connect URL

GET https://www.etsy.com/oauth/connect
  ?response_type=code
  &client_id={keystring}
  &redirect_uri=https://connectors.example.com/etsy/callback
  &scope=listings_r%20listings_w%20transactions_r%20transactions_w%20shops_r%20email_r
  &state={nonce}
  &code_challenge={challenge}
  &code_challenge_method=S256

Scopes are space separated and URL encoded. The ones a commerce connector needs: listings_r and listings_w for the catalogue, listings_d to delete, transactions_r and transactions_w for receipts and shipment tracking, shops_r and shops_w for shop level settings, address_r for buyer addresses, profile_r for the seller profile, and email_r for the seller's email address. Request the minimum: Etsy shows the seller the scope list in plain language on the consent screen.

Etsy redirects back with code and state. Check state against the stored nonce before doing anything else.

Step 3: exchange the code

curl -X POST "https://api.etsy.com/v3/public/oauth/token" \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "authorization_code",
    "client_id": "abc123keystring",
    "redirect_uri": "https://connectors.example.com/etsy/callback",
    "code": "xxxxxxxxxxxxxxxxxxxxxxxx",
    "code_verifier": "the-43-to-128-character-verifier-generated-in-step-one"
  }'

The token endpoint takes a JSON body, not form encoding, which is unusual and catches out clients written against other providers. There is no client secret in this exchange: PKCE replaces it.

{
  "access_token": "12345678.4pKCEfZKhwRVdzSJgqUUyMjBbhlqfjw1lHrYYsFTPpsSw3W5YRHAcdd",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "12345678.CFCVdrIoC3rnKAkBQEbWTlYrNBQGZ2hUFpk0rsHXJIxHelbA",
  "scope": "listings_r listings_w transactions_r shops_r"
}
Note

The digits before the full stop in both tokens are the Etsy user id of the authorising seller. The quickstart states that "an Etsy access token includes your shop/user ID as a token prefix", so the user id can be read straight off the token without an extra call. Do not use it as the shop id, though: a user can own more than one shop, and shop scoped endpoints want shop_id, not user_id. Resolve it once with GET /v3/application/users/{user_id}/shops.

Lifetimes, confirmed on 2026-09-21: the access token lives 3,600 seconds, that is one hour, and the refresh token lives 90 days. Both numbers matter. An hour is short enough that a batch job must refresh mid run, and 90 days is short enough that a shop which goes quiet for three months silently disconnects. The credential store needs a scheduled refresh well inside the hour and a re-consent prompt well inside 90 days.

Step 4: refresh

curl -X POST "https://api.etsy.com/v3/public/oauth/token" \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "refresh_token",
    "client_id": "abc123keystring",
    "refresh_token": "12345678.CFCVdrIoC3rnKAkBQEbWTlYrNBQGZ2hUFpk0rsHXJIxHelbA"
  }'

The response carries a new access token and a new refresh token. Persist both; the old refresh token should be treated as spent.

An authenticated call

curl -X GET "https://api.etsy.com/v3/application/shops/12345678/receipts?limit=100&min_last_modified=1758326400" \
  -H "x-api-key: abc123keystring:shared_secret" \
  -H "Authorization: Bearer 12345678.4pKCEfZKhwRVdzSJgqUUyMjBbhlqfjw1lHrYYsFTPpsSw3W5YRHAcdd"

GET /v3/application/openapi-ping is an unscoped health check that needs only the x-api-key header. It is the fastest way to confirm the key half of the credentials independently of the token half.

Multi account

One app serves every shop. Each seller has its own access and refresh token pair, and the shop is addressed by shop_id in the path. A seller with two shops is two shop_id values under one token. There is no account switching header.

Objects we can read

Orders, which Etsy calls receipts

getShopReceipts was confirmed at /v3/application/shops/{shop_id}/receipts from the published OpenAPI specification on 2026-09-21. Its filters are min_created, max_created, min_last_modified, max_last_modified, limit, offset, sort_on, sort_order, and the boolean state filters was_paid, was_shipped, was_delivered and was_canceled. Timestamps are Unix epoch seconds. Confidence medium on the exact parameter spelling, since the reference page itself did not render.

For an incremental sync use min_last_modified. The created filters miss status changes on older receipts, which on Etsy are common because processing times are long and a receipt can sit unshipped for weeks.

Pagination is limit and offset, with count and results on the envelope. limit defaults to 25 and caps at 100. Every list response in this API has the same shape:

{
  "count": 137,
  "results": []
}

Deep offsets over a changing result set are unsafe, so window by min_last_modified and max_last_modified and keep each window inside a page or two rather than walking the offset to 5,000.

{
  "receipt_id": 2456789012,
  "receipt_type": 0,
  "seller_user_id": 12345678,
  "buyer_user_id": 87654321,
  "status": "Paid",
  "payment_method": "cc",
  "name": "Jane Doe",
  "first_line": "123 Main St",
  "second_line": "Apt 4",
  "city": "Brooklyn",
  "state": "NY",
  "zip": "11205",
  "country_iso": "US",
  "formatted_address": "Jane Doe\n123 Main St\nApt 4\nBrooklyn, NY 11205\nUnited States",
  "is_paid": true,
  "is_shipped": false,
  "is_gift": false,
  "gift_message": "",
  "message_from_buyer": "Please leave the ribbon off",
  "created_timestamp": 1758440000,
  "updated_timestamp": 1758440600,
  "grandtotal": { "amount": 2565, "divisor": 100, "currency_code": "USD" },
  "subtotal": { "amount": 1900, "divisor": 100, "currency_code": "USD" },
  "total_price": { "amount": 1900, "divisor": 100, "currency_code": "USD" },
  "total_shipping_cost": { "amount": 500, "divisor": 100, "currency_code": "USD" },
  "total_tax_cost": { "amount": 165, "divisor": 100, "currency_code": "USD" },
  "discount_amt": { "amount": 0, "divisor": 100, "currency_code": "USD" },
  "shipments": [
    {
      "receipt_shipping_id": 55512345,
      "shipment_notification_timestamp": 1758526400,
      "carrier_name": "usps",
      "tracking_code": "9400111899223197428490"
    }
  ],
  "transactions": []
}

Field names follow the published ShopReceipt schema; values are illustrative, since the reference page did not serve operation detail to the fetcher on 2026-09-21. Points that matter:

Warning

Every money value in this API is an object of amount, divisor and currency_code, where the real value is amount / divisor. The divisor is usually 100 but must not be assumed: read it per field. Never coerce amount to a float and never store it without its currency, because the shop currency, the buyer currency and the payment currency can all differ on a single Etsy order.

  • Timestamps are Unix epoch seconds. Etsy also returns legacy duplicate fields, create_timestamp alongside created_timestamp and update_timestamp alongside updated_timestamp. Prefer the past tense forms.
  • The buyer address is real, unmasked PII, split into components and also flattened into formatted_address with embedded newlines. buyer_email is available only with the right scope and may be absent.
  • status and the boolean flags are parallel representations of the same thing. The flags, is_paid and is_shipped, are the reliable pair; the status string has historically carried values such as Open, Paid, Completed, Payment Processing and Canceled and its exact enumeration is medium confidence.
  • There is no total_vat_cost on every marketplace, and Etsy collects and remits tax in many jurisdictions. Tax that Etsy remitted is not seller revenue.

Order items, which Etsy calls transactions

Transactions also arrive embedded in the receipt, which is the cheaper route: read receipts with their transactions rather than making a second call per receipt.

{
  "transaction_id": 3456789012,
  "receipt_id": 2456789012,
  "listing_id": 1122334455,
  "product_id": 9988776655,
  "sku": "TSHIRT-BLK-M",
  "title": "Organic Cotton T Shirt",
  "quantity": 1,
  "transaction_type": "listing",
  "is_digital": false,
  "seller_user_id": 12345678,
  "buyer_user_id": 87654321,
  "created_timestamp": 1758440000,
  "paid_timestamp": 1758440100,
  "shipped_timestamp": null,
  "price": { "amount": 1900, "divisor": 100, "currency_code": "USD" },
  "shipping_cost": { "amount": 500, "divisor": 100, "currency_code": "USD" },
  "shipping_profile_id": 223344556,
  "min_processing_days": 1,
  "max_processing_days": 3,
  "expected_ship_date": 1758699200,
  "variations": [
    {
      "property_id": 200,
      "value_id": 1213,
      "formatted_name": "Colour",
      "formatted_value": "Black"
    }
  ],
  "buyer_coupon": 0,
  "shop_coupon": 0
}

Three identifiers to carry: listing_id is the listing, product_id is the specific variant inside it, and sku is the seller's own code, which may be empty because Etsy does not require a SKU. The join key into our products table is sku where present and product_id otherwise.

price is the unit price here, not the line total, which is the opposite convention to Walmart. Multiply by quantity. min_processing_days, max_processing_days and expected_ship_date are Etsy specific and genuinely useful: long processing times are normal on this channel, so a late shipment alert built on the order date rather than on expected_ship_date will produce constant false positives.

Products and listings

Paths and operation ids confirmed from the published OpenAPI specification on 2026-09-21: createDraftListing, getListingsByShop, getListing, deleteListing, findAllListingsActive, findAllActiveListingsByShop, uploadListingFile, getAllListingFiles, getListingFile, deleteListingFile, deleteListingImage.

Listing reads support an includes parameter to pull associated objects in one call, covering shipping, images, shop, user, translations, inventory and videos. Use it: pulling inventory alongside the listing halves the call count, which matters directly against a daily allowance.

{
  "listing_id": 1122334455,
  "shop_id": 12345678,
  "user_id": 12345678,
  "title": "Organic Cotton T Shirt, Hand Printed",
  "state": "active",
  "quantity": 24,
  "url": "https://www.etsy.com/listing/1122334455/organic-cotton-t-shirt-hand-printed",
  "price": { "amount": 1900, "divisor": 100, "currency_code": "USD" },
  "taxonomy_id": 1234,
  "shop_section_id": 33445566,
  "shipping_profile_id": 223344556,
  "processing_min": 1,
  "processing_max": 3,
  "who_made": "i_did",
  "when_made": "made_to_order",
  "is_supply": false,
  "is_customizable": true,
  "is_personalizable": true,
  "tags": ["organic cotton", "hand printed"],
  "materials": ["cotton"],
  "num_favorers": 42,
  "created_timestamp": 1750000000,
  "updated_timestamp": 1758000000,
  "ending_timestamp": 1760592000
}

state carries active, inactive, draft, expired, sold_out and edit. The distinction that matters for our listings.status is between inactive, which the seller chose, and expired, which happened because the four month listing period ran out without renewal. Both look dead but only one is a seller decision. sold_out is an active listing with zero stock.

ending_timestamp has no equivalent on any other channel in this catalogue. Etsy listings expire; a connector that reports a healthy catalogue while listings quietly expire is worse than useless. Surface the expiry date.

who_made, when_made and is_supply are the mandatory provenance attributes that enforce Etsy's handmade policy. Any listing creation flow must supply them.

Inventory

Etsy's inventory model is the most expressive in this catalogue and the least like the others. A listing contains products, one per combination of variation values, and each product contains offerings, each with its own price, quantity and enabled flag. The three arrays price_on_property, quantity_on_property and sku_on_property declare which variation properties price, stock and SKU actually vary by, which is how Etsy allows colour to change the price while size does not.

{
  "products": [
    {
      "product_id": 9988776655,
      "sku": "TSHIRT-BLK-M",
      "is_deleted": false,
      "offerings": [
        {
          "offering_id": 5544332211,
          "quantity": 24,
          "is_enabled": true,
          "is_deleted": false,
          "price": { "amount": 1900, "divisor": 100, "currency_code": "USD" }
        }
      ],
      "property_values": [
        {
          "property_id": 200,
          "property_name": "Colour",
          "scale_id": null,
          "value_ids": [1213],
          "values": ["Black"]
        },
        {
          "property_id": 100,
          "property_name": "Size",
          "scale_id": 301,
          "value_ids": [1444],
          "values": ["M"]
        }
      ]
    }
  ],
  "price_on_property": [200],
  "quantity_on_property": [200, 100],
  "sku_on_property": [200, 100]
}

There is a single quantity per offering and no location dimension, so our inventory.location_id is always null for Etsy, and quantity_reserved and quantity_inbound have no source.

Shipments and tracking

Shipments are an array on the receipt, not a resource. Each entry carries receipt_shipping_id, carrier_name, tracking_code and shipment_notification_timestamp. There are no carrier scan events, no delivery timestamp and no shipping cost paid by the seller, though total_shipping_cost records what the buyer paid.

Writing tracking back is POST /v3/application/shops/{shop_id}/receipts/{receipt_id}/tracking, confirmed from the fulfilment tutorial on 2026-09-21. It requires tracking_code and carrier_name, with carrier values such as fedex, ups and usps, and it needs the transactions_w scope. The tutorial recommends also sending package details (mail class, weight, dimensions, ship date), label cost fields, and for international shipments the origin and destination countries, the incoterm and customs data including country of origin, declared value and HS codes. A successful call sends the buyer a shipping notification email, so this is not a silent write: never call it as part of a backfill.

Returns and cancellations

There is no returns object in Open API v3. Etsy handles cases, refunds and cancellations through Seller Dashboard and the Etsy messaging system, and none of that is exposed. What can be inferred: a cancelled receipt through the was_canceled filter and the receipt status, and a refund through the ledger, where a refund appears as a negative entry referencing the original payment.

Our returns table therefore cannot be populated from Etsy. Record refunds from the ledger as settlement adjustments and note the gap.

Payments and settlements

Confidence medium on these paths: the reference page did not render, and they come from the published resource groups plus long standing behaviour rather than from a read operation definition.

The ledger is the right grain for our settlements table. Each entry has an amount, a currency, a running balance, a ledger type describing what happened, a reference type and reference id pointing at the payment or refund it came from, and a creation timestamp. The payments endpoint then gives the gross, fees and net for an individual sale, split into posted and adjusted figures.

{
  "entry_id": 445566778,
  "ledger_id": 998877,
  "sequence_number": 1421,
  "amount": 2565,
  "currency": "USD",
  "description": "Order #2456789012",
  "balance": 41230,
  "created_timestamp": 1758440200,
  "ledger_type": "Sale",
  "reference_type": "payment",
  "reference_id": "778899001"
}

Note that the ledger amount is a bare integer with a sibling currency, not the amount and divisor object used elsewhere in the API. Two money representations in one API is a real trap; write one parser per shape and pick it per endpoint, not per field name.

Fee types to expect in the ledger: listing fee, transaction fee, payment processing fee, Etsy Ads, Offsite Ads fee, shipping label purchase, refund and deposit. The Offsite Ads fee is the one that surprises sellers, because it is charged on orders that came through Etsy's external advertising and is compulsory above a revenue threshold.

Customers

No customer resource. What exists is buyer_user_id on the receipt and on each transaction, which is stable for the same buyer across orders, plus the unmasked name, address and, with email_r, the buyer email. That is enough to populate customers with a real identifier, which puts Etsy ahead of TikTok Shop and level with eBay.

Locations

No locations resource and no multi warehouse model. The shop has one address, readable through GET /v3/application/shops/{shop_id}. Shipping origins are expressed through shipping profiles at /v3/application/shops/{shop_id}/shipping-profiles, which carry the origin country and postal code. Map the shop itself to a single locations row.

Writing back: listings, price and stock

Price and stock

Both live on the offering, so both are written by the same call:

PUT https://api.etsy.com/v3/application/listings/1122334455/inventory
x-api-key: {keystring}:{shared_secret}
Authorization: Bearer {access_token}
Content-Type: application/json

{
  "products": [
    {
      "sku": "TSHIRT-BLK-M",
      "property_values": [
        { "property_id": 200, "value_ids": [1213], "values": ["Black"] },
        { "property_id": 100, "scale_id": 301, "value_ids": [1444], "values": ["M"] }
      ],
      "offerings": [
        { "price": 17.99, "quantity": 30, "is_enabled": true }
      ]
    }
  ],
  "price_on_property": [200],
  "quantity_on_property": [200, 100],
  "sku_on_property": [200, 100]
}
Warning

updateListingInventory is a full replacement, not a patch. The documentation states that "the entire set of products (based on variations) must be in the products array". Sending one variant to change its price deletes every other variant of that listing. Always read the current inventory, modify it in memory, and write the whole structure back. This is the single most destructive call in this API.

Note also that price in the request body is a plain decimal number, while price in the response is an amount and divisor object. The asymmetry is deliberate and it will break a round trip that reuses the read structure for the write.

The response is synchronous, so no polling is needed. product_id and offering_id values are regenerated on each update, so they cannot be used as stable keys across writes; sku is the only durable handle.

Listing content and state

Creating a listing is a sequence, confirmed from the listings tutorial on 2026-09-21:

  1. POST /v3/application/shops/{shop_id}/listings creates a draft, with the mandatory quantity, title, description, price, who_made, when_made, taxonomy_id and shipping_profile_id.
  2. uploadListingImage adds at least one image. The documentation is explicit that "all published listings require at least one listing image", so a publish before an upload will fail.
  3. PUT or PATCH the listing with state set to active to publish it. From that moment the listing fee applies and the four month expiry clock starts.

updateListing also handles deactivating, renewing and moving a listing between shop sections. Individual listing properties, that is the structured attributes for a taxonomy, are written through PUT /v3/application/shops/{shop_id}/listings/{listing_id}/properties/{property_id}.

Webhooks and notifications

There are none. Etsy Open API v3 publishes no webhook, push notification, event stream or polling hint mechanism. This is a design constraint, not an oversight to work around, and it has to be planned for from the start.

The consequence is that freshness is bought entirely with call budget, and call budget is capped per day per app across all connected shops. A workable schedule for a shop of a few hundred listings:

That lands around 130 calls per shop per day at a 15 minute order cadence. Against a 10,000 per day allowance that supports roughly 70 shops before the daily ceiling binds, which is the number to design the tenancy model around. Slowing the receipt poll to 30 minutes roughly doubles it.

Rate limits and pagination

Confirmed from the rate limits page read on 2026-09-21:

  • Limits are enforced per application, that is per API key, not per shop. Two dimensions apply: queries per second and queries per day, evaluated QPS first and then QPD.
  • The daily limit uses a progressive sliding window, dividing 24 hours into buckets. As an old bucket leaves the window its quota becomes immediately available again. There is no midnight reset to schedule around, and a burst does not lock the app out until the next calendar day.
  • Successful responses carry four headers: x-limit-per-second, x-remaining-this-second, x-limit-per-day and x-remaining-today. Read them on every response and drive the limiter from them rather than from a configured constant.
  • Exceeding a limit returns HTTP 429 with a retry-after header giving the estimated wait in seconds. Honour it.
  • The page itself does not print the numeric allowances and points at the developer portal for the app's actual figures. Etsy's long standing published defaults are 10,000 queries per day and 10 queries per second for an approved app; treat those as the planning figures, mark them medium confidence, and read the real values from the response headers at runtime.

Pagination is limit and offset with a count and results envelope throughout. limit caps at 100. There is no cursor, so date windowing is the only safe way to page a large or changing set.

Because the daily allowance is per app rather than per shop, the scheduler must be central. A per connection schedule will let one busy shop consume the allowance that every other shop needs.

Mapping to the unified model

orders

order_items

listings and inventory

shipments and settlements

Gaps and open questions

  • The API reference at developers.etsy.com renders from JavaScript and served navigation only. Operation parameter lists and response schemas were not read directly, so field names on this page come from the published OpenAPI specification, which itself truncated before the receipt and ledger schemas. Regenerate a client from that specification and diff it against this page before implementing.
  • The x-api-key format is ambiguous. The quickstart documents keystring:sharedsecret, while much working code sends the keystring alone. Resolve this at first call rather than by reading more documentation.
  • Exact numeric rate limits are not printed in the documentation, only in the developer portal per app. The 10,000 per day and 10 per second figures used for planning here are the long standing defaults, not a 2026 reading.
  • There are no returns objects at all. Whether refund and case data can be reached any other way, for example through a Seller Dashboard export, is unresolved, and until then our returns table has no Etsy source.
  • The ledger uses a bare integer amount with a sibling currency while the rest of the API uses an amount and divisor object. Whether any other endpoint shares the bare integer form has not been checked.
  • Receipt status enumeration and the exact set of ledger_type values are medium confidence.
  • There is no sandbox. Any write path has to be exercised against a real shop, which means draft listings and a test shop with nothing published.
  • Etsy owns Depop, which has its own separate API situation and needs its own page. Elo7 was wound down in 2024.

Sources

  • Etsy Open API v3 authentication essentials, read 2026-09-21: the connect URL, https://api.etsy.com/v3/public/oauth/token, PKCE requirements and verifier length, the three step authorization code flow, the token response shape, the 3,600 second access token and 90 day refresh token, and the x-api-key header format
  • Etsy Open API v3 rate limits, read 2026-09-21: per API key enforcement, QPS then QPD evaluation order, the four quota headers, HTTP 429 with retry-after, and the progressive sliding window
  • Etsy Open API v3 quickstart, read 2026-09-21: the https://api.etsy.com/v3/ base URL, the combined x-api-key header, the keystring doubling as client_id, and the user id prefix on the access token
  • Etsy listings tutorial, read 2026-09-21: createDraftListing, uploadListingImage, publishing through updateListing with state, and the updateListingInventory request structure with products, offerings and property values, including the full replacement requirement
  • Etsy fulfilment tutorial, read 2026-09-21: createReceiptShipment at /v3/application/shops/{shop_id}/receipts/{receipt_id}/tracking, its required tracking_code and carrier_name, the transactions_w scope, the optional package, label and customs fields, and the buyer notification email
  • Etsy Open API OpenAPI specification, read 2026-09-21: confirmed path templates and operation ids for listings, listing files, listing images and getShopReceipts at /v3/application/shops/{shop_id}/receipts
  • Etsy Open API v3 reference, attempted 2026-09-21: rendered navigation only, no operation detail