To get silver prices in Go, send a GET request to https://api.goldprice.dev/v1/spot/XAG-USD-SPOT with your API key in the X-API-Key header. Use net/http for requests and shopspring/decimal to decode price strings into decimals. Silver spot is quoted in US dollars per troy ounce. Free prices are fifteen minutes late; paid plans are live. The client below caches each symbol and retries requests that hit a rate limit.
Get your goldprice.dev API key
Create a free account at goldprice.dev/onboarding and get your API key. Set it in the shell where you will run the program:
export GOLDPRICE_API_KEY=ga_live_replace_with_your_key_hereFree includes 5,000 calls a month and 10 requests a minute, with no credit card required. Silver spot is available on every plan, in US dollars only. Free prices are fifteen minutes late.
Initialise the module and add the decimal dependency
The standard library handles HTTP requests and JSON decoding. Add shopspring/decimal to read price strings as decimals and avoid binary floating point rounding in price calculations:
go mod init goldprice-demogo get github.com/shopspring/decimalWrite the silver price client
Save this as goldprice.go. The Spot() method caches each symbol for sixty seconds behind a mutex. It makes up to three attempts when requests return 429 and waits longer after each failed attempt.
The response struct decodes price strings into decimal.Decimal. Check IsStale and ComputedAt before using a quote.
The cache controls local requests. It does not change your plan's price delay.
// go: goldprice.go// curl-equivalent: https://api.goldprice.dev/v1/spot/XAG-USD-SPOTpackage mainimport ( "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"` 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 latest 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 } if attempt < 2 { time.Sleep(time.Duration(1<<attempt) * time.Second) // 1s, then 2s } } 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("XAG-USD-SPOT") if err != nil { panic(err) } fmt.Printf("%s-%s: %s (computed %s)\n", r.Symbol, r.QuoteCurrency, r.Price.StringFixed(3), r.ComputedAt)}Run the client
Run the file from the shell where you set GOLDPRICE_API_KEY. The program prints the silver symbol, quote currency, price to three decimals, and computation timestamp. The output below is an example, not a current quote.
Repeated calls on the same client use its cache until the entry expires. Each new process starts with an empty cache.
go run goldprice.go# XAG-USD: 54.020 (computed 2026-10-23T18:12:06.354573+00:00)Switch metal or convert silver to another currency
Use Spot() with the symbols below to fetch silver, gold, or copper. Silver and gold use US dollars per troy ounce. Copper uses US dollars per pound. Each symbol has its own cache entry.
Silver spot symbols exist only in US dollars. For silver in euros per gram, use GET /v1/convert?from=XAG&to=EUR&amount=1&unit=gram instead. Conversion supports oz, gram, and kg. See the API reference for the response format.
c := NewClient()// Silver in USD per troy ouncec.Spot("XAG-USD-SPOT")// Gold in USD per troy ouncec.Spot("XAU-USD-SPOT")// Copper in USD per poundc.Spot("HG-USD-SPOT")Expected output
{ "symbol": "XAG", "quote_currency": "USD", "unit": "troy_ounce", "contract_type": "spot", "price": "54.020", "is_stale": false, "computed_at": "2026-10-23T18:12:06.354573+00:00"}The program prints the silver symbol, quote currency, price to three decimals, and computation timestamp. The JSON shows the response a Free key receives, with the price as a string. Read is_stale and computed_at from the actual response before using a price.
Common errors
| Code | What you see | Fix |
|---|---|---|
| 401 | goldprice: unexpected status 401 on the first request | Check that GOLDPRICE_API_KEY contains your valid API key, then restart the program. Get a key through onboarding if needed. An empty variable causes NewClient() to panic before sending a request. |
| 429 | All three attempts return 429, then Spot() returns the rate limit error | Reduce request volume and check your quota. Free allows 10 requests a minute and 5,000 calls a month. Reuse the client so calls share its cache. Concurrent cache misses can still send duplicate requests. Stagger calls or combine duplicate requests, and see pricing if you need more capacity. |
| N/A | Price decoding fails or the decimal package cannot be found | Prices arrive as JSON strings. Keep Price typed as decimal.Decimal; a plain float64 field does not decode these strings with the standard JSON decoder. Run go mod tidy if the decimal import is unresolved. |
Going further
- Add a
context.Contextparameter toSpot()and usehttp.NewRequestWithContextso callers can cancel requests - Combine simultaneous requests for the same uncached symbol with
golang.org/x/sync/singleflight - Handle
IsStaleandComputedAtbefore displaying or using a silver quote - Use optional decimal fields if your application needs to distinguish missing bid and ask values from zero
- See the quickstart for request setup and the API reference for response fields