ブログ

前回の記事はこちら eBay Trading API:ReviseFixedPriceItemで出品済みの価格・在庫・タイトルを安全に更新する はじめに 本記事は、全42回にわたる「eBay API 実践ガイド」の第7回です。 これまでの連載で、単一商品(#5)およびバリエーション商品(#6)の新規出品プロセスが完成しました。しかし、EC サイトの運用において「出品して終わり」ということはあり得ません。為替変動による価格調整、SEO 改善のためのタイトル変更など、出品後データの更新(Revision) は日常業務の大半を占めます。 この記事で得られること: ReviseFixedPriceItem を用いた部分更新(Partial Update)のアーキテクチャ理解と空タグの危険性。 変更不可フィールドと DeletedField の正しい使い方。 CSV を用いた一括更新バッチと、HTTP 429 (Too Many Requests) を防ぐ Exponential Backoff(指数バックオフ) の実装。 背景・なぜこれが重要か (Motivation) eBay の Trading API において、既存の出品を更新するメソッドは主に ReviseFixedPriceItem です。この API は非常に強力で、タイトルから画像、Item Specifics に至るまで、出品時に設定したほぼすべての項目を後から書き換えることができます。 しかし、その強力さゆえに、リクエストの組み方を一つ間違えると「更新したかったのは価格だけなのに、商品説明が全部消えてしまった」という大事故を引き起こします。「何を送り、何を送らないべきか」。この 部分更新(Delta Update) の思想を理解することが、安全な運用システムの絶対条件となります。 基本的な使い方(ベースライン):部分更新の原則 ReviseFixedPriceItem の最大の特徴は、「XML に含めたタグだけが更新され、省略したタグは現状維持される」 という点です。 例えば、ItemID: 112233445566 の商品の「タイトル」と「価格」だけを変更したい場合、ベースラインの XML は以下のようになります。 <?xml version="1.0" encoding="utf-8"?> <ReviseFixedPriceItemRequest xmlns="urn:ebay:apis:eBLBaseComponents"> <ErrorLanguage>en_US</ErrorLanguage> <WarningLevel>High</WarningLevel> <Item> <ItemID>112233445566</ItemID> <Title>New SEO Optimized Title for Vintage Camera</Title> <StartPrice currencyID="USD">289.99</StartPrice> </Item> </ReviseFixedPriceItemRequest> 【部分更新の処理フローイメージ】 送信するXML eBay側の処理 ───────────────── ───────────────────────── <Title>新しい値</Title> → Titleを「新しい値」に上書き (反映) <StartPrice>289</StartPrice> → Priceを289に上書き (反映) (Descriptionタグなし) → Descriptionは現状維持 (安全) <Description></Description> → Descriptionを空に上書き (危険) 実務で躓く場面・深いポイント (Core) ここからは、エンジニアがよくハマる「更新 API の罠」を解説します。 1. 空タグによる意図せぬデータ消失 (Accidental Wipe) 最も多いバグがこれです。プログラム側で「今回は価格だけ更新するから、タイトルは空文字でいいや」と考えて <Title></Title> を送信すると、eBay は「タイトルを空にしろ」と解釈します。 後述する CSV 一括更新スクリプトでは、「CSV の空欄」をプログラム側でフィルタリングし、XML タグ自体を生成しない(現状維持にする) という厳格な制御を行っています。 2. 変更不可フィールドの存在 Revise API でも変更できない(または条件付きでしか変更できない)フィールドがあります。これを変更しようとすると Error 21916635 などで弾かれます。 フィールド 制約内容 PrimaryCategory出品から14日経過後、または販売実績がある場合は変更不可。 ListingType固定(Fixed Price から Auction への変更などは不可)。 VariationSpecificsSetバリエーションの「軸の名前(例: Color)」の変更は不可(値の追加は可)。 3. 項目の明示的な削除 (DeletedField) 空タグが使えない項目において、既存データを完全に削除するためには eBay 特有の <DeletedField> タグを使用します。 <ReviseFixedPriceItemRequest xmlns="urn:ebay:apis:eBLBaseComponents"> <DeletedField>Item.SubTitle</DeletedField> <DeletedField>Item.SecondaryCategory</DeletedField> <DeletedField>Item.ShippingDetails.ShippingServiceOptions</DeletedField> # 配送オプション全体が削除されます。FreeShipping設定も消えるため注意! <Item><ItemID>112233445566</ItemID></Item> </ReviseFixedPriceItemRequest> 4. バリエーション商品(Multi-SKU)に関する罠 本記事の実装は 単一商品(Single-SKU) が対象です。前回(#6)で解説したバリエーション商品に対して、本記事のようにトップレベルの <Quantity> を送信しても無視されます。バリエーションの在庫を更新する場合は、特定の <Variation> ブロック内で <SKU> と <Quantity> を指定する必要があります。 5. UUID は Revise では冪等性を保証しない 第5回で紹介した <UUID> による冪等性(Idempotency)保証は、実は Add 系の呼び出し(新規出品)に限定された機能 です。ReviseFixedPriceItem に UUID を送っても無害ですが、二重更新を防ぐ効果はありません。そのため、バッチ処理側で適切なリトライ制御を行う必要があります。 堅牢な実装:動的 XML ビルダーによる安全な更新関数 「意図せぬデータ消失の防止」「NoneType クラッシュ対策」「処理スキップの明示」をすべて組み込んだ堅牢な更新関数を実装します。今回はバッチ処理等でも使い回せるよう、Rate Limit 対策のバックオフ関数も同じファイルに定義します。 # revise_item.py import requests import xml.etree.ElementTree as ET import html import time import random from dataclasses import dataclass, field from enum import Enum from typing import List from config import eBayConfig from ebay_token_manager import eBayTokenManager class ReviseStatus(Enum): SUCCESS = "success" SKIPPED = "skipped" @dataclass class ReviseResult: item_id: str status: ReviseStatus warnings: List[str] = field(default_factory=list) def _get_text(node: ET.Element, tag: str, ns: dict) -> str: """要素が存在しない場合の NoneType クラッシュを防ぐヘルパー関数""" if node is None: return "" el = node.find(tag, ns) return el.text if el is not None and el.text is not None else "" def api_call_with_backoff(fn, max_retries=3): """HTTP 429エラー時に待機時間を倍増させながらリトライするヘルパー""" for attempt in range(max_retries): try: return fn() except requests.exceptions.HTTPError as e: if e.response is not None and e.response.status_code == 429: # Too Many Requests wait = (2 ** attempt) + random.uniform(0, 1) print(f"[警告] Rate limited. Retrying in {wait:.2f}s...") time.sleep(wait) else: raise raise Exception("Max retries exceeded after HTTP errors") def revise_single_item(config: eBayConfig, token: str, item_id: str, updates: dict) -> ReviseResult: """ 指定された項目のみを安全に部分更新する関数。 """ if not item_id: raise ValueError("ItemID is required for revision.") xml_elements = [] if updates.get('title'): xml_elements.append(f"<Title>{html.escape(updates['title'])}</Title>") if updates.get('price') is not None: try: price_val = float(updates['price']) xml_elements.append(f'<StartPrice currencyID="USD">{price_val}</StartPrice>') except ValueError: raise ValueError("Price must be numeric.") if updates.get('quantity') is not None: try: qty_val = int(updates['quantity']) xml_elements.append(f"<Quantity>{qty_val}</Quantity>") except ValueError: raise ValueError("Quantity must be an integer.") # 【ポイント】 更新項目がない場合はAPIコールをスキップし、状態を明確に返す if not xml_elements: return ReviseResult(item_id=item_id, status=ReviseStatus.SKIPPED) inner_xml = "\n".join(xml_elements) headers = { "X-EBAY-API-CALL-NAME": "ReviseFixedPriceItem", "X-EBAY-API-SITEID": "0", "X-EBAY-API-COMPATIBILITY-LEVEL": "1323", "X-EBAY-API-IAF-TOKEN": token, "Content-Type": "text/xml" } xml_payload = f"""<?xml version="1.0" encoding="utf-8"?> <ReviseFixedPriceItemRequest xmlns="urn:ebay:apis:eBLBaseComponents"> <ErrorLanguage>en_US</ErrorLanguage> <WarningLevel>High</WarningLevel> <Item> <ItemID>{html.escape(str(item_id))}</ItemID> {inner_xml} </Item> </ReviseFixedPriceItemRequest> """ def execute_request(): res = requests.post( config.trading_api_url, headers=headers, data=xml_payload.encode('utf-8'), timeout=30 ) res.raise_for_status() return res # バックオフ関数を使ってリクエストを安全に実行 response = api_call_with_backoff(execute_request) # レスポンス解析 namespace = {'ns': 'urn:ebay:apis:eBLBaseComponents'} root = ET.fromstring(response.text) ack = _get_text(root, 'ns:Ack', namespace) warnings_list = [] if ack == 'Warning': warnings_list = [ _get_text(e, 'ns:LongMessage', namespace) for e in root.findall('ns:Errors', namespace) if _get_text(e, 'ns:SeverityCode', namespace) == 'Warning' ] if ack not in ['Success', 'Warning']: errors = [ { "code": _get_text(e, 'ns:ErrorCode', namespace), "message": _get_text(e, 'ns:LongMessage', namespace) } for e in root.findall('ns:Errors', namespace) if _get_text(e, 'ns:SeverityCode', namespace) == 'Error' ] raise Exception(f"Revision Failed for {item_id}: {errors}") return ReviseResult(item_id=item_id, status=ReviseStatus.SUCCESS, warnings=warnings_list) 実践:CSVを用いた一括更新バッチ処理とバックオフ制御 実務では「年末商戦に向けて、CSVで数百件の商品のタイトルと価格を一気に変更したい」といったバッチ処理が求められます。 用意するCSV (bulk_updates.csv) の例: item_id,title,price,quantity 112233445566,Holiday Special Vintage Camera,250.00, 998877665544,Limited Edition Lens 50mm,,5 776655443322,,, ※ 3行目の 776655443322,,, は更新項目がすべて空欄のため、プログラム内で SKIPPED 扱いとなります(実際の運用では事前に除外して構いません)。 Exponential Backoff(指数バックオフ)による Rate Limit 対策 Trading API の Rate Limit は「1日あたりの呼び出し回数上限(Daily Call Limit)」と「瞬間的な過負荷による 429 Too Many Requests」の両方が存在します。429 エラーが出た際に即座にリトライするとブロックされるため、前述の指数バックオフ機構が必須です。 # bulk_revise_from_csv.py import csv from config import eBayConfig from ebay_token_manager import eBayTokenManager from revise_item import revise_single_item, ReviseStatus def run_bulk_update(csv_file_path: str): config = eBayConfig() manager = eBayTokenManager(config.client_id, config.client_secret, config.refresh_token, config.env) stats = {"success": 0, "skipped": 0, "error": 0} with open(csv_file_path, mode='r', encoding='utf-8-sig') as f: reader = csv.DictReader(f) for row in reader: item_id = row.get('item_id', '').strip() if not item_id: continue # CSVの空欄をフィルタリングし、意図せぬデータ消失(空タグ送信)を防ぐ updates = {k: v for k, v in row.items() if k != 'item_id' and v.strip() != ''} try: token = manager.get_token() result = revise_single_item(config, token, item_id, updates) if result.status == ReviseStatus.SKIPPED: print(f"[SKIPPED]: {item_id} (No updates)") stats["skipped"] += 1 else: warn_text = f" (Warnings: {len(result.warnings)})" if result.warnings else "" print(f"[SUCCESS]: {item_id}{warn_text}") stats["success"] += 1 except Exception as e: print(f"[ERROR] updating {item_id}: {e}") stats["error"] += 1 print("\n--- Batch Update Complete ---") print(f"Success: {stats['success']} | Skipped: {stats['skipped']} | Errors: {stats['error']}") if __name__ == "__main__": run_bulk_update("bulk_updates.csv") パフォーマンス・スケーリング視点 (深度) 【高頻度な「在庫数・価格」同期における注意と使い分け】 上記で作成した CSV 一括更新バッチは、「タイトル、説明、Item Specifics などのカタログ情報を一括改修する(例: 季節ごとのSEO対策)」 という目的においては最適です。 しかし、「自社倉庫の在庫が1個売れたから、eBay の在庫数もすぐ減らしたい」「15分おきに価格と在庫だけを他モールと同期させたい」といった高頻度のトランザクション同期に、この ReviseFixedPriceItem をループで回すのはシステム崩壊の原因になります。Revise は eBay 内部で商品カタログ全体を再インデックスするため負荷が大きいです。 ReviseFixedPriceItem: カタログ情報(タイトル等)の変更。日次・週次ベースのバッチ処理や夜間の分散実行。 ReviseInventoryStatus: 「価格」と「在庫数」だけを更新する場合。次回解説するこちらの軽量 API を使用します。 まとめ 本記事では、出品済み商品のデータを安全にメンテナンスするための ReviseFixedPriceItem の使い方を解説しました。 ベースライン: 変更差分だけを XML に含める「部分更新(Delta Update)」の原則。 深いポイント: 空欄によるデータ消失の防止、DeletedField の使い方、そして安全な XML 解析とステータス管理。 スケーリング: HTTP 429 を防ぐ指数バックオフ制御と、目的(カタログ改修 vs 高頻度在庫同期)に応じた API の使い分け。 次のステップ 今回、パフォーマンスの章で触れた「軽量な在庫同期 API」の正体について掘り下げます。 次回(#8)は、「ReviseInventoryStatusで複数商品の在庫・価格を高速同期する」 です。前回のバリエーション出品で仕込んだ InventoryTrackingMethod=SKU がここで真価を発揮します。多店舗展開の在庫連動を組むエンジニア必見の内容です!お楽しみに。 技術的なサポートやご質問について APIの実装や仕様に関してご不明な点がございましたら、以下のeBay Japan 技術サポート窓口までお気軽にお問い合わせください: ebayjapan-techsupport@ebay.com
前回の記事はこちら eBay Trading API:AddFixedPriceItemでバリエーション商品(Multi-SKU)を構築する極意 はじめに 本記事は、全42回にわたる「eBay API 実践ガイド」の第6回です。 前回(#5)は、堅牢な単一商品(Single-SKU)の出品システムを構築しました。しかし、アパレル、靴、アクセサリーなどを扱う越境 EC においては、サイズや色ごとに異なる在庫と価格を持つ 「バリエーション商品(Multi-SKU)」 の出品が避けられません。 この記事で得られること: eBay API 屈指の複雑さを誇る <Variations> ブロックの完全な構造理解。 共通スペック(ItemSpecifics)と個別スペック(VariationSpecifics)の厳格な検証ロジック。 今後の在庫管理を劇的に楽にする InventoryTrackingMethod の SKU ベース化と OutOfStockControl の活用。 背景・なぜこれが重要か (Motivation) 「サイズ違いの商品を別々の ItemID で出品してはダメなのか?」という疑問を抱く方も多いですが、ビジネス的には絶対に避けるべきです。 別々に出品すると、eBay の出品手数料(Insertion Fee)が余分にかかるだけでなく、最も重要な 「販売履歴(Sales History)」 が分散してしまいます。バリエーションとして1つの ItemID に統合することで、「このページから累計1,000着売れている」という実績が合算され、Best Match(eBayの検索アルゴリズム)における SEO が飛躍的に向上します。 しかし、その代償としてバリエーション出品の XML ペイロードは非常に深く、ネストされた構造になります。1箇所でも整合性が崩れるとエラーが返るため、アーキテクチャの正確な理解が求められます。 基本的な使い方(ベースライン):バリエーションXMLの全体像 バリエーション出品では、特に「全体定義(Set)」と「個別定義(Variation)」の二重構造を理解することが重要です。 <Item> <Title>Premium Cotton T-Shirt</Title> <ListingDuration>GTC</ListingDuration> <OutOfStockControl>true</OutOfStockControl> <InventoryTrackingMethod>SKU</InventoryTrackingMethod> <ItemSpecifics> <NameValueList> <Name>Brand</Name> <Value>AwesomeApparel</Value> </NameValueList> </ItemSpecifics> <Variations> <!-- バリエーション全体で使用する軸の定義 --> <VariationSpecificsSet> <NameValueList> <Name>Color</Name> <Value>Red</Value> <Value>Blue</Value> </NameValueList> <NameValueList> <Name>Size</Name> <Value>S</Value> <Value>M</Value> </NameValueList> </VariationSpecificsSet> <!-- 個別の在庫・価格・属性定義(SKU単位) --> <Variation> <SKU>TSHIRT-RED-S</SKU> <StartPrice>19.99</StartPrice> <Quantity>10</Quantity> <VariationSpecifics> <NameValueList><Name>Color</Name><Value>Red</Value></NameValueList> <NameValueList><Name>Size</Name><Value>S</Value></NameValueList> </VariationSpecifics> </Variation> <!-- 他のバリエーションも同様に続く --> </Variations> </Item> 【用語解説】 GTC (Good 'Til Cancelled) とは GTC は手動で終了するまで自動継続される出品期間の設定です。現在の eBay では、バリエーション商品は基本的に GTC で運用し、後述する OutOfStockControl と組み合わせて使います。 実務で躓く場面・深いポイント (Core) 1. InventoryTrackingMethod は絶対に「SKU」にせよ デフォルトでは、eBay の商品は ItemID で管理されます。しかし、バリエーションを持つ場合、後から「特定のバリエーションだけ在庫を更新したい」という際、ItemID ベースだと非常に複雑な XML を組むことになります。出品時に InventoryTrackingMethod を SKU に指定しておくことで、eBay 側の管理キーが SKU に切り替わり、在庫同期が劇的に簡単になります。 2. VariationPictures の制約(画像は1軸でしか切り替えられない) 「赤いSサイズの画像」「青いMサイズの画像」のように、すべての組み合わせに画像を設定したくなりますが、eBayの仕様上それは不可能です。バリエーション画像(<Pictures> ブロック)は、必ず1つの軸(通常は Color)にしか紐付けられません。これを破ると Error 21916587 が発生します。 3. NameとValueの厳密な文字列一致 <VariationSpecificsSet> で定義した「Color」や「Red」という文字列は、その下の <Variation> ブロック、さらには <Pictures> ブロックでも、大文字小文字・スペースに至るまで完全に一致している必要があります。 【注意】 バリエーション数の上限 eBay のバリエーション数には上限があります(一般的に1出品につき最大250 SKU)。これを超えると Error 21916284 となります。 主要なエラーコード早見表 エラーコード 概要 対策 21916587 画像の切り替え軸が不正 VariationPictures は必ず VariationSpecificsSet の1つの軸(Color等)と完全に一致させること。 21916284 バリエーション上限超過 1出品あたりの上限(通常250SKU)以内に分割して出品する。 21916286 組み合わせの不備 VariationSpecificsSet で定義した属性の組み合わせが、下部の Variation で矛盾している。 堅牢な実装:バリエーションXMLの動的ビルダーと事前検証 API エラーを防ぐために、文字列の不一致や定義の欠落を事前に検知しつつ XML を構築する Python 実装例を紹介します。 import html from typing import Dict def _validate_consistency(variation_data: Dict) -> None: """ VariationSpecificsSet と各Variationの整合性を検証する """ defined_aspects = { name: set(values) for name, values in variation_data['aspects'].items() } defined_aspect_names = set(defined_aspects.keys()) for var in variation_data['skus']: var_aspect_names = set(var['specifics'].keys()) # 1. SKUの値がSetに存在するか(順方向チェック) for spec_name, spec_value in var['specifics'].items(): if spec_name not in defined_aspects: raise ValueError(f"SKU '{var['sku']}' の軸 '{spec_name}' は未定義です。") if spec_value not in defined_aspects[spec_name]: raise ValueError(f"SKU '{var['sku']}' の値 '{spec_value}' が定義済リストにありません。") # 2. Setの全軸がSKUに揃っているか(逆方向チェック) # ※eBayはスパースなバリエーション(全組み合わせの一部欠落)を許容しているため、 # 全組み合わせの網羅チェックは行いません。 missing = defined_aspect_names - var_aspect_names if missing: raise ValueError(f"SKU '{var['sku']}' に軸 {missing} の指定がありません。") def build_variations_xml(variation_data: Dict) -> str: """ 辞書データから堅牢な <Variations> ブロックのXML文字列を生成する """ _validate_consistency(variation_data) # 1. 全体定義 (VariationSpecificsSet) の構築 set_tags = "" for spec_name, spec_values in variation_data['aspects'].items(): val_tags = "".join([f"<Value>{html.escape(str(v))}</Value>" for v in spec_values]) set_tags += f""" <NameValueList> <Name>{html.escape(str(spec_name))}</Name> {val_tags} </NameValueList> """ # 2. 個別定義 (Variation) の構築 variation_tags = "" for var in variation_data['skus']: spec_tags = "".join([f"<NameValueList><Name>{html.escape(str(k))}</Name><Value>{html.escape(str(v))}</Value></NameValueList>" for k, v in var['specifics'].items()]) variation_tags += f""" <Variation> <SKU>{html.escape(var['sku'])}</SKU> <StartPrice>{var['price']}</StartPrice> <Quantity>{var['quantity']}</Quantity> <VariationSpecifics>{spec_tags}</VariationSpecifics> </Variation> """ return f""" <Variations> <VariationSpecificsSet>{set_tags}</VariationSpecificsSet> {variation_tags} </Variations> """ パフォーマンス・スケーリング視点 (深度) GTC と OutOfStockControl の魔法 実務でバリエーションを扱う際、在庫が0になった SKU は通常 Listing 自体が終了してしまいます。これを防ぐために、eBay では <OutOfStockControl>true</OutOfStockControl> という強力なフラグが用意されています。 このフラグを有効にしておくと、ある SKU の Quantity を 0 にしても出品自体は終了せず、検索結果では「Out of stock」として非表示になるだけで販売履歴(SEOパワー)を維持し続けます。商品が再入荷した際に Quantity を 1 以上に戻せば、即座に販売が再開されます。大規模セラーにとって、この機能の有効化は必須の戦略です。 まとめ 本記事では、アパレル等の販売に欠かせないバリエーション商品(Multi-SKU)の出品構造を解説しました。 ベースライン: VariationSpecificsSet と Variation の二重構造を理解する。 深いポイント: 軸の不一致による API エラーを、プログラム側で事前に検知する。 スケーリング: InventoryTrackingMethod=SKU と OutOfStockControl を組み合わせ、SEO を維持しつつ在庫管理を効率化する。 次のステップ 単一商品とバリエーション商品の「出品」が完了しました。しかし、EC の実務において出品は「始まり」に過ぎません。 次回(#7)からは、新しいフェーズである 【Trading API - 在庫管理】 に突入します。まずは 「ReviseFixedPriceItemで在庫数と価格を高速に同期する」 方法について解説します!お楽しみに。 次の記事はこちら 技術的なサポートやご質問について APIの実装や仕様に関してご不明な点がございましたら、以下のeBay Japan 技術サポート窓口までお気軽にお問い合わせください: ebayjapan-techsupport@ebay.com
前回の記事はこちら eBay Trading API:AddFixedPriceItemで単一商品(Single-SKU)を安全に出品する はじめに 本記事は、全42回にわたる「eBay API 実践ガイド」の第5回です。 これまでの連載で、OAuth 認証基盤、Sandbox 環境、配送などのメタデータ、そして画像(EPS)のアップロード機能が整いました。今回はこれらをすべて結合し、ついに eBay に商品を出品(Listing) します。 この記事で得られること: AddFixedPriceItem を用いた固定価格商品(即決)の XML ペイロード構造の理解。 ネットワークエラーによる「二重出品」を防ぐ、UUIDの永続化と冪等性(Idempotency)の確保。 実務で致命傷になる XML Injection の防止と、適切なデータ型バリデーション手法。 背景・なぜこれが重要か (Motivation) eBay に商品を出品する際、現在は Inventory API(REST)というモダンな選択肢もあります。しかし、越境 EC の実務において、依然として Trading API の AddFixedPriceItem が広く使われているのには理由があります。 それは 「圧倒的な即時性と柔軟性」 です。REST API が内部的に非同期処理を多用するのに対し、Trading API はリクエストを送った瞬間に ItemID が発行され、即座にサイトに反映されます。 ただし、AddFixedPriceItem の XML は巨大かつ複雑です。必須項目が1つでも欠ければエラー弾きに遭うため、「どのブロックが何のために必要なのか」をアーキテクチャの視点から理解することが、堅牢な出品システム構築の鍵となります。 基本的な使い方(ベースライン):出品XMLの全体像 AddFixedPriceItem のリクエストは、大きく以下のブロックに分かれています。 基本情報: タイトル、カテゴリ、価格、コンディション、数量。 ポリシー情報: 返品・支払い・配送のポリシー(現在主流の Business Policies を使用するか、レガシーな個別指定を行うか)、発送までの日数。 画像情報: 前回取得した EPS の URL。 商品詳細設定 (Item Specifics): ブランドやサイズなどの必須スペック情報。 これらを愚直に XML で組むと以下のようになります(一部省略)。 <?xml version="1.0" encoding="utf-8"?> <AddFixedPriceItemRequest xmlns="urn:ebay:apis:eBLBaseComponents"> <ErrorLanguage>en_US</ErrorLanguage> <WarningLevel>High</WarningLevel> <Item> <Title>Sample Product Title</Title> <Description><![CDATA[Detailed item description goes here.]]></Description> <PrimaryCategory> <CategoryID>12345</CategoryID> </PrimaryCategory> <StartPrice currencyID="USD">99.99</StartPrice> <ConditionID>1000</ConditionID> <Country>JP</Country> <Currency>USD</Currency> <DispatchTimeMax>3</DispatchTimeMax> <ListingDuration>GTC</ListingDuration> </Item> </AddFixedPriceItemRequest> 実務で躓く場面・深いポイント (Core) ここからは、実稼働するシステムを組む上で絶対に避けては通れない、実務レベルの落とし穴とその解決策を解説します。 1. 二重出品を防ぐ魔法のキー「UUID」とその「永続化」 出品 API は処理が重いため、eBay 側のサーバー都合やネットワークの瞬断でタイムアウトが発生することがあります。この時、プログラム側で「失敗した」と判定してリトライ(再送)をかけると、実は eBay 側では最初の処理が成功しており、同じ商品が二重に出品されてしまう 悲劇が起こります。 これを防ぐのが <UUID> タグです。しかし、「リクエストの直前で UUID を生成する」コードは絶対に書いてはいけません。 その直後にクラッシュした場合、UUID はメモリから消失し、再送時に新しい UUID が生成されて冪等性が失われるからです。 【ベストプラクティス】: API をコールする前に、必ず UUID(32桁の16進数)を生成し、自社のデータベース(DB)に出品予定データと一緒に 「保存(永続化)」 してください。リトライ時は常にその DB の UUID を読み込んで送信します。 2. XML Injection 防止と数値バリデーションの使い分け Title の値に & や < が含まれている(例: "Canon AE-1 & AE-1P")と、そのまま f-string で埋め込んだ瞬間に XML が壊れます。プレーンテキストは html.escape() で必ずエスケープし、HTML タグを含む Description は <![CDATA[ ... ]]> で囲む必要があります。 一方で、価格や数量といった数値フィールドに対してエスケープ処理を行うのはアンチパターンです(フォーマットエラーの原因になります)。数値フィールドや各種プロファイル ID は事前に Python 側で厳格な型チェック(バリデーション)を行うのが正解です。 3. Business Policies (ビジネスポリシー) の必須化 多くのアカウントでは現在、支払い・返品・発送の設定をまとめた Business Policies の利用が強制されています。これらが強制されているアカウントで古い形式の <ReturnPolicy> などを送ると、Error 21919187 で弾かれます。代わりに <SellerProfiles> を使用します。 【メモ】: 自分のアカウントが Business Policies 有効か確認するには? eBay の Web サイト(Seller Hub)から確認するか、GetUser API を叩くことで判定可能です。現在新規作成されたセラーアカウントの多くはデフォルトで有効化されています。 堅牢な実装:UUID と エスケープ処理を組み込んだ出品スクリプト 上記の実務的な課題をすべてクリアした安全な出品関数を実装します。 # add_fixed_price_item.py import requests import xml.etree.ElementTree as ET import uuid import html from config import eBayConfig from ebay_token_manager import eBayTokenManager # 【注意】: idempotency_key (UUID) は必ずAPI呼び出し前にDBへ保存済みのものを渡してください。 # Noneを渡すとクラッシュ時に冪等性が失われます(テスト用途のみ許容)。 def list_single_item(config: eBayConfig, token: str, item_data: dict, eps_urls: list, idempotency_key: str = None) -> str: """ 堅牢な単一商品出品処理。 """ if idempotency_key is None: idempotency_key = uuid.uuid4().hex # 【ポイント①】: 数値フィールドとIDの事前バリデーション(エスケープの代わり) try: price = float(item_data['price']) quantity = int(item_data['quantity']) category_id = int(item_data['category_id']) condition_id = int(item_data['condition_id']) dispatch_time = int(item_data.get('dispatch_time', 3)) shipping_profile_id = int(item_data['shipping_profile_id']) return_profile_id = int(item_data['return_profile_id']) payment_profile_id = int(item_data['payment_profile_id']) except (ValueError, TypeError, KeyError) as e: raise ValueError(f"Invalid or missing data in item_data: {e}") headers = { "X-EBAY-API-CALL-NAME": "AddFixedPriceItem", "X-EBAY-API-SITEID": "0", "X-EBAY-API-COMPATIBILITY-LEVEL": "1323", "X-EBAY-API-IAF-TOKEN": token, "Content-Type": "text/xml" } # 画像ブロックの動的生成 picture_tags = "".join([f"<PictureURL>{html.escape(url)}</PictureURL>" for url in eps_urls]) # 【ポイント②】: テキストフィールドの XML Injection 防止 (html.escape) specifics_tags = "" for name, value in item_data.get("item_specifics", {}).items(): specifics_tags += f""" <NameValueList> <Name>{html.escape(str(name))}</Name> <Value>{html.escape(str(value))}</Value> </NameValueList> """ # XML ペイロードの組み立て xml_payload = f"""<?xml version="1.0" encoding="utf-8"?> <AddFixedPriceItemRequest xmlns="urn:ebay:apis:eBLBaseComponents"> <ErrorLanguage>en_US</ErrorLanguage> <WarningLevel>High</WarningLevel> <Item> <Title>{html.escape(item_data['title'])}</Title> <Description><![CDATA[{item_data['description']}]]></Description> <PrimaryCategory> <CategoryID>{category_id}</CategoryID> </PrimaryCategory> <StartPrice currencyID="USD">{price}</StartPrice> <Quantity>{quantity}</Quantity> <ConditionID>{condition_id}</ConditionID> <Country>JP</Country> <Currency>USD</Currency> <DispatchTimeMax>{dispatch_time}</DispatchTimeMax> <ListingDuration>GTC</ListingDuration> <UUID>{idempotency_key}</UUID> <PictureDetails> {picture_tags} </PictureDetails> <ItemSpecifics> {specifics_tags} </ItemSpecifics> <SellerProfiles> <SellerShippingProfile> <ShippingProfileID>{shipping_profile_id}</ShippingProfileID> </SellerShippingProfile> <SellerReturnProfile> <ReturnProfileID>{return_profile_id}</ReturnProfileID> </SellerReturnProfile> <SellerPaymentProfile> <PaymentProfileID>{payment_profile_id}</PaymentProfileID> </SellerPaymentProfile> </SellerProfiles> </Item> </AddFixedPriceItemRequest> """ # 【ポイント③】: 無限待機を防ぐため timeout を明示。タイムアウト時はDBのUUIDで安全にリトライする response = requests.post( config.trading_api_url, headers=headers, data=xml_payload.encode('utf-8'), timeout=30 ) response.raise_for_status() # レスポンス解析 namespace = {'ns': 'urn:ebay:apis:eBLBaseComponents'} root = ET.fromstring(response.text) ack_node = root.find('ns:Ack', namespace) ack = ack_node.text if ack_node is not None else "" # 【ポイント④】: Warningログの記録と、複数エラーの網羅的キャッチ if ack == 'Warning': warnings = [ e.find('ns:LongMessage', namespace).text if e.find('ns:LongMessage', namespace) is not None else "" for e in root.findall('ns:Errors', namespace) if e.find('ns:SeverityCode', namespace) is not None and e.find('ns:SeverityCode', namespace).text == 'Warning' ] print(f"[WARNING] Listing succeeded with warnings: {warnings}") # 本番ではloggingモジュールを使用 if ack not in ['Success', 'Warning']: error_nodes = root.findall('ns:Errors', namespace) errors = [ { "code": e.find('ns:ErrorCode', namespace).text if e.find('ns:ErrorCode', namespace) is not None else "", "severity": e.find('ns:SeverityCode', namespace).text if e.find('ns:SeverityCode', namespace) is not None else "", "message": e.find('ns:LongMessage', namespace).text if e.find('ns:LongMessage', namespace) is not None else "", } for e in error_nodes if e.find('ns:SeverityCode', namespace) is not None and e.find('ns:SeverityCode', namespace).text == 'Error' ] if errors: raise Exception(f"Listing Failed with {len(errors)} errors: {errors}") # 成功時の ItemID を抽出 item_id_node = root.find('ns:ItemID', namespace) if item_id_node is None: raise Exception("Success returned but ItemID is missing.") return item_id_node.text パフォーマンス・スケーリング視点 (深度) 実務において、毎月数千〜数万件の出品を行う場合、f-string での巨大な XML 構築はメンテナンス性が著しく低下します。 【テンプレートエンジン(Jinja2)の導入】 スケーリングの第一歩として、XML の雛形を外部ファイルに切り離し、Jinja2 を使って Python コードから変数を流し込むアーキテクチャに移行することを強く推奨します。autoescape=True を使えば、XML Injection の心配も無くなります。 <Title>{{ title | e }}</Title> <Description><![CDATA[{{ description | safe }}]]></Description> <UUID>{{ idempotency_key }}</UUID> 【セキュリティ注意】: safe フィルタは CDATA ブロック内でのみ使用してください。autoescape の保護を無効化するため、プレーンテキストのフィールド(Title等)に使用すると XML Injection の脆弱性を生みます。 # Python側の呼び出し例 from jinja2 import Environment, FileSystemLoader env = Environment(loader=FileSystemLoader('templates'), autoescape=True) template = env.get_template('listing.xml.j2') # item_data辞書を展開してXMLを生成 xml_payload = template.render(**item_data, idempotency_key=db_saved_uuid) これにより、「新しいポリシー ID に一斉に変更したい」「説明文の HTML デザインを一新したい」といったビジネス要求に対し、Python のロジックに触れることなく、テンプレートファイルの修正だけで安全に対応できるようになります。 まとめ 本記事では、eBay 開発における第一の到達点である「商品の出品」を実装しました。 ベースライン: AddFixedPriceItem で要求される複雑な XML ペイロードの基本構造。 深いポイント: DB永続化を前提とした UUID と timeout による冪等性の確保。XML Injection 対策と複数エラー/警告のハンドリング。 スケーリング: f-string から Jinja2 テンプレートエンジン移行への具体的なアプローチ。 これであなたは、プログラム経由で eBay に安全かつ堅牢に商品カタログを展開できるようになりました。 次のステップ 単一商品の出品に成功したら、次なる壁はアパレルや靴などで必須となる 「バリエーション出品 (Multi-SKU)」 です。 次回(#6)は、親商品(Parent)と子商品(Child/Variation)を一つの AddFixedPriceItem リクエストにまとめ、サイズや色ごとに異なる在庫と価格を管理する高度な XML の構築方法を解説します。お楽しみに! 次の記事はこちら 技術的なサポートやご質問について APIの実装や仕様に関してご不明な点がございましたら、以下のeBay Japan 技術サポート窓口までお気軽にお問い合わせください: ebayjapan-techsupport@ebay.com
【重要】Trading API:GetCategoryFeatures メソッドの廃止に伴う移行のお願い 開発者の皆様、 日頃よりeBay Developers Programをご利用いただきありがとうございます。 eBayプラットフォームの簡素化に向けた継続的な取り組みの一環として、Trading APIの GetCategoryFeatures メソッドを完全に廃止(Decommission)いたします。 重要な日程 (Key Dates) 完全廃止日 (Decommission Date): 2026年5月4日 上記の日付以降、GetCategoryFeatures への呼び出しは利用できなくなります。ユーザーへのサービス中断を避けるため、期日までに統合プロセスの更新または該当コールの削除をお願いいたします。 推奨される移行先 (Recommended Action) カテゴリのタクソノミー(構造)やメタデータ情報を取得するには、最新の RESTful API である以下の移行先をご利用いただくことを強く推奨します: Taxonomy API: カテゴリツリーの構造やカテゴリ間の関係性を取得するために使用します。 Metadata API: 各カテゴリにおけるポリシーの遵守状況や、出品に関する制限事項などのメタデータを取得するために使用します。 詳細な移行手順やマッピングについては、GetCategoryFeatures Migration Guide(英語)をご参照ください。 今後ともeBay Developers Programをよろしくお願い申し上げます。 eBay Japan APIサポート
前回の記事はこちら eBay Trading API:UploadSiteHostedPicturesで画像をEPSに確実・高速にアップロードする はじめに 本記事は、全42回にわたる「eBay API 実践ガイド」の第4回です。 前回は出品に必須となるメタデータを取得しました。今回はいよいよ出品データを組み立てる直前の最終準備として、「商品画像のアップロード」 に焦点を当てます。 この記事で得られること: eBay Picture Services (EPS) の役割と、事前アップロードアーキテクチャの利点。 実務で最もハマりやすい multipart/form-data を用いたローカル画像のバイナリアップロードの正確な実装(順序保証と動的MIMEタイプ判定)。 ThreadPoolExecutor を用いた、複数画像の並列アップロードと順序保持(スケーリング手法)。 背景・なぜこれが重要か (Motivation) eBay に商品を出品する際、画像を指定する方法は主に2つあります。 自己ホスト型 (Self-Hosted): AddFixedPriceItem リクエストに自分のサーバーや Amazon S3 の URL を直接渡す。 EPS ホスト型 (eBay Picture Services): 事前に eBay のサーバー(EPS)に画像をアップロードし、返ってきた i.ebayimg.com の URL を出品リクエストに渡す。 一見すると、1の「URLを直接渡す」方が簡単に見えます。しかし、実務においてこの方法は出品エラーの最大の温床になります。 なぜなら、出品 API 呼び出しの同期処理中に eBay のクローラーがあなたの画像 URL にアクセスして取得を試みるため、少しでもネットワーク遅延や SSL 証明書のエラー、CDN のブロックが発生すると、「画像が取得できない」という理由で出品リクエスト全体が失敗(Drop)してしまうからです。 大規模かつ安定したシステムを構築するなら、事前に UploadSiteHostedPictures を叩いて EPS に画像をキャッシュさせ、確実に生成された EPS の URL を使って出品を行うアーキテクチャ(2の手法) が必須となります。 基本的な使い方(ベースライン):外部URLからの取得 UploadSiteHostedPictures には「ローカルファイルのアップロード」と「外部 URL を渡して eBay に取りに行かせる」の2パターンがあります。 まずは簡単な「外部 URL」パターンのベースラインを見てみましょう。 <?xml version="1.0" encoding="utf-8"?> <UploadSiteHostedPicturesRequest xmlns="urn:ebay:apis:eBLBaseComponents"> <ExternalPictureURL>https://your-domain.com/images/item1.jpg</ExternalPictureURL> <PictureSet>Supersize</PictureSet> <ExtensionInDays>30</ExtensionInDays> </UploadSiteHostedPicturesRequest> これを送信すると、<SiteHostedPictureDetails><FullURL> の中に、EPS 上の新しい URL(https://i.ebayimg.com/...)が返ってきます。 実務で躓く場面・深いポイント (Core) 実務において「画像の元データが S3 などのパブリック URL になく、ローカルディスクや非公開ストレージにある」というケースは多々あります。 この場合、画像を直接バイナリとして送信する必要がありますが、これが Python 開発者が最も躓くポイント です。 eBay の API は、1つのリクエスト内に「XML のメタデータ」と「画像のバイナリデータ」を混在させる multipart/form-data 形式を要求します。これを requests ライブラリで正しく構築するには、少し特殊な書き方が必要です。 堅牢な実装:マルチパート・バイナリアップロード ここでは、ローカルの画像ファイルを読み込み、動的にMIMEタイプを判定した上で、XML と一緒に安全に送信するクラスメソッドを実装します。 # upload_pictures.py import requests import xml.etree.ElementTree as ET import os import mimetypes from config import eBayConfig from ebay_token_manager import eBayTokenManager def upload_picture_to_eps(config: eBayConfig, token: str, file_path: str) -> str: """ ローカルの画像ファイルをeBay Picture Services (EPS) にアップロードし、URLを返す """ if not os.path.exists(file_path): raise FileNotFoundError(f"Image not found: {file_path}") headers = { "X-EBAY-API-CALL-NAME": "UploadSiteHostedPictures", "X-EBAY-API-SITEID": "0", "X-EBAY-API-COMPATIBILITY-LEVEL": "1323", "X-EBAY-API-IAF-TOKEN": token, # 【注意】: Content-Type は requests ライブラリが自動生成・境界(boundary)設定するためここでは指定しない! } # XML ペイロード(バイナリ送信時は ExternalPictureURL は不要) xml_payload = """<?xml version="1.0" encoding="utf-8"?> <UploadSiteHostedPicturesRequest xmlns="urn:ebay:apis:eBLBaseComponents"> <ErrorLanguage>en_US</ErrorLanguage> <WarningLevel>High</WarningLevel> <PictureSet>Supersize</PictureSet> <ExtensionInDays>30</ExtensionInDays> </UploadSiteHostedPicturesRequest> """ file_name = os.path.basename(file_path) # 【深いポイント①】: 拡張子からMIMEタイプを動的判定する mime_type, _ = mimetypes.guess_type(file_path) mime_type = mime_type or 'image/jpeg' with open(file_path, 'rb') as img_file: # 【深いポイント②】: マルチパートの順序保証 # eBay は「XMLが最初のパート、画像が次のパート」という順序を厳格に要求します。 # Python 3.7以降は辞書(dict)でも挿入順が保証されますが、意図を明示するためにリスト形式(タプルのリスト)を推奨します。 files = [ # 第1パート: XMLデータ(ファイル名は空文字を指定) ('XML Payload', ('', xml_payload, 'text/xml')), # 第2パート: 画像データ ('file', (file_name, img_file, mime_type)) ] response = requests.post(config.trading_api_url, headers=headers, files=files) response.raise_for_status() # XMLパースとエラーハンドリング namespace = {'ns': 'urn:ebay:apis:eBLBaseComponents'} root = ET.fromstring(response.text) ack_node = root.find('ns:Ack', namespace) ack = ack_node.text if ack_node is not None else "" if ack not in ['Success', 'Warning']: errors_node = root.find('ns:Errors/ns:LongMessage', namespace) error_msg = errors_node.text if errors_node is not None else "Upload Failed" raise Exception(f"API Error ({file_name}): {error_msg}") # EPSのURLを抽出 url_node = root.find('ns:SiteHostedPictureDetails/ns:FullURL', namespace) if url_node is None or not url_node.text: raise Exception(f"Failed to extract FullURL for {file_name}") return url_node.text 【エッジケース: 画像フォーマットとサイズ】 eBay は JPEG, PNG, TIFF, BMP, GIF をサポートしていますが、実務ではJPEG または PNG に統一することを強く推奨します。また、画像サイズが小さすぎると(長辺が500px未満など)PictureSet=Supersize(ズーム機能)が有効にならず、エラーまたは警告が返るため、最低でも 500x500、理想は 1600x1600 ピクセルの画像を用意してください。 パフォーマンス・スケーリング視点 (深度) eBay では1商品につき最大24枚の画像を登録できます。 上記のスクリプトで24枚の画像を for ループで順次アップロード(Sequential Upload)すると、1枚1秒かかったとして 24秒 もブロックされてしまいます。大量の出品処理を行うシステムでは致命的なボトルネックです。 ネットワーク I/O が主体の処理なので、Python の concurrent.futures.ThreadPoolExecutor を用いて並列アップロード(Concurrent Upload)を実装します。 ここで重要なのが 「画像の順序とインデックス」 です。eBay の出品リクエストでは、渡したURLリストの1枚目がメイン画像(検索結果に表示される画像)となります。そのため、並列処理を行いつつも、結果のURLリストは元のファイルリストの順序を厳密に維持しなければなりません。 import concurrent.futures from typing import List, Optional def upload_multiple_pictures(config: eBayConfig, token: str, file_paths: List[str]) -> List[Optional[str]]: """ 複数画像をマルチスレッドでEPSへ一括アップロードし、入力順序を維持したURLのリストを返す。 失敗した画像は None として返し、インデックスのズレ(メイン画像の意図せぬ入れ替わり)を防ぐ。 """ # 内部エラーキャッチ用のラッパー関数 def safe_upload(path): try: url = upload_picture_to_eps(config, token, path) print(f"[Success]: {path} -> {url}") return url except Exception as exc: print(f"[Error] uploading {path}: {exc}") return None # max_workers は環境に合わせて調整(eBayのRate Limitに配慮して5〜10程度が安全) with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor: # 【深いポイント③】: 順序とインデックスの保持 # as_completed ではなく map を使うことで、入力した file_paths の順序通りに結果が返却されます。 # 失敗時(None)を除外してしまうと「何枚目が何の画像か」が分からなくなるため、そのまま返却します。 results = list(executor.map(safe_upload, file_paths)) return results # 使い方の例 if __name__ == "__main__": config = eBayConfig() manager = eBayTokenManager(config.client_id, config.client_secret, config.refresh_token, config.env) token = manager.get_token() # アップロードしたいローカル画像のリスト(1枚目がメイン画像になる前提) images_to_upload = ["item_front.jpg", "item_back.jpg", "item_detail.jpg"] eps_urls = upload_multiple_pictures(config, token, images_to_upload) # エラーハンドリング:欠落がある場合は呼び出し元で検知する if None in eps_urls: print("【警告】: 一部の画像アップロードに失敗しました。対応するインデックスを確認してリトライしてください。") print("Final EPS URLs to use in AddFixedPriceItem:", eps_urls) この実装により、画像アップロードの所要時間を大幅に(数分の一に)短縮しつつ、メイン画像が意図せず入れ替わってしまう事故を完全に防ぐことができます。 まとめ 本記事では、出品プロセスを安定させるための「画像の事前アップロード」について解説しました。 ベースライン: EPS (eBay Picture Services) を介することで、出品 API (AddFixedPriceItem) の失敗リスクを極限まで減らすアーキテクチャ。 深いポイント: requests の files をリストで定義し順序を保証する実装。拡張子からの MIME タイプの動的判定による堅牢化。 スケーリング: ThreadPoolExecutor.map を用いた、順序とインデックス(メイン画像の対応関係)を維持した安全で高速な並列アップロード。 これで「メタデータ」と「EPS 画像URL」という、出品に必要な全ての材料が手元に揃いました。 次のステップ 準備はすべて整いました。次回(#5)は、いよいよ本丸となる 「AddFixedPriceItemで商品を出品する」 です。 これまでに取得したメタデータと画像 URL を組み合わせ、バリエーションを持たない単一商品(Single-SKU)の出品ペイロードを構築し、Sandbox 環境に実際に商品を並べてみます。お楽しみに! 次の記事はこちら 技術的なサポートやご質問について APIの実装や仕様に関してご不明な点がございましたら、以下のeBay Japan 技術サポート窓口までお気軽にお問い合わせください: ebayjapan-techsupport@ebay.com
Trading API入門:GeteBayDetailsでメタデータを取得し、レガシーAPIの構造を攻略する 前回の記事はこちら はじめに 本記事は、全42回にわたる「eBay API 実践ガイド」の第3回です。 前回までに認証(OAuth)と環境構築(Sandbox/Prod)の基盤が完成しました。今回からは、いよいよ実際のデータを eBay から取得します。 この記事で得られること: なぜモダンな REST API ではなく、レガシーな Trading API (XML) を使う必要があるのかの理解。 Trading API 独自の HTTP ヘッダー(X-EBAY-API-IAF-TOKEN など)と XML リクエストの構築方法。 GeteBayDetails を用いて、出品に必須となる「配送メタデータ」を取得し、ローカルに JSON キャッシュとして保存する実装パターン。 背景・なぜこれが重要か (Motivation) 「eBay の開発を始めるなら、最新の Sell REST API だけで完結させたい」。誰もがそう思います。 しかし、現在でも 「出品(Listing)の詳細な制御」や「一部の高度なセラー設定」においては、依然として Trading API が主役 です。eBay のコアシステムは巨大であり、すべての機能が完全に REST に移行しきっているわけではありません。 そして、Trading API を扱う上で最初に立ちはだかる壁が 「ハードコードの罠」 です。 例えば、出品時に「USPS Priority Mail」を指定したい場合、単にその文字列を送るのではなく、eBay 内部で定義された正確な Enum 値を送る必要があります。これらの値は予告なく追加・廃止されるため、eBay から最新のメタデータを取得(GeteBayDetails)してマスタデータとして保持しておくこと が、堅牢なシステムを組むための第一歩となります。 基本的な使い方(ベースライン) Trading API (エンドポイント: https://api.ebay.com/ws/api.dll) と REST API の最大の違いは以下の2点です。 ペイロードが JSON ではなく XML である。 OAuth トークンの渡し方が Authorization: Bearer ではなく、専用ヘッダー X-EBAY-API-IAF-TOKEN である。 必須の HTTP ヘッダー Trading API を叩くためには、以下のヘッダーが必須です。 X-EBAY-API-CALL-NAME: 実行するメソッド名 (例: GeteBayDetails) X-EBAY-API-SITEID: 対象とする eBay サイトの ID (US は 0、UK は 3、Japan は 201) X-EBAY-API-COMPATIBILITY-LEVEL: 使用する API のバージョン(※最新値は eBay Developer Portal の Trading API Release Notes で確認できます) X-EBAY-API-IAF-TOKEN: OAuth アクセストークン Content-Type: text/xml DetailNameCodeType:取得できるメタデータの種類 GeteBayDetails に渡せる DetailName(取得したい情報の種類)は、eBay が定義する DetailNameCodeType という Enum で管理されています。主なものを以下に示します。 DetailName 内容 ShippingServiceDetails 利用可能な配送方法と内部コード ShippingCarrierDetails 配送業者と内部キャリアコード ReturnPolicyDetails 返品ポリシーの選択肢 CountryDetails 国コード一覧 CurrencyDetails 通貨コード一覧 SiteDetails eBay サイト ID 一覧 TimeZoneDetails タイムゾーン一覧 本記事では、出品時に必ず必要となる ShippingServiceDetails(配送方法)と ShippingCarrierDetails(配送業者)の2つを取得します。 なぜ2つ必要か? 出品時の ShippingDetails には「どの業者(Carrier)の」「どのサービス(Service)を使うか」を別々のフィールドで指定する必要があります。例えば「USPS の Priority Mail」であれば、CarrierコードとServiceコードの両方が揃って初めて正しい出品データになります。 ※その他のメタデータ(返品ポリシーなど)も同様の手法で取得可能です。より複雑な出品要件に応じたメタデータ設計が必要な場合は、連載の後半で触れるか、個別にご相談ください。 実務で躓く場面・深いポイント (Core) GeteBayDetails を扱う上で初心者がよくやる失敗が、「パラメータを指定せずにリクエストを投げてしまうこと」 です。 何も指定しないと、すべてのメタデータを含む数MBにも及ぶ巨大な XML が返却され、パース処理でメモリを圧迫し、API のレスポンスタイムも劇的に悪化します。 実務では、必要な DetailName を明示的に指定して取得 し、Python の辞書(JSON)に変換して扱うのが鉄則です。また、XMLのパース時には要素が存在しない(None)ケースを考慮した堅牢なコードが求められます。 実装: 配送メタデータの取得と JSON キャッシュ化 第1回・第2回で作成したクラスをインポートし、配送業者と配送サービスのメタデータを安全に抽出するスクリプトを記述します。 # get_ebay_details.py import requests import xml.etree.ElementTree as ET import json from config import eBayConfig from ebay_token_manager import eBayTokenManager def fetch_ebay_details(config: eBayConfig, token: str, detail_names: list) -> dict: """ GeteBayDetailsを実行し、指定されたメタデータを取得する """ # 1. Trading API 固有のヘッダー構築 headers = { "X-EBAY-API-CALL-NAME": "GeteBayDetails", "X-EBAY-API-SITEID": "0", # USサイト "X-EBAY-API-COMPATIBILITY-LEVEL": "1323", # 常にリリースノートで最新を確認 "X-EBAY-API-IAF-TOKEN": token, "Content-Type": "text/xml" } # 2. XML ペイロードの動的生成(必要な DetailName のみ指定) detail_name_tags = "".join([f"<DetailName>{name}</DetailName>" for name in detail_names]) xml_payload = f"""<?xml version="1.0" encoding="utf-8"?> <GeteBayDetailsRequest xmlns="urn:ebay:apis:eBLBaseComponents"> <ErrorLanguage>en_US</ErrorLanguage> <WarningLevel>High</WarningLevel> {detail_name_tags} </GeteBayDetailsRequest> """ # 3. リクエスト送信 response = requests.post(config.trading_api_url, headers=headers, data=xml_payload) response.raise_for_status() # 4. XMLのパースと名前空間(Namespace)の処理 # 深いポイント: eBayのXMLには xmlns が定義されているため、検索時に namespace 指定が必須 namespace = {'ns': 'urn:ebay:apis:eBLBaseComponents'} root = ET.fromstring(response.text) # APIエラーのチェック ack_node = root.find('ns:Ack', namespace) ack = ack_node.text if ack_node is not None else "" if ack not in ['Success', 'Warning']: errors_node = root.find('ns:Errors/ns:LongMessage', namespace) errors = errors_node.text if errors_node is not None else "Unknown API Error" raise Exception(f"API Error: {errors}") # 5. 必要なデータを抽出して dict に格納 result_data = { "ShippingServices": [], "ShippingCarriers": [] } # ① 配送サービス (ShippingServiceDetails) の抽出 for shipping in root.findall('ns:ShippingServiceDetails', namespace): valid = shipping.find('ns:ValidForSellingFlow', namespace) # 本番稼働時のバグ防止: 要素が存在しない場合(None)のガードを必ず入れる if valid is not None and valid.text == 'true': service_node = shipping.find('ns:ShippingService', namespace) desc_node = shipping.find('ns:Description', namespace) result_data["ShippingServices"].append({ "Service": service_node.text if service_node is not None else "", "Description": desc_node.text if desc_node is not None else "" }) # ② 配送業者 (ShippingCarrierDetails) の抽出 for carrier in root.findall('ns:ShippingCarrierDetails', namespace): name_node = carrier.find('ns:ShippingCarrier', namespace) desc_node = carrier.find('ns:Description', namespace) if name_node is not None: result_data["ShippingCarriers"].append({ "CarrierCode": name_node.text, "Description": desc_node.text if desc_node is not None else "" }) return result_data if __name__ == "__main__": # 前回の基盤を利用してトークンと環境設定を解決 config = eBayConfig() manager = eBayTokenManager(config.client_id, config.client_secret, config.refresh_token, config.env) # 取得したいメタデータの種類を指定 target_details = ["ShippingServiceDetails", "ShippingCarrierDetails"] print(f"Fetching eBay Details from {config.env} environment...") details = fetch_ebay_details(config, manager.get_token(), target_details) # JSONファイルとしてローカルに保存(キャッシュ化) with open('ebay_shipping_metadata.json', 'w', encoding='utf-8') as f: json.dump(details, f, indent=2, ensure_ascii=False) print("Successfully saved metadata to ebay_shipping_metadata.json") パフォーマンス・スケーリング視点 (深度) この GeteBayDetails で取得できる「サイトメタデータ」は、頻繁に変更されるものではありません。 出品(Listing)処理を行うたびにこの API を叩く設計にしてしまうと、ネットワークのオーバーヘッドが発生し、eBay の Rate Limit(レート制限)を無駄に消費してしまいます。 【ベストプラクティス】 大規模なシステムでは、このデータを取得するバッチジョブ(Cron)を週に1回程度実行し、結果を Redis や RDB、あるいはローカルの JSON ファイルとしてキャッシュします。 出品ワーカーは API を叩くのではなく、「キャッシュされたローカルデータ」 を参照して、バリデーションや UI のセレクトボックス描画を行うアーキテクチャにすべきです。 まとめ 本記事では、eBay API のコアとも言える Trading API の基礎と、サイトメタデータの取得方法を解説しました。 ベースライン: REST とは異なるヘッダー (X-EBAY-API-IAF-TOKEN) と XML ペイロードの構造。 深いポイント: DetailName を指定して巨大なレスポンスを回避する技術と、xml.etree を用いた Namespace 付き XML のパース処理。要素の欠落(None)に耐える堅牢な実装。 スケーリング: マスタデータとしてローカルに JSON キャッシュを持ち、出品ごとの無駄な API 呼び出しを避けるアーキテクチャ。 このメタデータ(配送方法や業者の正確な Enum 値)が手元にあることで、今後の出品 API 構築時のエラー率は劇的に下がります。 次のステップ 出品に必要なデータが揃い始めました。次回(#4)は、出品の実装に入る前のもう一つの重要な準備、「画像のアップロード (UploadSiteHostedPictures)」 です。 外部 URL の画像を直接 eBay に渡すのではなく、一度 eBay のサーバー(EPS)にホスティングさせることで、表示速度を最適化し、出品時の画像エラーを防ぐ実務的な手法を解説します。お楽しみに! 次の記事はこちら
eBay Sandbox環境の完全セットアップ:本番を汚さずAPIをテストする構成管理パターン 前回の記事はこちら はじめに 本記事は、全42回にわたる「eBay API 実践ガイド」の第2回です。 前回(#1)では堅牢な OAuth トークン管理機構を構築しました。しかし、完成したコードをいきなり本番環境(Production)で動かすのは危険です。出品データの破壊や、誤った注文処理(Fulfillment)を引き起こす可能性があります。 この記事で得られること: eBay Sandbox(テスト環境)の特質と、本番環境との違いの理解。 python-dotenv を活用し、コードを1行も書き換えずに Production / Sandbox を切り替える設定管理(Configuration)の実装。 テスト自動化(CI/CD)を見据えたアーキテクチャ設計。 背景・なぜこれが重要か (Motivation) 「ハードコードされたエンドポイントやキーを、デプロイ前に手作業で書き換える」。これはバグと事故の温床です。 eBay API では、エンドポイントのドメインが本番と Sandbox で異なります(例: api.ebay.com vs api.sandbox.ebay.com)。また、OAuth のクレデンシャル(Client ID / Secret)も環境ごとに完全に独立しています。 実務においては、「ローカル開発や CI 上の自動テストでは Sandbox を向き、本番サーバーにデプロイされた瞬間のみ Production を向く」 という状態を、環境変数によって強制する設計(Twelve-Factor App の原則)が必須となります。 基本的な使い方(ベースライン):Sandbox特有の準備 実装に入る前に、eBay Developer Portal で Sandbox 環境の準備を行います。ここが初学者の躓きポイントになりやすいので、要点だけ整理します。 Sandbox 用 Keys の発行: Developer Portal の「Application Keys」から、Sandbox 用の Client ID / Secret を発行します(本番用とは別物です)。 テストアカウントの作成: Sandbox の世界で取引を行うための「架空のセラーアカウント」と「架空のバイヤーアカウント」を作成します(User Tokens > Sandbox User Register)。 Sandbox 用 Token の取得: 前回(#1)の手順を、Sandbox 用の Keys とテストアカウントを使って行い、Sandbox 用の refresh_token を取得します。 深いポイント: 完全に独立した世界 Sandbox は本番環境から切り離されたパラレルワールドです。本番環境にあるあなたの出品データは、Sandbox には一切存在しません。 実務で躓く場面・深いポイント (Core) ここからは Python の実装です。 環境変数を管理するために python-dotenv を使用し、シングルトン的にアプリケーション全体で設定を共有する eBayConfig クラスを実装します。 1. .env ファイルの設計 プロジェクトのルートディレクトリに .env ファイルを作成します。このファイルは機密情報を含むため、絶対に Git にコミットしてはいけません。 以下のように .gitignore に追加して追跡から除外してください。 # .gitignore .env .env.* 次に、.env ファイルの中身を記述します。 # .env EBAY_ENV=sandbox # Sandbox Credentials EBAY_SANDBOX_CLIENT_ID=your_sandbox_client_id EBAY_SANDBOX_CLIENT_SECRET=your_sandbox_client_secret EBAY_SANDBOX_REFRESH_TOKEN=your_sandbox_refresh_token # Production Credentials EBAY_PROD_CLIENT_ID=your_prod_client_id EBAY_PROD_CLIENT_SECRET=your_prod_client_secret EBAY_PROD_REFRESH_TOKEN=your_prod_refresh_token 2. 環境をシームレスに切り替える Config クラス if 文で毎回エンドポイントを切り替えるのは非効率です。環境変数 EBAY_ENV の値に基づいて、適切なエンドポイントとキーを動的に返すクラスを構築します。 # config.py import os from dotenv import load_dotenv # .env ファイルを読み込む load_dotenv() class eBayConfig: def __init__(self): # デフォルトは安全側に倒して 'sandbox' とする self.env = os.getenv("EBAY_ENV", "sandbox").lower() if self.env not in ["sandbox", "production"]: raise ValueError("EBAY_ENV must be 'sandbox' or 'production'") # 環境に応じたプレフィックスを決定 prefix = "EBAY_PROD" if self.env == "production" else "EBAY_SANDBOX" # クレデンシャルの読み込み self.client_id = os.getenv(f"{prefix}_CLIENT_ID") self.client_secret = os.getenv(f"{prefix}_CLIENT_SECRET") self.refresh_token = os.getenv(f"{prefix}_REFRESH_TOKEN") # 必須キーの欠落チェック(フェイルファスト) if not all([self.client_id, self.client_secret, self.refresh_token]): raise EnvironmentError(f"Missing required environment variables for {self.env} environment.") # エンドポイントの動的解決 self.base_url = "https://api.ebay.com" if self.env == "production" else "https://api.sandbox.ebay.com" # ※ 今後連載で Trading API を使うためのエンドポイントもここで定義しておくと便利です self.trading_api_url = "https://api.ebay.com/ws/api.dll" if self.env == "production" else "https://api.sandbox.ebay.com/ws/api.dll" # 使い方の例 if __name__ == "__main__": config = eBayConfig() print(f"Current Environment: {config.env}") print(f"REST API Base URL: {config.base_url}") print(f"Client ID: {config.client_id[:5]}...") # マスキングして表示 エッジケース処理:なぜフェイルファスト(Fail-Fast)なのか? if not all([...]) の部分が重要です。環境変数の設定ミスは、実行時エラーではなく、アプリケーションの起動時に即座に検知して落とすべきです(フェイルファストの原則)。これにより、「バッチ処理が3時間走った後にキーがないことに気づいて落ちる」という悲劇を防げます。 Sandbox 環境の注意点 (Caveats) 実装が完了し、さあテストを始めようという前に、Sandbox の限界について理解しておく必要があります。Sandbox は非常に便利ですが、完璧なクローンではありません。以下の点に留意してください。 最新 API の遅延: 新しくリリースされた機能(特に Sell REST API の一部)は、Sandbox での挙動が不安定だったり、反映が遅れたりする場合があります。 データのモックアップ: 商品カタログ(GTIN/UPC 検索)など、一部のデータは本番環境ほど充実していません。検索 API のテストで「本番ならヒットするはずの商品が出ない」といったことが起こり得ます。 定期的なメンテナンス: Sandbox は本番よりもメンテナンスによるダウンタイムが頻繁に発生します。 パフォーマンス・スケーリング視点 (深度) この構成管理パターンは、将来的に CI/CD(継続的インテグレーション)を組む際に真価を発揮します。 CI/CD 環境での自動テスト時は、.env ファイルを配置するのではなく、サーバーの環境変数(シークレットマネージャーなど)として直接 EBAY_ENV=sandbox や各種クレデンシャルを注入します。これにより、開発者のローカル環境から CI サーバー、そして本番サーバーに至るまで、同一の Python コードを一切修正することなく安全に動作させることができます。 第1回との統合:セキュアなAPIクライアントの完成 ここまでくれば、第1回で作成した eBayTokenManager と組み合わせることで、「環境(Sandbox/Prod)を自動判別し、常に有効なトークンを返す堅牢な認証基盤」 が完成します。 # main.py (第1回と第2回の知識を結合した完成形) from config import eBayConfig from ebay_token_manager import eBayTokenManager # 1. 環境変数を自動解決 config = eBayConfig() # 2. 環境設定をTokenManagerに渡す manager = eBayTokenManager( client_id=config.client_id, client_secret=config.client_secret, refresh_token=config.refresh_token, environment=config.env ) # 3. トークン取得(SandboxかProductionかは .env の一行で決まる!) token = manager.get_token() print(f"[{config.env}] Token Ready! Token starts with: {token[:15]}...") これで、今後のすべての API リクエストにおいて、認証と環境分けの悩みが完全に解消されました。 まとめ 本記事では、eBay API の Sandbox 環境の準備から、実稼働を見据えた環境変数による設定管理(Configuration)の実装までを解説しました。 ベースライン: Sandbox 用のキーとテストアカウントは本番とは完全に分離されている。 深いポイント: python-dotenv を用い、EBAY_ENV の値一つでクレデンシャルとベース URL を動的に切り替える Config クラスの構築。起動時のフェイルファスト設計。 これにて、「本番を汚さない、安全でスケーラブルな API 実行基盤」 の完成です。 次のステップ 認証と環境の準備が整いました。次回(#3)からは、いよいよ実際の API 呼び出しに入ります。 まずは 「Trading API入門:GeteBayDetailsでサイトメタデータを取得してAPIの構造を理解する」 です。REST API 全盛の今、なぜあえてレガシーな Trading API(SOAP/XML)から学ぶべきなのか?その理由と、実務での活用法を解説します! 次の記事はこちら
eBay OAuth 完全ガイド:Pythonでアクセストークン管理を堅牢に実装する はじめに 本記事は、前回の入門編の続きとして、中級編のスタートを切ります。OAuth 2.0 認証から一連の出品機能APIまで踏み込み、実戦で通用するあなたの出品システムを構築して行きましょう。サンプルコードや実装のベストプラクティスなどを豊富に盛り込んでいます。 まずは、eBay APIの利用に必須となる OAuth 2.0 認証 について深掘りします。入門編ではAPI Explorerなどの既存ツールで直接トークンを取得しましたが、実稼働する堅牢なシステムを構築するには、単にトークンを取得するだけのスクリプトと、実際のアプリケーションの認証機構との間に、乗り越えるべき大きな隔たりがあります。 この記事で得られること: 実稼働を前提とした、自動リフレッシュ機能付きの TokenManager クラスの実装。 複数スレッドから安全にトークンを参照・更新するための排他制御(Thread Safe)の考え方。 事前準備 (Prerequisites) 本記事のコードを動かすには、eBay Developer Portal にて開発者アカウントを作成し、Application Keys(Client ID と Client Secret) を事前に取得しておく必要があります。 背景・なぜこれが重要か (Motivation) 「API を叩いたら 401 Unauthorized が返ってきた」。これはAPI 開発において最もよくあるエラーです。 eBay の User Access Token の有効期限は 2時間(120分)と比較的短く設定されています。 数万件の在庫をバッチ処理で更新している最中にトークンが失効した場合、処理が途中で落ちてしまうとデータの不整合が発生します。そのため、「リクエストの直前に有効期限を確認し、切れていれば(あるいは切れそうであれば)自動で Refresh Token を使って再取得する」という自己修復型の認証基盤を最初に構築しておくことが、システム全体の安定性に直結します。 基本的な使い方(ベースライン):Refresh Token はどこから来る? 自動更新機構を作る前に、一番最初の疑問である 「そもそも refresh_token はどうやって手に入れるの?」 を解決しておきましょう。 eBay の User Token を取得するには、初回のみブラウザ経由でのユーザー同意(Authorization Code Grant)が必要です。手順は以下の通りです。 同意URLの発行: 必要なスコープ(権限)を指定した eBay のログインURLを生成し、ブラウザで開きます。 ログインと許可: 出品を行う eBay アカウントでログインし、アプリへのアクセスを許可(Grant)します。 Authorization Code の取得: 設定した Redirect URI に遷移した際、URLのパラメータに付与される code の文字列をコピーします。 Refresh Token の取得(初回のみ): その code を使って eBay のトークンエンドポイントを叩き、最初の access_token と refresh_token を取得します。 refresh_token の有効期限は通常18ヶ月と長いため、一度取得すればデータベースや環境変数に保存して使い回すことができます。本記事で実装するクラスは、「すでに取得済みの refresh_token を使って、2時間ごとに切れる access_token を自動で再取得し続ける」ためのものです。 実務で躓く場面・深いポイント (Core) 実務で躓くポイントは、「トークンの更新処理自体」よりも「いつ、どうやって更新するか」という状態管理です。 ここでは、Python の requests を拡張し、自動的にトークンの状態を管理する eBayTokenManager クラスを実装します。 堅牢な TokenManager の実装 注意点: eBay のトークンエンドポイントは、Authorization ヘッダに Basic <Base64(ClientID:ClientSecret)> を要求します。単なる JSON ペイロードではない点に注意してください。 # ebay_token_manager.py import requests import base64 import time import threading from typing import Optional class eBayTokenManager: def __init__(self, client_id: str, client_secret: str, refresh_token: str, environment: str = "production"): self.client_id = client_id self.client_secret = client_secret self.refresh_token = refresh_token # 環境の切り替え(次回連載のSandbox対応への布石) self.base_url = "https://api.ebay.com" if environment == "production" else "https://api.sandbox.ebay.com" self._access_token: Optional[str] = None self._expires_at: float = 0.0 # スレッドセーフな更新のためのロック self._lock = threading.Lock() def _get_auth_header(self) -> str: """Client ID と Secret を Base64 エンコードして Basic 認証ヘッダを生成""" cred = f"{self.client_id}:{self.client_secret}".encode('utf-8') b64_cred = base64.b64encode(cred).decode('utf-8') return f"Basic {b64_cred}" def _refresh_access_token(self) -> None: """Refresh Token を用いて新しい Access Token を取得する""" url = f"{self.base_url}/identity/v1/oauth2/token" headers = { "Content-Type": "application/x-www-form-urlencoded", "Authorization": self._get_auth_header() } data = { "grant_type": "refresh_token", "refresh_token": self.refresh_token } response = requests.post(url, headers=headers, data=data) response.raise_for_status() # 4xx/5xx エラー時に例外を送出 token_data = response.json() self._access_token = token_data["access_token"] # 深いポイント: 有効期限のバッファとして、実際の期限より60秒前に失効判定する(通信遅延などのエッジケース対策) self._expires_at = time.time() + int(token_data["expires_in"]) - 60 def get_token(self) -> str: """ 有効なアクセストークンを返す。 期限が切れている場合は自動的にリフレッシュする。 """ # ロックを取得して、複数スレッドからの同時更新(Race Condition)を防ぐ with self._lock: # トークンが未取得、または期限切れ(バッファ含む)の場合 if not self._access_token or time.time() >= self._expires_at: self._refresh_access_token() return self._access_token なぜロック (threading.Lock) が必要なのか? バッチ処理において、API の並列呼び出し(ThreadPoolExecutor など)を行う際、トークンが切れた瞬間に複数スレッドが同時に _refresh_access_token() を呼び出す可能性があります。 これにより、API Rate Limit(レート制限)に不必要に引っかかる、あるいは最新のトークンが上書きされ競合状態になるというバグが発生します。_lock を用いることで、最初の一つのスレッドだけが更新処理を行い、他のスレッドは安全に最新のトークンを利用できます。 使い方サンプル 作成したクラスは、以下のようにインスタンス化して使用します。API リクエストを送る直前に get_token() を呼ぶだけで、常に有効なトークンが保証されます。 # 使い方の例 if __name__ == "__main__": # 事前に取得した各種キーを設定(実務では環境変数から読み込むことを推奨) manager = eBayTokenManager( client_id="YOUR_CLIENT_ID", client_secret="YOUR_CLIENT_SECRET", refresh_token="YOUR_REFRESH_TOKEN", environment="production" ) # トークンを取得(初回なので内部で自動的にリフレッシュ通信が走る) token = manager.get_token() print(f"取得したトークン: {token[:20]}...") # セキュリティのため最初の20文字だけ表示 # 2回目の呼び出し(有効期限内なので、通信は発生せずキャッシュされたトークンが即座に返る) token2 = manager.get_token() # 実際のAPIリクエストではこのようにAuthorizationヘッダ(Bearer)に渡す headers = { "Authorization": f"Bearer {token2}", "Content-Type": "application/json" } # response = requests.get("https://api.ebay.com/sell/inventory/v1/inventory_item", headers=headers) # print(response.json()) まとめ 本記事では、eBay API 開発の第一歩として、実務に耐えうる堅牢な OAuth 2.0 アクセストークン管理の実装方法を解説しました。 ベースライン: grant_type="refresh_token" を用いた更新処理の自動化。 深いポイント: スレッドセーフな設計と、期限切れ直前のエッジケース(60秒バッファ)の考慮。 単に「API が叩けた」で満足せず、こうした基盤を最初に固めることで、今後の開発体験が劇的に向上します。 (今回はメモリ内での管理を実装しましたが、将来的に複数サーバーで分散処理を行う規模になった際は、Redis などを活用したトークンの一元管理も検討に値します。これについては連載の後半で扱う予定です。) 次のステップ 次回(#2)は、「eBay Sandbox環境の完全セットアップ」です。 本番環境を汚さずに API のテストを行うためのテストアカウント作成から、Python コード上で Production / Sandbox を環境変数 .env でシームレスに切り替える実装パターンを解説します。お楽しみに! 次の記事はこちら
【重要】Selling Marketing API:Promoted Listings「Quick Setup」機能の廃止について eBay開発者の皆様、 日頃よりeBay Developers Programをご利用いただきありがとうございます。 eBayでは、広告ツールの合理化と高性能な自動化への取り組みを継続的に行っております。その一環として、Selling Marketing APIにおける今後の重要な変更についてお知らせいたします。 変更内容 (What is changing?) プライオリティストラテジー(Priority Strategy)を利用したPromoted Listings(広告)の「クイック設定(Quick Setup)」機能が退役(Retire)となります。これに伴い、以下のAPIメソッドが影響を受けます: setupQuickCampaign launchCampaign 重要な日程 (Key Dates) 非推奨化 (Deprecation Date): これらのメソッドは、2026年1月26日をもって公式に非推奨(Deprecated)となりました。 完全廃止 (Decommission Date): これらのメソッドは、2026年3月31日に完全に廃止(Decommissioned)されます。この日付以降、これらのエンドポイントへの呼び出しは機能しなくなり、エラーが返されます。 キャンペーン管理の代替案 (Campaign Management Alternatives) Quick Setup機能は廃止されますが、以下のメソッドを通じて引き続きプライオリティキャンペーンを作成および管理することができます: スマートターゲティング (Smart targeting - 自動化に推奨): Quick Setupの目的と同様に、キャンペーンの作成と管理の手間を簡素化したい開発者には、スマートターゲティングへの移行を強くお勧めします。関心のあるバイヤーに自動的に広告をターゲットし、競争力を維持するために時間の経過とともにキャンペーンを更新することで、キャンペーンのパフォーマンスを設定および最適化する簡単な方法を提供します。 マニュアルターゲティング (Manual targeting): マニュアルターゲティングは引き続きパブリックAPI経由で利用可能です。これにより、自動設定よりも手動での管理を好む場合、広告グループ、キーワード、およびキーワード入札額(bids)をきめ細かく制御(グラニュラー・コントロール)することができます。 参考資料とサポート 既存のキャンペーン管理エンドポイントを活用することで、パブリックAPIを通じて引き続きプライオリティキャンペーンを管理できます。プラットフォームへのプライオリティストラテジーの統合に関する包括的なガイダンスとベストプラクティスについては、Promoted Listings Playbook をご参照ください。 現在の機能と制限に関する詳細については、Marketing API Overview をご確認ください。 今後ともよろしくお願い申し上げます。
Inventory Mapping APIの探索 (Explore the Inventory Mapping API) Inventory Mapping API が開発者のテスト用に利用可能になりました。GraphQLを基盤として構築されたこのAPIは、既存の製品データから生成されたAIによる推奨事項を使用して、セラーが高品質な出品を作成するのを支援し、出品の品質とパフォーマンスを向上させます。 なぜこのAPIを使用するのか? Inventory Mapping API は、セラーに以下のメリットをもたらします。 出品の品質を向上させ、可視性、バイヤーの信頼、およびコンバージョンを高める。 AIが生成したコンテンツ提案により迅速に出品を行い、在庫をより早くバイヤーの目に触れさせる。 誰のためのものか? Inventory Mapping API は米国マーケットプレイスで利用可能であり、現時点では結果は米国サイトの出品にのみ使用する必要があります。追加のマーケットプレイスへ適用範囲が拡大され次第、お知らせします。 はじめ方 Inventory Mapping API との統合を開始し、セラーがAIを活用した出品をより迅速に作成できるように支援します。ドキュメントはこちらで入手可能です: Inventory Mapping API。 新しい GraphQL API Explorer を使用して、テストをサポートしてください。 問題を迅速に診断して解決するために、Inventory Mapping APIの推奨事項を使用して生成または修正されたすべての出品に mappingReferenceID フィールドを含めてください。 セラーからの推奨事項やユースケースに関するフィードバックを収集し、製品の改善にご協力ください。 注: ユースケースをサポートするために拡張アクセスが必要な場合は、Application Growth Checkを申請 して統合をスケーリングできます。 サポートとフィードバック 統合がスムーズに進むことを確認したく、皆様からのフィードバックをお待ちしております。Inventory Mapping API をテストする際にサポートを得るための最良の方法は以下の通りです。 ドキュメント — 詳細なガイドとベストプラクティスについては、Inventory Mapping API をご覧ください。 GraphQL Explorer — 新しい GraphQL Explorer を使用してサンプルコールを実行し、テストをサポートしてください。 AIチャットボット — 統合を進める中で、素早い回答とガイダンスを得ることができます。ログイン中は developer.ebay.com の全ページで利用可能です。 開発者テクニカルサポートチケット — 技術的な支援については、Developer support ticket を提出してください。現在はすべての開発者が無料で利用できます!
トップに戻る