How to get live gold prices in Node.js

Call the goldprice.dev API from Node.js with native fetch, decimal.js, retry-on-429, and a 60-second TTL cache.

Updated

To get live gold prices in Node.js, send a GET to api.goldprice.dev/v1/spot/XAU-USD-SPOT with your API key in the X-API-Key header using native fetch — no HTTP library needed. Cache the response in a Map for 60 seconds and use decimal.js for price math. The free tier covers 1,000 calls/month, no credit card. Tested July 2026 against Node 22 LTS.

  1. 1.Get your goldprice.dev API key

    Sign up for a free account at goldprice.dev/onboarding. After email confirmation, your dashboard shows a key starting with ga_live_. Set it as an environment variable:

    export GOLDPRICE_API_KEY=ga_live_replace_with_your_key_here

    The free tier includes 1,000 calls/month and 31 supported currencies. No credit card required.

    Get your free API key1,000 calls/mo, no credit card
    Sign up free →
  2. 2.Install decimal.js

    Node 18+ ships native fetch, so no HTTP client is needed. The one dependency is decimal.js, so price math stays exact instead of drifting on JavaScript's Number type. Install it:

    BASH · shell
    npm install decimal.js
  3. 3.Write the goldprice client

    Save this as goldprice.mjs. The spot() function caches per symbol for 60 seconds in a Map, retries up to 3 times on 429 with exponential backoff, and returns prices as Decimal so downstream math does not drift from floating-point imprecision.

    JAVASCRIPT · goldprice.mjs
    // node: goldprice.mjs
    // curl-equivalent: https://api.goldprice.dev/v1/spot/XAU-USD-SPOT
    import Decimal from "decimal.js";
    
    const API_BASE = "https://api.goldprice.dev";
    const API_KEY = process.env.GOLDPRICE_API_KEY;
    if (!API_KEY) {
      throw new Error("GOLDPRICE_API_KEY not set");
    }
    
    const TTL_MS = 60_000;
    const cache = new Map();
    
    /** Fetch live spot price. Cached 60s per symbol, retries 3x on 429. */
    export async function spot(symbol = "XAU-USD-SPOT") {
      const cached = cache.get(symbol);
      if (cached && cached.expiresAt > Date.now()) {
        return cached.data;
      }
    
      let lastErr;
      for (let attempt = 0; attempt < 3; attempt++) {
        const res = await fetch(`${API_BASE}/v1/spot/${symbol}`, {
          headers: { "X-API-Key": API_KEY },
          signal: AbortSignal.timeout(5000),
        });
    
        if (res.status === 429) {
          lastErr = new Error("goldprice: 429 rate limited");
          const backoffMs = 2 ** attempt * 1000; // 1s, 2s, 4s
          await new Promise((resolve) => setTimeout(resolve, backoffMs));
          continue;
        }
        if (!res.ok) {
          throw new Error(`goldprice: unexpected status ${res.status}`);
        }
    
        const raw = await res.json();
        const data = {
          symbol: raw.symbol,
          quoteCurrency: raw.quote_currency,
          price: new Decimal(raw.price),
          bid: new Decimal(raw.bid),
          ask: new Decimal(raw.ask),
          isStale: raw.is_stale,
          computedAt: raw.computed_at,
        };
        cache.set(symbol, { data, expiresAt: Date.now() + TTL_MS });
        return data;
      }
      throw lastErr;
    }
  4. 4.Run it

    Import the module and call spot(). The first call hits the API; calls within 60 seconds return the cached Decimal without a network round-trip. Restarting the process clears the cache (in-process only) — back it with Redis if you need cross-process sharing.

    JAVASCRIPT · run.mjs
    import { spot } from "./goldprice.mjs";
    
    const r = await spot("XAU-USD-SPOT");
    console.log(`${r.symbol}-${r.quoteCurrency}: ${r.price} (computed ${r.computedAt})`);
    // XAU-USD: 4726.01 (computed 2026-07-05T04:49:01.706844+00:00)
  5. 5.Switch currency or metal

    Symbols are METAL-CURRENCY-SPOT. Pass any symbol from the 31 supported currencies to spot(). Each unique symbol caches independently in the Map, so a multi-currency dashboard polling 5 symbols at 60-second intervals fires 5 API calls/minute uncached and 0 cached.

    JAVASCRIPT · goldprice.mjs · multi-currency
    // USD spot — default
    await spot("XAU-USD-SPOT");
    
    // INR — Indian gold market (largest retail gold-search globally)
    await spot("XAU-INR-SPOT");
    
    // EUR — European pricing surfaces
    await spot("XAU-EUR-SPOT");
    
    // Silver in USD
    await spot("XAG-USD-SPOT");

Expected output

The API returns this shape:

JSON · GET /v1/spot/XAU-USD-SPOT
{
  "symbol": "XAU",
  "quote_currency": "USD",
  "unit": "troy_ounce",
  "contract_type": "spot",
  "price": "4726.01",
  "bid": "4726.68",
  "ask": "4725.33",
  "is_stale": false,
  "divergence_flag": false,
  "computed_at": "2026-07-05T04:49:01.706844+00:00",
  "sources": [
    {
      "source": "wgc.fsapi.usd",
      "display_name": "Continuous spot reference (live spot)",
      "price": "4726.01",
      "is_stale": false,
      "timestamp": "2026-07-05T04:47:24+00:00"
    }
    /* + cmc.paxg + cmc.xaut entries — full sources[] in live response */
  ],
  "value_stale": false,
  "price_gram_24k": "151.9447",
  "open_price": "4681.305",
  "high_price": "4729.56",
  "low_price": "4672.927",
  "prev_close_price": "4681.302",
  "ch": "44.6260",
  "chp": "0.9533",
  "open_time": 1783382400
}

Stdout shows one line: symbol, quote currency, the price as a decimal.js Decimal, and the ISO 8601 computedAt timestamp. The plain object returned by spot() is what your downstream code consumes.

Common errors

CodeSymptomFix
401goldprice: unexpected status 401 on the first callAPI key missing or invalid. Confirm the variable is set: echo $GOLDPRICE_API_KEY. If empty, re-export from your dashboard. The module throws at import time when the env var is empty, so the failure surfaces immediately rather than mid-request.
429Three retries with backoff all return 429, then spot() rejectsYou hit the per-minute cap. The 60-second cache prevents this for steady polling; if a burst of distinct symbols arrives at once, stagger the calls. For sustained high-volume use, see /pricing for Basic and Pro tiers.
N/APrices look right but drift slightly after repeated arithmeticJavaScript Number is IEEE-754 float: 0.1 + 0.2 === 0.30000000000000004. Gold prices carry 6+ decimal places, so summing thousands of values in Number introduces basis-point errors. Keep prices as decimal.js Decimal through arithmetic and only call .toString() or .toNumber() at the boundary where you display or serialize.

FAQ

How often does the price update?

The spot endpoint refreshes every 60 seconds (live oracle + continuous spot reference + futures settlement aggregation). The 60-second TTL above matches that cadence — repeated spot() calls within the window return the cached Decimal without a network round-trip.

Will this fit the free tier with auto-refresh?

It depends on your call site. A service polling once per minute = 1,440 calls/day = ~43,000/month — well over the 1,000/month free tier. Cache aggressively and call only on demand. Polling once per hour = ~720/month, inside the free tier. For continuous high-volume use, see /pricing for Basic and Pro tiers.

Can I use this commercially?

The Free tier is for personal use. For commercial use (apps you ship, services you sell, internal production systems), upgrade to Basic ($10/mo) or Pro ($30/mo) — monthly billing, cancel anytime, no annual lock-in. See /pricing.

Why decimal.js instead of Number?

JavaScript's only numeric type is a 64-bit float, so arithmetic drifts on summation: 0.1 + 0.2 is 0.30000000000000004. Gold prices carry 6+ decimal places; summing thousands of values in Number introduces basis-point errors over time. decimal.js keeps exact arithmetic, and the API returns prices as strings precisely so clients can construct a Decimal without loss.

Do I need TypeScript or a build step?

No. The sample above is plain ES modules (.mjs), which Node 18+ runs natively with no transpiler. If your project uses TypeScript, the same code works unchanged inside a .ts file — fetch, AbortSignal.timeout, and decimal.js all ship their own types.

What metals are supported?

Gold (XAU), silver (XAG), copper (HG), platinum (XPT), palladium (XPD). Substitute the metal code in the symbol, e.g. spot("XAG-USD-SPOT").

Is the free tier really free?

Yes — 1,000 calls/month, no credit card, no expiration. Get the key at /onboarding.

Can I have Claude write this for me?

Yes. Open Claude Desktop with the goldprice.dev MCP server installed and ask: Build a Node.js client for the goldprice.dev /v1/spot endpoint. Use native fetch, decimal.js, a 60-second Map-based cache, and retry-on-429 with exponential backoff. Read the API key from GOLDPRICE_API_KEY. The MCP server gives Claude the live schema, so generated code uses correct field names on first try.

Going further

  • Persist the cache across processes — replace the Map with a Redis-backed cache so multiple workers share one warm price
  • Add a history() wrapper for /v1/history/XAU-USD-SPOT?days=30 for backtesting
  • Wire the client into an Express or Fastify route and serve internally for non-Node services
  • Collapse duplicate in-flight requests for the same cold symbol so a burst of callers triggers one fetch instead of many
  • Expose Prometheus metrics for cache hit rate, p50/p95 latency, and 429-retry counts

Next steps

Try the same setup in a different platform:

Browse all 9 tutorials →