How to get live gold prices in .NET
Call the goldprice.dev API from C#/.NET with HttpClient, System.Text.Json, retry-on-429, and a 60-second IMemoryCache.
Updated
To get live gold prices in C#/.NET, send a GET to api.goldprice.dev/v1/spot/XAU-USD-SPOT with your API key in the X-API-Key header using HttpClient. Deserialize the JSON with System.Text.Json into a record with decimal fields, and cache the result for 60 seconds. The free tier covers 5,000 calls/month, no credit card. Tested July 2026 against .NET 8.
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 5,000 calls/month and 31 supported currencies. No credit card required.
Get your free API key5,000 calls/mo, no credit cardSign up free →2.Create the project
The .NET base class library covers everything needed:
HttpClientfor the request andSystem.Text.Jsonfor deserialization. No third-party package is required, so scaffold a plain console app:BASH · shelldotnet new console -o GoldpriceDemo cd GoldpriceDemo3.Write the goldprice client
Save this as
GoldpriceClient.cs.SpotAsync()caches per symbol for 60 seconds in aConcurrentDictionary, retries up to 3 times on 429 with exponential backoff, and deserializes prices intodecimalfields via a customJsonConverter— the API returns prices as JSON strings so precision survives the wire.CSHARP · GoldpriceClient.cs// GoldpriceClient.cs // curl-equivalent: https://api.goldprice.dev/v1/spot/XAU-USD-SPOT using System.Collections.Concurrent; using System.Net.Http.Json; using System.Text.Json; using System.Text.Json.Serialization; namespace GoldpriceDemo; /// Converts the API's string-encoded price fields to decimal without /// the precision loss a double round-trip would introduce. public sealed class DecimalStringConverter : JsonConverter<decimal> { public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => decimal.Parse(reader.GetString()!); public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options) => writer.WriteStringValue(value.ToString()); } public sealed record SpotResponse( [property: JsonPropertyName("symbol")] string Symbol, [property: JsonPropertyName("quote_currency")] string QuoteCurrency, [property: JsonPropertyName("price"), JsonConverter(typeof(DecimalStringConverter))] decimal Price, [property: JsonPropertyName("bid"), JsonConverter(typeof(DecimalStringConverter))] decimal? Bid, [property: JsonPropertyName("ask"), JsonConverter(typeof(DecimalStringConverter))] decimal? Ask, [property: JsonPropertyName("is_stale")] bool IsStale, [property: JsonPropertyName("computed_at")] string ComputedAt ); public sealed class GoldpriceRateLimitedException : Exception { public GoldpriceRateLimitedException() : base("goldprice: 429 rate limited") { } } public sealed class GoldpriceClient { private const string ApiBase = "https://api.goldprice.dev"; private static readonly TimeSpan Ttl = TimeSpan.FromSeconds(60); private readonly HttpClient _http; private readonly ConcurrentDictionary<string, (SpotResponse Data, DateTimeOffset Expires)> _cache = new(); public GoldpriceClient(HttpClient http) { var apiKey = Environment.GetEnvironmentVariable("GOLDPRICE_API_KEY") ?? throw new InvalidOperationException("GOLDPRICE_API_KEY not set"); _http = http; _http.BaseAddress = new Uri(ApiBase); _http.Timeout = TimeSpan.FromSeconds(5); _http.DefaultRequestHeaders.Add("X-API-Key", apiKey); } /// Fetches the live spot price. Cached 60s per symbol, retries 3x on 429. public async Task<SpotResponse> SpotAsync(string symbol = "XAU-USD-SPOT", CancellationToken ct = default) { if (_cache.TryGetValue(symbol, out var cached) && cached.Expires > DateTimeOffset.UtcNow) { return cached.Data; } for (var attempt = 0; attempt < 3; attempt++) { using var response = await _http.GetAsync($"/v1/spot/{symbol}", ct); if (response.StatusCode == System.Net.HttpStatusCode.TooManyRequests) { await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)), ct); // 1s, 2s, 4s continue; } if (!response.IsSuccessStatusCode) { throw new HttpRequestException($"goldprice: unexpected status {(int)response.StatusCode}"); } var data = await response.Content.ReadFromJsonAsync<SpotResponse>(cancellationToken: ct) ?? throw new JsonException("goldprice: empty response body"); _cache[symbol] = (data, DateTimeOffset.UtcNow.Add(Ttl)); return data; } throw new GoldpriceRateLimitedException(); } }4.Run it
From the same shell where you exported
GOLDPRICE_API_KEY, add aProgram.csthat constructs the client and callsSpotAsync(). The first call hits the API; calls within 60 seconds return the cached record without a round-trip. A fresh process starts with an empty cache (in-process only) — back it with a distributed cache such as Redis for cross-process sharing.CSHARP · Program.csusing GoldpriceDemo; using var http = new HttpClient(); var client = new GoldpriceClient(http); var spot = await client.SpotAsync("XAU-USD-SPOT"); Console.WriteLine($"{spot.Symbol}-{spot.QuoteCurrency}: {spot.Price} (computed {spot.ComputedAt})"); // XAU-USD: 4726.01 (computed 2026-07-06T04:49:01.706844+00:00)5.Switch currency or metal
Symbols are
METAL-CURRENCY-SPOT. Pass any of the 31 supported currencies toSpotAsync(). Each unique symbol caches independently in theConcurrentDictionary, so a dashboard polling 5 symbols at 60-second intervals fires 5 uncached calls per minute and 0 cached.CSHARP · Program.cs · multi-currency// USD spot — default await client.SpotAsync("XAU-USD-SPOT"); // INR — Indian gold market (largest retail gold-search globally) await client.SpotAsync("XAU-INR-SPOT"); // EUR — European pricing surfaces await client.SpotAsync("XAU-EUR-SPOT"); // Silver in USD, available on every plan await client.SpotAsync("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,
"computed_at": "2026-07-06T04:49:01.706844+00:00"
}Stdout shows one line: symbol, quote currency, the price as a decimal, and the ISO 8601 ComputedAt timestamp. The SpotResponse record 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 GoldpriceClient constructor throws immediately when the env var is empty, so the failure surfaces at startup rather than mid-request. |
| 400 | goldprice: unexpected status 400, response body has "error": "invalid_symbol" | The symbol does not match BASE-QUOTE-CONTRACT (e.g. XAU-USD-SPOT). A bare metal code like XAU is rejected — always include the quote currency and contract token, and use SPOT or FUTURES, never FUT. |
| 429 | Three retries with backoff all return 429, then SpotAsync throws GoldpriceRateLimitedException | 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 paid plans. |
| N/A | Deserialization throws JsonException or prices come through as 0 | The API sends prices as JSON strings (e.g. "4726.01"), not numbers. A plain decimal property without [JsonConverter(typeof(DecimalStringConverter))] fails to bind from a JSON string. Keep the converter attribute on every price field, or register it globally via JsonSerializerOptions.Converters. |
FAQ
How often does the price update?
The spot endpoint refreshes every 60 seconds (licensed live spot data; Free/keyless delayed 15 minutes, paid live). The 60-second TTL above matches that cadence — repeated SpotAsync() calls within the window return the cached record 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 5,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 paid plans.
Can I use this commercially?
Commercial use is allowed on every plan. Free needs a visible credit link; Basic starts at $19/mo. See pricing.
Why a custom JsonConverter instead of a plain decimal property?
System.Text.Json does not coerce a JSON string into a numeric property by default, and the API returns prices as strings precisely so clients decode them exactly instead of through a lossy double parse. DecimalStringConverter reads the string and hands back a decimal, .NET's base-10 exact type, so gold prices with 6+ decimal places keep their precision through arithmetic.
Is GoldpriceClient safe to use as a singleton with dependency injection?
Yes. Register it with IHttpClientFactory (services.AddHttpClient<GoldpriceClient>() in ASP.NET Core) so the underlying HttpClient is pooled correctly, and register GoldpriceClient itself as a singleton — the ConcurrentDictionary cache is thread-safe, so one instance can serve concurrent requests across a web app.
What metals are supported?
Gold (XAU), silver (XAG), and copper (HG) spot are on every plan, e.g. client.SpotAsync("XAG-USD-SPOT"). Futures and settlements are excluded from new plans. Platinum and palladium aren't offered.
Can I have Claude write this for me?
Yes. Open Claude Desktop with the goldprice.dev MCP server installed and ask: Build a C#/.NET client for the goldprice.dev /v1/spot endpoint. Use HttpClient, System.Text.Json with a decimal converter for string-encoded prices, a 60-second ConcurrentDictionary 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
- Swap the
ConcurrentDictionaryforIMemoryCacheor a Redis-backedIDistributedCacheso an ASP.NET Core app shares one warm price across requests - Add a
BarsAsync()method for/v1/bars?symbol=XAU-USD-SPOT&interval=1d&from=...&to=...and feed the series into a backtest - Register GoldpriceClient with IHttpClientFactory and Polly for a policy-driven retry/circuit-breaker instead of the hand-rolled backoff loop
- Wire the client into a minimal API endpoint and serve internally for other services on the network
- Add OpenTelemetry instrumentation for cache hit rate, request latency, and 429-retry counts
Next steps
Try the same setup in a different platform: