Calenzy API
APIs/Data feeds

Metals API

Hourly gold, silver, platinum and palladium prices in euros, with history since November 2023.

/v1/metalsNot counted in your quota2 endpoints

Made for systems that keep their own copy of the prices: load the whole history once, then ask every hour for what is new. We collect XAU (gold), XAG (silver), XPT (platinum) and XPD (palladium) around minute 40 of every hour, in euros per gram and per kilo.

Times are UTC, ISO 8601 with a Z. quoted_at is the market time of the quote, added_at the time we stored it.

We serve the feed as collected. Remove duplicates on metal and quoted_at (a handful exist), and skip any quote more than 50% away from the last one you kept: the provider sent a wrong price for two hours on 15 April 2024.
GET/v1/metals/latest

Latest prices

The newest quote of each of the four metals.

Response
statusstringok
countintegerRows returned, one per metal
rows[].idintegerQuote ID, growing over time: the cursor for the history
rows[].metalstringXAU, XAG, XPT or XPD
rows[].currencystringAlways EUR
rows[].per_gram, rows[].per_kilonumberPrice of a gram and of a kilo, in euros
rows[].quoted_atstringMarket time of the quote, UTC
rows[].added_atstringWhen we stored it, UTC
Request
GETapi.calenzy.app/v1/metals/latest
Response · 200 OK
{
  "status": "ok",
  "count": 4,
  "rows": [
    { "id": 96959, "metal": "XAU", "currency": "EUR", "per_gram": 127.51910322242, "per_kilo": 127519.10322242, "quoted_at": "2026-08-24T00:10:05Z", "added_at": "2026-08-24T00:40:03Z" },
    { "id": 96958, "metal": "XAG", "currency": "EUR", "per_gram": 1.9148696089158, "per_kilo": 1914.8696089158, "quoted_at": "2026-08-24T00:10:05Z", "added_at": "2026-08-24T00:40:03Z" },
    { "id": 96961, "metal": "XPT", "currency": "EUR", "per_gram": 51.9, "per_kilo": 51900.0, "quoted_at": "2026-08-24T00:10:05Z", "added_at": "2026-08-24T00:40:03Z" },
    { "id": 96960, "metal": "XPD", "currency": "EUR", "per_gram": 37.1, "per_kilo": 37100.0, "quoted_at": "2026-08-24T00:10:05Z", "added_at": "2026-08-24T00:40:03Z" }
  ]
}
GET/v1/metals/history

History

Quotes page by page, oldest first, from a cursor.

Start with after_id=0 to load everything, then pass the next_after_id of the last page you received. An empty page means you are up to date.

Query
after_idintegerQuotes after this ID. 0 by default, the start of the history
limitintegerRows per page, 1 to 5000. 1000 by default; a value out of range counts as 5000
metalstringOnly XAU, XAG, XPT or XPD
sincestringOnly quotes from this day on, YYYY-MM-DD, UTC. Works together with after_id
Response
statusstringok
countintegerRows in this page, 0 when you are up to date
next_after_idintegerThe ID of the last row, to send as after_id next time. Equal to your after_id when the page is empty
rows[]arrayQuotes, oldest first, as in Latest prices
GETapi.calenzy.app/v1/metals/history?after_id=0&limit=5000
Response · 200 OK
{
  "status": "ok",
  "count": 2,
  "next_after_id": 96959,
  "rows": [
    { "id": 96958, "metal": "XAG", "currency": "EUR", "per_gram": 1.9148696089158, "per_kilo": 1914.8696089158, "quoted_at": "2026-08-24T00:10:05Z", "added_at": "2026-08-24T00:40:03Z" },
    { "id": 96959, "metal": "XAU", "currency": "EUR", "per_gram": 127.51910322242, "per_kilo": 127519.10322242, "quoted_at": "2026-08-24T00:10:05Z", "added_at": "2026-08-24T00:40:03Z" }
  ]
}

Errors

Besides these, any call can answer one of the common errors.

CodeHTTPWhen
bad_request400An unknown metal, or a since that is not YYYY-MM-DD