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.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_hereThe 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 cardSign up free →2.Install decimal.js
Node 18+ ships native
fetch, so no HTTP client is needed. The one dependency isdecimal.js, so price math stays exact instead of drifting on JavaScript'sNumbertype. Install it:BASH · shellnpm install decimal.js3.Write the goldprice client
Save this as
goldprice.mjs. Thespot()function caches per symbol for 60 seconds in aMap, retries up to 3 times on 429 with exponential backoff, and returns prices asDecimalso 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.Run it
Import the module and call
spot(). The first call hits the API; calls within 60 seconds return the cachedDecimalwithout 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.mjsimport { 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.Switch currency or metal
Symbols are
METAL-CURRENCY-SPOT. Pass any symbol from the 31 supported currencies tospot(). Each unique symbol caches independently in theMap, 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:
{
"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
| Code | Symptom | Fix |
|---|---|---|
| 401 | goldprice: unexpected status 401 on the first call | API 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. |
| 429 | Three retries with backoff all return 429, then spot() rejects | You 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/A | Prices look right but drift slightly after repeated arithmetic | JavaScript 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
Mapwith a Redis-backed cache so multiple workers share one warm price - Add a
history()wrapper for/v1/history/XAU-USD-SPOT?days=30for 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: