To get the gold price in Rust, call https://api.goldprice.dev/v1/spot/XAU-USD-SPOT with reqwest and your goldprice.dev API key. Read the key from GOLDPRICE_API_KEY, decode the response with serde, and parse the price string as Decimal. Check is_stale before using the price. The example respects Retry-After and stops after three total attempts. Free prices are fifteen minutes late; paid plans are live.
Get your goldprice.dev API key
Get a free key at /onboarding. No credit card is needed. Keys start with ga_live_. In a Bash or Zsh terminal, replace the placeholder below with your key:
export GOLDPRICE_API_KEY='ga_live_replace_with_your_key'Keep this terminal open for the remaining steps. Keep the key out of source files and version control.
Create the Cargo project
Start with Rust installed through rustup. These commands update the stable toolchain and create a binary project. Keep using the same terminal after entering its directory.
rustup update stablecargo +stable new goldprice-rust --bin --edition 2021 --vcs nonecd goldprice-rustSet up the dependencies
Replace Cargo.toml with this manifest. Enable blocking and json for reqwest, and derive for serde. Rustls supplies HTTPS support without system OpenSSL.
The price is parsed from a string, so rust_decimal needs no serialization feature. The httpdate crate handles dates sent in Retry-After. The final step resolves dependencies against the minimum Rust version declared here.
[package]name = "goldprice-rust"version = "0.1.0"edition = "2021"rust-version = "1.80"[dependencies]reqwest = { version = "=0.12.28", default-features = false, features = ["blocking", "json", "rustls-tls"] }serde = { version = "1", features = ["derive"] }serde_json = "1"rust_decimal = { version = "=1.43.0", default-features = false, features = ["std"] }httpdate = "1"Write the gold price client
Replace src/main.rs with this code. It returns the response alongside a Decimal for calculations, rejects stale prices, and keeps computed_at in the output.
Rate limits trigger at most three total attempts. The client accepts Retry-After as seconds or an HTTP date. Missing or invalid headers use increasing waits. Other HTTP failures return immediately.
use reqwest::{blocking::Client, header::RETRY_AFTER, StatusCode};use rust_decimal::Decimal;use serde::{Deserialize, Serialize};use std::{ env, error::Error, io, str::FromStr, thread, time::{Duration, SystemTime},};const ENDPOINT: &str = "https://api.goldprice.dev/v1/spot/XAU-USD-SPOT";const MAX_TRIES: u32 = 3;#[derive(Deserialize, Serialize)]struct Spot { symbol: String, quote_currency: String, unit: String, contract_type: String, price: String, is_stale: bool, computed_at: String,}fn retry_delay(value: Option<&str>, attempt: u32) -> Duration { let minimum = Duration::from_secs(1); if let Some(value) = value.map(str::trim) { if let Ok(seconds) = value.parse::<u64>() { return Duration::from_secs(seconds).max(minimum); } if let Ok(date) = httpdate::parse_http_date(value) { return date .duration_since(SystemTime::now()) .unwrap_or_default() .max(minimum); } } Duration::from_secs(1_u64 << attempt)}fn fetch_spot(client: &Client, key: &str) -> Result<(Spot, Decimal), Box<dyn Error>> { for attempt in 0..MAX_TRIES { let response = client.get(ENDPOINT).bearer_auth(key).send()?; if response.status() == StatusCode::TOO_MANY_REQUESTS { if attempt + 1 == MAX_TRIES { return Err(io::Error::other("429: request limit reached").into()); } let delay = retry_delay( response .headers() .get(RETRY_AFTER) .and_then(|v| v.to_str().ok()), attempt, ); drop(response); eprintln!("Rate limited, waiting {delay:?}"); thread::sleep(delay); continue; } let spot: Spot = response.error_for_status()?.json()?; if spot.is_stale { return Err(io::Error::other(format!( "Stale price rejected, computed_at: {}", spot.computed_at )) .into()); } if spot.computed_at.trim().is_empty() { return Err(io::Error::other("Missing computed_at timestamp").into()); } let price = Decimal::from_str(&spot.price)?; return Ok((spot, price)); } Err(io::Error::other("Request attempts exhausted").into())}fn main() -> Result<(), Box<dyn Error>> { let key = env::var("GOLDPRICE_API_KEY")?; let key = key.trim(); if key.is_empty() { return Err(io::Error::other("GOLDPRICE_API_KEY is empty").into()); } let client = Client::builder().timeout(Duration::from_secs(10)).build()?; let (mut spot, price) = fetch_spot(&client, key)?; spot.price = price.to_string(); println!("{}", serde_json::to_string_pretty(&spot)?); Ok(())}Resolve dependencies and run
Use current stable Cargo to generate a lockfile that prefers dependencies compatible with the manifest's minimum Rust version. Then run with your installed Rust toolchain, which must meet that minimum.
Keep Cargo.lock with the project. Standard output contains the accepted response as JSON. Retry messages go to standard error. The example below shows the response shape; your price and timestamp will vary.
cargo +stable generate-lockfile --config 'resolver.incompatible-rust-versions="fallback"'cargo run --lockedExpected output
{ "symbol": "XAU", "quote_currency": "USD", "unit": "troy_ounce", "contract_type": "spot", "price": "4023.54", "is_stale": false, "computed_at": "2026-10-23T10:51:32.965761+00:00"}The terminal prints JSON with the price in US dollars per troy ounce. The client parses the price as Decimal and preserves it as a string in the output.
Common errors
| Code | What you see | Fix |
|---|---|---|
| N/A | The program reports a missing or empty environment variable | Set GOLDPRICE_API_KEY in the same terminal that runs Cargo. Replace the placeholder with your actual key from /onboarding. |
| 401 | The request returns an authentication error | Confirm that the environment variable contains your full API key without surrounding whitespace. The client adds the Bearer prefix itself, so store only the key. |
| 429 | The program reports that the request limit was reached | All three attempts were rate limited. Reduce request frequency and check your quota. Keep the Retry-After waits when adapting this code. See /pricing for plan limits. |
| N/A | The program reports that a stale price was rejected | The response has is_stale set to true. Inspect the reported computed_at timestamp. Leave the freshness check in place and try again later. |
| N/A | Cargo reports that a dependency requires a newer compiler | Run the stable toolchain update and lockfile command shown above before building. Keep the generated Cargo.lock and use --locked for later builds. |
Going further
- Reuse the client and cache accepted responses to reduce API calls
- Calculate a gram price with Decimal using 31.1035 g per troy ounce
- Add daily history requests using the API reference
- Keep the last accepted price and its timestamp when a later request fails
- Compare the setup with the Python tutorial