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:
{
"success": false,
"error": {
"code": 101,
"type": "no_api_key",
"info": "User did not supply an API Key"
}
}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.
Authentication and account errors
101no_api_key(401): the request has no API key.102invalid_api_key(401): the key is unknown or has been revoked.103account_inactive(403): the account is not active. Contact support.107origin_not_allowed(403): a domain-restricted key was used from a website that is not on its allowed list.
Limit and plan errors
104too_many_requests(429): the per-minute limit was reached. Wait for the number of seconds in theRetry-Afterheader.105quota_exceeded(429): the monthly quota is used up until the 1st of next month (UTC).106plan_restriction(403): the endpoint or parameter isn't included in your plan, such as/caraton Free orXAU-BIDbelow Standard.
Validation errors
These return HTTP status 400 and mean the request itself needs fixing:
201invalid_base: unknownbasesymbol.202invalid_fromand203invalid_to: unknownfromortosymbol in/convert.204invalid_amount:amountis missing or not a number.205invalid_date: malformed date, or a date in the future.206invalid_timeframe:start_dateorend_dateis missing, or the start is after the end.207timeframe_too_long: the range is longer than your plan allows (30 days on Free, 365 on paid plans).208invalid_symbols: none of the requested symbols exist.209invalid_unit: unsupportedunit.210invalid_carat: unsupported karat.
In currencies, unknown symbols are skipped as long as at least one symbol is valid.
Other errors
300no_results(404): the query is valid but there is no data, for example a date before a symbol’s history starts.404invalid_function(404): unknown endpoint. Check the URL.500server_error(500): an unexpected error on our side.503service_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:
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: waitRetry-Afterseconds, then retry.500,503and network errors: retry with exponential backoff (1 s, 2 s, 4 s) and give up after three retries.- Everything else (
101to103,105to107, the2xxvalidation 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.
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" }));Related guides
Ready to start building?
Create a free account to get your API key, or explore every endpoint in the API reference.