Etsy API 活用ガイド完全版 (2026年)

Ashley Innocent

Ashley Innocent

20 3月 2026

Etsy API 活用ガイド完全版 (2026年)

TL;DR (要約)

Etsy APIは、開発者がEtsyのマーケットプレイスと連携するアプリケーションを構築することを可能にします。OAuth 2.0認証、ショップ、出品、注文、在庫管理のためのRESTfulエンドポイントを使用し、アプリあたり毎秒10コールというレート制限があります。このガイドでは、認証設定、主要なエンドポイント、ウェブフック統合、および本番環境へのデプロイ戦略について説明します。

はじめに

Etsyは、230以上の国で年間130億ドルを超える商品総売上高を処理しています。Eコマースツール、在庫管理システム、または分析プラットフォームを構築する開発者にとって、Etsy APIとの連携は任意ではなく、必須です。

現実を見てみましょう。複数の販売チャネルを管理するセラーは、手動データ入力に毎週15〜20時間を費やしています。堅牢なEtsy API連携は、出品の同期、注文処理、およびプラットフォーム間の在庫更新を自動化します。

このガイドでは、Etsy API連携の全プロセスを説明します。OAuth 2.0認証、ショップと出品の管理、注文処理、ウェブフックの処理、エラーのトラブルシューティングについて学びます。最終的には、本番環境に対応したEtsy連携を構築できるようになります。

💡
ApidogはAPI連携テストを簡素化します。Etsyのエンドポイントをテストし、OAuthフローを検証し、ウェブフックのペイロードを検査し、認証の問題を単一のワークスペースでデバッグできます。API仕様をインポートし、レスポンスをモックし、テストシナリオをチームと共有しましょう。

Etsy APIとは?

Etsyは、マーケットプレイスデータへのアクセスとセラー業務の管理のためのRESTful APIを提供しています。APIが処理する内容は以下の通りです。

主要機能

機能 説明
RESTful設計 標準HTTPメソッドとJSONレスポンス
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 廃止 N/A 使用しないこと

V2連携は直ちにV3に移行してください。EtsyはV2の非推奨を発表し、2026年後半に完全な廃止が予定されています。

はじめに:認証設定

ステップ1:Etsy開発者アカウントの作成

APIにアクセスする前に、開発者アカウントが必要です。

  1. Etsy開発者ポータルにアクセスします。
  2. Etsyアカウントでサインインします(または作成します)。
  3. 開発者ダッシュボードのYour Appsに移動します。
  4. Create a new appをクリックします。

ステップ2:アプリケーションの登録

アプリ登録フォームに記入します。

送信後、以下の情報を受け取ります。

セキュリティ上の注意: 資格情報は環境変数に保存し、コードには決して含めないでください。

# .env ファイル
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. ユーザーがアプリで「Connect with Etsy」をクリックします
2. アプリがEtsy承認URLにリダイレクトします
3. ユーザーがログインし、権限を付与します
4. Etsyが承認コードとともにリダイレクトバックします
5. アプリがコードをアクセストークンと交換します
6. アプリがアクセストークンを使用してAPIコールを行います
7. アクセストークンの有効期限が切れたら(1時間)、トークンを更新します

ステップ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, // CSRF保護のためのランダムな文字列
    response_type: 'code'
  });

  return `${baseUrl}?${params.toString()}`;
};

// 使用例
const authUrl = generateAuthUrl(
  process.env.ETSY_KEY_STRING,
  'https://your-app.com/callback',
  crypto.randomBytes(16).toString('hex')
);

console.log(`Redirect user to: ${authUrl}`); // ユーザーをリダイレクトするURL

必要なスコープ

アプリが必要とする権限のみをリクエストしてください。

スコープ 説明 ユースケース
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();

  // これらをデータベースに安全に保存する
  return {
    access_token: data.access_token,
    refresh_token: data.refresh_token,
    expires_in: data.expires_in, // 通常は3600秒 (1時間)
    user_id: data.user_id,
    scope: data.scope
  };
};

// コールバックルートを処理する
app.get('/callback', async (req, res) => {
  const { code, state } = req.query;

  // 送信したものとstateが一致するか検証する (CSRF保護)
  if (state !== req.session.oauthState) {
    return res.status(400).send('Invalid state parameter'); // 無効なstateパラメータ
  }

  try {
    const tokens = await exchangeCodeForToken(code, 'https://your-app.com/callback');

    // トークンをユーザーに関連付けてデータベースに保存する
    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();

  // 保存されたトークンを更新する
  return {
    access_token: data.access_token,
    refresh_token: data.refresh_token, // 常に新しいリフレッシュトークンを保存する
    expires_in: data.expires_in
  };
};

// APIコール前に有効なトークンを確保するミドルウェア
const ensureValidToken = async (userId) => {
  const user = await db.users.findById(userId);

  // トークンが5分以内に期限切れになるかチェックする
  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}`); // Etsy APIエラー
  }

  return response.json();
};

ショップ管理エンドポイント

ショップ情報の取得

ショップの詳細、ポリシー、設定を取得します。

const getShopInfo = async (shopId) => {
  const response = await makeEtsyRequest(`/shops/${shopId}`, {
    method: 'GET'
  });

  return response;
};

// 使用例
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": "手作りジュエリー&アクセサリー",
  "announcement": "ようこそ!50ドル以上のご注文で送料無料",
  "currency_code": "USD",
  "is_vacation": false,
  "vacation_message": null,
  "sale_message": "中小企業を応援していただきありがとうございます!",
  "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;
};

// 応答例
{
  "count": 5,
  "results": [
    {
      "shop_section_id": 12345,
      "title": "ネックレス",
      "rank": 1,
      "num_listings": 23
    },
    {
      "shop_section_id": 12346,
      "title": "ピアス",
      "rank": 2,
      "num_listings": 45
    }
  ]
}

出品管理

新しい出品の作成

画像とバリエーションを含む商品出品を作成します。

const createListing = async (shopId, listingData) => {
  const payload = {
    title: listingData.title,
    description: listingData.description,
    price: listingData.price.toString(), // 文字列である必要があります
    quantity: listingData.quantity,
    sku: listingData.sku || [],
    tags: listingData.tags.slice(0, 13), // 最大13個のタグ
    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, など
    is_supply: listingData.isSupply, // クラフト用品の場合は true
    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, など
    occasion: listingData.occasion || null, // birthday, wedding, など
    style: listingData.style || [] // 最大2つのスタイル
  };

  const response = await makeEtsyRequest(`/shops/${shopId}/listings`, {
    method: 'POST',
    body: JSON.stringify(payload)
  });

  return response;
};

// 使用例
const listing = await createListing(12345678, {
  title: 'スターリングシルバームーンフェイズネックレス',
  description: '月の満ち欠けをモチーフにした手作りのスターリングシルバーネックレス...',
  price: 89.99,
  quantity: 15,
  sku: ['MOON-NECKLACE-001'],
  tags: ['ムーンネックレス', 'スターリングシルバー', '月の満ち欠け', '天体ジュエリー'],
  categoryId: 10623, // ジュエリー > ネックレス
  sectionId: 12345,
  state: 'active',
  whoMade: 'i_did', // 私が作った
  whenMade: 'made_to_order', // 受注生産
  isSupply: false,
  weight: 25,
  weightUnit: 'g'
});

出品画像のアップロード

画像は、出品作成後に別途アップロードされます。

const uploadListingImage = async (listingId, imagePath, imagePosition = 1) => {
  // 画像ファイルを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: '手作りのスターリングシルバームーンフェイズネックレス' // アクセシビリティのため
  };

  const response = await makeEtsyRequest(`/listings/${listingId}/images`, {
    method: 'POST',
    body: JSON.stringify(payload)
  });

  return response;
};

// 複数の画像をアップロードする
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;
};

// 使用例
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, // 最大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;
};

// 単一の出品を取得する
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;
};

// 単一の注文を取得する
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": "ニューヨーク",
    "state": "NY",
    "zip": "10001",
    "country": "US",
    "phone": "+1-555-0123"
  },
  "listings": [
    {
      "listing_id": 987654321,
      "title": "スターリングシルバームーンフェイズネックレス",
      "sku": ["MOON-NECKLACE-001"],
      "quantity": 1,
      "price": "89.99"
    }
  ]
}

注文ステータスの更新

注文を完了としてマークし、追跡情報を追加します。

const updateOrderStatus = async (shopId, orderId, trackingData) => {
  const payload = {
    carrier_id: trackingData.carrierId, // usps, fedex, ups, など
    tracking_code: trackingData.trackingCode,
    should_send_bcc_to_buyer: trackingData.notifyBuyer || true // 購入者にBCCを送信するか
  };

  const response = await makeEtsyRequest(
    `/shops/${shopId}/orders/${orderId}/shipping`,
    {
      method: 'POST',
      body: JSON.stringify(payload)
    }
  );

  return response;
};

// 使用例
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(Too Many Requests)応答が返されます。

レート制限処理の実装

再試行に指数関数的バックオフを使用します。

const makeRateLimitedRequest = async (endpoint, options = {}, maxRetries = 3) => {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await makeEtsyRequest(endpoint, options);

      // レート制限ヘッダーをチェックする
      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) {
        // 指数関数的バックオフ: 1秒、2秒、4秒
        const delay = Math.pow(2, attempt) * 1000;
        console.log(`Rate limited. Retrying in ${delay}ms...`); // レート制限。${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. 開発者ダッシュボードのYour Appsに移動します。
  2. アプリを選択します。
  3. Add Webhookをクリックします。
  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;

  // ウェブフックの署名を検証する
  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());

  // 適切なハンドラーにルーティングする
  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); // 未処理のイベントタイプ
  }

  // 5秒以内に受信を確認する
  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エラーが発生する。

診断:

// エラー応答を確認する
const error = await response.json();
console.error('OAuth error:', error); // OAuthエラー

解決策:

  1. リダイレクトURIが完全に一致するか(https://と末尾のスラッシュを含む)確認する
  2. client_idclient_secretが正しいことを確認する
  3. 承認コードが期限切れになっていないか確認する(コードは1回使用または5分後に期限切れになります)
  4. アプリが本番モードであることを確認する(開発アプリはテストアカウントにのみアクセスできます)

問題:レート制限を超過した

症状: HTTP 429応答を受け取る。

解決策:

  1. レート制限付きのリクエストキューイングを実装する
  2. 再試行に指数関数的バックオフを使用する
  3. 可能な場合はリクエストをバッチ処理する(例:複数の出品を一度に取得する)
  4. クォータヘッダーを監視し、積極的に調整する
// シンプルなレートリミッター
class RateLimiter {
  constructor(requestsPerSecond = 9) { // 1秒あたりの10リクエスト制限を下回るようにする
    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;
  }
}

// 使用例
const etsyRateLimiter = new RateLimiter(9);
const result = await etsyRateLimiter.add(() => makeEtsyRequest('/shops/12345/listings'));

問題:出品作成がバリデーションエラーで失敗する

症状: バリデーションエラーメッセージ付きの400 Bad Request。

一般的な原因:

  1. 無効なcategory_id: 有効なIDを取得するためにEtsyのカテゴリAPIを使用してください
  2. 価格フォーマット: 数値ではなく文字列である必要があります
  3. タグ制限: 出品あたり最大13個のタグ
  4. 必須フィールドの欠落: titledescriptionpricequantitywho_madewhen_made

解決策:

// 送信する前にバリデートする
const validateListing = (data) => {
  const errors = [];

  if (!data.title || data.title.length < 5) {
    errors.push('Title must be at least 5 characters'); // タイトルは5文字以上である必要があります
  }

  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'); // 最大13個のタグが許可されています
  }

  if (!['i_did', 'someone_else', 'collective'].includes(data.whoMade)) {
    errors.push('Invalid who_made value'); // 無効なwho_madeの値
  }

  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. 画像を並行ではなく、順次アップロードする

本番環境デプロイチェックリスト

本番稼働前に:

監視とアラート

以下のメトリクスを追跡します。

// 追跡するメトリクスの例
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
  }
};

// 高い失敗率でアラートを出す
const failureRate = metrics.apiCalls.failed / metrics.apiCalls.total;

if (failureRate > 0.05) { // 5%以上の失敗率
  sendAlert('Etsy API failure rate above 5%'); // Etsy APIの失敗率が5%を超えています
}

// クォータが低い場合にアラートを出す
if (metrics.quotaUsage.current < 500) {
  sendAlert('Etsy API quota below 500 calls remaining'); // Etsy APIの残りのクォータが500コールを下回っています
}

実世界のユースケース

マルチチャネル在庫同期

ホームデコレーションの販売者が、Etsy APIを使用してEtsy、Shopify、Amazon間で在庫を同期しています。

実装フロー:

  1. 注文作成時にEtsyウェブフックがトリガーされます
  2. 中央システムが在庫を減らします
  3. APIコールがShopifyとAmazonの数量を更新します
  4. 確認情報がデータベースにログされます

自動注文フルフィルメント

プリントオンデマンド事業者が注文処理を自動化しています。

主要な連携ポイント:

分析ダッシュボード

販売者向け分析ツールが複数のショップ間でデータを集計しています。

API経由で収集されたデータ:

結論

Etsy APIは、マーケットプレイス機能への包括的なアクセスを提供します。主なポイント:

ボタン

FAQセクション

Etsy APIは何のために使われますか?

Etsy APIは、開発者がEtsyのマーケットプレイスと連携するアプリケーションを構築することを可能にします。一般的なユースケースには、複数の販売チャネルにわたる在庫管理、自動化された注文フルフィルメント、分析ダッシュボード、出品作成ツール、顧客関係管理システムなどがあります。

Etsy APIキーはどのように取得しますか?

Etsy開発者ポータルでアカウントを作成し、Your Appsに移動してCreate a new appをクリックします。登録後、Key String(公開識別子)とShared Secret(秘密鍵)が提供されます。両方を環境変数を使用して安全に保管してください。

Etsy APIは無料で利用できますか?

はい、Etsy APIは開発者にとって無料です。ただし、レート制限が適用されます。アプリあたり毎秒10リクエスト、1時間あたり10,000コールです。より高い制限は、特定のユースケースについてEtsyからの承認が必要です。

Etsy APIはどのような認証を使用しますか?

Etsyは認証にOAuth 2.0を使用します。ユーザーはEtsyの認証ページを通じてアプリを承認し、アプリはアクセストークン(1時間有効)とリフレッシュトークン(新しいアクセストークンを取得するため)を受け取ります。

Etsy APIのレート制限はどのように処理しますか?

毎秒10リクエストの制限を下回るように、リクエストキューイングを実装してください。x-etsy-quota-remainingヘッダーを監視して時間ごとの使用量を追跡してください。HTTP 429(Too Many Requests)応答を受け取った場合は、指数関数的バックオフを使用してください。

ライブショップなしで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の開発と利用をよりシンプルなことにする方法を発見できる