Troy Ounces, Grams and Kilograms: Converting Precious Metal Prices Correctly
Troy vs avoirdupois ounces, exact gram and kilogram factors, the API's unit parameter, rounding rules and the bugs that put metal prices off by almost 10%.
Why the unit matters
Precious metals are quoted per troy ounce almost everywhere: in wholesale markets, in bullion dealer price lists and in price APIs. The people who buy and sell metal, on the other hand, think in grams, kilograms, tolas or taels, depending on where they live and what they trade. Any product that shows a metal price to people therefore contains at least one unit conversion, and that conversion is a common source of quiet, plausible-looking errors.
The classic mistake is treating a troy ounce as an ordinary ounce. The two differ by almost 10%, which is large enough to matter on every invoice but small enough that nobody notices it in a demo. This article covers the exact definitions, the conversion factors, how to let the API convert for you, how to round and display the results, and a list of bugs worth writing tests for.
Troy ounce vs avoirdupois ounce
Both ounces are defined in grains, and the grain has an exact metric definition: 1 grain = 64.79891 milligrams. Everything else follows from that:
- A troy ounce is 480 grains, exactly 31.1034768 grams. It is the unit gold, silver, platinum and palladium are priced in.
- An avoirdupois ounce, the everyday ounce for food and parcels in the US and UK, is 437.5 grains, exactly 28.349523125 grams.
- A troy pound has 12 troy ounces (373.2417216 g), while the avoirdupois pound has 16 avoirdupois ounces (453.59237 g). So the troy ounce is the heavier ounce, but the troy pound is the lighter pound.
| Unit | Grains | Grams (exact) | Troy ounces |
|---|---|---|---|
| Grain | 1 | 0.06479891 | 0.0020833… |
| Pennyweight (dwt) | 24 | 1.55517384 | 0.05 |
| Avoirdupois ounce | 437.5 | 28.349523125 | 0.9114583… |
| Troy ounce | 480 | 31.1034768 | 1 |
| Troy pound | 5,760 | 373.2417216 | 12 |
| Avoirdupois pound | 7,000 | 453.59237 | 14.5833… |
A troy ounce is about 9.7% heavier than an avoirdupois ounce (480 ÷ 437.5 ≈ 1.0971). That ratio is exactly the size of the error you get when the two are mixed up.
Unless a source says otherwise, the ounce in a precious metal price is a troy ounce, often written oz t or ozt. In MetalAPI the default unit is troy_oz, and every metal price is per troy ounce unless you ask for something else.
Exact conversion factors
Because all of these units are defined exactly, conversions need no approximations. To turn a price per troy ounce into a price per another unit, multiply by the number of troy ounces in that unit:
price per unit = price per troy ounce × grams per unit ÷ 31.1034768
| Price per | Grams in one unit | Multiply the troy ounce price by |
|---|---|---|
| Gram | 1 | 0.0321507466 |
| Kilogram | 1,000 | 32.1507466 |
| Avoirdupois ounce | 28.349523125 | 0.9114583333 |
| Avoirdupois pound | 453.59237 | 14.5833333 |
| Metric ton | 1,000,000 | 32,150.7466 |
| Short ton (2,000 lb) | 907,184.74 | 29,166.6667 |
| Tola | 11.6638038 | 0.375 |
| Pennyweight | 1.55517384 | 0.05 |
The tola, still widely used for gold in South Asia and the Gulf, is defined as 180 grains: exactly 11.6638038 g, or three eighths of a troy ounce. The tael is less tidy, because its weight depends on the market: about 37.4 g in the Hong Kong gold trade, 37.5 g in Taiwan and 50 g in mainland China. If you support taels, make the gram weight a configuration value and label which tael you mean.
Kilogram bars make a handy sanity check. A kilogram is about 32.15 troy ounces, so a kilo price should always be roughly 32 times the ounce price. If it is closer to 35 times, someone used the avoirdupois factor.
Converting by hand
If you already have a price per troy ounce, converting it yourself is one multiplication. Keep the exact constants in one module and convert through grams, so every pair of units works the same way:
// Grams in one of each unit. All exact: 1 grain = 64.79891 mg.
const GRAMS_PER_UNIT = {
troy_oz: 31.1034768, // 480 grains
gram: 1,
kilogram: 1000,
ounce: 28.349523125, // avoirdupois, 437.5 grains
pound: 453.59237, // avoirdupois, 7,000 grains
metric_ton: 1_000_000,
short_ton: 907_184.74, // 2,000 lb
tola: 11.6638038, // 180 grains
pennyweight: 1.55517384, // 24 grains
};
/** Convert a price per `from` unit into a price per `to` unit. */
export function convertPrice(price, from, to) {
const a = GRAMS_PER_UNIT[from];
const b = GRAMS_PER_UNIT[to];
if (a === undefined || b === undefined) throw new Error(`Unknown unit: ${a === undefined ? from : to}`);
return (price / a) * b;
}
console.log(convertPrice(3997.89, "troy_oz", "gram")); // 128.535148…
console.log(convertPrice(3997.89, "troy_oz", "kilogram")); // 128535.148…
console.log(convertPrice(3997.89, "troy_oz", "tola")); // 1499.20875In Python, use Decimal when the result ends up on an invoice or in a ledger. requests passes parse_float through to the JSON parser, so prices never go through a binary float:
import os
from decimal import Decimal, ROUND_HALF_UP
import requests
GRAMS_PER_TROY_OZ = Decimal("31.1034768")
CENT = Decimal("0.01")
r = requests.get(
"https://metalapi.com/api/v1/latest",
params={"currencies": "XAU"},
headers={"X-API-Key": os.environ["METALAPI_KEY"]},
timeout=10,
)
data = r.json(parse_float=Decimal)
if not data["success"]:
raise RuntimeError(data["error"])
per_oz = data["rates"]["USDXAU"] # e.g. Decimal("3997.89")
per_gram = per_oz / GRAMS_PER_TROY_OZ
per_kg = per_gram * 1000
print(per_gram.quantize(CENT, rounding=ROUND_HALF_UP)) # 128.54 at the example price
print(per_kg.quantize(CENT, rounding=ROUND_HALF_UP)) # 128535.15Inverse rates need the opposite operation. rates.XAU is troy ounces per one unit of the base currency, so grams per unit of currency is rates.XAU × 31.1034768, not ÷. That trap is one more reason to work with the prefixed price keys such as USDXAU and leave the inverse keys alone.
Letting the API convert: the unit parameter
You can also ask MetalAPI for the unit you need. /latest, the historical endpoints, /timeframe, /ohlc, /convert and /carat accept a unit parameter: troy_oz (default), gram, kilogram, ounce, pound, metric_ton or short_ton. This request returns gold and silver in euros per gram:
curl "https://metalapi.com/api/v1/latest?base=EUR¤cies=XAU,XAG&unit=gram" \
-H "X-API-Key: $METALAPI_KEY"When the unit is not the default, the response repeats it in a top-level unit field. The values below are an example from the docs, not live prices:
{
"success": true,
"timestamp": 1792765800,
"date": "2026-10-23",
"base": "EUR",
"unit": "gram",
"rates": {
"XAU": 0.009057444725,
"EURXAU": 110.4064149,
"XAG": 0.6713138244,
"EURXAG": 1.489616277
}
}The API uses the same exact factors as the tables above, and the inverse key follows the unit too: with unit=gram, rates.XAU is grams of gold per euro. A few rules worth knowing:
- Short forms are accepted, such as
g,kg,lbandtonne.ozmeans the avoirdupois ounce, so never use it as shorthand for troy ounces. - An unknown unit returns error
209(invalid_unit). unitapplies to precious and industrial metals only. Energy stays per barrel, MMBtu or gallon, and currencies and crypto are unaffected, so mixing symbols in one request is safe.- Industrial metals are also quoted per troy ounce by default, so all metals compare directly. Pass
unit=poundfor copper,unit=metric_tonfor aluminium and zinc, andunit=short_tonfor steel to get the units those markets trade in.
/convert accepts unit as well, which turns a weight into money in one step. This values 250 grams of gold in US dollars:
curl "https://metalapi.com/api/v1/convert?from=XAU&to=USD&amount=250&unit=gram" \
-H "X-API-Key: $METALAPI_KEY"info.rate is the price of one gram in US dollars and result is the value of the whole amount. With the example gold price, the response looks like this:
{
"success": true,
"query": { "from": "XAU", "to": "USD", "amount": 250, "unit": "gram" },
"info": { "timestamp": 1792834892, "rate": 128.5351482, "quote": 128.5351482 },
"date": "2026-10-24",
"result": 32133.78705
}Rounding and display
Rates arrive as JSON numbers with up to 10 significant digits. That precision is there so derived values stay accurate, so keep it until the last possible moment:
- Convert first, round last. Rounding the gram price to cents and then multiplying by 1,000 for a kilo bar turns a half-cent rounding error into an error of up to five units of currency.
- Round per currency. Most currencies use two decimals, the Japanese yen uses none and the Kuwaiti dinar three. Let
Intl.NumberFormator your locale library choose the defaults instead of hard-coding two. - Allow extra digits for small prices. At the example price of 53.94 USD per troy ounce, silver costs about 1.73 USD per gram, and two decimals hide most of its day-to-day movement.
- Use decimal types for money.
Decimalin Python,BigDecimalin Java, or integer minor units in your database for anything that is invoiced or booked. Binary floats are fine for charts. - Label the unit. Put per troy oz, per gram or per kg next to every price. A bare “per ounce” is ambiguous to many readers.
A small helper that takes the currency's standard decimals from Intl and optionally adds a few:
function formatPrice(value, currency, locale = "en-US", extraDigits = 0) {
const { maximumFractionDigits } = new Intl.NumberFormat(locale, { style: "currency", currency }).resolvedOptions();
const digits = maximumFractionDigits + extraDigits;
return new Intl.NumberFormat(locale, {
style: "currency",
currency,
minimumFractionDigits: digits,
maximumFractionDigits: digits,
}).format(value);
}
formatPrice(128.53514819925212, "USD"); // "$128.54" gold per gram
formatPrice(1.7342112699117933, "USD", "en-US", 2); // "$1.7342" silver per gram
formatPrice(110.4064149, "EUR", "de-DE"); // "110,41 €"A conversion table at a glance
The table converts the example prices used in the MetalAPI docs, 3,997.89 USD per troy ounce for gold and 53.94 USD per troy ounce for silver. They illustrate the arithmetic and are not current market prices:
| Price per | Gold (USD) | Silver (USD) |
|---|---|---|
| Troy ounce | 3,997.89 | 53.94 |
| Gram | 128.54 | 1.7342 |
| Kilogram | 128,535.15 | 1,734.21 |
| Tola (11.6638 g) | 1,499.21 | 20.23 |
| Pennyweight (1.5552 g) | 199.89 | 2.697 |
| Troy pound (12 oz t) | 47,974.68 | 647.28 |
| Avoirdupois ounce | 3,643.91 | 49.16 |
Look at the last row. If a page labels the avoirdupois figure as the ounce price, it understates the bullion price by almost 9%. The reverse mistake, dividing a troy ounce price by 28.35 to get a gram price, overstates it by about 9.7%: 141.02 instead of 128.54 per gram of gold.
Common conversion bugs and how to test for them
Most unit bugs fall into a handful of patterns:
- Dividing by 28.35. Using the avoirdupois factor for a troy ounce price. Use 31.1034768.
- Treating `oz` as troy. In MetalAPI, as in most unit libraries,
ozis the avoirdupois ounce. Bullion prices needtroy_oz, or nounitat all. - Converting the inverse key.
rates.XAUis ounces per currency unit, not a price. Converting it to grams means multiplying by 31.1034768, which looks wrong to anyone used to dividing. - Converting twice. Requesting
unit=gramand then dividing by 31.1034768 again gives a price about 31 times too small. Decide whether conversion happens in the API or in your code, never both. - Rounding too early. Round once, at display time.
- Caching across units. If your cache key ignores the query string, a gram response can be served where an ounce price was expected. Include
unitandbasein the key. - Applying weight units to energy.
unithas no effect on oil or gas, and dividing a barrel price by 31.1 makes no sense. Guard conversions by category, which/symbols?details=1returns for every symbol.
A few property tests catch most of these before they ship. They use Node's built-in test runner and the convertPrice function from above; run them with node --test:
import { test } from "node:test";
import assert from "node:assert/strict";
import { convertPrice } from "./units.mjs";
const PER_OZ = 3997.89; // example price, USD per troy ounce
test("a kilogram price is 1,000 gram prices", () => {
const g = convertPrice(PER_OZ, "troy_oz", "gram");
const kg = convertPrice(PER_OZ, "troy_oz", "kilogram");
assert.ok(Math.abs(kg - g * 1000) < 1e-6);
});
test("converting there and back returns the original price", () => {
const g = convertPrice(PER_OZ, "troy_oz", "gram");
assert.ok(Math.abs(convertPrice(g, "gram", "troy_oz") - PER_OZ) < 1e-9);
});
test("an avoirdupois ounce costs less than a troy ounce", () => {
assert.ok(convertPrice(PER_OZ, "troy_oz", "ounce") < PER_OZ);
});
test("a kilogram holds about 32.15 troy ounces", () => {
const kg = convertPrice(PER_OZ, "troy_oz", "kilogram");
assert.ok(Math.abs(kg / PER_OZ - 32.1507466) < 1e-6);
});If you price jewelry, the next step is purity: Gold karat prices explained builds 22K and 18K prices on top of these conversions. The data formats guide and the units section of the API docs document every unit value, and the symbols page shows the unit each market trades in.
Try it with your own data
Create a free account and make your first request in under two minutes. 100 requests a month, no credit card.
Keep reading
Gold Price API: The Complete Developer Guide
How to use a gold price API: get a key, call it from curl, JavaScript and Python, read the rates correctly, and handle history, errors, quotas and caching.
Gold Karat Prices Explained: 24K, 22K, 18K and How to Calculate Them
What 24K, 22K and 18K mean, how fineness marks like 916 and 750 relate, how to calculate karat gold prices per gram and how to get them from a gold price API.
Historical Gold and Silver Prices via API: 25 Years of Daily Data
Get daily historical gold and silver prices back to 2000 with /historical, /timeframe, /change and /ohlc, page long ranges by year, store the data and chart it.