TL;DR
Etsy API는 개발자가 Etsy 마켓플레이스와 상호 작용하는 애플리케이션을 구축할 수 있도록 지원합니다. 이 API는 OAuth 2.0 인증, 상점, 상품 목록, 주문 및 재고 관리를 위한 RESTful 엔드포인트를 사용하며, 앱당 초당 10회 호출의 속도 제한이 있습니다. 이 가이드는 인증 설정, 핵심 엔드포인트, 웹훅 통합 및 프로덕션 배포 전략을 다룹니다.
소개
Etsy는 230개 이상의 국가에서 연간 130억 달러 이상의 총 상품 판매를 처리합니다. 전자상거래 도구, 재고 관리 시스템 또는 분석 플랫폼을 구축하는 개발자에게 Etsy API 통합은 선택 사항이 아니라 필수적입니다.
현실은 이렇습니다. 여러 판매 채널을 관리하는 판매자들은 수동 데이터 입력에 매주 15-20시간을 낭비합니다. 견고한 Etsy API 통합은 플랫폼 전반에 걸쳐 상품 목록 동기화, 주문 처리 및 재고 업데이트를 자동화합니다.
이 가이드는 완전한 Etsy API 통합 프로세스를 안내합니다. OAuth 2.0 인증, 상점 및 상품 목록 관리, 주문 처리, 웹훅 처리 및 오류 해결 방법을 배우게 됩니다. 마지막에는 프로덕션 준비가 완료된 Etsy 통합을 갖추게 될 것입니다.
Etsy API란 무엇인가요?
Etsy는 마켓플레이스 데이터에 접근하고 판매자 작업을 관리하기 위한 RESTful API를 제공합니다. API는 다음을 처리합니다.
- 상점 및 프로필 정보 검색
- 상품 목록 생성, 업데이트 및 재고 관리
- 주문 처리 및 배송 추적
- 고객 및 거래 데이터 접근
- 배송 프로필 및 세금 계산
- 이미지 및 미디어 업로드 관리
주요 기능
| 기능 | 설명 |
|---|---|
| RESTful 디자인 | JSON 응답을 사용하는 표준 HTTP 메서드 |
| OAuth 2.0 | 액세스 토큰 갱신을 통한 보안 인증 |
| 웹훅 | 주문 및 상품 목록 이벤트에 대한 실시간 알림 |
| 속도 제한 | 앱당 초당 10회 요청 (버스트 허용량 포함) |
| 샌드박스 지원 | 실시간 데이터 없이 개발을 위한 테스트 환경 |
API 아키텍처 개요
Etsy는 버전 관리되는 REST API 구조를 사용합니다.
https://openapi.etsy.com/v3/application/
버전 3 (v3)는 현재 표준이며, v2에 비해 향상된 OAuth 2.0 지원과 간소화된 엔드포인트 구조를 제공합니다.
API 버전 비교
| 버전 | 상태 | 인증 | 사용 사례 |
|---|---|---|---|
| V3 | 현재 | OAuth 2.0 | 모든 신규 통합 |
| V2 | 지원 중단 예정 | OAuth 1.0a | 레거시 앱만 해당 |
| V1 | 지원 종료 | 해당 없음 | 사용하지 마세요 |
모든 V2 통합을 즉시 V3로 마이그레이션하세요. Etsy는 V2 지원 중단을 발표했으며, 2026년 말에 완전히 지원이 종료될 예정입니다.
시작하기: 인증 설정
1단계: Etsy 개발자 계정 생성
API에 접근하기 전에 개발자 계정이 필요합니다.
- Etsy 개발자 포털을 방문하세요.
- Etsy 계정으로 로그인하거나 (계정이 없으면 생성하세요)
- 개발자 대시보드에서 내 앱으로 이동하세요.
- 새 앱 생성을 클릭하세요.
2단계: 애플리케이션 등록
앱 등록 양식을 작성하세요.
- 앱 이름: 명확하고 설명적인 이름 (OAuth 중 사용자에게 표시됨)
- 앱 설명: 앱의 기능과 사용자를 설명하세요.
- 리디렉션 URI: Etsy가 인증 후 사용자를 보내는 위치 (HTTPS를 사용해야 합니다)
- 프로덕션/개발: 테스트를 위해 개발 모드로 시작하세요.
제출 후 다음을 받게 됩니다.
- 키 문자열: 공개 API 식별자
- 공유 비밀: 비공개 API 비밀 (절대 노출하지 마세요)
보안 참고: 자격 증명은 코드에 저장하지 말고 환경 변수에 저장하세요.
# .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"
3단계: OAuth 2.0 흐름 이해
Etsy는 인증에 OAuth 2.0을 사용합니다. 전체 흐름은 다음과 같습니다.
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)
4단계: OAuth 권한 부여 구현
권한 부여 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}`);
필수 스코프
앱에 필요한 권한만 요청하세요.
| 스코프 | 설명 | 사용 사례 |
|---|---|---|
listings_r |
상품 목록 읽기 | 상품 표시, 재고 동기화 |
listings_w |
상품 목록 쓰기 | 상품 생성/업데이트 |
orders_r |
주문 읽기 | 주문 관리, 주문 처리 |
orders_w |
주문 쓰기 | 주문 상태 업데이트, 추적 추가 |
shops_r |
상점 정보 읽기 | 상점 프로필, 분석 표시 |
transactions_r |
거래 읽기 | 재무 보고 |
email |
구매자 이메일 접근 | 주문 통신 |
5단계: 코드와 액세스 토큰 교환
OAuth 콜백을 처리하고 권한 부여 코드를 교환합니다.
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');
}
});
6단계: 토큰 갱신 구현
액세스 토큰은 1시간 후에 만료됩니다. 자동 갱신을 구현하세요.
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;
};
7단계: 인증된 API 호출 수행
모든 요청에 액세스 토큰을 포함하세요.
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();
};
상점 관리 엔드포인트
상점 정보 검색
상점 세부 정보, 정책 및 설정을 가져옵니다.
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}`);
예상 상점 응답
{
"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"
}
상점 섹션 검색
섹션별로 상품 목록을 정리합니다.
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
}
]
}
상품 목록 관리
새 상품 목록 생성
이미지와 옵션을 포함한 상품 목록을 생성합니다.
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'
});
상품 목록 이미지 업로드
이미지는 상품 목록 생성 후 별도로 업로드됩니다.
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;
};
상품 목록 재고 업데이트
기존 상품 목록의 수량을 업데이트합니다.
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
});
상품 목록 검색
모든 상품 목록을 가져오거나 상태별로 필터링합니다.
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;
};
상품 목록 삭제
상점에서 상품 목록을 제거합니다.
const deleteListing = async (listingId) => {
const response = await makeEtsyRequest(`/listings/${listingId}`, {
method: 'DELETE'
});
return response;
};
주문 관리
주문 검색
필터링 옵션으로 주문을 가져옵니다.
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_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"
}
]
}
주문 상태 업데이트
주문을 완료로 표시하고 추적 정보를 추가합니다.
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
});
일반적인 배송업체 ID
| 배송업체 | 배송업체 ID |
|---|---|
| USPS | usps |
| FedEx | fedex |
| UPS | ups |
| DHL | dhl_express |
| 캐나다 포스트 | canada_post |
| 로열 메일 | royal_mail |
| 호주 포스트 | australia_post |
속도 제한 및 할당량
속도 제한 이해
Etsy는 API 안정성을 보호하기 위해 속도 제한을 적용합니다.
- 표준 제한: 앱당 초당 10회 요청
- 버스트 허용량: 단일 초에 최대 50회 요청 (짧은 버스트)
- 할당량 시스템: 앱당 시간당 10,000회 호출 (시간당 재설정)
제한을 초과하면 HTTP 429 (너무 많은 요청) 응답이 발생합니다.
속도 제한 처리 구현
재시도를 위해 지수 백오프를 사용하세요.
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;
}
}
}
};
속도 제한 헤더
Etsy는 모든 응답에 다음 헤더를 포함합니다.
| 헤더 | 설명 |
|---|---|
x-etsy-quota-remaining |
현재 시간에 남은 호출 수 |
x-etsy-quota-reset |
할당량이 재설정되는 Unix 타임스탬프 |
x-etsy-limit-remaining |
현재 초에 남은 호출 수 |
x-etsy-limit-reset |
초당 제한이 재설정되는 Unix 타임스탬프 |
모니터링 및 디버깅을 위해 항상 이 헤더들을 기록하세요.
웹훅 통합
웹훅 구성
Etsy는 실시간 이벤트 알림을 위한 웹훅을 지원합니다.
- 개발자 대시보드에서 내 앱으로 이동하세요.
- 앱을 선택하세요.
- 웹훅 추가를 클릭하세요.
- HTTPS 엔드포인트 URL을 입력하세요.
- 구독할 이벤트를 선택하세요.
사용 가능한 웹훅 이벤트
| 이벤트 유형 | 트리거 | 사용 사례 |
|---|---|---|
v3/shops/{shop_id}/orders/create |
새 주문이 접수됨 | 확인서 발송, 주문 처리 시작 |
v3/shops/{shop_id}/orders/update |
주문 상태 변경됨 | 주문 상태 동기화 |
v3/shops/{shop_id}/listings/create |
새 상품 목록 생성됨 | 외부 재고 업데이트 |
v3/shops/{shop_id}/listings/update |
상품 목록 수정됨 | 상품 데이터 동기화 |
v3/shops/{shop_id}/listings/delete |
상품 목록 제거됨 | 외부 시스템에서 제거 |
웹훅 핸들러 생성
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')
);
}
웹훅 모범 사례
- 서명 확인 - 스푸핑된 웹훅 방지
- 200 OK를 신속하게 반환 - Etsy는 5초 이내에 200이 아닌 응답에 대해 재시도합니다.
- 비동기적으로 처리 - 백그라운드 처리를 위해 이벤트를 대기열에 추가
- 멱등성 구현 - 중복 웹훅 전달 처리
- 모든 이벤트 기록 - 타임스탬프가 있는 감사 추적으로 문제 디버그
일반적인 문제 해결
문제: OAuth 토큰 교환 실패
증상: 인증 중 401 또는 403 오류 발생.
진단:
// Check error response
const error = await response.json();
console.error('OAuth error:', error);
해결책:
- 리디렉션 URI가 정확히 일치하는지 확인 (https:// 및 후행 슬래시 포함)
- client_id와 client_secret이 올바른지 확인
- 권한 부여 코드가 만료되지 않았는지 확인 (코드는 1회 사용 또는 5분 후에 만료됨)
- 앱이 프로덕션 모드에 있는지 확인 (개발 앱은 테스트 계정에만 접근할 수 있음)
문제: 속도 제한 초과
증상: HTTP 429 응답 수신.
해결책:
- 속도 제한과 함께 요청 대기열 구현
- 재시도를 위해 지수 백오프 사용
- 가능한 경우 요청 일괄 처리 (예: 한 번의 호출로 여러 상품 목록 가져오기)
- 할당량 헤더를 모니터링하고 선제적으로 제한
// 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'));
문제: 유효성 검사 오류로 상품 목록 생성 실패
증상: 유효성 검사 오류 메시지와 함께 400 Bad Request 수신.
일반적인 원인:
- 잘못된 category_id: Etsy의 카테고리 API를 사용하여 유효한 ID를 가져오세요.
- 가격 형식: 숫자가 아닌 문자열이어야 합니다.
- 태그 제한: 상품 목록당 최대 13개의 태그.
- 필수 필드 누락: title, description, price, quantity, who_made, when_made.
해결책:
// 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;
};
문제: 웹훅이 도착하지 않음
증상: 주문은 처리되지만 웹훅 엔드포인트에서 아무것도 수신되지 않습니다.
진단:
- 개발자 대시보드에서 웹훅 전달 로그를 확인하세요.
- 엔드포인트가 5초 이내에 200 OK를 반환하는지 확인하세요.
- curl을 사용하여 엔드포인트를 수동으로 테스트하세요.
해결책:
- 유효한 SSL 인증서가 있는 HTTPS를 사용하고 있는지 확인하세요.
- 방화벽에서 Etsy 웹훅 IP를 화이트리스트에 추가하세요.
- 서명 확인 로직을 확인하세요.
- 개발 중 웹훅 테스트 도구를 사용하세요.
문제: 이미지 업로드 실패
증상: 상품 목록은 생성되지만 이미지에서 오류가 반환됩니다.
해결책:
- 이미지가 유효한 형식(JPEG, PNG, GIF)인지 확인하세요.
- 파일 크기를 확인하세요 (이미지당 최대 20MB).
- base64 인코딩이 올바른지 확인하세요.
- 이미지 업로드 전에 상품 목록이 존재하는지 확인하세요.
- 이미지를 병렬이 아닌 순차적으로 업로드하세요.
프로덕션 배포 체크리스트
서비스 출시 전에:
- [ ] 개발 모드에서 프로덕션 앱 모드로 전환
- [ ] 모든 리디렉션 URI를 프로덕션 URL로 업데이트
- [ ] 안전한 토큰 저장소 구현 (암호화된 데이터베이스)
- [ ] 자동 토큰 갱신 로직 추가
- [ ] 속도 제한 및 요청 대기열 설정
- [ ] HTTPS로 웹훅 엔드포인트 구성
- [ ] 포괄적인 오류 처리 구현
- [ ] 모든 API 호출에 대한 로깅 추가
- [ ] 할당량 사용량 모니터링 설정
- [ ] 일반적인 문제에 대한 런북 생성
- [ ] 여러 상점 계정으로 테스트
- [ ] 사용자 온보딩을 위한 OAuth 흐름 문서화
모니터링 및 경고
다음 지표를 추적하세요.
// 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');
}
실제 사용 사례
다중 채널 재고 동기화
한 홈 데코 판매자가 Etsy API를 사용하여 Etsy, Shopify, Amazon 전반에 걸쳐 재고를 동기화합니다.
- 과제: 수동 재고 업데이트로 인한 초과 판매
- 해결책: Etsy API 웹훅을 사용하는 중앙 재고 시스템
- 결과: 초과 판매 발생 없음, 주당 12시간 절약
구현 흐름:
- 주문 생성 시 Etsy 웹훅 트리거
- 중앙 시스템이 재고 감소
- API 호출이 Shopify 및 Amazon 수량 업데이트
- 확인서가 데이터베이스에 기록됨
자동화된 주문 처리
한 주문형 인쇄 사업체가 주문 처리를 자동화합니다.
- 과제: 하루 50개 이상의 주문에 수동 데이터 입력 필요
- 해결책: 주문 처리 업체와의 Etsy API 통합
- 결과: 5분 이내에 주문이 자동으로 생산으로 라우팅됨
주요 통합 지점:
- 웹훅이
orders/create이벤트를 수신합니다. - 주문 세부 정보가 인쇄 업체 API로 전송됩니다.
- 추적 번호가 Etsy API를 통해 반환 및 업데이트됩니다.
- 고객은 자동 배송 알림을 받습니다.
분석 대시보드
한 판매자 분석 도구가 여러 상점의 데이터를 집계합니다.
- 과제: 여러 상점을 가진 판매자는 통합 보고서 부족
- 해결책: OAuth 기반 다중 상점 데이터 집계
- 결과: 판매, 트래픽 및 전환 지표를 포함하는 실시간 대시보드
API를 통해 수집된 데이터:
- 상점 통계 (상품 목록, 판매, 수익)
- 주문 내역 및 추세
- 상품 목록 성능 지표
- 고객 리뷰 데이터
결론
Etsy API는 마켓플레이스 기능에 대한 포괄적인 접근을 제공합니다. 주요 요점은 다음과 같습니다.
- OAuth 2.0 인증은 신중한 토큰 관리와 자동 갱신을 필요로 합니다.
- 속도 제한 (초당 10회 요청, 시간당 1만 회)은 선제적인 모니터링과 대기열 관리를 필요로 합니다.
- 웹훅은 실시간 주문 및 재고 동기화를 가능하게 합니다.
- 프로덕션 안정성을 위해서는 적절한 오류 처리 및 재시도 로직이 필수적입니다.
- Apidog는 Etsy 통합을 위한 API 테스트 및 팀 협업을 간소화합니다.
자주 묻는 질문
Etsy API는 무엇에 사용되나요?
Etsy API는 개발자가 Etsy 마켓플레이스와 상호 작용하는 애플리케이션을 구축할 수 있도록 합니다. 일반적인 사용 사례로는 여러 판매 채널에 걸친 재고 관리, 자동화된 주문 처리, 분석 대시보드, 상품 목록 생성 도구 및 고객 관계 관리 시스템이 있습니다.
Etsy API 키는 어떻게 얻나요?
Etsy 개발자 포털에서 계정을 생성하고, 내 앱으로 이동하여 새 앱 생성을 클릭하세요. 등록 후 키 문자열 (공개 식별자)과 공유 비밀 (비공개 키)을 받게 됩니다. 이 둘을 환경 변수를 사용하여 안전하게 저장하세요.
Etsy API는 무료로 사용할 수 있나요?
예, Etsy API는 개발자를 위해 무료입니다. 하지만 앱당 초당 10회 요청 및 시간당 10,000회 호출이라는 속도 제한이 적용됩니다. 더 높은 제한은 특정 사용 사례에 대해 Etsy의 승인을 받아야 합니다.
Etsy API는 어떤 인증 방식을 사용하나요?
Etsy는 인증에 OAuth 2.0을 사용합니다. 사용자는 Etsy의 권한 부여 페이지를 통해 앱에 권한을 부여하며, 앱은 액세스 토큰 (1시간 유효)과 갱신 토큰 (새 액세스 토큰을 얻기 위함)을 받게 됩니다.
Etsy API 속도 제한은 어떻게 처리하나요?
초당 10회 요청 미만을 유지하기 위해 요청 대기열을 구현하세요. x-etsy-quota-remaining 헤더를 모니터링하여 시간당 사용량을 추적하세요. HTTP 429 (너무 많은 요청) 응답을 받을 때 지수 백오프를 사용하세요.
실제 상점 없이 Etsy API를 테스트할 수 있나요?
예. 개발 모드 앱은 통합 테스트를 위해 테스트 상점에 연결할 수 있습니다. 테스트 Etsy 계정을 생성하고 이를 사용하여 실제 데이터에 영향을 주지 않고 개발 앱을 인증하세요.
Etsy API에서 웹훅은 어떻게 작동하나요?
Etsy 웹훅은 이벤트가 발생할 때 (새 주문, 상품 목록 업데이트 등) HTTPS 엔드포인트로 POST 요청을 보냅니다. 앱 대시보드에서 웹훅을 구성하고, 서명 확인을 구현하며, 5초 이내에 200 OK를 반환하세요.
Etsy OAuth 토큰이 만료되면 어떻게 되나요?
액세스 토큰은 1시간 후에 만료됩니다. 만료 전에 갱신 토큰을 사용하여 새 액세스 토큰을 얻으세요. API 호출 중 인증 실패를 방지하기 위해 미들웨어에서 자동 토큰 갱신을 구현하세요.
API를 통해 상품 목록 이미지를 업로드할 수 있나요?
예. 이미지는 상품 목록 생성 후 별도의 API 호출에서 base64 인코딩된 문자열로 업로드됩니다. 각 이미지는 최대 20MB이며 JPEG, PNG 또는 GIF 형식이어야 합니다.
Etsy API V2에서 V3로 어떻게 마이그레이션하나요?
V3는 OAuth 1.0a 대신 OAuth 2.0을 사용하며 엔드포인트 구조가 다릅니다. 인증 흐름을 업데이트하고, 엔드포인트 경로를 /v2/에서 /v3/application/으로 수정하며, 2026년 말 V2 지원 종료 전에 철저히 테스트하세요.
