Build a Gold Jewelry Price Calculator with JavaScript
Build a gold jewelry price calculator from live per-gram karat prices, with exact decimal math, caching, margins, and clear valuation limits.
Read →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.
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.
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.
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.
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.
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.
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
Build a gold jewelry price calculator from live per-gram karat prices, with exact decimal math, caching, margins, and clear valuation limits.
Read →Build a reproducible local-currency gold backtest from settled XAU/USD bars and dated FX observations, without inserting future rates into historical rows.
Read →The real-time gold streaming questions that come up on Reddit, answered: whether a gold WebSocket exists, how the goldprice.dev XAU and XAG tick stream works end to end, and which other APIs actually offer one (most metals APIs just poll).
Read →goldprice.dev
Live gold prices, historical OHLC, and multi-source aggregation — available via REST and SSE.