TutorialSpreadsheet

Cara mendapatkan harga emas terkini di Google Sheets

Ambil harga emas terkini dan historis ke Google Sheets tanpa menyimpan kunci API, bandingkan harga acuan, dan perbarui setiap 4 jam sesuai kuota.

8 Sep 2026 · 8 menit baca

=GOLDPRICE() menampilkan harga XAU/USD terkini per troy ounce dari goldprice.dev setelah Anda menambahkan Apps Script. Harga Free terlambat 15 menit. IMPORTDATA tidak dapat membaca endpoint JSON ini, jadi diperlukan fungsi khusus. GOLDHISTORY() mengembalikan harga penutupan harian selama 30 hari; GOLDPRICEDELTA() mengembalikan selisih harga dan persentase bertanda terhadap sel acuan yang disetarakan. Kunci API tidak disimpan. Jalankan installGoldRefresh() sekali untuk mengikat sel pembaruan dan membuat pemicu setiap empat jam sesuai kuota.

Mulai tanpa menyimpan kunci API di spreadsheet

Endpoint spot XAU dan riwayat harian selama 30 hari dapat diakses tanpa autentikasi, jadi tutorial ini tidak menyimpan kredensial di Apps Script. Akses anonim mengikuti batas Free: 10 permintaan per menit, 100 per jam, dan 5.000 per bulan per IP. Pembaruan setiap empat jam masih dalam kuota.

Google dapat memakai IP keluar yang sama untuk beberapa pengguna Sheets, sehingga kapasitas anonim dapat berubah. Jika spreadsheet bersama atau produksi membutuhkan kuota akun yang pasti, gunakan proksi di server Anda dan simpan kunci goldprice.dev di sana. Jangan tempel token bearer ke skrip terikat yang dapat diperiksa editor lain. Kunci akun gratis mendapat kuota bulanan 5.000 panggilan yang sama, tetapi terikat pada akun Anda, bukan IP bersama.

Dapatkan kuota permintaan sendiri

Langkah berikut dapat dijalankan tanpa kunci. Kunci API gratis memberi Anda kuota sendiri. Riwayat lebih panjang dan batas lebih tinggi memerlukan paket berbayar.

Tambahkan tiga fungsi harga emas ke Apps Script

Buka spreadsheet Anda, lalu klik Ekstensi → Apps Script. Hapus function myFunction() {} bawaan, lalu tempel kode berikut. Kode ini menyediakan GOLDPRICE(metal, currency, refreshToken) untuk harga spot terkini, GOLDHISTORY(fromDate, toDate, refreshToken) untuk penutupan harian siap grafik, dan GOLDPRICEDELTA(referencePrice, metal, currency, refreshToken) untuk selisih harga dan persentase bertanda. Fungsi menyimpan permintaan identik melalui CacheService, tidak mengirim header Authorization, dan mengembalikan teks kesalahan tanpa melempar exception.

Untuk mencatat jumlah, biaya pembelian, dan perkiraan nilai dalam satu spreadsheet, gunakan templat pelacak portofolio emas.

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);}

Siapkan sel harga, riwayat, dan acuan

Masukkan token pembaruan di B1 dan harga acuan opsional dalam USD per troy ounce di C1. Kosongkan B2 dan B3 untuk riwayat berjalan selama 30 hari kalender, atau masukkan tanggal tetap. GOLDPRICEDELTA() mengembalikan selisih harga terkini dikurangi acuan beserta persentasenya, lengkap dengan tanda. Perbandingan hanya valid jika mata uang dan satuannya sama.

Google menyatakan harga GOOGLEFINANCE dapat terlambat hingga 20 menit, tidak semua kontrak berjangka didukung, dan riwayat tidak dapat diakses melalui Apps Script. Setarakan harga acuan terpisah menjadi USD per troy ounce di C1; goldprice.dev menyediakan spot dan riwayat melalui API. Jangan gunakan NOW() atau TODAY() sebagai argumen fungsi khusus: tanggal berjalan dihitung dalam GOLDHISTORY() saat B2/B3 kosong.

Rumus Sheets · pengaturan B1/B2/B3/C1
// B1: token penyegaran, ditulis oleh refreshGoldData()// B2/B3: kosongkan untuk rentang bergulir 30 hari kalender, atau masukkan tanggal tetap// C1: harga acuan opsional yang disetarakan ke USD per troy ounce// Harga spot USD saat ini, dihitung ulang setiap B1 berubah=GOLDPRICE("XAU", "USD", $B$1)// Harga spot langsung dikurangi C1: menghasilkan [selisih USD bertanda, selisih persentase bertanda]// Format sel hasil kedua sebagai Persen.=GOLDPRICEDELTA($C$1, "XAU", "USD", $B$1)// Harga spot EUR saat ini, keduanya memakai sel token yang sama=GOLDPRICE("XAU", "EUR", $B$1)// Harga penutupan harian selama 30 hari, siap dibuat grafik (tanggal, penutupan)=GOLDHISTORY($B$2, $B$3, $B$1)

Buat grafik garis dari hasil riwayat

Pilih baris hasil GOLDHISTORY(), lalu pilih Sisipkan → Diagram dan Diagram garis. Jumlah baris mengikuti rentang yang diminta dan data harian yang tersedia. Fungsi sudah membalik urutan data API dari terbaru dahulu menjadi urutan waktu, sehingga sumbu x berjalan dari terlama ke terbaru tanpa pengurutan tambahan.

Tambahkan pemicu pembaruan setiap 4 jam

Pilih satu akun Google sebagai pemilik jadwal. Editor yang mengikuti tutorial lama harus menghapus entri refreshGoldData miliknya dari Apps Script → Pemicu terlebih dahulu. Jalankan installGoldRefresh sekali dan berikan izin. Penginstal mengikat B1 dan membuat satu pemicu setiap empat jam. Kunci bersama dan penanda waktu membatasi pemicu lama yang tidak dapat diakses menjadi satu pembaruan setiap empat jam.

Untuk pergantian terencana, pemilik menjalankan uninstallGoldRefresh, lalu pemilik baru memasangnya. Jika pemilik tidak tersedia, hapus tiga entri GOLDPRICE_* di Setelan project → Properti skrip sebelum memasang ulang; pembatas interval bersama tetap mencegah panggilan ganda. Empat rumus × enam pembaruan menghasilkan maksimal 24 panggilan/hari, sekitar 720/bulan, di bawah batas Free anonim sebesar 100 permintaan/jam dan 5.000/bulan per IP.

Hasil yang diharapkan

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 menampilkan kolom price di atas sebagai teks (misalnya "4012.75"). GOLDPRICEDELTA mengisi dua sel dengan selisih harga bertanda dan persentase desimal; format sel kedua sebagai Persen. GOLDHISTORY mengisi baris [date, close] yang tersedia untuk rentang yang diminta.

Kesalahan umum

KodeGejalaSolusi
401Sel menampilkan #ACCESS_ERROR: use a server-side proxyPermintaan anonim ditolak. Periksa Apps Script → Eksekusi terlebih dahulu. Jika spreadsheet membutuhkan kuota dengan autentikasi, kirim permintaan melalui proksi di server; jangan tempel kunci ke skrip terikat.
403Sel menampilkan #PLAN_GATED: symbol not on your tierFree mencakup harga spot emas (XAU), perak (XAG), dan tembaga (HG). Periksa simbolnya; platinum dan paladium tidak tersedia. Lihat /pricing.
429Sel menampilkan #RATE_LIMIT: wait a minute and retryIP bersama mencapai batas Free: 10 permintaan/menit, 100/jam, atau 5.000/bulan. Cache biasanya mencegahnya; naikkan konstanta TTL, kurangi rumus berbeda, atau alihkan permintaan berautentikasi melalui proksi di server.
N/AGOLDHISTORY menampilkan #BAD_DATE: put real dates in B2/B3B2 atau B3 berisi teks yang tidak dapat dibaca Apps Script sebagai tanggal, misalnya label. Masukkan tanggal valid, atau rujuk sel berformat Tanggal melalui Format → Angka → Tanggal.
N/AGOLDPRICEDELTA menampilkan #BAD_REFERENCE: enter a positive priceC1 kosong, berisi teks, nol, atau angka negatif. Masukkan harga acuan positif dengan mata uang dan satuan per troy ounce yang sama dengan rumus spot terkini.
N/ASel terus menampilkan Loading…Apps Script memerlukan izin untuk mengambil data eksternal. Pilih nama fungsi pada toolbar editor dan jalankan sekali secara manual untuk memunculkan permintaan izin, lalu setujui.

Langkah berikutnya

  • Buat rumus peringatan harga: =IF(GOLDPRICE("XAU","USD",$B$1) > 4800, "ALERT", "OK")
  • Untuk riwayat harian lebih lama, gunakan proksi server berkunci: Basic mencakup 1 tahun; Plus dan Pro mencakup seluruh riwayat
  • Pantau perak bersama emas di Free dengan sel token pembaruan yang sama
  • Gunakan baris GOLDHISTORY siap grafik untuk uji historis, lihat menguji strategi emas dengan data OHLC
  • Rujuk =GOLDPRICE(...) dari model valuasi atau biaya agar memakai data terkini tanpa angka yang ditulis tetap

Pertanyaan

Seberapa sering harga diperbarui?

Endpoint spot diperbarui setiap 60 detik dengan data spot langsung berlisensi; harga Free/tanpa kunci terlambat 15 menit, sedangkan paket berbayar mendapat harga langsung. Cache GOLDPRICE mengikuti interval tersebut, sehingga panggilan berulang selama TTL mengembalikan nilai yang sama tanpa memakai panggilan API baru.

Apakah pembaruan setiap 4 jam cukup dengan paket gratis?

Ya. Sel pembaruan $B$1 mengendalikan empat rumus: dua GOLDPRICE, satu GOLDPRICEDELTA, dan satu GOLDHISTORY. Pemicu 4 jam berjalan maksimal 6 kali sehari × 4 panggilan = 24 panggilan/hari, sekitar 720/bulan. Cache biasanya mengurangi total karena GOLDPRICEDELTA memakai ulang permintaan spot USD. Ini di bawah batas anonim 10 permintaan/menit, 100/jam, dan 5.000/bulan per IP.

Mengapa tidak memakai GOOGLEFINANCE untuk harga emas?

GOOGLEFINANCE mendukung sekuritas dan mata uang tertentu, tanpa API spot XAU/USD terdokumentasi. Google menyebut harga dapat terlambat hingga 20 menit, sebagian kontrak berjangka tidak didukung, dan riwayat tidak tersedia melalui Apps Script. Gunakan GOLDPRICE() untuk spot, GOLDHISTORY() untuk riwayat terbaca skrip, serta GOLDPRICEDELTA() untuk membandingkan acuan yang disetarakan tanpa menganggap instrumennya sama.

Mengapa pemicu menjalankan refreshGoldData, bukan GOLDPRICE?

Apps Script meneruskan objek peristiwa waktu ke fungsi penangan, tetapi tidak dapat memasok argumen logam, mata uang, rentang tanggal, atau acuan yang diperlukan rumus. Karena itu, installGoldRefresh() membuat pemicu untuk refreshGoldData(event), yang hanya menulis penanda waktu baru ke rentang B1 bernama agar keempat rumus dihitung ulang.

Bisakah saya membagikan spreadsheet ini kepada editor lain?

Ya. Contoh ini memakai akses anonim tanpa menyimpan kunci API, sehingga editor dapat memeriksa atau menyalin skrip terikat tanpa memperoleh kredensial. Jika nantinya diperlukan kuota berautentikasi, simpan token bearer di proksi server dan arahkan spreadsheet ke layanan tersebut.

Bolehkah digunakan untuk keperluan komersial?

Penggunaan komersial diizinkan pada semua paket. Paket berbayar tidak memerlukan tautan atribusi; untuk Free, Anda wajib menampilkan tautan ke goldprice.dev yang terlihat di dekat harga emas.

Logam apa saja yang didukung?

Harga spot emas (XAU), perak (XAG), dan tembaga (HG) tersedia pada semua paket, termasuk Free. Platinum dan paladium tidak tersedia pada paket mana pun. Rincian lengkap ada di referensi API.

Apakah paket gratis benar-benar gratis?

Ya. Spot XAU dan riwayat harian 30 hari tersedia tanpa kunci dengan batas Free: 10 permintaan/menit, 100/jam, dan 5.000/bulan per IP. Kunci akun gratis memberi kuota bulanan sama, tanpa kartu atau kedaluwarsa, untuk kuota akun melalui integrasi di server.

Bisakah asisten AI seperti Claude menuliskannya untuk saya?

Ya. Arahkan Claude, Cursor, atau ChatGPT ke server MCP goldprice.dev atau llms.txt, lalu minta: Buat fungsi khusus Google Sheets dengan api.goldprice.dev serta penginstal idempoten yang mengikat sel pembaruan bernama dan membuat satu pemicu waktu setiap empat jam. Skema terkini membantu kode hasilnya memakai nama kolom dan kode kesalahan yang tepat sejak percobaan pertama.

Apakah Apps Script bisa digunakan di Excel?

Tidak. Untuk Excel, lihat tutorial Excel. Excel memakai Power Query M, bukan Apps Script.

Baca selanjutnya

Mulai dengan kunci API gratis

5.000 permintaan per bulan pada paket gratis. Tanpa kartu kredit.

Dapatkan kunci gratis