How to get live gold prices in Python
Call the goldprice.dev API from Python with requests, Decimal arithmetic, retry-on-429, and a 60-second TTL cache.
Updated
To get live gold prices in Python, send a GET to api.goldprice.dev/v1/spot/XAU-USD-SPOT with your API key in the X-API-Key header using requests. Wrap the call in cachetools.TTLCache for a 60-second cache and use Decimal for price math. The free tier covers 1,000 calls/month, no credit card. Tested April 2026 against Python 3.12 + requests 2.32.
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.Install dependencies
You need three packages:
requestsfor the HTTP call,cachetoolsfor the in-process TTL cache, andtenacityfor retry-on-429. Install with pip or uv:BASH · shell# pip pip install requests cachetools tenacity # or uv (recommended) uv add requests cachetools tenacity3.Write the goldprice client
Save this as
goldprice.py. Thespot()function caches per symbol for 60 seconds, retries up to 3 times on 429 with exponential backoff, and returns prices asDecimalso downstream math does not drift from float imprecision.PYTHON · goldprice.py# python: goldprice.py # curl-equivalent: https://api.goldprice.dev/v1/spot/XAU-USD-SPOT import os from decimal import Decimal from typing import TypedDict import requests from cachetools import TTLCache, cached from tenacity import ( retry, retry_if_exception_type, stop_after_attempt, wait_exponential, ) API_BASE = "https://api.goldprice.dev" API_KEY = os.environ["GOLDPRICE_API_KEY"] TIMEOUT = 5 # seconds class SpotResponse(TypedDict): symbol: str quote_currency: str price: Decimal bid: Decimal ask: Decimal is_stale: bool computed_at: str class RateLimited(Exception): """Raised on HTTP 429 so tenacity retries with backoff.""" @cached(cache=TTLCache(maxsize=128, ttl=60)) @retry( retry=retry_if_exception_type(RateLimited), wait=wait_exponential(multiplier=1, min=2, max=30), stop=stop_after_attempt(3), reraise=True, ) def spot(symbol: str = "XAU-USD-SPOT") -> SpotResponse: """Fetch live spot price. Cached 60s per symbol, retries 3x on 429.""" resp = requests.get( f"{API_BASE}/v1/spot/{symbol}", headers={"X-API-Key": API_KEY}, timeout=TIMEOUT, ) if resp.status_code == 429: raise RateLimited(resp.text) resp.raise_for_status() data = resp.json() return { "symbol": data["symbol"], "quote_currency": data["quote_currency"], "price": Decimal(data["price"]), "bid": Decimal(data["bid"]), "ask": Decimal(data["ask"]), "is_stale": data["is_stale"], "computed_at": data["computed_at"], } if __name__ == "__main__": r = spot("XAU-USD-SPOT") print(f"{r['symbol']}-{r['quote_currency']}: {r['price']} (computed {r['computed_at']})")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 instantly. Repeated runs from a fresh process miss the cache (in-process only); persist to Redis if you need cross-process caching.BASH · shellpython goldprice.py # XAU-USD: 4726.01 (computed 2026-04-27T04:49:01.706844+00:00)5.Switch currency or metal
Symbols are
METAL-CURRENCY-SPOT. Pass any symbol from the 13 supported currencies tospot(). Each unique symbol caches independently inTTLCache, so a multi-currency dashboard polling 5 symbols at 60s intervals fires 5 API calls/minute uncached and 0 cached.PYTHON · goldprice.py · multi-currency# USD spot — default spot("XAU-USD-SPOT") # INR — Indian gold market (largest retail gold-search globally) spot("XAU-INR-SPOT") # EUR — European pricing surfaces spot("XAU-EUR-SPOT") # Silver in USD 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-04-27T04:49:01.706844+00:00",
"sources": [
{
"source": "wgc.fsapi.usd",
"display_name": "World Gold Council (live spot)",
"price": "4726.01",
"is_stale": false,
"timestamp": "2026-04-27T04: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": 1777248000
}Stdout shows one line: symbol, quote_currency, price as Decimal, and the ISO 8601 computed_at timestamp. The TypedDict response is what your downstream code consumes.
Common errors
| Code | Symptom | Fix |
|---|---|---|
| 401 | requests.exceptions.HTTPError: 401 Client Error | API key missing or invalid. Confirm GOLDPRICE_API_KEY is set: echo $GOLDPRICE_API_KEY. If empty, re-export from your dashboard. The client raises on missing env at import time, not at call time, so the failure surfaces on the first import goldprice. |
| 429 | Three RateLimited retries fail with backoff, then raises out | You exhausted the per-minute cap. The 60-second cache prevents this for steady polling; if a burst of distinct symbols hits at once, raise TTLCache.maxsize or stagger calls. For sustained high-volume use, see /pricing for Basic and Pro tiers. |
| N/A | Float drift accumulates when summing many prices | Always pass Decimal through arithmetic — never float. The client returns prices as Decimal; if you cast to float for JSON serialization, use str(price) instead so precision is preserved across the wire. |
FAQ
How often does the price update?
The spot endpoint refreshes every 60 seconds (Pyth + WGC + CME aggregation). The TTLCache above matches that cadence — repeated spot() calls within 60s return the cached Decimal without an HTTP round-trip.
Will this fit the free tier with auto-refresh?
Depends on your call site. A trading bot polling once per minute = 1,440 calls/day = ~43,000/month — well over the 1,000/month free tier. Use the cache aggressively and call only on demand. Steady polling at 1 call/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 instead of float?
Float arithmetic drifts on summation: 0.1 + 0.2 == 0.30000000000000004. Gold prices are reported with 6+ decimal places (e.g. 4726.0100); summing thousands of trades in float introduces basis-point errors over time. Decimal preserves exact arithmetic. The API returns prices as strings precisely so clients can choose Decimal.
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 Python client for the goldprice.dev /v1/spot endpoint. Use requests, Decimal, a 60-second cachetools TTLCache, and tenacity 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
TTLCachewith a Redis-backed cache so multiple workers share one in-memory price - Add a
history()wrapper for/v1/history/XAU-USD-SPOT?days=30with pandas DataFrame conversion for backtesting - Switch
requestsforhttpx.AsyncClientif your service is async — the response shape is identical - Wire the client into a FastAPI endpoint and serve internally for non-Python services
- Expose Prometheus metrics for cache hit rate, p50/p95 latency, and 429-retry counts
Next steps
Try the same setup in a different platform: