Blog/Developer

Build an India Gold Price App with JavaScript

Build a live India gold-rate app from INR per-gram 24K and 22K prices, with server-side caching, Indian number formatting, GST estimates, and clear limits.

Developer.md

An India gold app needs a different default from a generic XAU/USD ticker. Users expect rupees per gram, 24K and 22K side by side, Indian number formatting, an observation time, and a clear line between spot-derived metal value and the amount a jeweler will charge.

This guide builds that data path with one free endpoint and a small Node server. The result is suitable for a rate card, savings tracker, or jewelry estimator. It is not a dealer quote or appraisal.

Use the INR carat endpoint

The free /v1/carat endpoint returns per-gram prices for eight purities. No API key is required:

GET https://api.goldprice.dev/v1/carat?currency=INR

The response has this shape:

{
  "currency": "INR",
  "timestamp": "2026-07-19T08:30:00Z",
  "price_gram_24k": "13250.00",
  "price_gram_22k": "12145.83",
  "price_gram_21k": "11593.75",
  "price_gram_20k": "11041.67",
  "price_gram_18k": "9937.50",
  "price_gram_16k": "8833.33",
  "price_gram_14k": "7729.17",
  "price_gram_10k": "5520.83"
}

The numbers above illustrate the response format; fetch the endpoint for the current values. Prices are decimal strings so the transmitted value is not changed by JSON floating-point conversion.

These are spot-derived reference prices in INR. For actual domestic dealer quotes and premium over spot, use the India physical-price endpoint.

Proxy and cache it on your server

The API intentionally is not a browser CORS surface. Call it from your server, cache the shared response for 60 seconds, and let browsers call your own route.

import { createServer } from "node:http";

const SOURCE = "https://api.goldprice.dev/v1/carat?currency=INR";
const TTL_MS = 60_000;
let cache = null;

async function getIndiaRates() {
  const now = Date.now();
  if (cache && now - cache.fetchedAt < TTL_MS) return cache.data;

  try {
    const response = await fetch(SOURCE, {
      signal: AbortSignal.timeout(5_000),
    });
    if (!response.ok) throw new Error(`upstream ${response.status}`);

    const data = await response.json();
    if (
      data.currency !== "INR" ||
      typeof data.price_gram_24k !== "string" ||
      typeof data.price_gram_22k !== "string" ||
      typeof data.timestamp !== "string"
    ) {
      throw new Error("invalid rate response");
    }

    cache = { data, fetchedAt: now };
    return data;
  } catch (error) {
    if (cache) return cache.data; // stale-if-error
    throw error;
  }
}

createServer(async (request, response) => {
  const url = new URL(request.url, "http://localhost");
  if (url.pathname !== "/api/india-gold") {
    response.writeHead(404).end("not found");
    return;
  }

  try {
    const data = await getIndiaRates();
    response.writeHead(200, {
      "content-type": "application/json",
      "cache-control": "public, max-age=30",
    });
    response.end(JSON.stringify(data));
  } catch {
    response.writeHead(503, { "content-type": "application/json" });
    response.end(JSON.stringify({ error: "rate_unavailable" }));
  }
}).listen(3001);

The stale fallback preserves the source timestamp. Do not replace it with the time your server sent the response; users need to know when the market value was observed.

Format rupees and calculate common weights

Convert the decimal strings only at the calculation boundary and format with the Indian grouping system:

const formatINR = new Intl.NumberFormat("en-IN", {
  style: "currency",
  currency: "INR",
  maximumFractionDigits: 0,
});

function rateCard(data, grams = 10) {
  const price24k = Number(data.price_gram_24k);
  const price22k = Number(data.price_gram_22k);

  if (!Number.isFinite(price24k) || !Number.isFinite(price22k)) {
    throw new Error("invalid numeric rate");
  }

  return {
    perGram24k: formatINR.format(price24k),
    perGram22k: formatINR.format(price22k),
    tenGram24k: formatINR.format(price24k * grams),
    tenGram22k: formatINR.format(price22k * grams),
    observedAt: new Date(data.timestamp).toLocaleString("en-IN", {
      timeZone: "Asia/Kolkata",
      dateStyle: "medium",
      timeStyle: "short",
    }),
  };
}

Call your route from the browser:

const response = await fetch("/api/india-gold");
if (!response.ok) throw new Error("rate unavailable");

const card = rateCard(await response.json());
document.querySelector("#rate-24k").textContent = card.perGram24k;
document.querySelector("#rate-22k").textContent = card.perGram22k;
document.querySelector("#observed-at").textContent = card.observedAt;

For money collected from users, keep decimal arithmetic in integer paise or use a decimal library. Number is acceptable for display-only estimates but not for invoices or accounting.

Add a GST estimate without calling it a quote

For a finished jewelry sale, the official CBIC jewelry FAQ generally applies 3% GST to the total transaction value, including making charges. Keep each part visible:

function estimateJewelryInvoice({ grams, perGram, makingCharge }) {
  const metalValue = grams * perGram;
  const subtotal = metalValue + makingCharge;
  const gst = subtotal * 0.03;

  return {
    metalValue,
    makingCharge,
    gst,
    estimatedTotal: subtotal + gst,
  };
}

Label the result “indicative estimate.” The spot-derived rate does not include a local dealer premium, stones, wastage, hallmarking fees, discounts, or buyback terms. Tax treatment also depends on the transaction. The India gold-tax guide explains the distinction between retail GST, import duty, and capital gains.

Production checks

Before shipping, handle non-200 responses, set a request timeout, validate the response fields, cache by currency, and display the source timestamp. Log failures without substituting zero. Keep the purity label next to every figure: a 22K rate without “22K” is easy to mistake for 24K.

If the product promises a price a customer can transact at, replace the spot-derived estimate with a dealer quote from GET /v1/physical/IN (Physical tier or higher) and disclose the dealer, product, sell or buyback side, and update time. Start with the live India gold-rate page to compare how the two views differ.

related guides

goldprice.dev

Live gold prices, historical OHLC, and multi-source aggregation — available via REST and SSE.