Etsy API 사용법: 완벽한 통합 가이드 (2026년)

Ashley Innocent

Ashley Innocent

20 March 2026

Etsy API 사용법: 완벽한 통합 가이드 (2026년)

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 통합을 갖추게 될 것입니다.

💡
Apidog는 API 통합 테스트를 간소화합니다. 단일 작업 공간에서 Etsy 엔드포인트를 테스트하고, OAuth 흐름을 검증하며, 웹훅 페이로드를 검사하고, 인증 문제를 디버그할 수 있습니다. API 사양을 가져오고, 응답을 모의하며, 팀과 테스트 시나리오를 공유하세요.

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에 접근하기 전에 개발자 계정이 필요합니다.

  1. Etsy 개발자 포털을 방문하세요.
  2. Etsy 계정으로 로그인하거나 (계정이 없으면 생성하세요)
  3. 개발자 대시보드에서 내 앱으로 이동하세요.
  4. 새 앱 생성을 클릭하세요.

2단계: 애플리케이션 등록

앱 등록 양식을 작성하세요.

제출 후 다음을 받게 됩니다.

보안 참고: 자격 증명은 코드에 저장하지 말고 환경 변수에 저장하세요.

# .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 안정성을 보호하기 위해 속도 제한을 적용합니다.

제한을 초과하면 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는 실시간 이벤트 알림을 위한 웹훅을 지원합니다.

  1. 개발자 대시보드에서 내 앱으로 이동하세요.
  2. 앱을 선택하세요.
  3. 웹훅 추가를 클릭하세요.
  4. HTTPS 엔드포인트 URL을 입력하세요.
  5. 구독할 이벤트를 선택하세요.

사용 가능한 웹훅 이벤트

이벤트 유형 트리거 사용 사례
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')
  );
}

웹훅 모범 사례

  1. 서명 확인 - 스푸핑된 웹훅 방지
  2. 200 OK를 신속하게 반환 - Etsy는 5초 이내에 200이 아닌 응답에 대해 재시도합니다.
  3. 비동기적으로 처리 - 백그라운드 처리를 위해 이벤트를 대기열에 추가
  4. 멱등성 구현 - 중복 웹훅 전달 처리
  5. 모든 이벤트 기록 - 타임스탬프가 있는 감사 추적으로 문제 디버그

일반적인 문제 해결

문제: OAuth 토큰 교환 실패

증상: 인증 중 401 또는 403 오류 발생.

진단:

// Check error response
const error = await response.json();
console.error('OAuth error:', error);

해결책:

  1. 리디렉션 URI가 정확히 일치하는지 확인 (https:// 및 후행 슬래시 포함)
  2. client_id와 client_secret이 올바른지 확인
  3. 권한 부여 코드가 만료되지 않았는지 확인 (코드는 1회 사용 또는 5분 후에 만료됨)
  4. 앱이 프로덕션 모드에 있는지 확인 (개발 앱은 테스트 계정에만 접근할 수 있음)

문제: 속도 제한 초과

증상: HTTP 429 응답 수신.

해결책:

  1. 속도 제한과 함께 요청 대기열 구현
  2. 재시도를 위해 지수 백오프 사용
  3. 가능한 경우 요청 일괄 처리 (예: 한 번의 호출로 여러 상품 목록 가져오기)
  4. 할당량 헤더를 모니터링하고 선제적으로 제한
// 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 수신.

일반적인 원인:

  1. 잘못된 category_id: Etsy의 카테고리 API를 사용하여 유효한 ID를 가져오세요.
  2. 가격 형식: 숫자가 아닌 문자열이어야 합니다.
  3. 태그 제한: 상품 목록당 최대 13개의 태그.
  4. 필수 필드 누락: 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;
};

문제: 웹훅이 도착하지 않음

증상: 주문은 처리되지만 웹훅 엔드포인트에서 아무것도 수신되지 않습니다.

진단:

  1. 개발자 대시보드에서 웹훅 전달 로그를 확인하세요.
  2. 엔드포인트가 5초 이내에 200 OK를 반환하는지 확인하세요.
  3. curl을 사용하여 엔드포인트를 수동으로 테스트하세요.

해결책:

  1. 유효한 SSL 인증서가 있는 HTTPS를 사용하고 있는지 확인하세요.
  2. 방화벽에서 Etsy 웹훅 IP를 화이트리스트에 추가하세요.
  3. 서명 확인 로직을 확인하세요.
  4. 개발 중 웹훅 테스트 도구를 사용하세요.

문제: 이미지 업로드 실패

증상: 상품 목록은 생성되지만 이미지에서 오류가 반환됩니다.

해결책:

  1. 이미지가 유효한 형식(JPEG, PNG, GIF)인지 확인하세요.
  2. 파일 크기를 확인하세요 (이미지당 최대 20MB).
  3. base64 인코딩이 올바른지 확인하세요.
  4. 이미지 업로드 전에 상품 목록이 존재하는지 확인하세요.
  5. 이미지를 병렬이 아닌 순차적으로 업로드하세요.

프로덕션 배포 체크리스트

서비스 출시 전에:

모니터링 및 경고

다음 지표를 추적하세요.

// 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 전반에 걸쳐 재고를 동기화합니다.

구현 흐름:

  1. 주문 생성 시 Etsy 웹훅 트리거
  2. 중앙 시스템이 재고 감소
  3. API 호출이 Shopify 및 Amazon 수량 업데이트
  4. 확인서가 데이터베이스에 기록됨

자동화된 주문 처리

한 주문형 인쇄 사업체가 주문 처리를 자동화합니다.

주요 통합 지점:

분석 대시보드

한 판매자 분석 도구가 여러 상점의 데이터를 집계합니다.

API를 통해 수집된 데이터:

결론

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 지원 종료 전에 철저히 테스트하세요.

Apidog에서 API 설계-첫 번째 연습

API를 더 쉽게 구축하고 사용하는 방법을 발견하세요