前回の記事はこちら 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環境の完全セットアップ
2026-03-15
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でアクセストークンを管理しよう
2026-03-10
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 でシームレスに切り替える実装パターンを解説します。お楽しみに! 次の記事はこちら
Marketing API一部の機能廃止について
2026-03-10
【重要】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を使ってみよう
2026-02-12
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 を提出してください。現在はすべての開発者が無料で利用できます!
AG関連機能を統合するための実用的なヒント
2026-02-09
本物保証(Authenticity Guarantee, AG)関連機能を統合するための実用的なヒント eBay APIの本物保証(Authenticity Guarantee、以下AG)関連機能を統合する際、開発者は以下の特徴を理解しておく必要があります: 商品が属するカテゴリが条件を満たす場合、システムはカテゴリルールに基づいて自動的にその商品のAGを有効にします。 同時に、出品は以下の要件も満たす必要があります: 少なくとも1つの配送方法を提供する ハンドリングタイム(Handling time)が3営業日を超えない 商品の出品要件を満たす 現在、AGは以下のサイトで適用されます:米国(US)、オーストラリア(AU)、カナダ(CA)、ドイツ(DE)、イギリス(GB)。 適用サイトのリストについては、AGヘルプページを参照してください: https://www.ebay.com/help/selling/selling-tools/ebay-authenticity-guarantee?id=4644 特定の出品がAG要件を満たしているかどうかを判断するには、開発者は Trading API の getItem 呼び出しを行い、レスポンス内の IsItemEMSEligible 属性を確認できます: https://developer.ebay.com/Devzone/XML/docs/Reference/eBay/GetItem.html#Response.Item.IsItemEMSEligible 商品出品を作成する際、その商品がシステムによってAG有効化された場合、出品作成インターフェースはレスポンスに警告 #21920343 を含みます: https://developer.ebay.com/Devzone/XML/docs/Reference/eBay/Errors/ErrorMessages.htm#21920343 鑑定センター(AG Center)へ送る必要がある商品について、eBayは無料の配送ラベルを提供します。これは以下のインターフェースフィールドで確認できます: Trading API – getOrders フィールド: eBayEstimatedLabelCost = 0.0 ドキュメントリンク Fulfillment API – getOrders フィールド: shippingLabelProvidedBy = EBAY ドキュメントリンク また、以下のインターフェースのレスポンスには Program.AuthenticityVerification フィールドも含まれます: Trading API – getOrders https://developer.ebay.com/devzone/xml/docs/reference/ebay/getorders.html...AuthenticityVerification Fulfillment API – getOrders https://developer.ebay.com/api-docs/sell/fulfillment/resources/order/methods/getOrders...authenticityVerification 詳細については、開発者は上記に対応するAPIドキュメントリンクを参照してください。
一部のAPI機能のMetadata APIへの移行について
2026-02-01
一部のAPI機能のMetadata APIへの移行について 物流関連のメソッド Metadata APIに、物流に関連する以下の5つのメソッドが新たに追加されました: getExcludeShippingLocations getHandlingTimes getShippingCarriers getShippingLocations getShippingServices 上記のメソッドは、Trading APIにおける GeteBayDetails 呼び出しの関連する代替手段として機能します。 製品メタデータ関連のAPI Product APIの getProductCompatibilities メソッドおよび Product Metadata API は、今年の第2四半期に廃止(Decommission)される予定です。現在これらのAPIインターフェースを使用している開発者の皆様には、Metadata API 内の getProductCompatibilities メソッドおよびその他の互換性関連メソッドへの移行を推奨いたします。 GetCategoryFeatures 現在 GetCategoryFeatures 呼び出しを使用している場合は、以下のメソッドへの移行を準備してください: getCategoryPolicies getClassifiedAdPolicies getListingTypePolicies getMotorsListingPolicies getShippingPolicies getSiteVisibilityPolicies 詳細については、GetCategoryFeatures移行ガイド を参照してください。 GetCategories 現在 GetCategories 呼び出しを使用している場合は、以下のメソッドへの移行を準備してください: getCategoryPolicies getCategoryTree (Taxonomy API) getCategorySubtree (Taxonomy API) 詳細については、GetCategories移行ガイド を参照してください。 注意: getCategoryFeatures および getCategories 呼び出しは、いずれも今年の第2四半期に廃止される予定です。 APIの廃止に関する詳細情報は、以下のページをご覧ください: https://developer.ebay.com/develop/apis/api-deprecation-status
出品アスペクトでエネルギー効率ラベル (EEK Label) がサポートされました エネルギー効率ラベル(Eneregy Efficiency Label、または略して EEK Label)が付いた製品について、出品アスペクト(aspect)に欧州エネルギーラベル製品登録番号(European Product Registry for Energy Labelling Registration Number、または EPREL Registration Number、以下「EPREL登録番号」)を追加できるようになりました。セラーが有効なEPREL登録番号を提供すると、システムは関連するエネルギー効率クラス(Energy Efficiency Class、略して EEK)情報(エネルギー効率ラベルや関連する製品データシートなど)を自動的に取得し、商品ページに表示します。 EU規制の要件に基づき、EU諸国および北アイルランド内で事業を行っている、または同地域へ発送するセラーは、特定の製品カテゴリ(家電製品、スマートフォン、タブレット、タイヤなど)に対してエネルギー効率ラベルを提供する必要があります。製品のEPREL登録番号は、製品のエネルギー効率ラベル上のQRコードをスキャンするか、EPRELデータベースで製品のGTIN値を検索することで取得できます。 開発者は、Metadata API の getRegulatoryPolicies メソッドを使用して、どのeBayリーフカテゴリ(leaf categories)がエネルギー効率ラベルをサポートしているかを確認できます。このメソッドの戻り値において、エネルギー効率ラベルをサポートするリーフカテゴリでは、supportedAttributes.name フィールドに ENERGY_EFFICIENCY が表示されます。開発者は、Taxonomy API の getItemAspectsForCategory メソッドを呼び出すか、fetchItemAspects メソッドと Taxonomy SDK を組み合わせることで、どのリーフカテゴリがエネルギー効率ラベルをサポートしているかを確認することもできます。 注: 出品アスペクトでEPREL登録番号を提供することは、Trading API の EnergyEfficiencyLabel フィールドや Inventory API の energyEfficiencyLabel フィールドを使用する代わりの方法です。もしこれらのフィールドとアスペクト内のEPREL登録番号の両方が提供された場合、上記のフィールド内の情報が優先されます(上記のフィールドはアスペクト内のEPREL登録番号よりも優先度が高くなります)。
Post-Order バイヤー向けAPIメソッドの提供終了
2026-01-11
[eBay 開発者ニュース] Post-Order バイヤー向けAPIメソッドの提供終了通知 Post-Order APIに含まれる15のバイヤー向け関連メソッドは、2026年1月20日(「提供終了日」)をもって正式に提供終了(Decommission)となります。 提供終了となるメソッド 提供終了となるメソッドは以下の通りです: Cancel Return Request Check Return Eligibility Check Shipping Label Print Eligibility Create Return Draft Delete Return Request Draft File Get Return Draft Get Return Estimate Get Return Request Draft Files Get Return Shipping Label Initiate Return Shipping Label Mark Return Refund Received Mark Return Shipped Send Return Shipping Label Update Return Draft Upload Return Request Draft File 利用率が低いため、上記のAPIメソッドは提供終了となります。提供終了後も、バイヤーは引き続きeBayウェブサイトおよびモバイルアプリを通じて、返品関連事項の作成と管理を行うことができます。 重要: アプリケーションで上記のAPIメソッドを使用している場合は、提供終了日までに必ず対応するコードの変更を行ってください。