# Fetch live gold prices in JavaScript

The goldprice.dev API returns the current spot price in a single authenticated GET request. This post walks through a minimal working implementation in both Node.js and the browser, then covers the error cases worth handling.

Get a free API key via the [quickstart guide](/docs/quickstart). The free tier includes 1,000 calls per month and access to gold spot (`XAU-USD-SPOT`).

## The endpoint

```
GET https://api.goldprice.dev/v1/spot/XAU-USD-SPOT
Authorization: Bearer ga_live_YOUR_KEY
```

The response is a flat JSON object:

```json
{
  "symbol": "XAU-USD-SPOT",
  "price": 2387.45,
  "currency": "USD",
  "unit": "troy_oz",
  "timestamp": "2026-06-19T08:32:11Z"
}
```

## Node.js (18+)

Node 18 ships with the `fetch` API, so no extra dependencies are needed.

```js
const API_KEY = process.env.GOLDPRICE_API_KEY;
const BASE_URL = "https://api.goldprice.dev";

async function getGoldSpot() {
  const res = await fetch(`${BASE_URL}/v1/spot/XAU-USD-SPOT`, {
    headers: {
      Authorization: `Bearer ${API_KEY}`,
    },
  });

  if (!res.ok) {
    const body = await res.text();
    throw new Error(`API error ${res.status}: ${body}`);
  }

  return res.json();
}

getGoldSpot()
  .then((data) => {
    console.log(`Gold spot: $${data.price} USD/troy oz`);
    console.log(`As of: ${data.timestamp}`);
  })
  .catch(console.error);
```

Run it:

```bash
GOLDPRICE_API_KEY=ga_live_your_key node gold.js
```

Never hardcode the key string in source files. Reading it from `process.env` is the minimum safe approach; for production, use a secrets manager or environment injection at deploy time.

## Browser

The same `fetch` API works in the browser with one caveat: your API key must not be exposed to the client. If you are building a public-facing app, proxy the request through your own backend. The browser code calls your server, and your server calls the goldprice.dev API with the key.

For internal tools, dashboards, or demos where key exposure is acceptable:

```js
async function getGoldSpot(apiKey) {
  const res = await fetch("https://api.goldprice.dev/v1/spot/XAU-USD-SPOT", {
    headers: {
      Authorization: `Bearer ${apiKey}`,
    },
  });

  if (!res.ok) {
    throw new Error(`Request failed: ${res.status} ${res.statusText}`);
  }

  return res.json();
}

// Usage
getGoldSpot("ga_live_your_key").then((data) => {
  document.getElementById("gold-price").textContent =
    `$${data.price.toLocaleString()} USD/oz`;
});
```

## Error handling

Three HTTP status codes cover almost every failure case:

- `401 Unauthorized`: missing or invalid `Authorization` header. Check that the key is present and starts with `ga_live_`.
- `429 Too Many Requests`: rate limit exceeded. The response body includes a `retry_after` field in seconds. Back off and retry after that interval.
- `5xx`: server-side error. These are transient; retry with exponential backoff.

A more complete handler:

```js
async function getGoldSpotSafe() {
  let attempt = 0;
  const maxAttempts = 3;

  while (attempt < maxAttempts) {
    const res = await fetch(
      "https://api.goldprice.dev/v1/spot/XAU-USD-SPOT",
      {
        headers: { Authorization: `Bearer ${process.env.GOLDPRICE_API_KEY}` },
      }
    );

    if (res.status === 401) {
      throw new Error("Invalid API key. Check GOLDPRICE_API_KEY.");
    }

    if (res.status === 429) {
      const body = await res.json();
      const wait = (body.retry_after ?? 60) * 1000;
      await new Promise((r) => setTimeout(r, wait));
      attempt++;
      continue;
    }

    if (!res.ok) {
      const delay = Math.min(1000 * 2 ** attempt, 10000);
      await new Promise((r) => setTimeout(r, delay));
      attempt++;
      continue;
    }

    return res.json();
  }

  throw new Error("Max retry attempts reached");
}
```

## Caching on the free tier

With 1,000 calls per month, you have roughly 33 calls per day, enough for a polling interval of around 45 minutes. If you need to display a frequently refreshing price, cache the response on your server and serve it to your front end from there. A simple in-memory cache with a 60-second TTL is enough for most use cases:

```js
let cache = null;
let cacheTime = 0;
const TTL_MS = 60_000;

async function getCachedGoldSpot() {
  if (cache && Date.now() - cacheTime < TTL_MS) {
    return cache;
  }
  cache = await getGoldSpot();
  cacheTime = Date.now();
  return cache;
}
```

Upgrading to a paid tier removes the call limit. At that point, you can poll more aggressively or drop the cache for applications that need genuinely live prices.

## What's in the response

The `price` field is in USD per troy ounce. The `timestamp` is ISO 8601 in UTC. If you need the price in grams or other currencies, see [Convert gold prices across currencies in code](/blog/convert-gold-prices-currencies) — that post covers the conversion arithmetic and a complete working example. The API documentation covers additional symbols available on paid tiers, including silver (`XAG-USD-SPOT`) and copper.
