# MetalAPI — full documentation
> MetalAPI is a JSON REST API with real-time and historical prices for precious metals (gold XAU, silver XAG, platinum XPT, palladium XPD), industrial metals, energy, 57 currencies and 13 cryptocurrencies, plus an MCP server for AI assistants.
In `rates`, the key `` (e.g. `USDXAU`) is the price of one unit of the symbol in the base currency (USD per troy ounce of gold); the plain key (`XAU`) is its inverse, kept for compatibility with older clients.
## API reference
### Latest rates: GET https://metalapi.com/api/v1/latest
Current rates for every requested symbol. The data you receive is as fresh as your plan's update interval allows (daily on Free, down to 5 seconds on Enterprise).
- Add `XAU-BID` / `XAU-ASK` (any metal or currency) to `currencies` for the book (Standard plan and above).
- `timestamp` is the time of the data, not of the request.
Parameters:
- `api_key` (required): Your API key. Alternatively send `Authorization: Bearer ` or `X-API-Key: `.
- `base`: Currency or symbol the rates are expressed in. Default `USD`. Any supported symbol works, e.g. `EUR`, `TRY`, `XAU`.
- `currencies`: Comma-separated symbols to return (alias: `symbols`). Default: all symbols. Unknown codes are ignored.
- `unit`: Weight unit for metals: `troy_oz` (default), `gram`, `kilogram`, `ounce`, `pound`, `metric_ton`, `short_ton`. Ignored for currencies, energy and crypto.
Request:
```bash
curl "https://metalapi.com/api/v1/latest?api_key=YOUR_API_KEY&base=USD¤cies=XAU,XAG,EUR"
```
Response:
```json
{
"success": true,
"timestamp": 1792834892,
"date": "2026-10-24",
"base": "USD",
"rates": {
"XAU": 0.0002501319,
"USDXAU": 3997.89,
"XAG": 0.0185392857,
"USDXAG": 53.94,
"EUR": 0.8925,
"USDEUR": 1.1204
},
"rate_limit": { "limit": 10000, "remaining": 9875, "reset": 1793491200, "used": 125, "plan": "Standard", "reset_date": "2026-11-01T00:00:00.000Z" }
}
```
### Historical rates: GET https://metalapi.com/api/v1/{YYYY-MM-DD}
Closing rates for a past day. Use `/yesterday` as a shortcut, or `/historical?date=YYYY-MM-DD`. For weekends and holidays the most recent trading day's close is returned.
- History: currencies since 1999-01-04, metals and energy since 2000, crypto since 2014.
Parameters:
- `api_key` (required): Your API key. Alternatively send `Authorization: Bearer ` or `X-API-Key: `.
- `base`: Currency or symbol the rates are expressed in. Default `USD`. Any supported symbol works, e.g. `EUR`, `TRY`, `XAU`.
- `currencies`: Comma-separated symbols to return (alias: `symbols`). Default: all symbols. Unknown codes are ignored.
- `unit`: Weight unit for metals: `troy_oz` (default), `gram`, `kilogram`, `ounce`, `pound`, `metric_ton`, `short_ton`. Ignored for currencies, energy and crypto.
Request:
```bash
curl "https://metalapi.com/api/v1/2024-01-15?api_key=YOUR_API_KEY¤cies=XAU,XAG"
```
Response:
```json
{
"success": true,
"timestamp": 1705276800,
"historical": true,
"date": "2024-01-15",
"base": "USD",
"rates": {
"XAU": 0.0004874244,
"USDXAU": 2051.6,
"XAG": 0.0431741646,
"USDXAG": 23.162
},
"rate_limit": { "...": "..." }
}
```
### Convert: GET https://metalapi.com/api/v1/convert
Convert an amount from one symbol to another, at the latest rate or on a given date.
Parameters:
- `api_key` (required): Your API key. Alternatively send `Authorization: Bearer ` or `X-API-Key: `.
- `from` (required): Symbol to convert from.
- `to` (required): Symbol to convert to.
- `amount` (required): Amount to convert (decimal).
- `date`: Optional `YYYY-MM-DD` for a historical conversion.
- `unit`: Weight unit for metals: `troy_oz` (default), `gram`, `kilogram`, `ounce`, `pound`, `metric_ton`, `short_ton`. Ignored for currencies, energy and crypto.
Request:
```bash
curl "https://metalapi.com/api/v1/convert?api_key=YOUR_API_KEY&from=USD&to=XAU&amount=1000&unit=gram"
```
Response:
```json
{
"success": true,
"query": { "from": "USD", "to": "XAU", "amount": 1000, "unit": "gram" },
"info": { "timestamp": 1792834892, "rate": 0.0077799, "quote": 0.0077799 },
"date": "2026-10-24",
"result": 7.7799,
"rate_limit": { "...": "..." }
}
```
### Timeframe: GET https://metalapi.com/api/v1/timeframe
Daily closing rates between two dates (inclusive). `/timeseries` is an alias that returns `"timeseries": true`. Maximum range: 30 days on Free, 365 days on paid plans.
Parameters:
- `api_key` (required): Your API key. Alternatively send `Authorization: Bearer ` or `X-API-Key: `.
- `start_date` (required): `YYYY-MM-DD`
- `end_date` (required): `YYYY-MM-DD`
- `base`: Currency or symbol the rates are expressed in. Default `USD`. Any supported symbol works, e.g. `EUR`, `TRY`, `XAU`.
- `currencies`: Comma-separated symbols to return (alias: `symbols`). Default: all symbols. Unknown codes are ignored.
- `unit`: Weight unit for metals: `troy_oz` (default), `gram`, `kilogram`, `ounce`, `pound`, `metric_ton`, `short_ton`. Ignored for currencies, energy and crypto.
Request:
```bash
curl "https://metalapi.com/api/v1/timeframe?api_key=YOUR_API_KEY&start_date=2026-10-19&end_date=2026-10-23¤cies=XAU"
```
Response:
```json
{
"success": true,
"timeframe": true,
"start_date": "2026-10-19",
"end_date": "2026-10-23",
"base": "USD",
"rates": {
"2026-10-19": { "XAU": 0.0002403788, "USDXAU": 4160.1 },
"2026-10-20": { "XAU": 0.0002413943, "USDXAU": 4142.6 },
"2026-10-21": { "XAU": 0.0002409639, "USDXAU": 4150 },
"2026-10-22": { "XAU": 0.0002462812, "USDXAU": 4060.4 },
"2026-10-23": { "XAU": 0.0002495072, "USDXAU": 4007.9 }
},
"rate_limit": { "...": "..." }
}
```
### Change: GET https://metalapi.com/api/v1/change
How each rate moved between two dates, as absolute and percentage change.
Parameters:
- `api_key` (required): Your API key. Alternatively send `Authorization: Bearer ` or `X-API-Key: `.
- `start_date` (required): `YYYY-MM-DD`
- `end_date` (required): `YYYY-MM-DD`
- `base`: Currency or symbol the rates are expressed in. Default `USD`. Any supported symbol works, e.g. `EUR`, `TRY`, `XAU`.
- `currencies`: Comma-separated symbols to return (alias: `symbols`). Default: all symbols. Unknown codes are ignored.
Request:
```bash
curl "https://metalapi.com/api/v1/change?api_key=YOUR_API_KEY&start_date=2026-01-02&end_date=2026-10-23¤cies=XAU"
```
Response:
```json
{
"success": true,
"change": true,
"start_date": "2026-01-02",
"end_date": "2026-10-23",
"base": "USD",
"rates": {
"XAU": { "start_rate": 0.0002309682, "end_rate": 0.0002495072, "change": 0.000018539, "change_pct": 8.02665 },
"USDXAU": { "start_rate": 4329.6, "end_rate": 4007.9, "change": -321.7, "change_pct": -7.43025 }
},
"rate_limit": { "...": "..." }
}
```
### Open / High / Low / Close: GET https://metalapi.com/api/v1/ohlc
Daily OHLC for a date (default: today, intraday so far). Both directions are returned: `USDXAU` in USD per unit and `XAU` as the inverse.
Parameters:
- `api_key` (required): Your API key. Alternatively send `Authorization: Bearer ` or `X-API-Key: `.
- `date`: `YYYY-MM-DD` or `latest` (default).
- `base`: Currency or symbol the rates are expressed in. Default `USD`. Any supported symbol works, e.g. `EUR`, `TRY`, `XAU`.
- `currencies`: Comma-separated symbols to return (alias: `symbols`). Default: all symbols. Unknown codes are ignored.
- `unit`: Weight unit for metals: `troy_oz` (default), `gram`, `kilogram`, `ounce`, `pound`, `metric_ton`, `short_ton`. Ignored for currencies, energy and crypto.
Request:
```bash
curl "https://metalapi.com/api/v1/ohlc?api_key=YOUR_API_KEY&date=2026-10-23¤cies=XAU"
```
Response:
```json
{
"success": true,
"ohlc": true,
"date": "2026-10-23",
"base": "USD",
"rates": {
"USDXAU": { "open": 4042.2, "high": 4050.2, "low": 3989.1, "close": 4007.9 },
"XAU": { "open": 0.000247390, "high": 0.000250683, "low": 0.000246901, "close": 0.000249507 }
},
"rate_limit": { "...": "..." }
}
```
### Gold by karat: GET https://metalapi.com/api/v1/carat
Gold price for jewellery karats (purity = karat / 24). Paid plans.
Parameters:
- `api_key` (required): Your API key. Alternatively send `Authorization: Bearer ` or `X-API-Key: `.
- `carat`: `24` (default), `23`, `22`, `21`, `20`, `18`, `16`, `14`, `12`, `10`, `9`, `8` or `all`.
- `base`: Currency or symbol the rates are expressed in. Default `USD`. Any supported symbol works, e.g. `EUR`, `TRY`, `XAU`.
- `unit`: Weight unit for metals: `troy_oz` (default), `gram`, `kilogram`, `ounce`, `pound`, `metric_ton`, `short_ton`. Ignored for currencies, energy and crypto.
- `date`: Optional `YYYY-MM-DD`.
Request:
```bash
curl "https://metalapi.com/api/v1/carat?api_key=YOUR_API_KEY&carat=22&unit=gram&base=EUR"
```
Response:
```json
{
"success": true,
"carat": "22",
"date": "2026-10-24",
"base": "EUR",
"timestamp": 1792834892,
"rates": {
"XAU22K": { "price": 105.5297, "unit": "gram", "carat": "22", "purity": 0.916667 }
},
"rate_limit": { "...": "..." }
}
```
### Symbols: GET https://metalapi.com/api/v1/symbols
Every supported symbol and its name. Add `details=1` for category and unit.
Quota: Free — does not count towards your quota.
Parameters:
- `api_key` (required): Your API key. Alternatively send `Authorization: Bearer ` or `X-API-Key: `.
- `details`: `1` to return an array with `code`, `name`, `category`, `unit`.
Request:
```bash
curl "https://metalapi.com/api/v1/symbols?api_key=YOUR_API_KEY"
```
Response:
```json
{
"success": true,
"symbols": {
"XAU": "Gold",
"XAG": "Silver",
"XPT": "Platinum",
"XPD": "Palladium",
"XCU": "Copper",
"USD": "US Dollar",
"EUR": "Euro",
"BTC": "Bitcoin"
},
"rate_limit": { "...": "..." }
}
```
### Usage: GET https://metalapi.com/api/v1/usage
Your requests this month and what is left.
Quota: Free — does not count towards your quota.
Parameters:
- `api_key` (required): Your API key. Alternatively send `Authorization: Bearer ` or `X-API-Key: `.
Request:
```bash
curl "https://metalapi.com/api/v1/usage?api_key=YOUR_API_KEY"
```
Response:
```json
{
"success": true,
"usage": {
"requests_made": 125,
"requests_quota": 10000,
"requests_remaining": 9875,
"plan": "standard",
"period_start": "2026-10-01T00:00:00.000Z",
"period_end": "2026-11-01T00:00:00.000Z"
},
"result": { "plan": "Standard", "used": 125, "total": 10000, "remaining": 9875 },
"rate_limit": { "...": "..." }
}
```
### Error codes
| Code | HTTP | Type | Meaning |
|---|---|---|---|
| 101 | 401 | no_api_key | No API key was supplied. |
| 102 | 401 | invalid_api_key | The key does not exist or was revoked. |
| 103 | 403 | account_inactive | The account is suspended. Contact support. |
| 104 | 429 | too_many_requests | Per-minute rate limit exceeded. Retry after the `Retry-After` seconds. |
| 105 | 429 | quota_exceeded | Monthly quota used up. Resets on the 1st (UTC) or upgrade your plan. |
| 106 | 403 | plan_restriction | Endpoint or parameter not available on your plan. |
| 107 | 403 | origin_not_allowed | Browser request from a domain the key is not restricted to. |
| 201 | 400 | invalid_base | Unknown `base`. |
| 202 | 400 | invalid_from | Unknown `from`. |
| 203 | 400 | invalid_to | Missing or unknown `to`. |
| 204 | 400 | invalid_amount | Missing or non-numeric `amount`. |
| 205 | 400 | invalid_date | Not a valid `YYYY-MM-DD` date, or a future date. |
| 206 | 400 | invalid_timeframe | Missing dates or `start_date` after `end_date`. |
| 207 | 400 | timeframe_too_long | Range longer than your plan allows. |
| 208 | 400 | invalid_symbols | None of the requested `currencies` exist. |
| 209 | 400 | invalid_unit | Unknown `unit`. |
| 210 | 400 | invalid_carat | Unknown `carat`. |
| 300 | 404 | no_results | No data for this query (e.g. before history starts). |
| 404 | 404 | invalid_function | Unknown endpoint. |
| 500 | 500 | server_error | Unexpected error. The request is not billed; retry later. |
| 503 | 503 | service_unavailable | Price data temporarily unavailable. |
## Guides
### Getting Started with MetalAPI
https://metalapi.com/guides/getting-started
Create a free account, get your API key and make your first request for live gold, silver, platinum and palladium prices.
#### Introduction
MetalAPI is a JSON REST API for spot precious metals, industrial metals, energy, around 55 currencies and 13 cryptocurrencies. It serves real-time quotes, daily history back to 1999, conversions, OHLC bars and gold prices by karat.
Every endpoint is a plain HTTPS `GET` request under `https://metalapi.com/api/v1`, so it works from any language or HTTP client. This guide takes you from sign-up to your first response in a few minutes.
#### Get your API key
[Create a free account](/register) with your email address. No credit card is required. The Free plan includes 100 requests per month with daily price updates, which is enough to try the API.
Open **API keys** in the dashboard and create a key. Keys start with `mk_live_` followed by 32 characters. We only store a hash of each key, so the full key is shown once. Copy it into your password manager or environment variables straight away.
#### Make your first request
Request the latest prices of the four precious metals in US dollars. Replace `YOUR_API_KEY` with your key:
```bash
curl "https://metalapi.com/api/v1/latest?api_key=YOUR_API_KEY&base=USD¤cies=XAU,XAG,XPT,XPD"
```
#### Understand the response
`base` is the currency the prices are expressed in (default `USD`). `currencies` limits the response to the symbols you need. Leave it out to receive every symbol in one response. A successful call returns JSON like this:
Every symbol appears twice. `USDXAU` is the price of one troy ounce of gold in the base currency, which is usually the number you want to display. `XAU` is the inverse: how many troy ounces one US dollar buys. The [Data formats guide](/guides/data-formats) explains both directions in detail.
The `rate_limit` block, also sent as `X-RateLimit-*` headers, shows how much of your monthly quota is left and when it resets.
```json
{
"success": true,
"timestamp": 1792713600,
"date": "2026-10-23",
"base": "USD",
"rates": {
"XAU": 0.0002501319446,
"USDXAU": 3997.89,
"XAG": 0.01853911754,
"USDXAG": 53.94,
"XPT": 0.000620212733,
"USDXPT": 1612.35,
"XPD": 0.0006775985906,
"USDXPD": 1475.8
},
"rate_limit": {
"limit": 100,
"remaining": 99,
"reset": 1793491200,
"used": 1,
"plan": "Free",
"reset_date": "2026-11-01T00:00:00.000Z"
}
}
```
#### Call it from code
Here is the same request from JavaScript (Node.js 18+, Deno or Bun). This time the key is sent in a header instead of the URL:
```javascript
const res = await fetch("https://metalapi.com/api/v1/latest?currencies=XAU,XAG", {
headers: { Authorization: `Bearer ${process.env.METALAPI_KEY}` },
});
const data = await res.json();
if (!data.success) throw new Error(`${data.error.code}: ${data.error.info}`);
console.log("Gold, USD per troy ounce:", data.rates.USDXAU);
```
#### Next steps
- [Authentication](/guides/authentication): header-based keys and domain-restricted keys for browser use.
- [Available endpoints](/guides/endpoints): historical rates, conversion, time series, OHLC and karat prices.
- [Rate limits & plans](/guides/rate-limits): quotas, per-minute limits and update intervals.
- [Error handling](/guides/error-handling): error codes and a sensible retry strategy.
### API Authentication
https://metalapi.com/guides/authentication
Send your API key as a query parameter or header, restrict keys to your own domains and keep them safe.
#### API keys
Every API request is authenticated with an API key that belongs to your account. Keys look like `mk_live_` followed by 32 characters and are created in the dashboard under **API keys**.
We store only a SHA-256 hash of each key, so the full key is displayed once, when you create it. If you lose a key, create a new one and revoke the old one. Depending on your plan you can hold several keys at once. Using one per application or environment is a good habit.
#### Three ways to send your key
Pass the key in whichever way suits your HTTP client. All three are equivalent:
Prefer a header in production. We remove the key from the query string before writing our request log, but your own logs, proxies and error trackers may keep the full URL.
A missing key returns error `101` and an unknown or revoked key returns `102`, both with HTTP status 401.
```bash
# 1. Query parameter
curl "https://metalapi.com/api/v1/latest?api_key=YOUR_API_KEY"
# 2. Authorization header (Bearer token)
curl "https://metalapi.com/api/v1/latest" \
-H "Authorization: Bearer YOUR_API_KEY"
# 3. X-API-Key header
curl "https://metalapi.com/api/v1/latest" \
-H "X-API-Key: YOUR_API_KEY"
```
#### Domain-restricted keys
Calling the API from client-side JavaScript exposes your key to every visitor. If you have to do it, restrict the key to your own domains in the dashboard first.
A restricted key only answers browser requests whose `Origin` (or `Referer`) header matches one of the allowed domains or their subdomains. Requests from any other website are rejected with error `107` (HTTP 403) and are not billed.
Requests without an `Origin` or `Referer` header, such as server-to-server calls from your backend, cron jobs or scripts, are always allowed. One restricted key can therefore serve both your website and your server. With `example.com` on the allowed list:
```bash
# Allowed: browser request from a subdomain of an allowed domain
curl "https://metalapi.com/api/v1/latest?currencies=XAU" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Origin: https://shop.example.com"
# Rejected with 107: origin is not on the list
curl "https://metalapi.com/api/v1/latest?currencies=XAU" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Origin: https://another-site.com"
# Allowed: server-to-server request without Origin or Referer
curl "https://metalapi.com/api/v1/latest?currencies=XAU" \
-H "X-API-Key: YOUR_API_KEY"
```
#### Keeping keys safe
- Keep keys on the server and load them from environment variables or a secrets manager. Never commit them to Git.
- Use a separate key per application and environment, so you can revoke one without touching the others.
- If a key leaks, create a replacement, deploy it, then revoke the old key in the dashboard.
- Always call the API over `https://`.
- For public web pages, an [embeddable widget](/guides/widgets) shows live prices without any key in the page.
```javascript
// Server-side only: the key never reaches the browser
const res = await fetch("https://metalapi.com/api/v1/latest?currencies=XAU,XAG", {
headers: { "X-API-Key": process.env.METALAPI_KEY },
});
```
### Available Endpoints
https://metalapi.com/guides/endpoints
Every MetalAPI endpoint and its parameters: latest, historical, convert, timeframe, change, OHLC, carat, symbols, usage and stream.
#### Base URL and common parameters
All endpoints are `GET` requests under `https://metalapi.com/api/v1` and require an API key (see [Authentication](/guides/authentication)). Most of them accept the same optional parameters:
- `base`: the currency prices are expressed in. Defaults to `USD`; any supported symbol works.
- `currencies`, or its alias `symbols`: a comma-separated list of symbols to return, such as `XAU,XAG,EUR`. Omit it to get every symbol.
- `unit`: the mass unit for metal prices. One of `troy_oz` (default), `gram`, `kilogram`, `ounce`, `pound`, `metric_ton` or `short_ton`.
Dates use the `YYYY-MM-DD` format and are interpreted in UTC.
#### Latest rates: /latest
Current prices at your plan’s update interval, from daily on Free to every 5 seconds on Enterprise. On the Standard plan and above, add symbols like `XAU-BID` and `XAU-ASK` to `currencies` to receive bid and ask prices.
```http
GET https://metalapi.com/api/v1/latest?api_key=YOUR_API_KEY&base=USD¤cies=XAU,XAG,EUR
```
#### Historical rates: /{date}
Closing prices of a past day. Three forms are accepted: the date as the path, `/historical?date=`, or `/yesterday`.
```http
GET /api/v1/2026-10-01?api_key=YOUR_API_KEY¤cies=XAU,XAG
GET /api/v1/historical?date=2026-10-01&api_key=YOUR_API_KEY¤cies=XAU,XAG
GET /api/v1/yesterday?api_key=YOUR_API_KEY¤cies=XAU,XAG
```
#### Convert: /convert
Converts `amount` of `from` into `to`, for example dollars into ounces of gold or silver into euros. Add `date` to convert at a historical closing price, and `unit` to convert grams or kilograms of a metal.
```http
GET /api/v1/convert?api_key=YOUR_API_KEY&from=USD&to=XAU&amount=1000
# 500 grams of silver in euros at the close of 1 October 2026
GET /api/v1/convert?api_key=YOUR_API_KEY&from=XAG&to=EUR&amount=500&unit=gram&date=2026-10-01
```
#### Timeframe: /timeframe
Daily closing prices for every day between `start_date` and `end_date`. One request can span up to 30 days on the Free plan and 365 days on paid plans. Longer ranges return error `207`. `/timeseries` is an alias kept for compatibility.
```http
GET /api/v1/timeframe?api_key=YOUR_API_KEY&start_date=2026-09-01&end_date=2026-09-30¤cies=XAU
```
#### Change: /change
The start rate, end rate, absolute change and percentage change between two dates for each symbol. Useful for “gold is up 3.6% this month” displays. The range limits are the same as for `/timeframe`.
```http
GET /api/v1/change?api_key=YOUR_API_KEY&start_date=2026-09-30&end_date=2026-10-23¤cies=XAU,XAG
```
#### OHLC: /ohlc
Open, high, low and close for a single day. Omit `date`, or pass `latest`, to get today’s bar so far.
```http
GET /api/v1/ohlc?api_key=YOUR_API_KEY&date=2026-10-22¤cies=XAU,XAG
```
#### Gold by karat: /carat
Gold prices at jewellery purities from 24K down to 8K, in any currency and unit. Pass `carat=24` (the default), a single karat such as `18`, or `carat=all`. Supported karats are 24, 23, 22, 21, 20, 18, 16, 14, 12, 10, 9 and 8. Add `date` for historical karat prices.
This endpoint is available on paid plans. Free keys receive error `106`.
```http
GET /api/v1/carat?api_key=YOUR_API_KEY&carat=all&base=EUR&unit=gram
```
#### Symbols: /symbols
The list of supported symbols and their names. Add `details=1` to include each symbol’s category and unit. This endpoint does not count towards your monthly quota.
```http
GET /api/v1/symbols?api_key=YOUR_API_KEY&details=1
```
#### Usage: /usage
Requests used and remaining in the current billing month. It is also free, so you can call it from monitoring without spending quota.
```http
GET /api/v1/usage?api_key=YOUR_API_KEY
```
#### Realtime stream: /stream
A Server-Sent Events stream that pushes price updates as they happen, so you don't have to poll `/latest`. Available on the Pro plan and above. The [streaming documentation](/docs#streaming) describes the event format.
```bash
curl -N "https://metalapi.com/api/v1/stream" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Rate Limits & Plans
https://metalapi.com/guides/rate-limits
How monthly quotas, per-minute limits and update intervals work on each plan, and how to track your usage.
#### Two kinds of limits
Every plan has a **monthly quota**, the number of billable requests per calendar month, and a **per-minute limit** that protects the API from bursts. Quotas reset at 00:00 UTC on the 1st of each month.
Plans also differ in **update interval**, which is how fresh the prices are. Polling more often than your interval returns the same prices again and only uses up quota.
#### Plans at a glance
- **Free**: 100 requests/month, daily updates, 10 requests/minute
- **Lite**: 1,000 requests/month, 30-minute updates, 30 requests/minute
- **Standard**: 10,000 requests/month, 10-minute updates, 60 requests/minute
- **Advanced**: 50,000 requests/month, 60-second updates, 120 requests/minute
- **Pro**: 100,000 requests/month, 60-second updates, 300 requests/minute
- **Ultra**: 500,000 requests/month, 15-second updates, 600 requests/minute
- **Business**: 500,000 requests/month, 15-second updates, 600 requests/minute, plus an uptime SLA
- **Enterprise**: 1,500,000 requests/month, 5-second updates, 1,200 requests/minute
Features grow with the plan too. Karat prices need a paid plan, bid/ask prices start at Standard, webhook alerts at Advanced and the realtime stream at Pro. See [pricing](/pricing) for current prices.
#### What counts as a request
- Each successful call to a data endpoint counts as one request, no matter how many symbols it returns.
- `/symbols` and `/usage` are free and never count.
- Requests rejected with `104` (per-minute limit), `106` (not on your plan), `107` (origin not allowed) or a server error (`500`, `503`) are not billed.
- Requests that fail because of the query itself do count. That covers validation errors `201` to `210` and `300` (no results), so validate dates and symbols before sending them.
#### Tracking your usage
Every authenticated response reports your quota in headers:
The same numbers are in the `rate_limit` object of the JSON body. `reset` is a Unix timestamp and `reset_date` is the same moment in ISO 8601: the start of next month in UTC.
```http
X-RateLimit-Limit: 10000
X-RateLimit-Remaining: 9876
X-RateLimit-Used: 124
X-RateLimit-Reset: 1793491200
X-RateLimit-Reset-Date: 2026-11-01T00:00:00.000Z
X-RateLimit-Plan: Standard
X-RateLimit-Minute-Limit: 60
X-RateLimit-Minute-Remaining: 59
```
#### Checking usage without spending quota
Call `/usage` from dashboards or monitoring. It is free:
```json
{
"success": true,
"usage": {
"requests_made": 124,
"requests_quota": 10000,
"requests_remaining": 9876,
"plan": "standard",
"period_start": "2026-10-01T00:00:00.000Z",
"period_end": "2026-11-01T00:00:00.000Z"
},
"result": { "plan": "Standard", "used": 124, "total": 10000, "remaining": 9876 },
"rate_limit": {
"limit": 10000,
"remaining": 9876,
"reset": 1793491200,
"used": 124,
"plan": "Standard",
"reset_date": "2026-11-01T00:00:00.000Z"
}
}
```
#### When you hit a limit
Both limits respond with HTTP status 429 and an error body:
- `104` `too_many_requests`: too many requests in the current minute. The response includes a `Retry-After` header with the number of seconds to wait. Slow down and retry.
- `105` `quota_exceeded`: the monthly quota is used up. Requests keep failing until the quota resets on the 1st (UTC). Upgrading your plan takes effect immediately.
```json
{
"success": false,
"error": {
"code": 105,
"type": "quota_exceeded",
"info": "User has reached or exceeded his subscription plan's monthly API request allowance"
}
}
```
#### Staying within your quota
- Cache responses for at least your plan’s update interval.
- Request all the symbols you need in one call instead of one call per symbol.
- Polling every 10 minutes uses about 4,500 requests a month. Polling every minute uses about 45,000. Pick the plan that matches your polling rate.
- On Pro and above, use the realtime stream instead of polling, and use [price alerts](/guides/webhooks) to react to thresholds.
More tips are in [Best practices](/guides/best-practices).
### Embeddable Price Widgets
https://metalapi.com/guides/widgets
Show live metal prices on any website with an embeddable widget. No backend code, and no API key in your page.
#### Overview
Widgets are ready-made price boards that you configure in the dashboard and embed on any website. They load from MetalAPI directly, so there is no backend code to write and your API key never appears in your page.
Every plan includes widgets: 1 on Free, 3 on Lite, 10 on Standard and up to 500 on Enterprise. You can see examples on the [widgets page](/widgets).
#### Create a widget
1. Sign in and open **Widgets** in the dashboard.
2. Create a new widget and give it a name.
3. Pick the symbols to show (metals, energy, currencies or crypto) and the currency to price them in.
4. Choose a layout and theme, then save.
5. Copy the embed code.
#### Embed it on your site
Paste the embed code from the dashboard where the widget should appear. Widgets are served from `metalapi.com/embed/…` and can be framed by any site, so they work in plain HTML pages, WordPress, Shopify themes and site builders alike. An embed looks like this:
Use the exact snippet from the dashboard, since it contains your widget’s ID.
```html
```
#### Updating a widget
Changes you save in the dashboard apply to every page that embeds the widget. You don't need to update the snippet on your site.
#### When to use the API instead
Widgets cover the common case of displaying prices. If you need your own design, calculations or prices inside an app, call the API from your server, or from the browser with a [domain-restricted key](/guides/authentication#domain-restricted-keys).
### Webhooks & Price Alerts
https://metalapi.com/guides/webhooks
Receive signed HTTP callbacks when a price crosses your threshold, and verify them with HMAC-SHA256.
#### How alerts and webhooks work
MetalAPI sends webhooks when a **price alert** fires. An alert watches one symbol in a currency of your choice. It triggers when the price rises above or falls below a threshold, or moves up or down by a percentage since the previous close.
Alerts fire on the crossing: you are notified once when the condition becomes true, and the alert re-arms after the condition has been false again. A cooldown (60 minutes by default, configurable from 1 minute to 7 days) prevents a stream of notifications when a price hovers around the threshold.
Alerts notify you by email on the Lite plan and above, or by webhook (an HTTPS `POST` to your server) on the Advanced plan and above.
#### Setting up a webhook
1. Open **Alerts** in the dashboard and create an alert.
2. Pick the symbol, the currency, the condition and the threshold.
3. Choose webhook delivery and enter your endpoint URL.
4. Copy your webhook signing secret (it starts with `whsec_`) from the dashboard into your server’s configuration.
Webhook URLs must use `https://` on a public host. URLs that contain credentials or resolve to private or internal IP addresses are rejected, and redirects are not followed.
#### The payload
Each delivery is a JSON `POST` with these headers and body:
`condition` is one of `above`, `below`, `change_pct_up` or `change_pct_down`. `price` is the price that triggered the alert, in `base` per `unit`. `id` is unique per event, so you can use it to ignore duplicate deliveries.
```http
POST /webhooks/metalapi HTTP/1.1
Content-Type: application/json
User-Agent: MetalAPI-Webhooks/1.0
X-MetalAPI-Timestamp: 1792765800
X-MetalAPI-Signature: t=1792765800,v1=3b9f0c4e…a71d
{
"event": "alert.triggered",
"id": 18342,
"created": 1792765800,
"alert": {
"id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"symbol": "XAU",
"base": "USD",
"condition": "above",
"threshold": 4000,
"note": "Gold above 4k"
},
"price": 4001.25,
"unit": "troy_oz"
}
```
#### Verifying the signature
Always check that a request really comes from MetalAPI before acting on it. The `X-MetalAPI-Signature` header has the form `t=,v1=`. The signature is the hex-encoded HMAC-SHA256 of the string `.`, keyed with your signing secret.
- Compute the HMAC over the raw body, before any JSON parsing.
- Compare signatures in constant time.
- Reject timestamps older than five minutes to block replayed requests.
In Node.js with Express:
```javascript
import crypto from "node:crypto";
import express from "express";
const app = express();
const SECRET = process.env.METALAPI_WEBHOOK_SECRET;
app.post("/webhooks/metalapi", express.raw({ type: "application/json" }), (req, res) => {
const header = req.get("X-MetalAPI-Signature") ?? "";
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const body = req.body.toString("utf8");
const expected = crypto.createHmac("sha256", SECRET).update(`${parts.t}.${body}`).digest("hex");
const valid =
typeof parts.v1 === "string" &&
parts.v1.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
if (!valid || !fresh) return res.status(400).send("invalid signature");
const event = JSON.parse(body);
console.log(event.alert.symbol, event.alert.condition, event.alert.threshold, event.price);
res.sendStatus(200);
});
```
#### Verifying in PHP
The same check in plain PHP:
```php
300) {
http_response_code(400);
exit('invalid signature');
}
$event = json_decode($payload, true);
// Handle $event['alert'] and $event['price'] here.
http_response_code(200);
```
#### Responding and retries
Return any `2xx` status within 8 seconds. Do slow work after responding, for example in a queue, because timeouts and non-2xx responses count as failed deliveries.
Failed deliveries are retried with growing delays, up to three attempts in total within the first hour after the alert fired. A retry can arrive after a delivery that timed out on our side but succeeded on yours, so store the event `id` and make your handler idempotent.
### Error Handling
https://metalapi.com/guides/error-handling
MetalAPI error codes, their HTTP status codes, and how to handle and retry failed requests.
#### Error response format
When a request fails, the API responds with an HTTP error status and a JSON body with `success: false`. The `error` object contains a numeric `code`, a machine-readable `type` and a human-readable `info` message:
Branch on `code` or `type`, not on the `info` text, which may change. The codes are the same as in earlier versions of MetalAPI, so existing integrations keep working.
```json
{
"success": false,
"error": {
"code": 101,
"type": "no_api_key",
"info": "User did not supply an API Key"
}
}
```
#### Authentication and account errors
- `101` `no_api_key` (401): the request has no API key.
- `102` `invalid_api_key` (401): the key is unknown or has been revoked.
- `103` `account_inactive` (403): the account is not active. Contact support.
- `107` `origin_not_allowed` (403): a domain-restricted key was used from a website that is not on its allowed list.
#### Limit and plan errors
- `104` `too_many_requests` (429): the per-minute limit was reached. Wait for the number of seconds in the `Retry-After` header.
- `105` `quota_exceeded` (429): the monthly quota is used up until the 1st of next month (UTC).
- `106` `plan_restriction` (403): the endpoint or parameter isn't included in your plan, such as `/carat` on Free or `XAU-BID` below Standard.
#### Validation errors
These return HTTP status 400 and mean the request itself needs fixing:
- `201` `invalid_base`: unknown `base` symbol.
- `202` `invalid_from` and `203` `invalid_to`: unknown `from` or `to` symbol in `/convert`.
- `204` `invalid_amount`: `amount` is missing or not a number.
- `205` `invalid_date`: malformed date, or a date in the future.
- `206` `invalid_timeframe`: `start_date` or `end_date` is missing, or the start is after the end.
- `207` `timeframe_too_long`: the range is longer than your plan allows (30 days on Free, 365 on paid plans).
- `208` `invalid_symbols`: none of the requested symbols exist.
- `209` `invalid_unit`: unsupported `unit`.
- `210` `invalid_carat`: unsupported karat.
In `currencies`, unknown symbols are skipped as long as at least one symbol is valid.
#### Other errors
- `300` `no_results` (404): the query is valid but there is no data, for example a date before a symbol’s history starts.
- `404` `invalid_function` (404): unknown endpoint. Check the URL.
- `500` `server_error` (500): an unexpected error on our side.
- `503` `service_unavailable` (503): price data is temporarily unavailable.
#### Handling errors in code
Check `success` on every response and turn failures into errors your code can inspect. This helper also copes with non-JSON responses from proxies:
```javascript
async function metalapi(path, params = {}) {
const url = new URL(`https://metalapi.com/api/v1/${path}`);
for (const [key, value] of Object.entries(params)) url.searchParams.set(key, value);
const res = await fetch(url, { headers: { "X-API-Key": process.env.METALAPI_KEY } });
const data = await res.json().catch(() => ({
success: false,
error: { code: res.status, type: "http_error", info: res.statusText },
}));
if (!data.success) {
const err = new Error(`MetalAPI ${data.error.code} (${data.error.type}): ${data.error.info}`);
err.code = data.error.code;
err.retryAfter = Number(res.headers.get("Retry-After")) || undefined;
throw err;
}
return data;
}
```
#### Retry strategy
Only retry errors that can succeed on a second attempt:
- `104`: wait `Retry-After` seconds, then retry.
- `500`, `503` and network errors: retry with exponential backoff (1 s, 2 s, 4 s) and give up after three retries.
- Everything else (`101` to `103`, `105` to `107`, the `2xx` validation codes, `300`, `404`): don't retry. Fix the request, the key or the plan instead.
Retrying `104` and `5xx` errors costs nothing, because those failed requests are not billed.
```javascript
async function withRetry(fn, retries = 3) {
for (let attempt = 0; ; attempt++) {
try {
return await fn();
} catch (err) {
const retryable = err.code === undefined || err.code === 104 || err.code >= 500;
if (!retryable || attempt >= retries) throw err;
const delay = err.retryAfter ? err.retryAfter * 1000 : 1000 * 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
}
const data = await withRetry(() => metalapi("latest", { currencies: "XAU,XAG" }));
```
### Data Formats & Units
https://metalapi.com/guides/data-formats
How rates are expressed, choosing a base currency and mass unit, bid/ask and karat prices, timestamps and market hours.
#### Two directions for every rate
For each symbol you request, `rates` contains two numbers. For gold (`XAU`) with base `USD`:
- `USDXAU`: the price of one troy ounce of gold in US dollars, such as `3997.89`. This is the number to show to people.
- `XAU`: the inverse, troy ounces of gold per one US dollar, such as `0.0002501319446`. This format is kept for compatibility with forex-style APIs.
Currencies follow the same rule. With base `USD`, `USDEUR` is the price of one euro in dollars and `EUR` is euros per dollar.
```json
{
"success": true,
"timestamp": 1792765800,
"date": "2026-10-23",
"base": "USD",
"rates": {
"XAU": 0.0002501319446,
"USDXAU": 3997.89,
"EUR": 0.8589589418,
"USDEUR": 1.1642
}
}
```
#### Choosing a base currency
`base` sets the currency of every price in the response. It defaults to `USD` and accepts any supported symbol: `EUR`, `GBP`, `JPY`, or even `BTC` or `XAU` if you want to value things in bitcoin or gold. Cross rates are calculated from the same snapshot, so they are always consistent with each other.
This request returns `EURXAU`, the price of gold in euros per troy ounce:
```http
GET /api/v1/latest?api_key=YOUR_API_KEY&base=EUR¤cies=XAU,XAG
```
#### Mass units
Metal prices are per troy ounce (31.1035 g) unless you pass `unit`. Supported values:
- `troy_oz` (default), `gram`, `kilogram`, `ounce` (avoirdupois, 28.35 g), `pound`, `metric_ton` and `short_ton`
Short forms such as `g`, `kg`, `lb` and `tonne` are accepted too. Note that `oz` means the avoirdupois ounce. Use `troy_oz`, or leave `unit` out, for the troy ounce that bullion is priced in.
`unit` applies to precious and industrial metals only. Energy stays per barrel, MMBtu or gallon, and currencies and crypto are unaffected. When a non-default unit is used, the response includes a `unit` field. For `GET /api/v1/latest?base=EUR¤cies=XAU,XAG&unit=gram`:
```json
{
"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
}
}
```
#### Industrial metals and energy
Industrial metals (`XCU` copper, `ALU` aluminium, `ZNC` zinc and `HRC` hot-rolled coil steel) are quoted per troy ounce by default like every other metal, so all metals can be compared directly. To get the unit each market trades in, pass `unit=pound` for copper, `unit=metric_ton` for aluminium and zinc, and `unit=short_ton` for steel.
Energy prices are per contract unit: `WTI` and `BRENT` per barrel, `NATURALGAS` per MMBtu, and `GASOLINE` and `HEATINGOIL` per gallon.
```http
GET /api/v1/latest?api_key=YOUR_API_KEY¤cies=XCU&unit=pound
```
#### Supported symbols
- **Precious metals:** `XAU` gold, `XAG` silver, `XPT` platinum, `XPD` palladium
- **Industrial metals:** `XCU` copper, `ALU` aluminium, `ZNC` zinc, `HRC` steel
- **Energy:** `WTI`, `BRENT`, `NATURALGAS`, `GASOLINE`, `HEATINGOIL`
- **Currencies:** around 55 ISO 4217 currencies, including `USD`, `EUR`, `GBP`, `JPY`, `CHF`, `CAD`, `AUD`, `CNY` and `INR`
- **Crypto:** `BTC`, `ETH`, `SOL`, `XRP`, `BNB`, `ADA`, `DOGE`, `DOT`, `LINK`, `LTC`, `TRX`, `USDT`, `USDC`
The full, current list is on the [symbols page](/symbols) and available from `/api/v1/symbols`.
#### Bid and ask prices
On the Standard plan and above, add `-BID` or `-ASK` to a symbol in `currencies` to receive the bid and ask prices next to the mid price. They are available for symbols quoted from interbank feeds, which are the precious metals and major currencies. For `currencies=XAU,XAU-BID,XAU-ASK`:
```json
"rates": {
"XAU": 0.0002501319446,
"USDXAU": 3997.89,
"XAU-BID": 0.0002501488386,
"USDXAU-BID": 3997.62,
"XAU-ASK": 0.0002501150529,
"USDXAU-ASK": 3998.16
}
```
#### Karat gold prices
`/carat` returns gold prices at jewellery purities, where purity is karat ÷ 24, in any currency and unit. It is available on paid plans. For `GET /api/v1/carat?carat=all&unit=gram` (abbreviated):
```json
{
"success": true,
"carat": "all",
"date": "2026-10-23",
"base": "USD",
"timestamp": 1792765800,
"rates": {
"XAU24K": { "price": 128.53515, "unit": "gram", "carat": "24", "purity": 1 },
"XAU22K": { "price": 117.82389, "unit": "gram", "carat": "22", "purity": 0.916667 },
"XAU18K": { "price": 96.401361, "unit": "gram", "carat": "18", "purity": 0.75 },
"XAU14K": { "price": 74.978836, "unit": "gram", "carat": "14", "purity": 0.583333 }
}
}
```
#### Timestamps and dates
`timestamp` is the Unix time in seconds (UTC) of the prices returned. `date` is the same day as `YYYY-MM-DD` in UTC. Historical responses are stamped at 00:00 UTC of the requested day and contain that day’s closing prices.
```javascript
const date = new Date(data.timestamp * 1000);
console.log(date.toISOString()); // 2026-10-23T14:30:00.000Z
```
#### Precision
Rates are JSON numbers with up to 10 significant digits, which is enough for inverse rates such as `0.0002501319446`. Round only when you display a value, not before you calculate with it.
#### Market hours and weekends
Spot metals and currencies trade around the clock on weekdays. Markets close on Friday at 17:00 New York time and reopen on Sunday at 17:00. Over the weekend prices freeze at the last quote and the API keeps returning Friday’s closing price (the symbol pages show “Market closed”).
Crypto trades 24/7 and keeps updating at weekends. Industrial metals and energy follow the trading hours of their futures exchanges.
### Accessing Historical Data
https://metalapi.com/guides/historical-data
Query daily prices back to 1999, fetch date ranges, compare two dates, get OHLC bars and analyse price trends.
#### What history is available
MetalAPI stores one daily bar (open, high, low and close) per symbol and day:
- Currencies since 1999
- Precious metals, industrial metals and energy since 2000
- Crypto since 2014
Metal prices before October 2026 are derived from front-month futures, so they can differ slightly from the spot price on the same day. All plans can query the full history. Plans only differ in how many days a single range request may cover.
#### A single day
Use the date as the path, the `/historical` endpoint with a `date` parameter, or `/yesterday`. The response contains that day’s closing prices (UTC). For `GET /api/v1/2026-10-01?currencies=XAU,XAG`:
```json
{
"success": true,
"timestamp": 1790812800,
"historical": true,
"date": "2026-10-01",
"base": "USD",
"rates": {
"XAU": 0.0002583044893,
"USDXAU": 3871.4,
"XAG": 0.02097315436,
"USDXAG": 47.68
}
}
```
#### Date ranges with /timeframe
`/timeframe` returns one entry per day between `start_date` and `end_date`, keyed by date. For `GET /api/v1/timeframe?start_date=2026-10-19&end_date=2026-10-23¤cies=XAU`:
Days without a closing price, such as weekends or market holidays, can be missing. Don't assume one entry per calendar day. `/timeseries` is an alias that returns `"timeseries": true` instead of `"timeframe": true`.
```json
{
"success": true,
"timeframe": true,
"start_date": "2026-10-19",
"end_date": "2026-10-23",
"base": "USD",
"rates": {
"2026-10-19": { "XAU": 0.0002537298285, "USDXAU": 3941.2 },
"2026-10-20": { "XAU": 0.0002526049889, "USDXAU": 3958.75 },
"2026-10-21": { "XAU": 0.0002517369852, "USDXAU": 3972.4 },
"2026-10-22": { "XAU": 0.0002506642603, "USDXAU": 3989.4 },
"2026-10-23": { "XAU": 0.0002501319446, "USDXAU": 3997.89 }
}
}
```
#### Ranges longer than your plan allows
A single request can span up to 30 days on Free and 365 days on paid plans. Longer spans return error `207`. Split long ranges into chunks, where each chunk is one request:
```javascript
const DAY = 86_400_000;
const MAX_DAYS = 365; // 30 on the Free plan
const iso = (d) => d.toISOString().slice(0, 10);
async function fetchRange(start, end, currencies) {
const rates = {};
let from = new Date(`${start}T00:00:00Z`);
const last = new Date(`${end}T00:00:00Z`);
while (from <= last) {
const to = new Date(Math.min(from.getTime() + MAX_DAYS * DAY, last.getTime()));
const params = new URLSearchParams({ start_date: iso(from), end_date: iso(to), currencies });
const res = await fetch(`https://metalapi.com/api/v1/timeframe?${params}`, {
headers: { "X-API-Key": process.env.METALAPI_KEY },
});
const data = await res.json();
if (!data.success) throw new Error(`${data.error.code}: ${data.error.info}`);
Object.assign(rates, data.rates);
from = new Date(to.getTime() + DAY);
}
return rates;
}
// Ten years of daily gold prices in 10 requests
const gold = await fetchRange("2016-01-01", "2025-12-31", "XAU");
```
#### Changes between two dates
`/change` compares two days directly, so you don't have to download the range in between. For `GET /api/v1/change?start_date=2026-09-30&end_date=2026-10-23¤cies=XAU`:
Use the prefixed keys such as `USDXAU` for price changes. The inverse keys move in the opposite direction.
```json
{
"success": true,
"change": true,
"start_date": "2026-09-30",
"end_date": "2026-10-23",
"base": "USD",
"rates": {
"XAU": { "start_rate": 0.0002591613539, "end_rate": 0.0002501319446, "change": -0.000009029409258, "change_pct": -3.48409 },
"USDXAU": { "start_rate": 3858.6, "end_rate": 3997.89, "change": 139.29, "change_pct": 3.60986 }
}
}
```
#### Daily OHLC bars
`/ohlc` returns the open, high, low and close of one day, which is what you need for candlestick charts and daily ranges. For `GET /api/v1/ohlc?date=2026-10-22¤cies=XAU`:
```json
{
"success": true,
"ohlc": true,
"date": "2026-10-22",
"base": "USD",
"rates": {
"USDXAU": { "open": 3962.1, "high": 4012.75, "low": 3951.3, "close": 3989.4 },
"XAU": { "open": 0.0002523914086, "high": 0.0002530812644, "low": 0.000249205657, "close": 0.0002506642603 }
}
}
```
#### Historical conversions
Add `date` to `/convert` to convert at a past closing price. For example, this is what 10 troy ounces of silver were worth in euros on 1 October 2026:
```http
GET /api/v1/convert?api_key=YOUR_API_KEY&from=XAG&to=EUR&amount=10&date=2026-10-01
```
#### Analysing trends
The `/timeframe` response is easy to turn into a series. This example computes a 20-day simple moving average of the gold price:
Closing prices of past days never change, so store them permanently instead of requesting them again. Only today’s bar is still moving.
```javascript
// data = response of /timeframe?currencies=XAU
const series = Object.entries(data.rates)
.sort(([a], [b]) => a.localeCompare(b))
.map(([date, rates]) => ({ date, price: rates.USDXAU }));
function movingAverage(points, period) {
return points.slice(period - 1).map((point, i) => ({
date: point.date,
sma: points.slice(i, i + period).reduce((sum, p) => sum + p.price, 0) / period,
}));
}
const sma20 = movingAverage(series, 20);
```
### Best Practices
https://metalapi.com/guides/best-practices
Cache wisely, batch symbols, stream instead of polling, and keep your keys and webhooks secure.
#### Match your polling to your plan
Each plan delivers prices at a fixed update interval: daily on Free, 30 minutes on Lite, 10 minutes on Standard, 60 seconds on Advanced and Pro, 15 seconds on Ultra and Business, and 5 seconds on Enterprise. Requesting `/latest` more often than that returns the same prices and spends quota for nothing.
As a rule of thumb, cache `/latest` for your plan’s update interval and cache data for past days forever.
#### Request many symbols at once
One request can return any number of symbols and costs the same as a request for a single symbol. Ask for everything you need in one call:
Polling once every 10 minutes takes about 4,500 requests a month, and polling once a minute takes about 45,000. Choose your plan accordingly.
```http
GET /api/v1/latest?api_key=YOUR_API_KEY¤cies=XAU,XAG,XPT,XPD,EUR,GBP
```
#### Cache on your side
A small cache in front of the API keeps your app fast and your usage predictable. It also lets you keep serving the last good prices if a request fails:
```javascript
const TTL = 10 * 60 * 1000; // Standard plan: 10-minute updates
let cache = { at: 0, data: null };
export async function getPrices() {
if (cache.data && Date.now() - cache.at < TTL) return cache.data;
const res = await fetch("https://metalapi.com/api/v1/latest?currencies=XAU,XAG,XPT,XPD", {
headers: { "X-API-Key": process.env.METALAPI_KEY },
});
const data = await res.json();
if (!data.success) {
if (cache.data) return cache.data; // serve the last good prices
throw new Error(`${data.error.code}: ${data.error.info}`);
}
cache = { at: Date.now(), data };
return data;
}
```
#### Stream instead of polling
For dashboards and trading tools that need every update, use `/stream` on the Pro plan and above. A single long-lived Server-Sent Events connection delivers new prices as soon as they arrive, with no polling loop to tune.
For threshold logic like “tell me when gold crosses $4,000”, use [price alerts and webhooks](/guides/webhooks) and skip polling entirely.
#### Handle errors gracefully
- Check `success` on every response and branch on `error.code`.
- Retry `104`, `500` and `503` with backoff. Don't retry validation errors.
- If the API is unreachable, keep showing the last good price together with its timestamp.
- Watch the `X-RateLimit-Remaining` header and the [system status](/status) page.
Details and code are in [Error handling](/guides/error-handling).
#### Keep your integration secure
- Store keys in environment variables or a secrets manager, never in Git or client-side bundles.
- Use [domain-restricted keys](/guides/authentication#domain-restricted-keys) for any calls from the browser.
- Use one key per application and environment, and rotate keys when someone leaves or a key may have leaked.
- Verify the `X-MetalAPI-Signature` header of every webhook and reject old timestamps.
#### Performance tips
- Reuse one HTTP client so connections are kept alive between requests.
- Accept compressed responses (`gzip` or `br`). Most HTTP clients do this by default.
- Set a request timeout of around 10 seconds so a slow network never blocks your app.
- Load `/symbols` once at startup and validate user input against it instead of letting bad symbols fail requests.
- Call `/usage` from your monitoring to warn you before you run out of quota. It's free.
### API Documentation Overview
https://metalapi.com/guides/documentation
A one-page tour of the MetalAPI REST API: base URL, authentication, parameters, response format, errors, rate limits and code examples.
#### Base URL
All requests go to `https://metalapi.com/api/v1/` over HTTPS and use `GET`. Responses are JSON with a `success` flag.
This page is a quick tour. The complete reference with every parameter is in the [API documentation](/docs).
#### Authentication
Send your API key as a query parameter, a Bearer token or an `X-API-Key` header. Keys look like `mk_live_` followed by 32 characters. Keep them secret; see [Authentication](/guides/authentication).
```http
# Query parameter
GET https://metalapi.com/api/v1/latest?api_key=YOUR_API_KEY
# Authorization header
GET https://metalapi.com/api/v1/latest
Authorization: Bearer YOUR_API_KEY
# X-API-Key header
GET https://metalapi.com/api/v1/latest
X-API-Key: YOUR_API_KEY
```
#### Endpoints
- `/latest`: current prices
- `/{YYYY-MM-DD}`, `/yesterday` or `/historical?date=`: closing prices of a past day
- `/convert?from=&to=&amount=`: convert any amount, optionally at a past `date`
- `/timeframe?start_date=&end_date=` (alias `/timeseries`): daily prices for a date range
- `/change?start_date=&end_date=`: change between two days
- `/ohlc?date=`: open, high, low and close of a day
- `/carat?carat=24` or `carat=all`: gold prices by karat (paid plans)
- `/symbols`: supported symbols (free, no quota)
- `/usage`: your quota (free, no quota)
- `/stream`: realtime Server-Sent Events (Pro plan and above)
Parameters and examples for each one are in [Available endpoints](/guides/endpoints).
#### Request parameters
- `api_key`: your key, unless you send it in a header
- `base`: the currency of the returned prices, `USD` by default
- `currencies` or `symbols`: comma-separated symbols to return. All symbols by default.
- `unit`: mass unit for metals. One of `troy_oz` (default), `gram`, `kilogram`, `ounce`, `pound`, `metric_ton` or `short_ton`.
```http
GET /api/v1/latest?api_key=YOUR_API_KEY&base=EUR¤cies=XAU,XAG,USD,GBP&unit=gram
```
#### Response format
Successful responses include `success: true`, the data, and a `rate_limit` block. Each symbol appears twice: `XAU` (troy ounces per one unit of the base currency) and `USDXAU` (the base-currency price of one troy ounce). See [Data formats & units](/guides/data-formats).
```json
{
"success": true,
"timestamp": 1792765800,
"date": "2026-10-23",
"base": "USD",
"rates": {
"EUR": 0.8589589418,
"USDEUR": 1.1642,
"XAG": 0.01853911754,
"USDXAG": 53.94,
"XAU": 0.0002501319446,
"USDXAU": 3997.89
},
"rate_limit": {
"limit": 10000,
"remaining": 9876,
"reset": 1793491200,
"used": 124,
"plan": "Standard",
"reset_date": "2026-11-01T00:00:00.000Z"
}
}
```
#### Errors
Errors return an HTTP error status and a `success: false` body with a numeric code, a type and a message. The full list is in [Error handling](/guides/error-handling).
```json
{
"success": false,
"error": {
"code": 102,
"type": "invalid_api_key",
"info": "User did not supply an access key or supplied an invalid access key"
}
}
```
#### Rate limiting
Each plan has a monthly quota, which resets on the 1st at 00:00 UTC, and a per-minute limit. Every authenticated response includes:
- `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Used` for the monthly quota
- `X-RateLimit-Reset` and `X-RateLimit-Reset-Date` for when the quota resets
- `X-RateLimit-Plan` with your plan name
- `X-RateLimit-Minute-Limit` and `X-RateLimit-Minute-Remaining` for the per-minute limit
The same values are in the `rate_limit` JSON block. See [Rate limits & plans](/guides/rate-limits).
#### Supported symbols
- **Precious metals:** `XAU`, `XAG`, `XPT`, `XPD`
- **Industrial metals:** `XCU`, `ALU`, `ZNC`, `HRC`
- **Energy:** `WTI`, `BRENT`, `NATURALGAS`, `GASOLINE`, `HEATINGOIL`
- **Currencies:** around 55, including `USD`, `EUR`, `GBP`, `JPY`, `CHF`, `CAD`, `AUD` and `CNY`
- **Crypto:** 13 coins, including `BTC`, `ETH`, `SOL`, `USDT` and `USDC`
Browse them all on the [symbols page](/symbols).
#### Node.js example
Using the built-in `fetch` (Node.js 18+):
```javascript
const API_KEY = process.env.METALAPI_KEY;
const BASE_URL = "https://metalapi.com/api/v1";
async function getLatestRates(currencies = "XAU,XAG,EUR") {
const res = await fetch(`${BASE_URL}/latest?currencies=${currencies}`, {
headers: { Authorization: `Bearer ${API_KEY}` },
});
const data = await res.json();
if (!data.success) throw new Error(`${data.error.code}: ${data.error.info}`);
return data.rates;
}
const rates = await getLatestRates();
console.log("Gold (USD/oz):", rates.USDXAU);
console.log("Silver (USD/oz):", rates.USDXAG);
```
#### PHP example
Using PHP with cURL:
```php
true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => ["X-API-Key: {$apiKey}"],
]);
$response = curl_exec($curl);
$error = curl_error($curl);
curl_close($curl);
if ($error) {
exit("cURL error: {$error}\n");
}
$data = json_decode($response, true);
if (!$data['success']) {
exit("API error {$data['error']['code']}: {$data['error']['info']}\n");
}
echo "Gold (USD/oz): " . $data['rates']['USDXAU'] . "\n";
echo "Silver (USD/oz): " . $data['rates']['USDXAG'] . "\n";
```
#### Python example
Using the `requests` library:
```python
import os
import requests
API_KEY = os.environ["METALAPI_KEY"]
BASE_URL = "https://metalapi.com/api/v1"
def get_metal_rates(currencies="XAU,XAG,XPT"):
"""Fetch the latest metal prices from MetalAPI."""
response = requests.get(
f"{BASE_URL}/latest",
params={"currencies": currencies},
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=10,
)
data = response.json()
if not data["success"]:
raise RuntimeError(f"{data['error']['code']}: {data['error']['info']}")
return data["rates"]
rates = get_metal_rates()
print(f"Gold: {rates['USDXAU']} USD/oz")
print(f"Silver: {rates['USDXAG']} USD/oz")
print(f"Platinum: {rates['USDXPT']} USD/oz")
```
#### Example request
Converting 1,000 US dollars into troy ounces of gold:
```bash
curl "https://metalapi.com/api/v1/convert?api_key=YOUR_API_KEY&from=USD&to=XAU&amount=1000"
```
#### Example response
`result` is the converted amount and `info.rate` the rate that was applied:
```json
{
"success": true,
"query": { "from": "USD", "to": "XAU", "amount": 1000 },
"info": { "timestamp": 1792765800, "rate": 0.0002501319446, "quote": 0.0002501319446 },
"date": "2026-10-23",
"result": 0.2501319446,
"rate_limit": {
"limit": 10000,
"remaining": 9876,
"reset": 1793491200,
"used": 124,
"plan": "Standard",
"reset_date": "2026-11-01T00:00:00.000Z"
}
}
```
### Use MetalAPI with AI Assistants (MCP)
https://metalapi.com/guides/ai
Connect Claude, ChatGPT, Cursor, VS Code and other AI assistants to live metal prices with the MetalAPI MCP server, by signing in or with an API key.
#### What the MCP server does
MetalAPI runs a [Model Context Protocol](https://modelcontextprotocol.io) server at `https://metalapi.com/mcp`. Once connected, your assistant can look up live and historical prices, convert amounts, chart time series and check your plan and usage, all from a chat.
There are two ways to connect:
- **Sign in (OAuth)**: Claude, ChatGPT and other apps that support custom connectors. You sign in to MetalAPI (or create a free account) and approve the connection.
- **API key**: Claude Code, Cursor, VS Code and any client that can send a header. Use a key from your [dashboard](/dashboard/keys).
#### Claude (claude.ai and Claude Desktop)
1. Open **Settings → Connectors** and choose **Add custom connector**.
2. Name it MetalAPI and enter the URL `https://metalapi.com/mcp`.
3. Click **Connect**, sign in to MetalAPI and click **Allow**.
Then ask, for example: "What is the gold price in euros per gram today?"
#### ChatGPT
In **Settings → Apps & Connectors**, create a connector (developer mode) with the URL `https://metalapi.com/mcp` and OAuth authentication. ChatGPT opens the MetalAPI sign-in; approve the connection and the tools are ready.
#### Claude Code
Add the server with your API key:
```bash
claude mcp add --transport http metalapi https://metalapi.com/mcp \
--header "Authorization: Bearer YOUR_API_KEY"
# Or sign in instead of using a key: add it without --header, then run /mcp in Claude Code.
```
#### Cursor
Add MetalAPI to `~/.cursor/mcp.json` (or `.cursor/mcp.json` in a project):
```json
{
"mcpServers": {
"metalapi": {
"url": "https://metalapi.com/mcp",
"headers": { "Authorization": "Bearer YOUR_API_KEY" }
}
}
}
```
#### VS Code (GitHub Copilot)
Create `.vscode/mcp.json`; VS Code asks for the key once and stores it securely:
```json
{
"inputs": [
{ "type": "promptString", "id": "metalapi-key", "description": "MetalAPI API key", "password": true }
],
"servers": {
"metalapi": {
"type": "http",
"url": "https://metalapi.com/mcp",
"headers": { "Authorization": "Bearer ${input:metalapi-key}" }
}
}
}
```
#### Tools
- `get_latest_prices`, `get_historical_prices`, `convert`, `get_timeseries`, `get_fluctuation`, `get_ohlc`, `get_carat_prices`: market data. Each call counts as one request against your plan, exactly like the REST API, and is as fresh as your plan allows.
- `list_symbols`, `get_account`, `list_plans`, `list_api_keys`: free of charge.
- `get_upgrade_link`: a secure Stripe checkout link for a bigger plan. Your assistant can prepare it, but only you can pay.
- `create_api_key`: creates a key for your own apps (only when connected by signing in).
#### For AI coding assistants
Building an integration with an AI coding tool? Point it at [llms.txt](/llms.txt), the complete [llms-full.txt](/llms-full.txt) or the [OpenAPI specification](/openapi.json); they describe every endpoint, parameter and response.
#### Security
Connections made by signing in are listed under **Settings → Connected apps** in your [dashboard](/dashboard/settings), where you can disconnect them at any time. API keys can be rolled or revoked under [API keys](/dashboard/keys); a key in an MCP config is as sensitive as one in your code.