Use the Etsy v3 API to update listing quantities automatically. Listen for order.paid webhooks to decrement stock after each sale, and use PUT /listings/{listing_id}/inventory to sync quantities from your master inventory source. This prevents overselling and eliminates manual stock updates.
| Fact | Value |
|---|---|
| Platform | Etsy |
| Auth method | OAuth 2.0 with mandatory PKCE |
| Rate limits | 10 req/sec, 10,000 req/day (new apps: 5/sec, 5,000/day) |
| Difficulty | Intermediate |
| Time required | 45–60 minutes |
| Last updated | May 2026 |
API Quick Reference
OAuth 2.0 with mandatory PKCE
10 req/sec, 10,000 req/day (new apps: 5/sec, 5,000/day)
JSON
REST only
API overview
https://openapi.etsy.com/v3/applicationAuthentication
Every Etsy v3 API request requires both Authorization: Bearer {access_token} and x-api-key: {keystring} headers. Access tokens expire in 1 hour. Refresh tokens are valid for 90 days and rotate on each use. Store the latest refresh token after every refresh call.
Key endpoints
/listings/{listing_id}/inventoryReturns current inventory for a listing, including quantity, SKU, and price for each product variant. Use this before updating to read current state.
| Parameter | Type | Required | Description |
|---|---|---|---|
| optional | ||
| optional |
/listings/{listing_id}/inventoryReplaces the entire inventory for a listing. You must send ALL products and their offerings — partial updates are not supported. Read current inventory first, modify quantities, then PUT the full object.
| Parameter | Type | Required | Description |
|---|---|---|---|
| optional | ||
| optional | ||
| optional | ||
| optional | ||
| optional |
/listings/{listing_id}Returns listing state, including quantity and state (active/inactive). A listing with quantity=0 automatically moves to inactive state.
| Parameter | Type | Required | Description |
|---|---|---|---|
| optional |
/shops/{shop_id}/listings/activeReturns all active listings for the shop. Use for full catalog inventory sync when initializing your local inventory database.
| Parameter | Type | Required | Description |
|---|---|---|---|
| optional | ||
| optional | ||
| optional |
Step-by-step automation
Build a Local Inventory Map from All Active Listings
Initialize your inventory database by fetching all active listings and their current stock levels. Store listing_id, sku, and quantity locally so you can track changes without re-fetching every time.
1# Fetch all active listings (paginated)2curl -X GET "https://openapi.etsy.com/v3/application/shops/$SHOP_ID/listings/active?limit=100" \3 -H "Authorization: Bearer $ETSY_ACCESS_TOKEN" \4 -H "x-api-key: $ETSY_KEYSTRING"56# Get inventory for a specific listing7curl -X GET "https://openapi.etsy.com/v3/application/listings/$LISTING_ID/inventory" \8 -H "Authorization: Bearer $ETSY_ACCESS_TOKEN" \9 -H "x-api-key: $ETSY_KEYSTRING"Decrement Stock on Order.paid Webhook
When an order.paid webhook fires, fetch the receipt to get purchased listing IDs and quantities, then update each affected listing's inventory by decrementing the sold quantity. The PUT /inventory endpoint requires sending the full products array — read first, modify, then PUT.
1# First GET current inventory, then PUT updated inventory2# GET3curl -X GET "https://openapi.etsy.com/v3/application/listings/$LISTING_ID/inventory" \4 -H "Authorization: Bearer $ETSY_ACCESS_TOKEN" \5 -H "x-api-key: $ETSY_KEYSTRING"67# PUT with decremented quantity8curl -X PUT "https://openapi.etsy.com/v3/application/listings/$LISTING_ID/inventory" \9 -H "Authorization: Bearer $ETSY_ACCESS_TOKEN" \10 -H "x-api-key: $ETSY_KEYSTRING" \11 -H "Content-Type: application/json" \12 -d '{13 "products": [{14 "product_id": 1234,15 "sku": "MUG-BLUE-SM",16 "offerings": [{17 "offering_id": 5678,18 "quantity": 12,19 "is_enabled": true,20 "price": {"amount": 2999, "divisor": 100, "currency_code": "USD"}21 }]22 }]23 }'Sync Inventory from External Source (Warehouse or Spreadsheet)
For multi-channel sellers, sync Etsy quantities from a master inventory source. Map your SKUs to Etsy listing IDs, then push updated quantities via the inventory PUT endpoint. Run this on a schedule (every 15-30 minutes or after each warehouse update).
1# Sync a batch of listings from a JSON file2# inventory-updates.json: [{"listing_id": 9876, "new_quantity": 25}, ...]3cat inventory-updates.json | python3 -c "4import sys, json, subprocess, os5updates = json.load(sys.stdin)6for item in updates:7 print(f'Updating listing {item[\"listing_id\"]} to qty {item[\"new_quantity\"]}')"Monitor for Zero-Stock Listings and Re-enable When Restocked
Etsy automatically deactivates listings when quantity hits 0. When you restock, you must explicitly set quantity > 0 AND is_enabled=true in the PUT request. Build a polling job to check for deactivated listings that have been restocked.
1# Get inactive listings that need to be re-enabled2curl -X GET "https://openapi.etsy.com/v3/application/shops/$SHOP_ID/listings?state=inactive&limit=100" \3 -H "Authorization: Bearer $ETSY_ACCESS_TOKEN" \4 -H "x-api-key: $ETSY_KEYSTRING"Complete working code
Complete Etsy inventory automation: webhook listener to decrement on sale + scheduled sync from a master inventory source (JSON file or database).
Error handling
The PUT /inventory endpoint is a full replacement — if you omit any product or offering from the products array, those variants are deleted.
Always GET the current inventory first, modify only the quantity/is_enabled fields you intend to change, and PUT back the complete products array unchanged except for your modifications. Never construct the products array from scratch without reading first.
For large catalog syncs with hundreds of listings, the 1-hour access token can expire before the job finishes.
Implement per-request token refresh check. Before each API call, compare the current time to token_expiry - 60 seconds and refresh if needed. This is critical for any sync job that runs longer than 50 minutes.
Etsy automatically sets state=inactive when quantity hits 0. Simply updating quantity back to > 0 via PUT inventory re-enables the offerings, but you must explicitly set is_enabled=true.
When restocking a listing, set both quantity = new_stock AND is_enabled = true in the PUT request. The listing state will revert to active automatically once inventory has enabled offerings with quantity > 0.
Each listing requires 2 API calls (GET + PUT), so syncing 100 listings = 200 calls. At 10/sec that's 20 seconds minimum, but add processing time and you can easily spike above the limit.
Add 200ms minimum delay between each listing update (GET + PUT pair). This reduces throughput to ~5 listings/sec, keeping you well under the 10 req/sec limit.
If an order.paid webhook fires while your scheduled inventory sync is running, two processes may read the same inventory and apply conflicting writes.
Use a distributed lock (Redis SETNX or database row lock) before reading/writing inventory for a listing. Alternatively, serialize all inventory writes through a single queue.
Rate limits & throttling
Security checklist
- Store ETSY_KEYSTRING and tokens in environment variables — never in source code or version control
- Verify webhook signatures using HMAC-SHA256 and constant-time comparison before processing any inventory change
- Use express.raw() for webhook endpoints to preserve raw body for signature verification
- Save the rotated refresh token after every OAuth token refresh
- Use minimum required scopes: listings_r, listings_w, transactions_r
- Implement a distributed lock before inventory read-modify-write operations to prevent race conditions
- Log all inventory changes with timestamps and receipt IDs for audit trail
Automation use cases
Multi-Channel Inventory Sync
Keep Etsy in sync with your Shopify, Amazon, or WooCommerce stores. After each sale on any channel, push the updated master quantity to all channels via their respective APIs.
Oversell Prevention
Decrement Etsy inventory immediately on order.paid webhook — before the order is even fulfilled — to prevent selling items you don't have in stock.
Restock Alert and Auto-Reactivation
When new inventory arrives and your warehouse system is updated, automatically push the new quantities to Etsy and re-enable any listings that were deactivated due to zero stock.
Low Stock Warning
After each inventory update, check if any listing quantity drops below a threshold (e.g., 3 units) and send a Slack or email alert to trigger a reorder.
No-code alternatives
Don't want to write code? These platforms can automate the same workflows visually.
Zapier
Zapier's Etsy integration can trigger on new orders, but updating listing inventory requires the HTTP action to call the Etsy API directly. Complex for multi-step read-modify-write inventory updates.
Make (Integromat)
More suitable than Zapier for multi-step inventory operations. Can chain GET inventory → modify → PUT with the HTTP module. Supports error handling and retries.
n8n
Self-hosted n8n with HTTP Request nodes can handle the full read-modify-write inventory pattern. Code nodes allow custom logic for multi-channel sync and SKU mapping.
Best practices
- Always GET inventory before PUT — never construct the products array from scratch as you'll delete unlisted variants
- Implement distributed locking for inventory writes to prevent race conditions when webhooks fire during batch syncs
- Add a 200ms delay between each listing update pair (GET+PUT) to stay within rate limits during bulk operations
- Cache Etsy listing IDs mapped to your SKUs locally to avoid extra API calls on every update
- Set is_enabled=true explicitly when restocking a listing that went to zero — setting quantity alone may not re-enable offerings
- Log every inventory change with receipt_id, listing_id, old_quantity, new_quantity, and timestamp for debugging
- Implement a polling fallback for the order.paid webhook — run a scheduled job every 30 minutes to catch any missed webhook events
- Test your sync logic with a single test listing before running a full catalog sync
Ask AI to help
Copy one of these prompts to get a personalized, working implementation.
Help me build a Node.js inventory sync system for Etsy that: (1) listens for order.paid webhooks (verify signature with HMAC-SHA256 on raw body), (2) for each order, fetches receipt details to get listing IDs and quantities sold, (3) for each listing, calls GET /listings/{listing_id}/inventory, decrements the quantity sold, and PUTs the full modified products array back — IMPORTANT: the PUT requires the complete products array, not just the changed offering, (4) handles 429 rate limits with 200ms delays. Include both Authorization: Bearer and x-api-key headers. Access tokens expire in 1 hour so include automatic refresh.
Build me an Etsy inventory management dashboard that: (1) shows all active listings with current stock levels fetched from GET /shops/{shop_id}/listings/active, (2) has an inline edit field to update quantity for each listing, (3) when quantity is changed, calls GET /listings/{listing_id}/inventory then PUT with the full modified products array (required by Etsy), (4) shows a warning badge on listings with quantity < 5. Use Supabase to store the listing inventory data and the Etsy OAuth tokens. Include both Authorization: Bearer and x-api-key headers on every API call.
Frequently asked questions
Does the PUT inventory endpoint support partial updates — can I just change one variant's quantity?
No. The PUT /listings/{listing_id}/inventory endpoint is a full replacement. You must include ALL products and their offerings in the request body. If you omit a product or offering, it is deleted. Always GET the current inventory first, modify only the fields you need to change, and PUT the complete object back.
What happens to a listing when inventory hits zero?
Etsy automatically sets the listing to inactive state when quantity reaches 0. The listing disappears from search results and shop pages. To reactivate it when you restock, PUT the inventory with quantity > 0 AND is_enabled=true. The listing returns to active state automatically.
Can I sync inventory by SKU instead of listing_id?
Etsy's API uses listing_id as the primary identifier. SKUs are stored within the products array of each listing's inventory. You'll need to maintain a local SKU-to-listing_id mapping table. Build this mapping during initial setup by fetching all listings and their inventory.
Is there a way to lock a listing to prevent overselling during high-traffic periods?
Etsy doesn't have a reservation or lock concept. The most reliable oversell prevention is to use the order.paid webhook to decrement inventory immediately after each sale — before fulfillment — and to use a distributed lock in your own system when updating inventory to prevent concurrent write conflicts.
How many API calls does a full inventory sync cost?
Each listing requires 2 API calls: GET inventory + PUT inventory. For a 100-listing shop, that's 200 calls per full sync cycle. With the 10,000/day limit, you can run about 50 full syncs per day. In practice, use webhook-driven updates for real-time accuracy and full syncs less frequently (every few hours).
Can RapidDev help me build multi-channel inventory sync across Etsy, Shopify, and Amazon?
Yes. RapidDev can design and build a centralized inventory hub that syncs stock levels across Etsy, Shopify, Amazon, and other platforms in real time, preventing overselling across all channels simultaneously.
What scopes do I need to request for inventory management?
You need listings_r and listings_w to read and write listing inventory. If you're using webhooks triggered by orders, you also need transactions_r. Request the minimum scopes needed — avoid requesting write scopes for workflows that only read data.
Need this automated?
Our team has built 600+ apps with API automations. We can build this for you.
Book a free consultation