How to get live gold prices in Go
Call the goldprice.dev API from Go with net/http, shopspring/decimal, retry-on-429, and a 60-second TTL cache.
Updated
To get live gold prices in Go, send a GET to api.goldprice.dev/v1/spot/XAU-USD-SPOT with your API key in the X-API-Key header using net/http. Decode the JSON into a struct, parse prices with shopspring/decimal for exact math, and cache per symbol for 60 seconds. The free tier covers 1,000 calls/month, no credit card. Tested June 2026 against Go 1.23.
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
goldprice_live_. Set it as an environment variable:export GOLDPRICE_API_KEY=goldprice_live_replace_with_your_key_hereThe free tier includes 1,000 calls/month and 13 supported currencies. No credit card required.
Get your free API key1,000 calls/mo, no credit cardSign up free →2.Initialise the module and add the decimal dependency
The standard library covers the HTTP call and JSON decode. The one external package is
shopspring/decimal, so price math stays exact instead of drifting onfloat64. Create a module and add it:BASH · shellgo mod init goldprice-demo go get github.com/shopspring/decimal3.Write the goldprice client
Save this as
goldprice.go. TheSpot()method caches per symbol for 60 seconds behind a mutex, retries up to 3 times on 429 with exponential backoff, and decodes prices straight intodecimal.Decimal(itsUnmarshalJSONaccepts the string prices the API returns).GO · goldprice.go// go: goldprice.go // curl-equivalent: https://api.goldprice.dev/v1/spot/XAU-USD-SPOT package main import ( "encoding/json" "errors" "fmt" "net/http" "os" "sync" "time" "github.com/shopspring/decimal" ) const ( apiBase = "https://api.goldprice.dev" timeout = 5 * time.Second ttl = 60 * time.Second ) // SpotResponse holds the fields we consume from /v1/spot/{symbol}. type SpotResponse struct { Symbol string `json:"symbol"` QuoteCurrency string `json:"quote_currency"` Price decimal.Decimal `json:"price"` Bid decimal.Decimal `json:"bid"` Ask decimal.Decimal `json:"ask"` IsStale bool `json:"is_stale"` ComputedAt string `json:"computed_at"` } var errRateLimited = errors.New("goldprice: 429 rate limited") type cacheEntry struct { resp SpotResponse expires time.Time } // Client is safe for concurrent use. type Client struct { apiKey string http *http.Client mu sync.Mutex cache map[string]cacheEntry } func NewClient() *Client { key := os.Getenv("GOLDPRICE_API_KEY") if key == "" { panic("GOLDPRICE_API_KEY not set") } return &Client{ apiKey: key, http: &http.Client{Timeout: timeout}, cache: make(map[string]cacheEntry), } } // Spot fetches the live spot price. Cached 60s per symbol, retries 3x on 429. func (c *Client) Spot(symbol string) (SpotResponse, error) { c.mu.Lock() if e, ok := c.cache[symbol]; ok && time.Now().Before(e.expires) { c.mu.Unlock() return e.resp, nil } c.mu.Unlock() var out SpotResponse var err error for attempt := 0; attempt < 3; attempt++ { out, err = c.fetch(symbol) if err == nil { c.mu.Lock() c.cache[symbol] = cacheEntry{resp: out, expires: time.Now().Add(ttl)} c.mu.Unlock() return out, nil } if !errors.Is(err, errRateLimited) { return SpotResponse{}, err } time.Sleep(time.Duration(1<<attempt) * time.Second) // 1s, 2s, 4s } return SpotResponse{}, err } func (c *Client) fetch(symbol string) (SpotResponse, error) { req, err := http.NewRequest(http.MethodGet, apiBase+"/v1/spot/"+symbol, nil) if err != nil { return SpotResponse{}, err } req.Header.Set("X-API-Key", c.apiKey) resp, err := c.http.Do(req) if err != nil { return SpotResponse{}, err } defer resp.Body.Close() if resp.StatusCode == http.StatusTooManyRequests { return SpotResponse{}, errRateLimited } if resp.StatusCode != http.StatusOK { return SpotResponse{}, fmt.Errorf("goldprice: unexpected status %d", resp.StatusCode) } var out SpotResponse if err := json.NewDecoder(resp.Body).Decode(&out); err != nil { return SpotResponse{}, err } return out, nil } func main() { c := NewClient() r, err := c.Spot("XAU-USD-SPOT") if err != nil { panic(err) } fmt.Printf("%s-%s: %s (computed %s)\n", r.Symbol, r.QuoteCurrency, r.Price, r.ComputedAt) }4.Run it
From the same shell where you exported
GOLDPRICE_API_KEY, run the file. The first call hits the API; calls within 60 seconds return the cached value without a round-trip. A fresh process starts with an empty cache (in-process only) — back it with Redis if you need cross-process sharing.BASH · shellgo run goldprice.go # XAU-USD: 4726.01 (computed 2026-06-26T04:49:01.706844+00:00)5.Switch currency or metal
Symbols are
METAL-CURRENCY-SPOT. Pass any of the 13 supported currencies toSpot(). Each unique symbol caches independently behind the mutex, so a dashboard polling 5 symbols at 60-second intervals fires 5 uncached calls per minute and 0 cached.GO · goldprice.go · multi-currencyc := NewClient() // USD spot — default c.Spot("XAU-USD-SPOT") // INR — Indian gold market (largest retail gold-search globally) c.Spot("XAU-INR-SPOT") // EUR — European pricing surfaces c.Spot("XAU-EUR-SPOT") // Silver in USD c.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-06-26T04: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-06-26T04: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": 1782259200
}Stdout shows one line: symbol, quote currency, the price as a decimal.Decimal, and the ISO 8601 ComputedAt timestamp. The SpotResponse struct 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. NewClient() panics at construction 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 returns the rate-limit error | 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 or add a golang.org/x/sync/singleflight group to collapse duplicate in-flight requests. For sustained volume, see /pricing for Basic and Pro tiers. |
| N/A | price decodes as 0 or the build complains about the decimal type | The API sends prices as JSON strings (e.g. "4726.01"). decimal.Decimal from shopspring/decimal unmarshals strings correctly; a plain float64 field would lose precision and a custom int type would fail to decode. Keep the field typed as decimal.Decimal and run go mod tidy if the import is unresolved. |
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 struct without an HTTP 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 or Pro — see /pricing.
Why decimal.Decimal instead of float64?
Float arithmetic drifts on summation: 0.1 + 0.2 is 0.30000000000000004 in IEEE-754. Gold prices carry 6+ decimal places; summing thousands of values in float64 introduces basis-point errors over time. decimal.Decimal keeps exact arithmetic, and the API returns prices as strings precisely so clients can decode them without loss.
Is the client safe for concurrent goroutines?
Yes. The cache map is guarded by a sync.Mutex, so multiple goroutines can call Spot() on the same Client. For high concurrency on the same cold symbol, add a singleflight.Group so only one goroutine performs the fetch while the others wait for its result.
What metals are supported?
Gold (XAU), silver (XAG), copper (HG), platinum (XPT), palladium (XPD). Substitute the metal code in the symbol, e.g. c.Spot("XAG-USD-SPOT").
Can I have Claude write this for me?
Yes. Open Claude Desktop with the goldprice.dev MCP server installed and ask: Build a Go client for the goldprice.dev /v1/spot endpoint. Use net/http, shopspring/decimal, a 60-second per-symbol cache behind a mutex, 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 and tags on the first try.
Going further
- Add a
context.Contextparameter toSpot()and usehttp.NewRequestWithContextso callers can set deadlines and cancellation - Collapse duplicate in-flight requests with
golang.org/x/sync/singleflightso a thundering herd on one symbol makes a single API call - Add a
History()method for/v1/history/XAU-USD-SPOT?days=30and feed the rows into a backtest - Back the cache with Redis so a fleet of pods shares one warm price instead of each holding its own
- Expose Prometheus counters for cache hit rate, request latency, and 429-retry totals
Next steps
Try the same setup in a different platform: