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連携を構築できるようになります。
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にアクセスする前に、開発者アカウントが必要です。
- Etsy開発者ポータルにアクセスします。
- Etsyアカウントでサインインします(または作成します)。
- 開発者ダッシュボードのYour Appsに移動します。
- Create a new appをクリックします。
ステップ2:アプリケーションの登録
アプリ登録フォームに記入します。
- App Name(アプリ名):明確で分かりやすい名前(OAuth中にユーザーに表示されます)
- App Description(アプリの説明):アプリが何をするのか、誰が使用するのかを説明します
- Redirect URI(リダイレクトURI):Etsyが認証後にユーザーを送信する場所(HTTPSを使用する必要があります)
- Production/Development(本番/開発):テスト用に開発モードから始めます
送信後、以下の情報を受け取ります。
- Key String(キー文字列):公開API識別子
- Shared Secret(共有シークレット):プライベートAPIシークレット(決して公開しないでください)
セキュリティ上の注意: 資格情報は環境変数に保存し、コードには決して含めないでください。
# .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の安定性を保護するためにレート制限を課しています。
- 標準制限:アプリあたり毎秒10リクエスト
- バースト許容:1秒間に最大50リクエスト(短期間のバースト)
- クォータシステム:アプリあたり1時間あたり10,000コール(1時間ごとにリセット)
制限を超えると、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は、リアルタイムイベント通知のためのウェブフックをサポートしています。
- 開発者ダッシュボードのYour Appsに移動します。
- アプリを選択します。
- Add Webhookをクリックします。
- 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;
// ウェブフックの署名を検証する
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')
);
}
ウェブフックのベストプラクティス
- 署名を検証する - スプーフィングされたウェブフックを防ぎます
- 200 OKを迅速に返す - Etsyは5秒以内に200以外の応答の場合に再試行します
- 非同期で処理する - バックグラウンド処理のためにイベントをキューに入れます
- 冪等性を実装する - 重複するウェブフックの配信を処理します
- すべてのイベントをログに記録する - タイムスタンプ付きの監査証跡で問題をデバッグします
よくある問題のトラブルシューティング
問題:OAuthトークン交換の失敗
症状: 認証中に401または403エラーが発生する。
診断:
// エラー応答を確認する
const error = await response.json();
console.error('OAuth error:', error); // OAuthエラー
解決策:
- リダイレクトURIが完全に一致するか(https://と末尾のスラッシュを含む)確認する
client_idとclient_secretが正しいことを確認する- 承認コードが期限切れになっていないか確認する(コードは1回使用または5分後に期限切れになります)
- アプリが本番モードであることを確認する(開発アプリはテストアカウントにのみアクセスできます)
問題:レート制限を超過した
症状: HTTP 429応答を受け取る。
解決策:
- レート制限付きのリクエストキューイングを実装する
- 再試行に指数関数的バックオフを使用する
- 可能な場合はリクエストをバッチ処理する(例:複数の出品を一度に取得する)
- クォータヘッダーを監視し、積極的に調整する
// シンプルなレートリミッター
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。
一般的な原因:
- 無効な
category_id: 有効なIDを取得するためにEtsyのカテゴリAPIを使用してください - 価格フォーマット: 数値ではなく文字列である必要があります
- タグ制限: 出品あたり最大13個のタグ
- 必須フィールドの欠落:
title、description、price、quantity、who_made、when_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;
};
問題:ウェブフックが届かない
症状: 注文は処理されるが、ウェブフックエンドポイントには何も届かない。
診断:
- 開発者ダッシュボードでウェブフック配信ログを確認する
- エンドポイントが5秒以内に200 OKを返すことを確認する
- curlでエンドポイントを手動でテストする
解決策:
- 有効なSSL証明書を持つHTTPSを確保する
- ファイアウォールでEtsyウェブフックIPをホワイトリストに追加する
- 署名検証ロジックを確認する
- 開発中にウェブフックテストツールを使用する
問題:画像のアップロードに失敗する
症状: 出品は作成されるが、画像がエラーを返す。
解決策:
- 画像が有効な形式(JPEG、PNG、GIF)であることを確認する
- ファイルサイズを確認する(画像あたり最大20MB)
- Base64エンコーディングが正しいことを確認する
- 画像をアップロードする前に出品が存在することを確認する
- 画像を並行ではなく、順次アップロードする
本番環境デプロイチェックリスト
本番稼働前に:
- [ ] 開発モードから本番アプリモードに切り替える
- [ ] すべてのリダイレクトURIを本番URLに更新する
- [ ] 安全なトークンストレージ(暗号化されたデータベース)を実装する
- [ ] 自動トークン更新ロジックを追加する
- [ ] レート制限とリクエストキューイングを設定する
- [ ] HTTPSでウェブフックエンドポイントを設定する
- [ ] 包括的なエラー処理を実装する
- [ ] すべてのAPIコールにログを追加する
- [ ] クォータ使用量の監視を設定する
- [ ] 一般的な問題に対するランブックを作成する
- [ ] 複数のショップアカウントでテストする
- [ ] ユーザーオンボーディングのためのOAuthフローを文書化する
監視とアラート
以下のメトリクスを追跡します。
// 追跡するメトリクスの例
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間で在庫を同期しています。
- 課題:手動での在庫更新が過剰販売につながっていました
- 解決策:Etsy APIウェブフックと連携した中央在庫システム
- 結果:過剰販売の発生ゼロ、週12時間の時間節約
実装フロー:
- 注文作成時にEtsyウェブフックがトリガーされます
- 中央システムが在庫を減らします
- APIコールがShopifyとAmazonの数量を更新します
- 確認情報がデータベースにログされます
自動注文フルフィルメント
プリントオンデマンド事業者が注文処理を自動化しています。
- 課題:毎日50件以上の注文があり、手動でのデータ入力が必要でした
- 解決策:Etsy APIとフルフィルメントプロバイダーとの連携
- 結果:注文は5分以内に自動的に生産にルーティングされます
主要な連携ポイント:
- ウェブフックが
orders/createイベントをリッスンします - 注文の詳細が印刷プロバイダーAPIに送信されます
- 追跡番号が返され、Etsy API経由で更新されます
- 顧客は自動発送通知を受け取ります
分析ダッシュボード
販売者向け分析ツールが複数のショップ間でデータを集計しています。
- 課題:複数のショップを持つ販売者は、統合されたレポートが不足していました
- 解決策:OAuthベースの複数ショップデータ集約
- 結果:売上、トラフィック、コンバージョン率のリアルタイムダッシュボード
API経由で収集されたデータ:
- ショップ統計(出品数、売上、収益)
- 注文履歴とトレンド
- 出品のパフォーマンス指標
- 顧客レビューデータ
結論
Etsy APIは、マーケットプレイス機能への包括的なアクセスを提供します。主なポイント:
- OAuth 2.0認証は、慎重なトークン管理と自動更新を必要とします
- レート制限(10リクエスト/秒、10K/時間)は、積極的な監視とキューイングを必要とします
- ウェブフックは、リアルタイムの注文および在庫同期を可能にします
- 適切なエラー処理と再試行ロジックは、本番環境の信頼性に不可欠です
- Apidogは、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廃止前に徹底的にテストしてください。
