チュートリアルスプレッドシート

Google Sheetsでリアルタイムの金価格を取得する方法

APIキーを保存せずにGoogle Sheetsへ金の現在価格と過去価格を取り込み、基準価格と比較して、4時間ごとに無理なく更新できます。

2026年9月8日 · 読了時間:9分

Apps Scriptを追加すると、任意のセルで=GOLDPRICE()を使い、goldprice.devのXAU/USD価格をトロイオンス単位で取得できます。無料価格は15分遅れです。IMPORTDATAはこのJSONエンドポイントを読み込めないため、カスタム関数が必要です。GOLDHISTORY()は30日間の日次終値を、GOLDPRICEDELTA()は単位をそろえた基準セルとの差額と騰落率を符号付きで返します。APIキーは保存しません。installGoldRefresh()を一度実行すると、更新用セルを指定し、利用枠に収まる4時間間隔のトリガーを作成できます。

シートにAPIキーを保存せずに始める

XAUのスポット価格と30日間の日次履歴は、認証なしで取得できます。この手順ではApps Scriptに認証情報を保存しません。匿名アクセスには、IPごとに毎分10件、毎時100件、毎月5,000件のFree制限が適用されます。4時間間隔で更新するブックなら、この枠に収まります。

Google Sheetsの複数ユーザーが同じ送信元IPを共有する場合があるため、匿名アクセスで使える枠は変動します。共有用や本番用のブックでアカウント専用の枠が必要なら、自分のサーバー側プロキシ経由で呼び出し、goldprice.devのキーをそこで管理してください。他の編集者が閲覧できるスクリプトにBearerトークンを貼り付けないでください。無料アカウントのキーも月5,000回ですが、枠は共有IPではなくアカウントに割り当てられます。

自分専用のリクエスト枠を取得

以下の手順はキーなしで使えます。無料のAPIキーを取得すると、自分専用の利用枠が使えます。より長い履歴や大きな利用枠には有料プランが必要です。

Apps Scriptに金価格用の関数を追加する

シートを開き、拡張機能 → Apps Scriptを選びます。初期コードのfunction myFunction() {}を削除し、以下のコードを貼り付けてください。現在のスポット価格を取得するGOLDPRICE(metal, currency, refreshToken)、グラフ用の日次終値を取得するGOLDHISTORY(fromDate, toDate, refreshToken)、符号付きの差額と騰落率を取得するGOLDPRICEDELTA(referencePrice, metal, currency, refreshToken)を定義します。同じリクエストはCacheServiceでキャッシュし、Authorizationヘッダーは送信せず、例外を投げずにエラー文字列を返します。

保有量、購入費用、参考評価額を同じブックで管理するには、金ポートフォリオ管理テンプレートを使ってください。

Code.gs
// google-sheets: Apps Script Code.gsconst SPOT_CACHE_TTL = 55; // Cache successful spot responses for 55 seconds.const BARS_CACHE_TTL = 300; // Cache successful daily bars for 5 minutes.const REFRESH_RANGE_NAME = 'GOLDPRICE_REFRESH_TOKEN';const SPREADSHEET_ID_KEY = 'GOLDPRICE_SPREADSHEET_ID';const INSTALLER_EMAIL_KEY = 'GOLDPRICE_INSTALLER_EMAIL';const LAST_REFRESH_AT_KEY = 'GOLDPRICE_LAST_REFRESH_AT';const MIN_REFRESH_INTERVAL_MS = 4 * 60 * 60 * 1000;/** * Returns the current spot price for a metal/currency pair. * @customfunction */function GOLDPRICE(metal, currency, refreshToken) {  const symbol = metal + '-' + currency + '-SPOT';  const cache = CacheService.getScriptCache();  const cacheKey = 'spot_' + symbol;  const cached = cache.get(cacheKey);  if (cached) return cached;  const url = `https://api.goldprice.dev/v1/spot/${symbol}`;  const resp = UrlFetchApp.fetch(url, {    muteHttpExceptions: true,  });  const code = resp.getResponseCode();  if (code === 401) return '#ACCESS_ERROR: use a server-side proxy';  if (code === 403) return '#PLAN_GATED: symbol not on your tier';  if (code === 429) return '#RATE_LIMIT: wait a minute and retry';  if (code !== 200) return '#ERROR_' + code;  const data = JSON.parse(resp.getContentText());  if (!data || !data.price) return '#NO_DATA';  // Price stays a string, same as the API wire format: Sheets displays and  // sorts it fine. Wrap in VALUE() if you need it in arithmetic.  cache.put(cacheKey, data.price, SPOT_CACHE_TTL);  return data.price;}/** * Compares live spot with a reference cell in the same currency and unit. * Spills [signed price delta, signed percentage delta] into two cells. * @customfunction */function GOLDPRICEDELTA(referencePrice, metal, currency, refreshToken) {  const reference = Number(referencePrice);  if (!Number.isFinite(reference) || reference <= 0) {    return [['#BAD_REFERENCE: enter a positive price', '']];  }  const liveResult = GOLDPRICE(metal, currency, refreshToken);  const live = Number(liveResult);  if (!Number.isFinite(live)) return [[liveResult, '']];  const signedDelta = live - reference;  return [[Number(signedDelta.toFixed(2)), signedDelta / reference]];}/** * Returns chart-ready daily closes for a date range. * @customfunction */function GOLDHISTORY(fromDate, toDate, refreshToken) {  const now = new Date();  const rollingFrom = new Date(now.getTime() - 29 * 24 * 60 * 60 * 1000);  const from = toIsoDate_(    fromDate === '' || fromDate == null ? rollingFrom : fromDate  );  const to = toIsoDate_(toDate === '' || toDate == null ? now : toDate);  if (!from || !to) return [['#BAD_DATE: put real dates in B2/B3']];  const cache = CacheService.getScriptCache();  const cacheKey = 'bars_' + from + '_' + to;  const cached = cache.get(cacheKey);  if (cached) return JSON.parse(cached);  const url = 'https://api.goldprice.dev/v1/bars?symbol=XAU-USD-SPOT&interval=1d'    + '&from=' + from + '&to=' + to;  const resp = UrlFetchApp.fetch(url, {    muteHttpExceptions: true,  });  const code = resp.getResponseCode();  if (code === 401) return [['#ACCESS_ERROR: use a server-side proxy']];  if (code === 403) return [['#PLAN_GATED: range not on your tier']];  if (code === 429) return [['#RATE_LIMIT: wait a minute and retry']];  if (code !== 200) return [['#ERROR_' + code]];  const data = JSON.parse(resp.getContentText());  const bars = Array.isArray(data.bars) ? data.bars : [];  if (bars.length === 0) return [['#NO_DATA: empty range']];  // /v1/bars returns bars newest-first; reverse to chronological order so  // a line chart built from this range reads left-to-right in time.  const rows = bars.slice().reverse().map(function (bar) {    return [bar.bar_start.slice(0, 10), Number(bar.close)];  });  cache.put(cacheKey, JSON.stringify(rows), BARS_CACHE_TTL);  return rows;}function installGoldRefresh() {  const lock = LockService.getScriptLock();  lock.waitLock(30000);  try {    const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();    if (!spreadsheet) throw new Error('Open the bound Google Sheet and run again');    const installerEmail = Session.getEffectiveUser().getEmail();    if (!installerEmail) throw new Error('Run the installer from your Google account');    const properties = PropertiesService.getScriptProperties();    const existingInstaller = properties.getProperty(INSTALLER_EMAIL_KEY);    if (existingInstaller && existingInstaller !== installerEmail) {      throw new Error('Gold refresh is already owned by another editor');    }    const refreshRange = spreadsheet.getActiveSheet().getRange('B1');    spreadsheet.setNamedRange(REFRESH_RANGE_NAME, refreshRange);    // getProjectTriggers() only returns this user's triggers. The shared    // installer-email claim above prevents a second editor from creating a    // second schedule, while this replacement keeps owner reruns idempotent.    ScriptApp.getProjectTriggers()      .filter(function (trigger) {        return trigger.getHandlerFunction() === 'refreshGoldData';      })      .forEach(function (trigger) {        ScriptApp.deleteTrigger(trigger);      });    ScriptApp.newTrigger('refreshGoldData')      .timeBased()      .everyHours(4)      .create();    properties.setProperties({      [SPREADSHEET_ID_KEY]: spreadsheet.getId(),      [INSTALLER_EMAIL_KEY]: installerEmail,    });  } finally {    lock.releaseLock();  }  refreshGoldData();}function uninstallGoldRefresh() {  const lock = LockService.getScriptLock();  lock.waitLock(30000);  try {    const properties = PropertiesService.getScriptProperties();    const installerEmail = Session.getEffectiveUser().getEmail();    const existingInstaller = properties.getProperty(INSTALLER_EMAIL_KEY);    if (existingInstaller && existingInstaller !== installerEmail) {      throw new Error('Only the trigger owner can uninstall gold refresh');    }    ScriptApp.getProjectTriggers()      .filter(function (trigger) {        return trigger.getHandlerFunction() === 'refreshGoldData';      })      .forEach(function (trigger) {        ScriptApp.deleteTrigger(trigger);      });    properties.deleteProperty(SPREADSHEET_ID_KEY);    properties.deleteProperty(INSTALLER_EMAIL_KEY);    properties.deleteProperty(LAST_REFRESH_AT_KEY);  } finally {    lock.releaseLock();  }}function refreshGoldData(event) {  const lock = LockService.getScriptLock();  lock.waitLock(30000);  try {    // Clock triggers pass an event object, but they cannot supply the metal,    // currency, date-range, or reference arguments required by sheet formulas.    // This wrapper only changes the named token cell those formulas reference.    const properties = PropertiesService.getScriptProperties();    const spreadsheetId = properties.getProperty(SPREADSHEET_ID_KEY);    if (!spreadsheetId) throw new Error('Run installGoldRefresh first');    // Script properties and the script lock are shared across editors. This    // gate coalesces legacy triggers owned by different users into one refresh    // per four hours even though Apps Script cannot enumerate their triggers.    const now = Date.now();    const lastRefreshAt = Number(properties.getProperty(LAST_REFRESH_AT_KEY));    if (lastRefreshAt && now - lastRefreshAt < MIN_REFRESH_INTERVAL_MS) return;    const spreadsheet = SpreadsheetApp.openById(spreadsheetId);    const refreshRange = spreadsheet.getRangeByName(REFRESH_RANGE_NAME);    if (!refreshRange) throw new Error('Run installGoldRefresh to restore B1');    refreshRange.setValue(new Date(now).toISOString());    properties.setProperty(LAST_REFRESH_AT_KEY, String(now));  } finally {    lock.releaseLock();  }}function toIsoDate_(value) {  const d = value instanceof Date ? value : new Date(value);  if (isNaN(d.getTime())) return null;  return d.toISOString().slice(0, 10);}

価格、履歴、基準価格のセルを設定する

B1に更新トークンを入れ、必要に応じてC1にトロイオンス当たりのUSD基準価格を入力します。直近30暦日分の履歴にはB2とB3を空欄にし、期間を固定する場合は日付を入力してください。GOLDPRICEDELTA()は現在価格から基準価格を引いた差額と騰落率を符号付きで返します。比較するセルの通貨と単位をそろえてください。

Googleによると、GOOGLEFINANCEの価格は最大20分遅れる場合があります。すべての先物に対応しているわけではなく、過去データはApps Scriptから取得できません。別の基準価格を使う場合は、トロイオンス当たりのUSD価格に換算してC1に入れてください。スポット価格と履歴はgoldprice.devからプログラムで取得します。カスタム関数の引数にNOW()やTODAY()を渡さないでください。B2/B3が空欄なら、GOLDHISTORY()内で日付範囲を計算します。

Sheetsの数式 · B1/B2/B3/C1の参照設定
// B1: 更新用トークンです。refreshGoldData()が書き込みます。// B2/B3: 空欄にすると直近30暦日が対象になります。期間を固定する場合は日付を入力します。// C1: トロイオンス当たりのUSD建てに換算した基準価格です。入力は任意です。// 現在のUSD建てスポット価格です。B1が変わるたびに再計算します。=GOLDPRICE("XAU", "USD", $B$1)// 現在のスポット価格からC1を引き、[符号付きのUSD差額, 符号付きの変化率]を複数セルに展開します。// 結果の2番目のセルの表示形式をパーセントに設定します。=GOLDPRICEDELTA($C$1, "XAU", "USD", $B$1)// 現在のEUR建てスポット価格です。同じトークンセルで両方の価格を更新します。=GOLDPRICE("XAU", "EUR", $B$1)// グラフに使える30日分の日次終値です(日付, 終値)。=GOLDHISTORY($B$2, $B$3, $B$1)

