Silver price in Python
Fetch XAG/USD spot and daily bars in Python with Decimal, auth, and freshness checks.
Updated
Use goldprice.dev to fetch silver with GET https://api.goldprice.dev/v1/spot/XAG-USD-SPOT and a Basic, Plus, or Pro API key in the Bearer header. Parse price strings as Decimal, reject stale quotes, and request daily history from /v1/bars. The example below keeps authentication in an environment variable, converts troy ounces to grams, and handles 401, 403, and 429 responses.
1.Create an API key
Silver spot is on every plan; 90-day daily history needs Basic or higher. Store the key outside your code:
export GOLDPRICE_API_KEY=ga_live_replace_with_your_keyKeep keys out of committed notebooks and client-side apps. Free includes XAG spot and 30-day daily history.
Get a silver API keyFree silver, 15 minutes late.Sign up free →2.Install the HTTP client
Install
requests. The standard librarydecimalmodule avoids binary floating-point representation errors; division still uses the configured Decimal precision.BASH · shellpython -m pip install requests3.Fetch spot and daily history
Save this as
silver_price.py. It uses the canonical Bearer header, checks the tier response, parses decimal strings, rejects stale data, and reverses newest-first bars into chronological order. The canonical response usesbars,bar_start, andis_closed; a forming daily bar is provisional. This example limits the range to 90 days with a 100-row page, so it does not silently truncate a longer series.PYTHON · silver_price.pyimport os from datetime import datetime, timedelta, timezone from decimal import Decimal import requests BASE = "https://api.goldprice.dev" KEY = os.environ["GOLDPRICE_API_KEY"] HEADERS = {"Authorization": f"Bearer {KEY}"} TROY_OUNCE_GRAMS = Decimal("31.1034768") def get_json(path, params=None): response = requests.get(BASE + path, headers=HEADERS, params=params, timeout=10) if response.status_code == 401: raise RuntimeError("401: check GOLDPRICE_API_KEY") if response.status_code == 403: raise RuntimeError("403: 90-day history needs Basic or higher") if response.status_code == 429: raise RuntimeError("429: wait for the Retry-After window before trying again") response.raise_for_status() return response.json() def silver_spot(): data = get_json("/v1/spot/XAG-USD-SPOT") price = Decimal(data["price"]) if data.get("is_stale"): raise RuntimeError("the API marked this quote stale") return { "price_per_oz": price, "price_per_gram": price / TROY_OUNCE_GRAMS, "computed_at": data["computed_at"], } def silver_history(days=30): if not 1 <= days <= 90: raise ValueError("this example supports 1 to 90 daily bars per request") end = datetime.now(timezone.utc).date() start = end - timedelta(days=days - 1) data = get_json("/v1/bars", { "symbol": "XAG-USD-SPOT", "interval": "1d", "from": start.isoformat(), "to": end.isoformat(), "limit": 100, }) return [ {"date": bar["bar_start"][:10], "close": Decimal(bar["close"]), "is_closed": bar["is_closed"]} for bar in reversed(data["bars"]) if bar.get("close") is not None ] if __name__ == "__main__": quote = silver_spot() print(f"XAG/USD per oz: {quote['price_per_oz']}") print(f"XAG/USD per gram: {quote['price_per_gram']}") print(f"Computed: {quote['computed_at']}") print(f"Daily bars returned: {len(silver_history())}")4.Run the client
Run
python silver_price.py. Keep thecomputed_attimestamp with every stored value. A quote can be current at request time yet still be marked stale by the service, so treat that flag as a data-quality check.BASH · shellpython silver_price.py # XAG/USD per gram: 1.04... # Daily bars returned: depends on available observations5.Control request volume
One spot call plus one history call per run is two calls. A script run every 15 minutes uses about 5,760 calls/month, before retries. Cache spot for at least 60 seconds, batch work where possible, and choose a tier whose monthly quota covers your schedule.
PYTHON · quota.pycalls_per_month = 2 * 4 * 24 * 30 # 5,760 calls for a 15-minute schedule print(calls_per_month)
Expected output
The API returns this shape:
{
"symbol": "XAG",
"quote_currency": "USD",
"price": "32.50",
"is_stale": false,
"computed_at": "2026-09-29T10:00:00+00:00"
}The script prints the Decimal price per troy ounce, the Decimal price per gram, the source timestamp, and the number of daily bars returned (which can be less than the requested calendar-day range on weekends or when data is unavailable).
Common errors
| Code | Symptom | Fix |
|---|---|---|
| 401 | The request returns 401. | Check that GOLDPRICE_API_KEY is set and send it as Authorization: Bearer .... |
| 403 | The request returns 403 plan_gated. | Free includes XAG spot; 90-day daily history needs Basic or higher. See pricing. |
| 429 | The request returns 429. | Honor Retry-After, cache repeated symbols, and reduce the schedule before increasing tier. |
FAQ
Is silver available on the Free tier?
Yes. Free has XAG spot and 30-day daily history. The 90-day example needs Basic or higher.
How many calls does the example make?
One spot request and one daily-history request per run. A 15-minute schedule is about 5,760 calls per 30-day month before retries.
Why use Decimal?
The API sends numeric prices as strings. Decimal preserves decimal inputs and makes the rounding context explicit instead of introducing binary float rounding; choose a quantization policy before displaying or storing derived values.
How do I detect an old quote?
Check is_stale and retain computed_at. Apply an application-specific age policy to the timestamp before displaying or storing a quote.
Can an AI agent use the same silver data?
Yes. The goldprice.dev MCP server exposes the same product surface for supported plans. Use the API docs and the MCP setup guide to keep tool and tier assumptions current.
Can I use this in a commercial application?
All plans allow commercial use. Free needs a visible credit link under the terms. Keep the API key on your server.
Going further
- Store the raw response beside each normalized bar for reproducibility.
- Add a circuit breaker that stops retries when the service reports a stale quote.
- Use the same Decimal unit boundary when valuing silver holdings by gram.
Next steps
Try the same setup in a different platform: