TL;DR
The Etsy API enables developers to build applications that interact with Etsy’s marketplace. It uses OAuth 2.0 authentication, RESTful endpoints for shops, listings, orders, and inventory management, with rate limits of 10 calls per second per app. This guide covers authentication setup, core endpoints, webhook integration, and production deployment strategies.
Introduction
Etsy processes over $13 billion in annual gross merchandise sales across 230+ countries. For developers building e-commerce tools, inventory management systems, or analytics platforms, Etsy API integration isn’t optional—it’s essential.
Here’s the reality: sellers managing multiple sales channels lose 15-20 hours weekly on manual data entry. A solid Etsy API integration automates listing synchronization, order processing, and inventory updates across platforms.
This guide walks through the complete Etsy API integration process. You’ll learn OAuth 2.0 authentication, shop and listing management, order processing, webhook handling, and error troubleshooting. By the end, you’ll have a production-ready Etsy integration.
What Is the Etsy API?
Etsy provides a RESTful API for accessing marketplace data and managing seller operations. The API handles:
- Shop and profile information retrieval
- Listing creation, updates, and inventory management
- Order processing and fulfillment tracking
- Customer and transaction data access
- Shipping profiles and tax calculations
- Image and media upload management
Key Features
| Feature | Description |
|---|---|
| RESTful Design | Standard HTTP methods with JSON responses |
| OAuth 2.0 | Secure authentication with access token refresh |
| Webhooks | Real-time notifications for order and listing events |
| Rate Limiting | 10 requests per second per app (with burst allowance) |
| Sandbox Support | Test environment for development without live data |
API Architecture Overview
Etsy uses a versioned REST API structure:
https://openapi.etsy.com/v3/application/
Version 3 (v3) is the current standard, offering improved OAuth 2.0 support and simplified endpoint structures compared to v2.
API Versions Compared
| Version | Status | Authentication | Use Case |
|---|---|---|---|
| V3 | Current | OAuth 2.0 | All new integrations |
| V2 | Deprecated | OAuth 1.0a | Legacy apps only |
| V1 | Retired | N/A | Do not use |
Migrate any V2 integrations to V3 immediately. Etsy announced V2 deprecation with full retirement scheduled for late 2026.
Getting Started: Authentication Setup
Step 1: Create Your Etsy Developer Account
Before accessing the API, you need a developer account:
- Visit the Etsy Developer Portal
- Sign in with your Etsy account (or create one)
- Navigate to Your Apps in the developer dashboard
- Click Create a new app
Step 2: Register Your Application
Fill out the app registration form:
- App Name: Clear, descriptive name (visible to users during OAuth)
- App Description: Explain what your app does and who uses it
- Redirect URI: Where Etsy sends users after authentication (must use HTTPS)
- Production/Development: Start with development mode for testing
After submission, you’ll receive:
- Key String: Your public API identifier
- Shared Secret: Your private API secret (never expose this)
Security note: Store credentials in environment variables, never in code:
# .env file
ETSY_KEY_STRING="your_key_string_here"
ETSY_SHARED_SECRET="your_shared_secret_here"
ETSY_ACCESS_TOKEN="generated_via_oauth"
ETSY_REFRESH_TOKEN="generated_via_oauth"
Step 3: Understand OAuth 2.0 Flow
Etsy uses OAuth 2.0 for authentication. Here’s the complete flow:
1. User clicks "Connect with Etsy" in your app
2. Your app redirects to Etsy authorization URL
3. User logs in and grants permissions
4. Etsy redirects back with authorization code
5. Your app exchanges code for access token
6. Your app uses access token for API calls
7. Refresh token when access token expires (1 hour)
Step 4: Implement OAuth Authorization
Generate the authorization URL:
const generateAuthUrl = (clientId, redirectUri, state) => {
const baseUrl = 'https://www.etsy.com/oauth/connect';
const params = new URLSearchParams({
client_id: clientId,
redirect_uri: redirectUri,
scope: 'listings_r listings_w orders_r orders_w shops_r',
state: state, // Random string for CSRF protection
response_type: 'code'
});
return `${baseUrl}?${params.toString()}`;
};
// Usage
const authUrl = generateAuthUrl(
process.env.ETSY_KEY_STRING,
'https://your-app.com/callback',
crypto.randomBytes(16).toString('hex')
);
console.log(`Redirect user to: ${authUrl}`);
Required Scopes
Request only the permissions your app needs:
| Scope | Description | Use Case |
|---|---|---|
listings_r |
Read listings | Display products, sync inventory |
listings_w |
Write listings | Create/update products |
orders_r |
Read orders | Order management, fulfillment |
orders_w |
Write orders | Update order status, add tracking |
shops_r |
Read shop info | Display shop profile, analytics |
transactions_r |
Read transactions | Financial reporting |
email |
Access buyer email | Order communication |
Step 5: Exchange Code for Access Token
Handle the OAuth callback and exchange the authorization code:
const exchangeCodeForToken = async (code, redirectUri) => {
const response = await fetch('https://api.etsy.com/v3/public/oauth/token', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
grant_type: 'authorization_code',
client_id: process.env.ETSY_KEY_STRING,
client_secret: process.env.ETSY_SHARED_SECRET,
redirect_uri: redirectUri,
code: code
})
});
const data = await response.json();
// Store these securely in your database
return {
access_token: data.access_token,
refresh_token: data.refresh_token,
expires_in: data.expires_in, // Typically 3600 seconds (1 hour)
user_id: data.user_id,
scope: data.scope
};
};
// Handle callback route
app.get('/callback', async (req, res) => {
const { code, state } = req.query;
// Verify state matches what you sent (CSRF protection)
if (state !== req.session.oauthState) {
return res.status(400).send('Invalid state parameter');
}
try {
const tokens = await exchangeCodeForToken(code, 'https://your-app.com/callback');
// Store tokens in database associated with user
await db.users.update(req.session.userId, {
etsy_access_token: tokens.access_token,
etsy_refresh_token: tokens.refresh_token,
etsy_token_expires: Date.now() + (tokens.expires_in * 1000),
etsy_user_id: tokens.user_id
});
res.redirect('/dashboard');
} catch (error) {
console.error('Token exchange failed:', error);
res.status(500).send('Authentication failed');
}
});
Step 6: Implement Token Refresh
Access tokens expire after 1 hour. Implement automatic refresh:
const refreshAccessToken = async (refreshToken) => {
const response = await fetch('https://api.etsy.com/v3/public/oauth/token', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
grant_type: 'refresh_token',
client_id: process.env.ETSY_KEY_STRING,
client_secret: process.env.ETSY_SHARED_SECRET,
refresh_token: refreshToken
})
});
const data = await response.json();
// Update stored tokens
return {
access_token: data.access_token,
refresh_token: data.refresh_token, // Always save the new refresh token
expires_in: data.expires_in
};
};
// Middleware to ensure valid token before API calls
const ensureValidToken = async (userId) => {
const user = await db.users.findById(userId);
// Check if token expires within 5 minutes
if (user.etsy_token_expires < Date.now() + 300000) {
const newTokens = await refreshAccessToken(user.etsy_refresh_token);
await db.users.update(userId, {
etsy_access_token: newTokens.access_token,
etsy_refresh_token: newTokens.refresh_token,
etsy_token_expires: Date.now() + (newTokens.expires_in * 1000)
});
return newTokens.access_token;
}
return user.etsy_access_token;
};
Step 7: Make Authenticated API Calls
Include the access token in every request:
const makeEtsyRequest = async (endpoint, options = {}) => {
const accessToken = await ensureValidToken(options.userId);
const response = await fetch(`https://openapi.etsy.com/v3/application${endpoint}`, {
...options,
headers: {
'Authorization': `Bearer ${accessToken}`,
'x-api-key': process.env.ETSY_KEY_STRING,
'Accept': 'application/json',
'Content-Type': 'application/json',
...options.headers
}
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Etsy API Error: ${error.message}`);
}
return response.json();
};
Shop Management Endpoints
Retrieving Shop Information
Fetch shop details, policies, and settings:
const getShopInfo = async (shopId) => {
const response = await makeEtsyRequest(`/shops/${shopId}`, {
method: 'GET'
});
return response;
};
// Usage
const shop = await getShopInfo(12345678);
console.log(`Shop: ${shop.title}`);
console.log(`Currency: ${shop.currency_code}`);
console.log(`Listings count: ${shop.num_listings_active}`);
Expected Shop Response
{
"shop_id": 12345678,
"shop_name": "MyHandmadeShop",
"title": "Handmade Jewelry & Accessories",
"announcement": "Welcome! Free shipping on orders over $50",
"currency_code": "USD",
"is_vacation": false,
"vacation_message": null,
"sale_message": "Thank you for supporting small businesses!",
"digital_sale_message": null,
"created_timestamp": 1609459200,
"updated_timestamp": 1710950400,
"num_listings_active": 127,
"num_listings_sold": 1543,
"gaussian_alphas": {
"overall": 4.8,
"last_30_days": 4.9
},
"vacation_autoreply": null,
"url": "https://www.etsy.com/shop/MyHandmadeShop",
"image_url_760x100": "https://i.etsystatic.com/.../banner_760x100.jpg"
}
Retrieving Shop Sections
Organize listings by sections:
const getShopSections = async (shopId) => {
const response = await makeEtsyRequest(`/shops/${shopId}/sections`, {
method: 'GET'
});
return response;
};
// Example response
{
"count": 5,
"results": [
{
"shop_section_id": 12345,
"title": "Necklaces",
"rank": 1,
"num_listings": 23
},
{
"shop_section_id": 12346,
"title": "Earrings",
"rank": 2,
"num_listings": 45
}
]
}
Listing Management
Creating a New Listing
Create a product listing with images and variations:
const createListing = async (shopId, listingData) => {
const payload = {
title: listingData.title,
description: listingData.description,
price: listingData.price.toString(), // Must be string
quantity: listingData.quantity,
sku: listingData.sku || [],
tags: listingData.tags.slice(0, 13), // Max 13 tags
category_id: listingData.categoryId,
shop_section_id: listingData.sectionId,
state: listingData.state || 'active', // active, inactive, draft
who_made: listingData.whoMade, // i_did, someone_else, collective
when_made: listingData.whenMade, // 2020_2026, 2010_2019, etc.
is_supply: listingData.isSupply, // true for craft supplies
item_weight: listingData.weight || null,
item_weight_unit: listingData.weightUnit || 'g',
item_length: listingData.length || null,
item_width: listingData.width || null,
item_height: listingData.height || null,
item_dimensions_unit: listingData.dimensionsUnit || 'mm',
is_private: listingData.isPrivate || false,
recipient: listingData.recipient || null, // men, women, unisex, etc.
occasion: listingData.occasion || null, // birthday, wedding, etc.
style: listingData.style || [] // Max 2 styles
};
const response = await makeEtsyRequest(`/shops/${shopId}/listings`, {
method: 'POST',
body: JSON.stringify(payload)
});
return response;
};
// Usage example
const listing = await createListing(12345678, {
title: 'Sterling Silver Moon Phase Necklace',
description: 'Handcrafted sterling silver necklace featuring moon phases...',
price: 89.99,
quantity: 15,
sku: ['MOON-NECKLACE-001'],
tags: ['moon necklace', 'sterling silver', 'moon phase', 'celestial jewelry'],
categoryId: 10623, // Jewelry > Necklaces
sectionId: 12345,
state: 'active',
whoMade: 'i_did',
whenMade: 'made_to_order',
isSupply: false,
weight: 25,
weightUnit: 'g'
});
Uploading Listing Images
Images are uploaded separately after listing creation:
const uploadListingImage = async (listingId, imagePath, imagePosition = 1) => {
// Read image file as base64
const fs = require('fs');
const imageBuffer = fs.readFileSync(imagePath);
const base64Image = imageBuffer.toString('base64');
const payload = {
image: base64Image,
listing_image_id: null,
position: imagePosition,
is_watermarked: false,
alt_text: 'Handcrafted sterling silver moon phase necklace' // For accessibility
};
const response = await makeEtsyRequest(`/listings/${listingId}/images`, {
method: 'POST',
body: JSON.stringify(payload)
});
return response;
};
// Upload multiple images
const uploadListingImages = async (listingId, imagePaths) => {
const results = [];
for (let i = 0; i < imagePaths.length; i++) {
const result = await uploadListingImage(listingId, imagePaths[i], i + 1);
results.push(result);
}
return results;
};
Updating Listing Inventory
Update quantity for existing listings:
const updateListingInventory = async (shopId, listingId, inventory) => {
const payload = {
products: inventory.products.map(product => ({
sku: product.sku,
quantity: product.quantity
})),
is_over_selling: inventory.isOverSelling || false,
on_property: inventory.onProperty || []
};
const response = await makeEtsyRequest(
`/shops/${shopId}/listings/${listingId}/inventory`,
{
method: 'PUT',
body: JSON.stringify(payload)
}
);
return response;
};
// Usage
await updateListingInventory(12345678, 987654321, {
products: [
{ sku: 'MOON-NECKLACE-001', quantity: 10 },
{ sku: 'MOON-NECKLACE-002', quantity: 5 }
],
isOverSelling: false
});
Retrieving Listings
Fetch all listings or filter by status:
const getListings = async (shopId, options = {}) => {
const params = new URLSearchParams({
limit: options.limit || 25, // Max 100
offset: options.offset || 0
});
if (options.state) {
params.append('state', options.state); // active, inactive, draft, sold_out
}
const response = await makeEtsyRequest(
`/shops/${shopId}/listings?${params.toString()}`,
{ method: 'GET' }
);
return response;
};
// Get a single listing
const getListing = async (listingId) => {
const response = await makeEtsyRequest(`/listings/${listingId}`, {
method: 'GET'
});
return response;
};
Deleting a Listing
Remove a listing from your shop:
const deleteListing = async (listingId) => {
const response = await makeEtsyRequest(`/listings/${listingId}`, {
method: 'DELETE'
});
return response;
};
Order Management
Retrieving Orders
Fetch orders with filtering options:
const getOrders = async (shopId, options = {}) => {
const params = new URLSearchParams({
limit: options.limit || 25,
offset: options.offset || 0
});
if (options.status) {
params.append('status', options.status); // open, completed, cancelled
}
if (options.minLastModified) {
params.append('min_last_modified', options.minLastModified);
}
const response = await makeEtsyRequest(
`/shops/${shopId}/orders?${params.toString()}`,
{ method: 'GET' }
);
return response;
};
// Get a single order
const getOrder = async (shopId, orderId) => {
const response = await makeEtsyRequest(`/shops/${shopId}/orders/${orderId}`, {
method: 'GET'
});
return response;
};
Order Response Structure
{
"order_id": 1234567890,
"user_id": 98765432,
"shop_id": 12345678,
"buyer_user_id": 11223344,
"creation_timestamp": 1710864000,
"last_modified_timestamp": 1710950400,
"completed_timestamp": 1710950400,
"state": "complete",
"user_id_fob": null,
"is_guest": false,
"name": "Jane Doe",
"email": "jane.doe@email.com",
"buyer_phone_number": "+1-555-0123",
"total_price": "89.99",
"total_shipping_cost": "5.95",
"total_tax": "7.65",
"grand_total": "103.59",
"currency_code": "USD",
"payment_method": "credit_card",
"shipping_address": {
"name": "Jane Doe",
"address_line1": "123 Main Street",
"address_line2": "Apt 4B",
"city": "New York",
"state": "NY",
"zip": "10001",
"country": "US",
"phone": "+1-555-0123"
},
"listings": [
{
"listing_id": 987654321,
"title": "Sterling Silver Moon Phase Necklace",
"sku": ["MOON-NECKLACE-001"],
"quantity": 1,
"price": "89.99"
}
]
}
Updating Order Status
Mark orders as complete and add tracking:
const updateOrderStatus = async (shopId, orderId, trackingData) => {
const payload = {
carrier_id: trackingData.carrierId, // usps, fedex, ups, etc.
tracking_code: trackingData.trackingCode,
should_send_bcc_to_buyer: trackingData.notifyBuyer || true
};
const response = await makeEtsyRequest(
`/shops/${shopId}/orders/${orderId}/shipping`,
{
method: 'POST',
body: JSON.stringify(payload)
}
);
return response;
};
// Usage
await updateOrderStatus(12345678, 1234567890, {
carrierId: 'usps',
trackingCode: '9400111899223456789012',
notifyBuyer: true
});
Common Carrier IDs
| Carrier | Carrier ID |
|---|---|
| USPS | usps |
| FedEx | fedex |
| UPS | ups |
| DHL | dhl_express |
| Canada Post | canada_post |
| Royal Mail | royal_mail |
| Australia Post | australia_post |
Rate Limiting and Quotas
Understanding Rate Limits
Etsy enforces rate limits to protect API stability:
- Standard Limit: 10 requests per second per app
- Burst Allowance: Up to 50 requests in a single second (short bursts)
- Quota System: 10,000 calls per hour per app (resets hourly)
Exceeding limits results in HTTP 429 (Too Many Requests) responses.
Implementing Rate Limit Handling
Use exponential backoff for retries:
const makeRateLimitedRequest = async (endpoint, options = {}, maxRetries = 3) => {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await makeEtsyRequest(endpoint, options);
// Check rate limit headers
const remaining = response.headers.get('x-etsy-quota-remaining');
const resetTime = response.headers.get('x-etsy-quota-reset');
if (remaining < 100) {
console.warn(`Low quota remaining: ${remaining}, resets at ${resetTime}`);
}
return response;
} catch (error) {
if (error.message.includes('429') && attempt < maxRetries) {
// Exponential backoff: 1s, 2s, 4s
const delay = Math.pow(2, attempt) * 1000;
console.log(`Rate limited. Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
} else {
throw error;
}
}
}
};
Rate Limit Headers
Etsy includes these headers in every response:
| Header | Description |
|---|---|
x-etsy-quota-remaining |
Remaining calls in current hour |
x-etsy-quota-reset |
Unix timestamp when quota resets |
x-etsy-limit-remaining |
Remaining calls in current second |
x-etsy-limit-reset |
Unix timestamp when per-second limit resets |
Always log these headers for monitoring and debugging.
Webhook Integration
Configuring Webhooks
Etsy supports webhooks for real-time event notifications:
- Navigate to Your Apps in the developer dashboard
- Select your app
- Click Add Webhook
- Enter your HTTPS endpoint URL
- Select events to subscribe to
Available Webhook Events
| Event Type | Trigger | Use Case |
|---|---|---|
v3/shops/{shop_id}/orders/create |
New order placed | Send confirmation, begin fulfillment |
v3/shops/{shop_id}/orders/update |
Order status changed | Sync order status |
v3/shops/{shop_id}/listings/create |
New listing created | Update external inventory |
v3/shops/{shop_id}/listings/update |
Listing modified | Sync product data |
v3/shops/{shop_id}/listings/delete |
Listing removed | Remove from external systems |
Creating Webhook Handler
const express = require('express');
const crypto = require('crypto');
const app = express();
app.post('/webhooks/etsy', express.raw({ type: 'application/json' }), async (req, res) => {
const signature = req.headers['x-etsy-signature'];
const payload = req.body;
// Verify webhook signature
const isValid = verifyWebhookSignature(payload, signature, process.env.ETSY_WEBHOOK_SECRET);
if (!isValid) {
console.error('Invalid webhook signature');
return res.status(401).send('Unauthorized');
}
const event = JSON.parse(payload.toString());
// Route to appropriate handler
switch (event.type) {
case 'v3/shops/*/orders/create':
await handleNewOrder(event.data);
break;
case 'v3/shops/*/orders/update':
await handleOrderUpdate(event.data);
break;
case 'v3/shops/*/listings/create':
await handleListingCreated(event.data);
break;
case 'v3/shops/*/listings/update':
await handleListingUpdated(event.data);
break;
case 'v3/shops/*/listings/delete':
await handleListingDeleted(event.data);
break;
default:
console.log('Unhandled event type:', event.type);
}
// Acknowledge receipt within 5 seconds
res.status(200).send('OK');
});
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expectedSignature, 'hex')
);
}
Webhook Best Practices
- Verify signatures - Prevents spoofed webhooks
- Return 200 OK quickly - Etsy retries on non-200 responses within 5 seconds
- Process asynchronously - Queue events for background processing
- Implement idempotency - Handle duplicate webhook deliveries
- Log all events - Debug issues with timestamped audit trail
Troubleshooting Common Issues
Issue: OAuth Token Exchange Fails
Symptoms: Getting 401 or 403 errors during authentication.
Diagnosis:
// Check error response
const error = await response.json();
console.error('OAuth error:', error);
Solutions:
- Verify redirect URI matches exactly (including https:// and trailing slash)
- Confirm client_id and client_secret are correct
- Ensure authorization code hasn’t expired (codes expire after 1 use or 5 minutes)
- Check app is in production mode (development apps can only access test accounts)
Issue: Rate Limit Exceeded
Symptoms: Receiving HTTP 429 responses.
Solutions:
- Implement request queuing with rate limiting
- Use exponential backoff for retries
- Batch requests where possible (e.g., fetch multiple listings in one call)
- Monitor quota headers and throttle proactively
// Simple rate limiter
class RateLimiter {
constructor(requestsPerSecond = 9) { // Stay under 10/s limit
this.queue = [];
this.interval = 1000 / requestsPerSecond;
this.processing = false;
}
async add(requestFn) {
return new Promise((resolve, reject) => {
this.queue.push({ requestFn, resolve, reject });
this.process();
});
}
async process() {
if (this.processing || this.queue.length === 0) return;
this.processing = true;
while (this.queue.length > 0) {
const { requestFn, resolve, reject } = this.queue.shift();
try {
const result = await requestFn();
resolve(result);
} catch (error) {
reject(error);
}
if (this.queue.length > 0) {
await new Promise(r => setTimeout(r, this.interval));
}
}
this.processing = false;
}
}
// Usage
const etsyRateLimiter = new RateLimiter(9);
const result = await etsyRateLimiter.add(() => makeEtsyRequest('/shops/12345/listings'));
Issue: Listing Creation Fails with Validation Errors
Symptoms: 400 Bad Request with validation error messages.
Common Causes:
- Invalid category_id: Use Etsy’s categories API to get valid IDs
- Price format: Must be a string, not a number
- Tag limit: Maximum 13 tags per listing
- Required fields missing: title, description, price, quantity, who_made, when_made
Solution:
// Validate before sending
const validateListing = (data) => {
const errors = [];
if (!data.title || data.title.length < 5) {
errors.push('Title must be at least 5 characters');
}
if (typeof data.price !== 'string') {
errors.push('Price must be a string');
}
if (data.tags && data.tags.length > 13) {
errors.push('Maximum 13 tags allowed');
}
if (!['i_did', 'someone_else', 'collective'].includes(data.whoMade)) {
errors.push('Invalid who_made value');
}
return errors;
};
Issue: Webhooks Not Arriving
Symptoms: Orders process but webhook endpoint receives nothing.
Diagnosis:
- Check webhook delivery logs in developer dashboard
- Verify endpoint returns 200 OK within 5 seconds
- Test endpoint manually with curl
Solutions:
- Ensure HTTPS with valid SSL certificate
- Whitelist Etsy webhook IPs in firewall
- Check signature verification logic
- Use webhook testing tools during development
Issue: Images Fail to Upload
Symptoms: Listing creates but images return errors.
Solutions:
- Verify image is valid format (JPEG, PNG, GIF)
- Check file size (max 20MB per image)
- Ensure base64 encoding is correct
- Confirm listing exists before uploading images
- Upload images sequentially, not in parallel
Production Deployment Checklist
Before going live:
- [ ] Switch from development to production app mode
- [ ] Update all redirect URIs to production URLs
- [ ] Implement secure token storage (encrypted database)
- [ ] Add automatic token refresh logic
- [ ] Set up rate limiting and request queuing
- [ ] Configure webhook endpoint with HTTPS
- [ ] Implement comprehensive error handling
- [ ] Add logging for all API calls
- [ ] Set up monitoring for quota usage
- [ ] Create runbook for common issues
- [ ] Test with multiple shop accounts
- [ ] Document OAuth flow for user onboarding
Monitoring and Alerting
Track these metrics:
// Example metrics to track
const metrics = {
apiCalls: {
total: 0,
successful: 0,
failed: 0,
rateLimited: 0
},
quotaUsage: {
current: 0,
limit: 10000,
resetTime: null
},
oauthTokens: {
active: 0,
expiring_soon: 0,
refresh_failures: 0
},
webhooks: {
received: 0,
processed: 0,
failed: 0
}
};
// Alert on high failure rate
const failureRate = metrics.apiCalls.failed / metrics.apiCalls.total;
if (failureRate > 0.05) { // More than 5% failure rate
sendAlert('Etsy API failure rate above 5%');
}
// Alert on low quota
if (metrics.quotaUsage.current < 500) {
sendAlert('Etsy API quota below 500 calls remaining');
}
Real-World Use Cases
Multi-Channel Inventory Sync
A home decor seller uses Etsy API to synchronize inventory across Etsy, Shopify, and Amazon:
- Challenge: Manual inventory updates led to overselling
- Solution: Central inventory system with Etsy API webhooks
- Result: Zero overselling incidents, 12 hours/week time savings
Implementation flow:
- Etsy webhook triggers on order creation
- Central system decrements inventory
- API calls update Shopify and Amazon quantities
- Confirmation logged to database
Automated Order Fulfillment
A print-on-demand business automates order processing:
- Challenge: 50+ daily orders required manual data entry
- Solution: Etsy API integration with fulfillment provider
- Result: Orders auto-routed to production within 5 minutes
Key integration points:
- Webhook listens for
orders/createevents - Order details sent to print provider API
- Tracking number returned and updated via Etsy API
- Customer receives automatic shipping notification
Analytics Dashboard
A seller analytics tool aggregates data across multiple shops:
- Challenge: Sellers with multiple shops lacked unified reporting
- Solution: OAuth-based multi-shop data aggregation
- Result: Real-time dashboard with sales, traffic, and conversion metrics
Data collected via API:
- Shop statistics (listings, sales, revenue)
- Order history and trends
- Listing performance metrics
- Customer review data
Conclusion
The Etsy API provides comprehensive access to marketplace functionality. Key takeaways:
- OAuth 2.0 authentication requires careful token management and automatic refresh
- Rate limiting (10 req/s, 10K/hour) requires proactive monitoring and queuing
- Webhooks enable real-time order and inventory synchronization
- Proper error handling and retry logic are essential for production reliability
- Apidog streamlines API testing and team collaboration for Etsy integrations
FAQ Section
What is the Etsy API used for?
The Etsy API enables developers to build applications that interact with Etsy’s marketplace. Common use cases include inventory management across multiple sales channels, automated order fulfillment, analytics dashboards, listing creation tools, and customer relationship management systems.
How do I get an Etsy API key?
Create an account at the Etsy Developer Portal, navigate to Your Apps, and click Create a new app. After registration, you’ll receive a Key String (public identifier) and Shared Secret (private key). Store both securely using environment variables.
Is the Etsy API free to use?
Yes, the Etsy API is free for developers. However, rate limits apply: 10 requests per second and 10,000 calls per hour per app. Higher limits require approval from Etsy for specific use cases.
What authentication does Etsy API use?
Etsy uses OAuth 2.0 for authentication. Users authorize your app through Etsy’s authorization page, and your app receives an access token (valid for 1 hour) and refresh token (for obtaining new access tokens).
How do I handle Etsy API rate limits?
Implement request queuing to stay under 10 requests per second. Monitor the x-etsy-quota-remaining header to track hourly usage. Use exponential backoff when receiving HTTP 429 (Too Many Requests) responses.
Can I test Etsy API without a live shop?
Yes. Development mode apps can connect to test shops for integration testing. Create a test Etsy account and use it to authenticate your development app without affecting live data.
How do webhooks work with Etsy API?
Etsy webhooks send POST requests to your HTTPS endpoint when events occur (new orders, listing updates). Configure webhooks in your app dashboard, implement signature verification, and return 200 OK within 5 seconds.
What happens when Etsy OAuth token expires?
Access tokens expire after 1 hour. Use the refresh token to obtain a new access token before expiration. Implement automatic token refresh in your middleware to prevent authentication failures during API calls.
Can I upload listing images via the API?
Yes. Images are uploaded as base64-encoded strings in a separate API call after listing creation. Each image can be up to 20MB and must be JPEG, PNG, or GIF format.
How do I migrate from Etsy API V2 to V3?
V3 uses OAuth 2.0 instead of OAuth 1.0a and has a different endpoint structure. Update authentication flow, modify endpoint paths from /v2/ to /v3/application/, and test thoroughly before V2 retirement in late 2026.