履歴データから折れ線グラフを作る

GOLDHISTORY()の出力先の行を選び、挿入 → グラフから折れ線グラフを選択します。行数は指定期間と取得できる日次データによって変わります。関数がAPIの新しい順のデータを時系列順に並べ替えるため、追加の並べ替えなしで横軸が古い日付から新しい日付へ進みます。

4時間ごとの更新トリガーを追加する

更新スケジュールを管理するGoogleアカウントを決めます。旧手順を実行した編集者は、それぞれApps Script → トリガーで自分のrefreshGoldDataを削除してください。その後、installGoldRefreshを一度実行し、権限を許可します。B1が更新用セルに設定され、4時間間隔のトリガーが作成されます。削除できない旧トリガーが残っていても、共有ロックとタイムスタンプが更新を4時間に一度にまとめます。

管理者を引き継ぐ場合は、現在の管理者がuninstallGoldRefreshを実行してから、新しい管理者がインストールします。現在の管理者に連絡できない場合は、プロジェクトの設定 → スクリプトプロパティでGOLDPRICE_*の3項目を削除して再インストールしてください。共有の間隔チェックが重複呼び出しを防ぎます。数式4個×更新6回で最大24回/日、約720回/月となり、匿名Free枠のIPごとに毎時100件、毎月5,000件に収まります。

出力例

JSON
{  "symbol": "XAU",  "quote_currency": "USD",  "unit": "troy_ounce",  "contract_type": "spot",  "price": "4012.75",  "is_stale": false,  "computed_at": "2026-10-23T07:00:18Z"}

GOLDPRICEは上のpriceフィールドを文字列で表示します(例:"4012.75")。GOLDPRICEDELTAは符号付きの差額と小数形式の騰落率を隣り合うセルに出力します。騰落率のセルはパーセント表示にしてください。GOLDHISTORYは指定期間内で取得できる[date, close]の行を出力します。

よくあるエラー

コード症状対処方法
401セルに#ACCESS_ERROR: use a server-side proxyと表示されます匿名リクエストが受け付けられませんでした。まずApps Script → 実行数を確認してください。認証付きの利用枠が必要なら、サーバー側プロキシ経由でリクエストします。シートに紐づくスクリプトにキーを貼り付けないでください。
403セルに#PLAN_GATED: symbol not on your tierと表示されますFreeでは金(XAU)、銀(XAG)、銅(HG)のスポット価格を利用できます。シンボルを確認してください。プラチナとパラジウムは提供していません。詳しくは/pricingをご覧ください。
429セルに#RATE_LIMIT: wait a minute and retryと表示されます共有IPがFreeの制限である毎分10件、毎時100件、毎月5,000件のいずれかに達しました。通常はキャッシュで防げます。TTL定数を大きくする、異なる数式を減らす、認証付きリクエストをサーバー側プロキシ経由にする方法があります。
N/AGOLDHISTORYに#BAD_DATE: put real dates in B2/B3と表示されますB2またはB3に、Apps Scriptが日付として解釈できない文字列が入っています。値ではなくラベルを入力していないか確認してください。日付を入力するか、表示形式 → 数字 → 日付で設定したセルを参照してください。
N/AGOLDPRICEDELTAに#BAD_REFERENCE: enter a positive priceと表示されますC1が空欄、文字列、ゼロ、負の値のいずれかです。現在のスポット価格の数式と同じ通貨を使い、トロイオンス当たりの正の基準価格を入力してください。
N/AセルのLoading…表示が消えませんApps Scriptで外部取得の権限を許可する必要があります。エディタのツールバーで関数を選び、手動で一度実行してください。認証画面が表示されたら許可します。

活用方法

  • 価格アラートの数式を作る:=IF(GOLDPRICE("XAU","USD",$B$1) > 4800, "ALERT", "OK")
  • より古い日次履歴には、キーを管理するサーバー側プロキシを使います。Basicは1年分、PlusとProは全期間の履歴に対応します
  • Freeで金と銀を並べて確認し、同じ更新トークンのセルを使います
  • グラフ用のGOLDHISTORYの行をバックテストに使います。OHLCデータで金の戦略をバックテストする方法をご覧ください
  • 評価額や費用の計算モデルから=GOLDPRICE(...)を参照し、固定値を入力せずに現在価格で更新します

よくある質問

価格はどのくらいの頻度で更新されますか

スポットエンドポイントは60秒ごとに更新されます。ライセンスに基づくリアルタイムのスポット価格で、Freeとキーなしの価格は15分遅れ、有料プランはリアルタイムです。GOLDPRICEのキャッシュはこの間隔に合わせており、TTL内の呼び出しは同じ値を返すため、API呼び出しを追加で消費しません。

4時間ごとの更新は無料枠に収まりますか

はい。$B$1を更新用セルとして、GOLDPRICEを2個、GOLDPRICEDELTAとGOLDHISTORYを各1個、計4個の数式を設定します。4時間間隔なら最大で1日6回×4回=24回/日、約720回/月です。GOLDPRICEDELTAはUSDのスポットリクエストを再利用するため、通常はキャッシュでさらに減ります。匿名制限のIPごとに毎分10件、毎時100件、毎月5,000件に収まります。

金価格にGOOGLEFINANCEを使わないのはなぜですか

GOOGLEFINANCEは対応する有価証券や通貨向けの機能で、XAU/USDスポットAPIとしては文書化されていません。Googleによると、価格は最大20分遅れる場合があります。すべての先物には対応せず、過去データもApps Scriptから取得できません。スポットにはGOLDPRICE()、スクリプトで読む履歴にはGOLDHISTORY()を使います。基準価格は単位をそろえてGOLDPRICEDELTA()で比較し、異なる商品の価格を同一視しないでください。

トリガーがGOLDPRICEではなくrefreshGoldDataを呼ぶのはなぜですか

Apps Scriptは選択したハンドラーに時計イベントのオブジェクトを渡しますが、数式に必要な金属、通貨、日付範囲、基準価格の引数は渡せません。そのため、installGoldRefresh()はrefreshGoldData(event)用のトリガーを作ります。この関数が名前付きのB1範囲に新しいタイムスタンプを書き込み、4個の数式を再計算させます。

他の編集者とシートを共有できますか

はい。この例は匿名アクセスを使い、APIキーを保存しません。編集者がシートに紐づくスクリプトを閲覧やコピーしても、認証情報は渡りません。後から認証付きの利用枠が必要になった場合は、Bearerトークンをサーバー側プロキシで管理し、シートからそのサービスを呼び出してください。

商用利用できますか

すべてのプランで商用利用できます。有料プランにはクレジットリンクの義務はありません。Freeでは、価格の近くにgoldprice.devへのリンクを見える形で表示する必要があります。

対応している金属は何ですか

Freeを含むすべてのプランで、金(XAU)、銀(XAG)、銅(HG)のスポット価格を利用できます。プラチナとパラジウムは、どのプランでも提供していません。詳しくはAPIリファレンスをご覧ください。

無料プランは本当に無料ですか

はい。XAUのスポット価格と30日間の日次履歴は、キーなしで利用できます。匿名Free枠はIPごとに毎分10件、毎時100件、毎月5,000件です。無料アカウントのキーも月間枠は同じで、カード登録も有効期限もありません。サーバー側の連携でアカウント専用の枠が必要な場合に使えます。

ClaudeなどのAIアシスタントに作成を頼めますか

はい。Claude、Cursor、ChatGPTにgoldprice.devのMCPサーバーまたはllms.txtを参照させ、次のように依頼してください。api.goldprice.devを使うGoogle Sheetsのカスタム関数と、繰り返し実行しても重複しないインストーラーを作成してください。名前付きの更新用セルを指定し、4時間間隔の時計トリガーを作成してください。最新のスキーマを参照するため、生成コードは最初から正しいフィールド名とエラーコードを使います。

Apps ScriptはExcelでも使えますか

いいえ。ExcelではApps Scriptの代わりにPower Query Mを使います。設定手順はExcelチュートリアルをご覧ください。

続きを読む

無料のAPIキーで始める

無料プランで月5,000リクエスト利用できます。クレジットカードは不要です。

無料キーを取得