【eBay Trading API】UploadSiteHostedPicturesを使って画像エラーを解決する方法

eBay API

前回の記事では、Google Apps Script(GAS)を使って Trading API(AddItem)での単品出品の流れを紹介しました。

(→「eBay Trading API で商品を出品する方法(スプレッドシート+GAS)」)

しかし、実際に AddItem を試すと、多くの方が次の“壁”にぶつかります。

「画像だけエラーになる」「外部URLだと読み込めない」

特に Googleドライブの共有URLや、自前サーバーの画像URLを PictureURL に指定した場合、出品時に画像取り込みエラーが多発します。本記事では、これを根本的に解決するための API「UploadSiteHostedPictures」を GAS で使う方法を詳しく解説します。


1. なぜ外部URLだと画像エラーが発生するのか?

AddItem の <PictureURL> に外部URLをそのまま指定すると、次の理由で eBay が画像を取得できない場合があります。

  • GoogleドライブURLが認証・リダイレクト付きである
  • 一部サーバーは画像取得時に 403 / 404 を返す
  • URLに有効期限があり、出品時に切れている
  • 圧縮や画像サイズが eBay の判定に合わない

結論:外部URLを直接 PictureURL に入れるのは不安定。
これを避けるには、一度 eBay Picture Service に画像をアップロードしておく必要があります。


2. UploadSiteHostedPictures とは?

UploadSiteHostedPictures は、eBayの「Picture Service」に画像をアップロードするための Trading API の1つです。アップロード成功後には、eBay ホストの公式画像URL(例:https://i.ebayimg.com/...)が返ってきます。このURLを <PictureURL> に指定することで、画像エラーが解消されます。

公式ドキュメント(UploadSiteHostedPictures): https://developer.ebay.com/devzone/xml/docs/reference/ebay/uploadsitehostedpictures.html

使用するキー

  • DevID
  • AppID
  • CertID
  • Auth’n’Auth トークン

AddItem と同じ情報を使用でき、新しいキーの取得は不要です。


3. 全体の流れ(AddItem の前処理として追加)

  1. 外部の画像URLを用意
  2. UploadSiteHostedPictures を呼び出す
  3. 返ってきた eBay 画像URLを取得
  4. AddItem の <PictureURL> に設定して出品

この“前処理を追加するだけ”で画像エラーが大幅に減ります。


4. GASで UploadSiteHostedPictures を呼び出すコード

以下は 「test」シートのA1~A3セルに記載した画像URLをeBayの画像URLに変換しB1~B3セルに出力する例 です。

/***** eBay Trading API 設定 *****/
const EBAY_TRADING_ENDPOINT = 'https://api.ebay.com/ws/api.dll'; 
// サンドボックスの場合:'https://api.sandbox.ebay.com/ws/api.dll'

const EBAY_COMPAT_LEVEL = '967';      // 環境に合わせて
const EBAY_SITE_ID      = '0';        // 0 = US

const setsheet  = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("設定");

// 設定シート(APIキー類)
const ADDR_SET_APPID         = 'E6';           // 設定シート内 APPID(CERT-NAME 用)
const ADDR_SET_DEVID         = 'E7';           // 設定シート内 DEVID(DEV-NAME 用)
const ADDR_SET_AUTHTOKEN     = 'E8';           // 設定シート内 eBayAuthToken
const ADDR_SET_CERTID         = 'E9';           // 設定シート内 CERTID


// Developer アカウントから取得した値
const EBAY_DEV_ID  = setsheet.getRange(ADDR_SET_DEVID).getValue();
const EBAY_APP_ID  = setsheet.getRange(ADDR_SET_APPID).getValue();
const EBAY_CERT_ID = setsheet.getRange(ADDR_SET_CERTID).getValue();

// Auth'n'Auth のユーザートークン
const EBAY_AUTH_TOKEN = setsheet.getRange(ADDR_SET_AUTHTOKEN).getValue();

// Google Drive 内で画像を探すフォルダ名
const IMAGE_FOLDER_NAME = 'aaa';

// 最大何枚アップロードするか
const MAX_PICTURES = 24;
/**
 * testシートの A1〜A3 に記載された画像URLを
 * eBay Picture Service にアップロードし、
 * 結果の FullURL を testシートの B1〜B3 に出力する。
 */
function uploadPicturesFromAaaFolder2() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('test');
  if (!sheet) {
    throw new Error('シート「test」が見つかりません。');
  }

  // A1〜A3 → 入力URL
  const rangeIn = sheet.getRange('A1:A3');
  const values = rangeIn.getValues(); // [[url1],[url2],[url3]]

  // 空セルを除外してURL配列
  const urls = values
    .map(row => (row[0] || '').toString().trim())
    .filter(u => u);

  if (urls.length === 0) {
    Logger.log('アップロード対象のURLが見つかりません。');
    return [];
  }

  const results = [];
  const count = Math.min(urls.length, MAX_PICTURES);

  // 出力用配列(3行×1列)
  const outValues = [[''], [''], ['']];

  for (let i = 0; i < count; i++) {
    const externalUrl = urls[i];

    // PictureName (aaa_01, aaa_02...)
    const localName = IMAGE_FOLDER_NAME + '_' + ('0' + (i + 1)).slice(-2);

    // eBayへアップロード
    const ebayUrl = uploadSiteHostedPicture(externalUrl, localName);

    results.push({
      localName: localName,
      sourceUrl: externalUrl,
      ebayUrl: ebayUrl
    });

    Logger.log(`Uploaded: ${localName} -> ${ebayUrl}`);

    // B1〜B3 に書き込む値をセット
    outValues[i][0] = ebayUrl;
  }

  // B1〜B3 へ書き込み
  const rangeOut = sheet.getRange('B1:B3');
  rangeOut.setValues(outValues);

  return results;
}


/**
 * eBay Trading API 呼び出し(共通)
 * @param {string} callName - 例: 'UploadSiteHostedPictures'
 * @param {string} xmlBody  - ルート要素を含む XML 本文
 * @returns {string} - eBay からのレスポンス XML 文字列
 */
function callEbayTrading(callName, xmlBody) {
  const headers = {
    'X-EBAY-API-CALL-NAME': callName,
    'X-EBAY-API-COMPATIBILITY-LEVEL': EBAY_COMPAT_LEVEL,
    'X-EBAY-API-SITEID': EBAY_SITE_ID,
    'X-EBAY-API-DEV-NAME': EBAY_DEV_ID,
    'X-EBAY-API-APP-NAME': EBAY_APP_ID,
    'X-EBAY-API-CERT-NAME': EBAY_CERT_ID,
    'Content-Type': 'text/xml'
  };

  const options = {
    method: 'post',
    headers: headers,
    payload: xmlBody,
    muteHttpExceptions: true
  };

  const res = UrlFetchApp.fetch(EBAY_TRADING_ENDPOINT, options);
  const text = res.getContentText();
  Logger.log(text);
  return text;
}

/**
 * 単一の画像URLを eBay Picture Service に登録する。
 * - ExternalPictureURL を使うパターン
 * @param {string} externalUrl - 画像の外部URL
 * @param {string} pictureName - PictureName(aaa_01 など)
 * @returns {string} - 登録された画像の eBay ホスト URL(FullURL)
 */
function uploadSiteHostedPicture(externalUrl, pictureName) {
  const xmlBody =
    '<?xml version="1.0" encoding="utf-8"?>' +
    '<UploadSiteHostedPicturesRequest xmlns="urn:ebay:apis:eBLBaseComponents">' +
      '<RequesterCredentials>' +
        '<eBayAuthToken>' + EBAY_AUTH_TOKEN + '</eBayAuthToken>' +
      '</RequesterCredentials>' +
      '<WarningLevel>High</WarningLevel>' +
      '<ExternalPictureURL>' + xmlEscape(externalUrl) + '</ExternalPictureURL>' +
      '<PictureName>' + xmlEscape(pictureName) + '</PictureName>' +
    '</UploadSiteHostedPicturesRequest>';

  const resXml = callEbayTrading('UploadSiteHostedPictures', xmlBody);

  const ackMatch = resXml.match(/<Ack>([^<]+)<\/Ack>/);
  const ack = ackMatch ? ackMatch[1] : '';
  if (ack !== 'Success' && ack !== 'Warning') {
    throw new Error('UploadSiteHostedPictures エラー: ' + ack + '\n' + resXml);
  }

  const urlMatch = resXml.match(/<FullURL>([^<]+)<\/FullURL>/);
  const fullUrl = urlMatch ? urlMatch[1] : '';

  if (!fullUrl) {
    throw new Error('FullURL がレスポンスから取得できませんでした。\n' + resXml);
  }

  return fullUrl;
}

/**
 * XML 用の簡易エスケープ
 */
function xmlEscape(str) {
  return String(str)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&apos;');
}


5. 実行結果サンプル

アップロードが成功すると、「test」シートのB1~B3セル内に次のようなURLが返ってきます。

このURLを AddItem の <PictureURL> に入れるだけで画像が表示されます。


6. ポイント

1. 返却されたURLは必ず保存すること
eBayには「アップロードした画像一覧」を取得するAPIがありません。
そのため、UploadSiteHostedPictures のレスポンスで返ってきた画像URLは、必ず自分でスプレッドシートなどに記録して管理する必要があります。
記録していなかった場合、後から同じURLを再取得することはできず、同じ画像を 再度アップロードする しか方法がありません。

2. アップロード後の画像はeBay側でリサイズされることがある
UploadSiteHostedPictures でアップロードした画像は、eBay側で自動的に圧縮・リサイズされる場合があります。
そのため、返ってきたURLを開いて画像が小さく見えても、それは仕様上の動作であり異常ではありません。

7. まとめ

外部URLをそのまま PictureURL に指定すると、認証・リダイレクト・アクセス期限 などの影響で eBay が画像を取得できず、AddItem 時に画像エラーが発生しやすくなります。

そのため、画像は UploadSiteHostedPictures を使って事前に eBay Picture Service(EPS)へアップロードしておく のが最も安定します。

アップロード後に返される eBayホスト画像URL を PictureURL に設定すれば、出品時の画像取り込みが安定し、エラーが大幅に減ります。

なお、返却される画像URLは eBay が再通知してくれるものではない ため、必ず自分のスプレッドシート等に記録して管理してください。

画像まわりの処理を安定化させることで、AddItem 出品全体の成功率も大きく向上します。