Metal Price API reference
Live gold, silver, platinum and palladium prices in USD, plus gold value by karat, hallmark fineness prices, melt value and cross-metal ratios. JSON over HTTPS with one API key, and every endpoint on every plan, including Free.
- Base URL
- https://api.karatology.com/v1
- Format
- JSON, UTF-8
- Currency
- USD
- New prices
- Collected hourly
On this page
Getting started
Quickstart
Three steps from nothing to a live gold price.
- Step 1: Create a free accountSign up in a minute. The Free plan includes 10 calls a month and every endpoint. No card required.
- Step 2: Create an API keyOpen API keys in your dashboard and create a key. It is shown once, so store it somewhere safe, such as an environment variable.
- Step 3: Call /v1/latestSend the key in the
X-API-Keyheader. The response has all four metals with today's change.
curl "https://api.karatology.com/v1/latest" \
-H "X-API-Key: kt_live_YOUR_API_KEY"const response = await fetch("https://api.karatology.com/v1/latest", {
headers: { "X-API-Key": "kt_live_YOUR_API_KEY" },
});
const data = await response.json();
if (!data.success) {
throw new Error(data.error.code + ": " + data.error.message);
}
console.log(data.metals.gold.price);import requests
response = requests.get(
"https://api.karatology.com/v1/latest",
headers={"X-API-Key": "kt_live_YOUR_API_KEY"},
timeout=10,
)
data = response.json()
if not data["success"]:
raise RuntimeError(data["error"]["message"])
print(data["metals"]["gold"]["price"])<?php
$url = "https://api.karatology.com/v1/latest";
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["X-API-Key: kt_live_YOUR_API_KEY"],
CURLOPT_TIMEOUT => 10,
]);
$data = json_decode(curl_exec($ch), true);
if (!$data["success"]) {
throw new RuntimeException($data["error"]["message"]);
}
echo $data["metals"]["gold"]["price"];Try it in the Playground
Call any endpoint from your browser with your own key and see the live response, headers and usage.
Getting started
Authentication
Every endpoint except /v1, /v1/metals and /v1/openapi.json needs an API key. Send it in any one of these three ways. If a request carries more than one, the first match in this order wins.
| Method | How | Notes |
|---|---|---|
| 1. Header (recommended) | X-API-Key: kt_live_... | Works everywhere and keeps the key out of URLs and logs. |
| 2. Bearer token | Authorization: Bearer kt_live_... | For HTTP clients and tools built around bearer tokens. |
| 3. Query parameter | ?api_key=kt_live_... | Only for tools that cannot set headers. URLs can end up in server logs and browser history. |
curl "https://api.karatology.com/v1/latest" \
-H "X-API-Key: kt_live_YOUR_API_KEY"curl "https://api.karatology.com/v1/latest" \
-H "Authorization: Bearer kt_live_YOUR_API_KEY"curl "https://api.karatology.com/v1/latest?api_key=kt_live_YOUR_API_KEY"About keys
- Keys look like
kt_live_followed by 32 URL-safe characters. The examples in these docs use the placeholderkt_live_YOUR_API_KEY. - A key is shown once, when you create it. Karatology stores only a SHA-256 hash of it, so a lost key cannot be shown again: create or roll a key from your dashboard instead.
- Keys per account depend on your plan: Free 1, Starter 1, Basic 2, Pro 5, Business 10. All keys on an account share its quota and per-minute limit.
- A missing key returns
401 missing_api_key. An unknown or revoked key returns401 invalid_api_key.
Keep keys secret
Store keys in environment variables or a secrets manager, never in a public repository or in front-end code. If a key leaks, roll or revoke it from the dashboard. See CORS and browsers before calling the API from a web page.
Getting started
Base URL
https://api.karatology.com/v1- Requests use HTTPS and
GET.OPTIONSis answered for CORS preflight. Any other method returns405 method_not_allowed, and an unknown path returns404 not_found. - Responses are JSON in UTF-8.
- Prices are in USD. Price endpoints accept
currency=USD; any other value returns400 unsupported_currency. - The version is part of the path (
/v1). Write your code to ignore response fields it does not recognize. GET /v1returns an index with links to these docs and every endpoint. No key needed.
All endpoints
| Endpoint | Returns | Quota | Key |
|---|---|---|---|
| GET /v1 | Index with links to these docs and every endpoint | Not counted | Not needed |
| GET /v1/latest | Latest prices for all four metals | Counts as 1 call | Required |
| GET /v1/spot | Spot price for one metal in every unit | Counts as 1 call | Required |
| GET /v1/karat | Gold value by karat | Counts as 1 call | Required |
| GET /v1/fineness | Prices by hallmark fineness | Counts as 1 call | Required |
| GET /v1/melt | Melt value of a piece | Counts as 1 call | Required |
| GET /v1/ratios | Cross-metal price ratios | Counts as 1 call | Required |
| GET /v1/usage | Your plan and this month's usage | Not counted | Required |
| GET /v1/metals | Reference data | Not counted | Not needed |
| GET /v1/openapi.json | OpenAPI 3.1 description of this API | Not counted | Not needed |
Getting started
Responses and freshness
Every price endpoint returns the same envelope. Endpoint fields are added at the top level next to these:
{
"success": true,
"timestamp": "2026-11-10T01:04:07.389Z",
"age_seconds": 2040,
"stale": false,
"currency": "USD",
"unit": "toz"
}| Field | Type | Meaning |
|---|---|---|
success | boolean | Always true on success; false on errors. |
timestamp | string (ISO 8601) | When the price was collected. If a response includes several metals, this is the oldest of their timestamps. |
age_seconds | integer | Seconds between timestamp and this response. |
stale | boolean | true when the price is more than 2 hours old, which means a collection was missed. |
currency | string | Always USD. |
unit | string | The unit prices are expressed in. On /v1/melt, the unit of the weight you sent. |
How fresh is a price?
Prices are live: every call fetches current prices at request time, and Karatology does not cache prices. The price source collects new prices hourly, so calls made within the same hour usually return the same price, and a price can be up to about an hour old when you receive it.
Every response tells you exactly how fresh it is:
timestampis when the price you received was collected. A response is as fresh as its oldest price.age_secondsis how long ago that was, measured when the response was built.stale: truemeans the price is more than 2 hours old. If freshness matters to your users, show the time of the price or a notice when this happens.
Save calls without losing freshness
Since new prices arrive hourly, store the last response on your side and reuse it until its timestamp is about an hour old. Calling more often returns the same price and uses your quota.
Numbers and headers
- Prices are JSON numbers. Per-gram and per-pennyweight prices have 4 decimals; troy ounce, kilogram and tola prices have 2.
change,change_percentandprevious_closecompare with the previous day's close.- Price, usage and error responses carry
Cache-Control: no-storeand anX-Request-Idheader. Rate limit and quota headers are listed under Rate limits and quotas. - Errors have
success: falseand anerrorobject. See Errors.
Endpoint
Latest prices
Gold, silver, platinum and palladium in one call. Use metals to return only some of them, for example metals=gold,silver. Each quote includes the change since the previous day's close. A successful call counts as 1 call, whichever metals you request.
Query parameters
| Name | Required | Default | Allowed values | Description |
|---|---|---|---|---|
unit | Optional | toz | toz, g, kg, dwt, tola | Unit the prices are expressed in. Per-gram and per-pennyweight prices have 4 decimals; other units have 2. |
metals | Optional | None | Comma list of gold, silver, platinum, palladium (or symbols) | Limit the response to these metals, for example gold,silver. All four are returned when omitted. |
currency | Optional | USD | USD | Only USD is available. Any other value returns 400 unsupported_currency. |
Request
curl "https://api.karatology.com/v1/latest?unit=toz" \
-H "X-API-Key: kt_live_YOUR_API_KEY"const response = await fetch("https://api.karatology.com/v1/latest?unit=toz", {
headers: { "X-API-Key": "kt_live_YOUR_API_KEY" },
});
const data = await response.json();
if (!data.success) {
throw new Error(data.error.code + ": " + data.error.message);
}
console.log(data.metals.gold.price);import requests
response = requests.get(
"https://api.karatology.com/v1/latest",
params={"unit": "toz"},
headers={"X-API-Key": "kt_live_YOUR_API_KEY"},
timeout=10,
)
data = response.json()
if not data["success"]:
raise RuntimeError(data["error"]["message"])
print(data["metals"]["gold"]["price"])<?php
$url = "https://api.karatology.com/v1/latest?" . http_build_query(["unit" => "toz"]);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["X-API-Key: kt_live_YOUR_API_KEY"],
CURLOPT_TIMEOUT => 10,
]);
$data = json_decode(curl_exec($ch), true);
if (!$data["success"]) {
throw new RuntimeException($data["error"]["message"]);
}
echo $data["metals"]["gold"]["price"];Response
{
"success": true,
"timestamp": "2026-11-10T01:04:07.389Z",
"age_seconds": 2040,
"stale": false,
"currency": "USD",
"unit": "toz",
"metals": {
"gold": {
"metal": "gold",
"symbol": "XAU",
"price": 4130.4,
"change": 59.81,
"change_percent": 1.47,
"previous_close": 4070.59
},
"silver": {
"metal": "silver",
"symbol": "XAG",
"price": 51.62,
"change": 0.84,
"change_percent": 1.65,
"previous_close": 50.78
},
"platinum": {
"metal": "platinum",
"symbol": "XPT",
"price": 1588.2,
"change": -6.1,
"change_percent": -0.38,
"previous_close": 1594.3
},
"palladium": {
"metal": "palladium",
"symbol": "XPD",
"price": 1421.75,
"change": 12.4,
"change_percent": 0.88,
"previous_close": 1409.35
}
}
}Response fields
| Field | Type | Description |
|---|---|---|
timestamp, age_seconds, stale, currency, unit | envelope | Shared by every price endpoint. See Responses and freshness. |
metals | object | One quote per metal, keyed by name. Only the metals you asked for when metals is set. |
metals.<metal>.metal | string | Metal name. |
metals.<metal>.symbol | string | Symbol: XAU, XAG, XPT, XPD. |
metals.<metal>.price | number | USD price per unit. |
metals.<metal>.change | number | USD change per unit since the previous day's close. |
metals.<metal>.change_percent | number | Percent change since the previous day's close. |
metals.<metal>.previous_close | number | Previous day's close, per unit. |
Endpoint
Spot price
One metal's price in the unit you ask for, plus the same price in every supported unit under prices. Useful when you show a metal per gram and per ounce side by side. Metal names and symbols are both accepted, so metal=XAU works too. Counts as 1 call.
Query parameters
| Name | Required | Default | Allowed values | Description |
|---|---|---|---|---|
metal | Required | None | gold, silver, platinum, palladium (or XAU, XAG, XPT, XPD) | The metal to price. Names and symbols are both accepted. |
unit | Optional | toz | toz, g, kg, dwt, tola | Unit for price, change and previous_close. Per-gram and per-pennyweight prices have 4 decimals; other units have 2. |
currency | Optional | USD | USD | Only USD is available. Any other value returns 400 unsupported_currency. |
Request
curl "https://api.karatology.com/v1/spot?metal=gold&unit=toz" \
-H "X-API-Key: kt_live_YOUR_API_KEY"const response = await fetch("https://api.karatology.com/v1/spot?metal=gold&unit=toz", {
headers: { "X-API-Key": "kt_live_YOUR_API_KEY" },
});
const data = await response.json();
if (!data.success) {
throw new Error(data.error.code + ": " + data.error.message);
}
console.log(data.price);import requests
response = requests.get(
"https://api.karatology.com/v1/spot",
params={"metal": "gold", "unit": "toz"},
headers={"X-API-Key": "kt_live_YOUR_API_KEY"},
timeout=10,
)
data = response.json()
if not data["success"]:
raise RuntimeError(data["error"]["message"])
print(data["price"])<?php
$url = "https://api.karatology.com/v1/spot?" . http_build_query(["metal" => "gold", "unit" => "toz"]);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["X-API-Key: kt_live_YOUR_API_KEY"],
CURLOPT_TIMEOUT => 10,
]);
$data = json_decode(curl_exec($ch), true);
if (!$data["success"]) {
throw new RuntimeException($data["error"]["message"]);
}
echo $data["price"];Response
{
"success": true,
"timestamp": "2026-11-10T01:04:07.389Z",
"age_seconds": 2040,
"stale": false,
"currency": "USD",
"unit": "toz",
"metal": "gold",
"symbol": "XAU",
"price": 4130.4,
"change": 59.81,
"change_percent": 1.47,
"previous_close": 4070.59,
"prices": {
"toz": 4130.4,
"g": 132.7953,
"kg": 132795.34,
"dwt": 206.5193,
"tola": 1548.9
}
}Response fields
| Field | Type | Description |
|---|---|---|
timestamp, age_seconds, stale, currency, unit | envelope | Shared by every price endpoint. See Responses and freshness. |
metal | string | Metal name. |
symbol | string | Symbol: XAU, XAG, XPT, XPD. |
price | number | USD price per unit. |
change | number | USD change per unit since the previous day's close. |
change_percent | number | Percent change since the previous day's close. |
previous_close | number | Previous day's close, per unit. |
prices | object | The same price in every unit: toz, g, kg, dwt, tola. |
Endpoint
Gold by karat
The value of gold at each common karat (24K, 23K, 22K, 21K, 20K, 18K, 16K, 14K, 12K, 10K, 9K, 8K), per gram by default. Each price is the spot price in your unit times the karat's purity. There is no markup, labor or refining fee in it, so it is the value of the gold content, not a retail price or an offer. Counts as 1 call.
Query parameters
| Name | Required | Default | Allowed values | Description |
|---|---|---|---|---|
unit | Optional | g | toz, g, kg, dwt, tola | Unit the karat prices are expressed in. Per-gram and per-pennyweight prices have 4 decimals; other units have 2. |
currency | Optional | USD | USD | Only USD is available. Any other value returns 400 unsupported_currency. |
Request
curl "https://api.karatology.com/v1/karat?unit=g" \
-H "X-API-Key: kt_live_YOUR_API_KEY"const response = await fetch("https://api.karatology.com/v1/karat?unit=g", {
headers: { "X-API-Key": "kt_live_YOUR_API_KEY" },
});
const data = await response.json();
if (!data.success) {
throw new Error(data.error.code + ": " + data.error.message);
}
console.log(data.karats[0].price);import requests
response = requests.get(
"https://api.karatology.com/v1/karat",
params={"unit": "g"},
headers={"X-API-Key": "kt_live_YOUR_API_KEY"},
timeout=10,
)
data = response.json()
if not data["success"]:
raise RuntimeError(data["error"]["message"])
print(data["karats"][0]["price"])<?php
$url = "https://api.karatology.com/v1/karat?" . http_build_query(["unit" => "g"]);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["X-API-Key: kt_live_YOUR_API_KEY"],
CURLOPT_TIMEOUT => 10,
]);
$data = json_decode(curl_exec($ch), true);
if (!$data["success"]) {
throw new RuntimeException($data["error"]["message"]);
}
echo $data["karats"][0]["price"];Response
{
"success": true,
"timestamp": "2026-11-10T01:04:07.389Z",
"age_seconds": 2040,
"stale": false,
"currency": "USD",
"unit": "g",
"metal": "gold",
"spot_price_toz": 4130.4,
"karats": [
{ "karat": "24K", "purity": 0.999, "fineness": 999, "price": 132.6625 },
{ "karat": "23K", "purity": 0.9583, "fineness": 958.3, "price": 127.2578 },
{ "karat": "22K", "purity": 0.9167, "fineness": 916.7, "price": 121.7335 },
{ "karat": "21K", "purity": 0.875, "fineness": 875, "price": 116.1959 },
{ "karat": "20K", "purity": 0.8333, "fineness": 833.3, "price": 110.6584 },
{ "karat": "18K", "purity": 0.75, "fineness": 750, "price": 99.5965 },
{ "karat": "16K", "purity": 0.6667, "fineness": 666.7, "price": 88.5347 },
{ "karat": "14K", "purity": 0.5833, "fineness": 583.3, "price": 77.4595 },
{ "karat": "12K", "purity": 0.5, "fineness": 500, "price": 66.3977 },
{ "karat": "10K", "purity": 0.4167, "fineness": 416.7, "price": 55.3358 },
{ "karat": "9K", "purity": 0.375, "fineness": 375, "price": 49.7983 },
{ "karat": "8K", "purity": 0.3333, "fineness": 333.3, "price": 44.2607 }
]
}Response fields
| Field | Type | Description |
|---|---|---|
timestamp, age_seconds, stale, currency, unit | envelope | Shared by every price endpoint. See Responses and freshness. |
metal | string | Always gold. |
spot_price_toz | number | Gold spot price, USD per troy ounce. |
karats | array | One entry per karat, from 24K down to 8K. |
karats[].karat | string | Karat label, for example 18K. |
karats[].purity | number | Fraction of pure gold: karat / 24 to 4 decimals. 24K is 0.999. |
karats[].fineness | number | Parts per thousand (purity x 1000). |
karats[].price | number | USD value of the gold content per unit. |
Endpoint
Prices by fineness
Prices for the hallmark fineness grades of one metal, such as 925 sterling silver, 950 platinum or 750 gold. Fineness is parts per thousand of pure metal, the number stamped on the piece. Each price is the spot price in your unit times fineness / 1000. The grades for every metal are listed under Units, karats and fineness. Counts as 1 call.
Query parameters
| Name | Required | Default | Allowed values | Description |
|---|---|---|---|---|
metal | Required | None | gold, silver, platinum, palladium (or XAU, XAG, XPT, XPD) | The metal to price. Names and symbols are both accepted. |
unit | Optional | g | toz, g, kg, dwt, tola | Unit the grade prices are expressed in. Per-gram and per-pennyweight prices have 4 decimals; other units have 2. |
currency | Optional | USD | USD | Only USD is available. Any other value returns 400 unsupported_currency. |
Request
curl "https://api.karatology.com/v1/fineness?metal=silver&unit=g" \
-H "X-API-Key: kt_live_YOUR_API_KEY"const response = await fetch("https://api.karatology.com/v1/fineness?metal=silver&unit=g", {
headers: { "X-API-Key": "kt_live_YOUR_API_KEY" },
});
const data = await response.json();
if (!data.success) {
throw new Error(data.error.code + ": " + data.error.message);
}
console.log(data.grades[3].price);import requests
response = requests.get(
"https://api.karatology.com/v1/fineness",
params={"metal": "silver", "unit": "g"},
headers={"X-API-Key": "kt_live_YOUR_API_KEY"},
timeout=10,
)
data = response.json()
if not data["success"]:
raise RuntimeError(data["error"]["message"])
print(data["grades"][3]["price"])<?php
$url = "https://api.karatology.com/v1/fineness?" . http_build_query(["metal" => "silver", "unit" => "g"]);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["X-API-Key: kt_live_YOUR_API_KEY"],
CURLOPT_TIMEOUT => 10,
]);
$data = json_decode(curl_exec($ch), true);
if (!$data["success"]) {
throw new RuntimeException($data["error"]["message"]);
}
echo $data["grades"][3]["price"];Response
{
"success": true,
"timestamp": "2026-11-10T01:04:07.389Z",
"age_seconds": 2040,
"stale": false,
"currency": "USD",
"unit": "g",
"metal": "silver",
"symbol": "XAG",
"spot_price_toz": 51.62,
"grades": [
{ "fineness": 999, "name": "Fine silver", "purity": 0.999, "price": 1.658 },
{ "fineness": 958, "name": "Britannia silver", "purity": 0.958, "price": 1.5899 },
{ "fineness": 950, "name": "French first standard", "purity": 0.95, "price": 1.5766 },
{ "fineness": 925, "name": "Sterling silver", "purity": 0.925, "price": 1.5351 },
{ "fineness": 900, "name": "Coin silver", "purity": 0.9, "price": 1.4937 },
{ "fineness": 835, "name": "Continental silver", "purity": 0.835, "price": 1.3858 },
{
"fineness": 800,
"name": "German and Italian silver",
"purity": 0.8,
"price": 1.3277
}
]
}Response fields
| Field | Type | Description |
|---|---|---|
timestamp, age_seconds, stale, currency, unit | envelope | Shared by every price endpoint. See Responses and freshness. |
metal | string | The metal you asked for. |
symbol | string | Its symbol, for example XAG. |
spot_price_toz | number | Spot price, USD per troy ounce. |
grades | array | One entry per hallmark grade for the metal. |
grades[].fineness | number | Parts per thousand, as stamped (925, 750, 950). |
grades[].name | string | Common name of the grade. |
grades[].purity | number | Fraction of pure metal (fineness / 1000). |
grades[].price | number | USD value of the metal content per unit. |
Endpoint
Melt value
The value of the pure metal in a piece: weight times purity times the spot price. Give the weight in any supported unit and the purity in one of three ways: karat (gold only, 1 to 24), fineness (1 to 1000, any metal) or purity (a fraction above 0 and up to 1). Send only one of them; sending two returns 400 invalid_parameter. When none is sent, 0.999 (fine metal) is used. Counts as 1 call.
Worked example: 5 g of 14K gold is 5 g x 0.5833 = 2.9165 g of pure gold. At $4,130.40 per troy ounce (31.1035 g), that is 2.9165 / 31.1035 x $4,130.40 = $387.30. With payout=85, payout_value is $329.20.
Query parameters
| Name | Required | Default | Allowed values | Description |
|---|---|---|---|---|
metal | Optional | gold | gold, silver, platinum, palladium (or XAU, XAG, XPT, XPD) | The metal the piece is made of. Names and symbols are both accepted. |
weight | Required | None | Number greater than 0, up to 1,000,000 | Weight of the piece, in unit. |
unit | Optional | g | toz, g, kg, dwt, tola | Unit of weight. The response unit field echoes it. |
karat | Optional | None | Number from 1 to 24 (gold only) | Gold karat. Purity is karat / 24 (24K is 0.999). Send only one of karat, fineness or purity. |
fineness | Optional | None | Number from 1 to 1000 | Parts per thousand, as stamped (585, 750, 925, 950). Works for every metal. |
purity | Optional | 0.999 | Number greater than 0, up to 1 | Fraction of pure metal. Used as 0.999 when no karat, fineness or purity is sent. |
payout | Optional | None | Number from 1 to 100 | Percent of melt value a buyer pays. Adds payout_percent and payout_value to the response. |
currency | Optional | USD | USD | Only USD is available. Any other value returns 400 unsupported_currency. |
Request
curl "https://api.karatology.com/v1/melt?metal=gold&weight=5&unit=g&karat=14&payout=85" \
-H "X-API-Key: kt_live_YOUR_API_KEY"const response = await fetch("https://api.karatology.com/v1/melt?metal=gold&weight=5&unit=g&karat=14&payout=85", {
headers: { "X-API-Key": "kt_live_YOUR_API_KEY" },
});
const data = await response.json();
if (!data.success) {
throw new Error(data.error.code + ": " + data.error.message);
}
console.log(data.melt_value);import requests
response = requests.get(
"https://api.karatology.com/v1/melt",
params={"metal": "gold", "weight": "5", "unit": "g", "karat": "14", "payout": "85"},
headers={"X-API-Key": "kt_live_YOUR_API_KEY"},
timeout=10,
)
data = response.json()
if not data["success"]:
raise RuntimeError(data["error"]["message"])
print(data["melt_value"])<?php
$url = "https://api.karatology.com/v1/melt?" . http_build_query(["metal" => "gold", "weight" => "5", "unit" => "g", "karat" => "14", "payout" => "85"]);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["X-API-Key: kt_live_YOUR_API_KEY"],
CURLOPT_TIMEOUT => 10,
]);
$data = json_decode(curl_exec($ch), true);
if (!$data["success"]) {
throw new RuntimeException($data["error"]["message"]);
}
echo $data["melt_value"];Response
{
"success": true,
"timestamp": "2026-11-10T01:04:07.389Z",
"age_seconds": 2040,
"stale": false,
"currency": "USD",
"unit": "g",
"metal": "gold",
"weight": 5,
"purity": 0.5833,
"spot_price_toz": 4130.4,
"pure_weight_g": 2.9165,
"pure_weight_toz": 0.093768,
"melt_value": 387.3,
"payout_percent": 85,
"payout_value": 329.2
}Response fields
| Field | Type | Description |
|---|---|---|
timestamp, age_seconds, stale, currency | envelope | Shared by every price endpoint. See Responses and freshness. |
unit | string | The unit of the weight you sent (default g). |
metal | string | The metal of the piece. |
weight | number | The weight you sent, in unit. |
purity | number | The purity used: from karat, fineness or purity, or 0.999 when none was sent. |
spot_price_toz | number | Spot price, USD per troy ounce. |
pure_weight_g | number | Pure metal content in grams (4 decimals). |
pure_weight_toz | number | Pure metal content in troy ounces (6 decimals). |
melt_value | number | Pure content times spot, in USD. No markup, labor or refining fees. |
payout_percent | number | Only when payout is sent: the percent you sent. |
payout_value | number | Only when payout is sent: melt_value times payout_percent / 100. |
Melt value is an estimate, not an appraisal
Melt value is spot price times pure metal content. It does not include markup, labor, refining fees, gemstones or collector value, and it is not an appraisal, a guaranteed value or an offer to buy. Buyers usually pay a share of melt value; use payout to show that figure.
Endpoint
Metal ratios
Cross-metal ratios from current prices per troy ounce. A gold_silver ratio of 80.02 means one ounce of gold is worth 80.02 ounces of silver. A ratio is null only if a price it needs is missing. Counts as 1 call.
Query parameters
| Name | Required | Default | Allowed values | Description |
|---|---|---|---|---|
currency | Optional | USD | USD | Only USD is available. Any other value returns 400 unsupported_currency. |
Request
curl "https://api.karatology.com/v1/ratios" \
-H "X-API-Key: kt_live_YOUR_API_KEY"const response = await fetch("https://api.karatology.com/v1/ratios", {
headers: { "X-API-Key": "kt_live_YOUR_API_KEY" },
});
const data = await response.json();
if (!data.success) {
throw new Error(data.error.code + ": " + data.error.message);
}
console.log(data.ratios.gold_silver);import requests
response = requests.get(
"https://api.karatology.com/v1/ratios",
headers={"X-API-Key": "kt_live_YOUR_API_KEY"},
timeout=10,
)
data = response.json()
if not data["success"]:
raise RuntimeError(data["error"]["message"])
print(data["ratios"]["gold_silver"])<?php
$url = "https://api.karatology.com/v1/ratios";
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["X-API-Key: kt_live_YOUR_API_KEY"],
CURLOPT_TIMEOUT => 10,
]);
$data = json_decode(curl_exec($ch), true);
if (!$data["success"]) {
throw new RuntimeException($data["error"]["message"]);
}
echo $data["ratios"]["gold_silver"];Response
{
"success": true,
"timestamp": "2026-11-10T01:04:07.389Z",
"age_seconds": 2040,
"stale": false,
"currency": "USD",
"unit": "toz",
"ratios": {
"gold_silver": 80.02,
"gold_platinum": 2.6,
"gold_palladium": 2.91,
"platinum_silver": 30.77,
"platinum_palladium": 1.12
},
"prices": { "gold": 4130.4, "silver": 51.62, "platinum": 1588.2, "palladium": 1421.75 }
}Response fields
| Field | Type | Description |
|---|---|---|
timestamp, age_seconds, stale, currency, unit | envelope | Shared by every price endpoint. See Responses and freshness. |
ratios.gold_silver | number or null | Ounces of silver one troy ounce of gold is worth. |
ratios.gold_platinum | number or null | Ounces of platinum one troy ounce of gold is worth. |
ratios.gold_palladium | number or null | Ounces of palladium one troy ounce of gold is worth. |
ratios.platinum_silver | number or null | Ounces of silver one troy ounce of platinum is worth. |
ratios.platinum_palladium | number or null | Ounces of palladium one troy ounce of platinum is worth. |
prices | object | The USD per troy ounce prices used, one per metal. |
Endpoint
Usage
Your plan and the calls counted this month. Use it to show usage in your own tools or to alert before you run out. It requires a key and is not counted toward your monthly quota, but it does count toward your per-minute limit. This endpoint has no price envelope.
Query parameters
This endpoint takes no query parameters.
Request
curl "https://api.karatology.com/v1/usage" \
-H "X-API-Key: kt_live_YOUR_API_KEY"const response = await fetch("https://api.karatology.com/v1/usage", {
headers: { "X-API-Key": "kt_live_YOUR_API_KEY" },
});
const data = await response.json();
if (!data.success) {
throw new Error(data.error.code + ": " + data.error.message);
}
console.log(data.usage.remaining);import requests
response = requests.get(
"https://api.karatology.com/v1/usage",
headers={"X-API-Key": "kt_live_YOUR_API_KEY"},
timeout=10,
)
data = response.json()
if not data["success"]:
raise RuntimeError(data["error"]["message"])
print(data["usage"]["remaining"])<?php
$url = "https://api.karatology.com/v1/usage";
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["X-API-Key: kt_live_YOUR_API_KEY"],
CURLOPT_TIMEOUT => 10,
]);
$data = json_decode(curl_exec($ch), true);
if (!$data["success"]) {
throw new RuntimeException($data["error"]["message"]);
}
echo $data["usage"]["remaining"];Response
{
"success": true,
"plan": { "id": "starter", "name": "Starter", "calls_per_month": 500, "per_minute": 10 },
"period": { "month": "2026-11", "resets_at": "2026-12-01T00:00:00.000Z" },
"usage": { "used": 120, "remaining": 380, "limit": 500, "hard_limit": 550 }
}Response fields
| Field | Type | Description |
|---|---|---|
plan.id | string | Plan id, for example free or starter. |
plan.name | string | Plan name. |
plan.calls_per_month | integer | Calls included per month. |
plan.per_minute | integer | Requests allowed per minute. |
period.month | string | The current month in UTC, as YYYY-MM. |
period.resets_at | string | When the quota resets: 00:00 UTC on the 1st of next month. |
usage.used | integer | Calls counted so far this month. |
usage.remaining | integer | Included calls left this month (never below 0). |
usage.limit | integer | Calls included per month. |
usage.hard_limit | integer | Calls allowed before quota_exceeded: included calls plus any grace. |
Endpoint
Reference data
The metals, symbols, units, karats and fineness grades the API supports. Use it to build forms and validate input. No key needed and not counted. The same values are listed under Units, karats and fineness.
Query parameters
This endpoint takes no query parameters.
Request
curl "https://api.karatology.com/v1/metals"const response = await fetch("https://api.karatology.com/v1/metals");
const data = await response.json();
if (!data.success) {
throw new Error(data.error.code + ": " + data.error.message);
}
console.log(data.units);import requests
response = requests.get(
"https://api.karatology.com/v1/metals",
timeout=10,
)
data = response.json()
if not data["success"]:
raise RuntimeError(data["error"]["message"])
print(data["units"])<?php
$url = "https://api.karatology.com/v1/metals";
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
]);
$data = json_decode(curl_exec($ch), true);
if (!$data["success"]) {
throw new RuntimeException($data["error"]["message"]);
}
echo $data["units"];Response
{
"success": true,
"metals": [
{
"metal": "gold",
"symbol": "XAU",
"fineness_grades": [
{ "fineness": 999.9, "name": "Four nines fine gold" },
{ "fineness": 999, "name": "Fine gold (24K)" },
{ "fineness": 995, "name": "Good delivery bar minimum" },
{ "fineness": 916, "name": "22K" },
{ "fineness": 875, "name": "21K" },
{ "fineness": 750, "name": "18K" },
{ "fineness": 585, "name": "14K" },
{ "fineness": 417, "name": "10K" },
{ "fineness": 375, "name": "9K" },
{ "fineness": 333, "name": "8K" }
]
},
{
"metal": "silver",
"symbol": "XAG",
"fineness_grades": [
{ "fineness": 999, "name": "Fine silver" },
{ "fineness": 958, "name": "Britannia silver" },
{ "fineness": 950, "name": "French first standard" },
{ "fineness": 925, "name": "Sterling silver" },
{ "fineness": 900, "name": "Coin silver" },
{ "fineness": 835, "name": "Continental silver" },
{ "fineness": 800, "name": "German and Italian silver" }
]
},
{
"metal": "platinum",
"symbol": "XPT",
"fineness_grades": [
{ "fineness": 999, "name": "Fine platinum" },
{ "fineness": 950, "name": "Platinum 950 (jewelry standard)" },
{ "fineness": 900, "name": "Platinum 900" },
{ "fineness": 850, "name": "Platinum 850" }
]
},
{
"metal": "palladium",
"symbol": "XPD",
"fineness_grades": [
{ "fineness": 999, "name": "Fine palladium" },
{ "fineness": 950, "name": "Palladium 950 (jewelry standard)" },
{ "fineness": 500, "name": "Palladium 500" }
]
}
],
"units": [
{ "unit": "toz", "name": "Troy ounce", "grams": 31.1035 },
{ "unit": "g", "name": "Gram", "grams": 1 },
{ "unit": "kg", "name": "Kilogram", "grams": 1000 },
{ "unit": "dwt", "name": "Pennyweight", "grams": 1.55517 },
{ "unit": "tola", "name": "Tola", "grams": 11.6638 }
],
"karats": [
{ "karat": "24K", "purity": 0.999 },
{ "karat": "23K", "purity": 0.9583 },
{ "karat": "22K", "purity": 0.9167 },
{ "karat": "21K", "purity": 0.875 },
{ "karat": "20K", "purity": 0.8333 },
{ "karat": "18K", "purity": 0.75 },
{ "karat": "16K", "purity": 0.6667 },
{ "karat": "14K", "purity": 0.5833 },
{ "karat": "12K", "purity": 0.5 },
{ "karat": "10K", "purity": 0.4167 },
{ "karat": "9K", "purity": 0.375 },
{ "karat": "8K", "purity": 0.3333 }
],
"currency": "USD"
}Response fields
| Field | Type | Description |
|---|---|---|
metals | array | Supported metals, one entry each. |
metals[].metal | string | Metal name, as accepted by the metal parameter. |
metals[].symbol | string | Its symbol, also accepted by the metal parameter. |
metals[].fineness_grades | array | Hallmark grades for the metal: fineness (parts per thousand) and name. |
units | array | Supported units: unit, name and grams per unit. |
karats | array | Gold karats: karat label and purity. |
currency | string | Always USD. |
Reference
Errors
Errors use standard HTTP status codes and a JSON body with success: false. Errors are never counted toward your monthly quota.
{
"success": false,
"error": {
"code": "invalid_parameter",
"message": "unit must be one of toz, g, kg, dwt, tola.",
"param": "unit",
"docs": "https://karatology.com/docs#errors"
}
}| Field | Meaning |
|---|---|
error.code | Stable, machine-readable code. Branch on this, not on the message. |
error.message | Human-readable explanation. The wording may change. |
error.param | The parameter at fault, on parameter errors. |
error.docs | Link to this section. |
Error codes
| Status | Code | When |
|---|---|---|
| 400 | missing_parameter | A required parameter is absent. param names it. |
| 400 | invalid_parameter | A parameter has a bad value. param names it. |
| 400 | unsupported_currency | currency is not USD. |
| 401 | missing_api_key | No API key was sent. |
| 401 | invalid_api_key | The key is unknown or has been revoked. |
| 402 | payment_required | A paid plan has a failed payment past the 7 day grace period. |
| 404 | not_found | Unknown endpoint. |
| 405 | method_not_allowed | The method is not GET or OPTIONS. |
| 429 | rate_limited | The per-minute limit was reached. Wait Retry-After seconds. |
| 429 | quota_exceeded | This month's calls are used up. Retry-After is the seconds until the monthly reset; upgrading restores access sooner. |
| 500 | internal_error | Unexpected error. Retry, then contact support with the X-Request-Id. |
| 503 | prices_unavailable | Live prices could not be fetched. Retry shortly; see Retry-After. |
Handling errors
400,401,402,404and405: fix the request or the account. Sending the same request again will fail the same way.429and503: wait the number of seconds inRetry-After, then retry. If it repeats, back off further between attempts.500: retry once. If it persists, email support with theX-Request-Idheader.
Reference
Rate limits and quotas
Two limits apply, both set by your plan: a per-minute rate limit and a monthly quota of calls. Every plan, including Free, gets every endpoint; plans differ by volume, speed, keys and support.
| Plan | Price a month | Calls a month | Hard stop | Requests a minute | Keys |
|---|---|---|---|---|---|
| Free | $0 | 10 | 10 | 5 | 1 |
| Starter | $15 | 500 | 550 | 10 | 1 |
| Basic | $29 | 5,000 | 5,500 | 30 | 2 |
| Pro | $59 | 25,000 | 27,500 | 120 | 5 |
| Business | $149 | 100,000 | 110,000 | 600 | 10 |
| Enterprise | Quote | Custom | Custom | Custom | Custom |
Yearly billing is 10 months' price for 12. See plans and pricing or email contact@karatology.com for Enterprise.
Metering rules
- Only successful (200) responses from price endpoints count toward the monthly quota. Errors,
/v1/usage,/v1/metals,/v1,/v1/openapi.jsonand preflight requests do not. - Quotas reset at 00:00 UTC on the 1st of each month.
- Free: hard stop at 10 calls a month.
- Paid plans: 10% grace above the included calls (the hard stop column), then
429 quota_exceededuntil the month resets or you upgrade. There are no overage bills. - The per-minute limit counts every authenticated request, whatever its result, per account: all keys on an account share one limit. Going over returns
429 rate_limited. - If a payment on a paid plan fails, the API keeps working for a 7 day grace period, then returns
402 payment_requireduntil billing is fixed in your dashboard.
Response headers
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Requests allowed per minute on your plan. |
X-RateLimit-Remaining | Requests left in the current minute. |
X-RateLimit-Reset | Unix time in seconds when the minute window resets. |
X-Quota-Limit | Calls included per month on your plan. |
X-Quota-Used | Calls counted so far this month. |
X-Quota-Remaining | Included calls left this month (never below 0). |
X-Request-Id | Unique id (a UUID) for this request. Include it in support questions. |
Retry-After | Seconds to wait before retrying. Sent with 429 and 503. |
Cache-Control | no-store on price, usage and error responses. |
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Cache-Control: no-store
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 9
X-RateLimit-Reset: 1794272700
X-Quota-Limit: 500
X-Quota-Used: 121
X-Quota-Remaining: 379
X-Request-Id: 3f6c1a52-9d4e-4b8a-a7c2-5e1f0b9d8c47Staying within your limits
- Read
X-Quota-Remainingon any response, or call/v1/usage, which is not counted. - Reuse the last response until its price is about an hour old, since new prices are collected hourly.
- Use
/v1/latestfor several metals at once: it costs 1 call, not 4.
Reference
CORS and browsers
Every /v1 response, including errors and preflight requests, carries these headers, so the API works from browsers. Preflight OPTIONS requests are never counted.
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, OPTIONS
Access-Control-Allow-Headers: X-API-Key, Authorization, Content-Type
Access-Control-Expose-Headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-Quota-Limit, X-Quota-Used, X-Quota-Remaining, X-Request-Id, Retry-After
Access-Control-Max-Age: 86400A key in front-end code is visible to everyone
Anything sent to the browser, including an API key in JavaScript, can be read by visitors and used against your quota. Call the API from your server and pass the prices to your page. If you must call from the browser, use a dedicated key for that site so you can roll it from your dashboard without touching your other integrations.
// app/api/gold/route.js: runs on your server, so the key stays private
export async function GET() {
const response = await fetch("https://api.karatology.com/v1/spot?metal=gold&unit=g", {
headers: { "X-API-Key": process.env.KARATOLOGY_API_KEY },
});
const data = await response.json();
if (!data.success) {
return Response.json({ error: data.error.code }, { status: response.status });
}
return Response.json({ price: data.price, timestamp: data.timestamp });
}Reference
Units, karats and fineness
Prices are converted from the price per troy ounce: price per unit = price per troy ounce / 31.1035 x grams per unit. These tables are generated from the same values the API uses.
Units
| unit | Name | Grams | Price decimals |
|---|---|---|---|
toz | Troy ounce | 31.1035 | 2 |
g | Gram | 1 | 4 |
kg | Kilogram | 1000 | 2 |
dwt | Pennyweight | 1.55517 | 4 |
tola | Tola | 11.6638 | 2 |
Metals
| metal | Symbol |
|---|---|
gold | XAU |
silver | XAG |
platinum | XPT |
palladium | XPD |
Gold karats
Purity is karat / 24, rounded to 4 decimals. 24K is commercial fine gold at 0.999.
| Karat | Purity | Fineness | Gold content |
|---|---|---|---|
| 24K | 0.999 | 999 | 99.9% |
| 23K | 0.9583 | 958.3 | 95.83% |
| 22K | 0.9167 | 916.7 | 91.67% |
| 21K | 0.875 | 875 | 87.5% |
| 20K | 0.8333 | 833.3 | 83.33% |
| 18K | 0.75 | 750 | 75% |
| 16K | 0.6667 | 666.7 | 66.67% |
| 14K | 0.5833 | 583.3 | 58.33% |
| 12K | 0.5 | 500 | 50% |
| 10K | 0.4167 | 416.7 | 41.67% |
| 9K | 0.375 | 375 | 37.5% |
| 8K | 0.3333 | 333.3 | 33.33% |
Fineness grades
gold (XAU)
| Fineness | Name |
|---|---|
| 999.9 | Four nines fine gold |
| 999 | Fine gold (24K) |
| 995 | Good delivery bar minimum |
| 916 | 22K |
| 875 | 21K |
| 750 | 18K |
| 585 | 14K |
| 417 | 10K |
| 375 | 9K |
| 333 | 8K |
silver (XAG)
| Fineness | Name |
|---|---|
| 999 | Fine silver |
| 958 | Britannia silver |
| 950 | French first standard |
| 925 | Sterling silver |
| 900 | Coin silver |
| 835 | Continental silver |
| 800 | German and Italian silver |
platinum (XPT)
| Fineness | Name |
|---|---|
| 999 | Fine platinum |
| 950 | Platinum 950 (jewelry standard) |
| 900 | Platinum 900 |
| 850 | Platinum 850 |
palladium (XPD)
| Fineness | Name |
|---|---|
| 999 | Fine palladium |
| 950 | Palladium 950 (jewelry standard) |
| 500 | Palladium 500 |
Reference
OpenAPI and Postman
Use the machine-readable description to generate a client, or import the ready-made Postman collection to try every endpoint.
OpenAPI 3.1
Every path, parameter, schema, header and error. Import it into Postman, Insomnia or an OpenAPI client generator. No key needed.
https://api.karatology.com/v1/openapi.json
Open openapi.jsonPostman collection
- Download the collection and import it in Postman.
- Open the collection's Variables tab and paste your key into
apiKey. - Send any request. Each one has tests that check the status,
successand, on price endpoints,timestamp.
Reference
Support and changelog
Email contact@karatology.com. Include the endpoint, the time of the request and the X-Request-Id header from the response. Never send your full API key.
Support by plan: Free: documentation; Starter: email support; Basic: email support; Pro: priority email support; Business: priority support and usage export.
Changelog
v1.0.0, November 2026
Initial release: /v1/latest, /v1/spot, /v1/karat, /v1/fineness, /v1/melt, /v1/ratios, /v1/usage, /v1/metals, the OpenAPI description and the Postman collection.
Prices and values from the Karatology Metal Price API are provided for information. Karat, fineness and melt values are spot price times metal content and are not appraisals, guaranteed values or offers to buy.