TutorialsCode

How to get live gold prices in Gold price in Rust

Fetch the gold price with reqwest, keep decimal precision, check freshness, and respect Retry-After.

23 Oct 2026 · 4 min read

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:

Shell
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.

shell
rustup update stablecargo +stable new goldprice-rust --bin --edition 2021 --vcs nonecd goldprice-rust

Set 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.

Cargo.toml
[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.

src/main.rs
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.

shell
cargo +stable generate-lockfile --config 'resolver.incompatible-rust-versions="fallback"'cargo run --locked

Expected output

JSON
{  "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

CodeWhat you seeFix
N/AThe program reports a missing or empty environment variableSet GOLDPRICE_API_KEY in the same terminal that runs Cargo. Replace the placeholder with your actual key from /onboarding.
401The request returns an authentication errorConfirm that the environment variable contains your full API key without surrounding whitespace. The client adds the Bearer prefix itself, so store only the key.
429The program reports that the request limit was reachedAll 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/AThe program reports that a stale price was rejectedThe response has is_stale set to true. Inspect the reported computed_at timestamp. Leave the freshness check in place and try again later.
N/ACargo reports that a dependency requires a newer compilerRun 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

Questions

What does the free tier include?

The Free plan includes 5,000 calls a month and 10 requests a minute, with prices fifteen minutes late. No credit card is needed. Keep a visible link to goldprice.dev near any prices you display on Free.

Why parse the price as Decimal?

The API returns the price as a string. Parse it directly with Decimal::from_str so a floating point conversion cannot change the input. Keep the resulting Decimal for calculations and serialize displayed prices as strings.

How do I check whether a price is stale?

The is_stale field marks a stale response, and computed_at records when the price was computed. This example rejects stale responses and prints the timestamp with accepted prices. A false flag does not remove the Free plan delay.

Can Claude help write the Rust client?

Yes. Ask Claude to preserve the response fields, parse the price string with Decimal::from_str, and respect Retry-After. The goldprice.dev MCP server can help Claude search the API documentation. Review generated code before running it.

Can I request gold in another currency?

Yes. Change ENDPOINT to request XAU-EUR-SPOT or XAU-GBP-SPOT. Read quote_currency from the response rather than assuming USD. Gold remains priced per troy ounce, so changing currency does not change the weight unit.

Can I call the endpoint without an API key?

Yes. Gold spot also accepts requests without a key, with prices fifteen minutes late and limits per IP. This tutorial deliberately requires GOLDPRICE_API_KEY. To use keyless access, remove both the environment check and bearer_auth call.

Does this example need an async runtime?

No. The blocking client waits on its calling thread, so this command line program needs no async main function. Inside an async application, use the asynchronous reqwest client or move the blocking work to spawn_blocking.

Keep reading

Start with a free API key

5,000 requests a month on the free plan. No credit card.

Get a free key