API de preços do ouro em Python: consulta, cache e gráficos
API de preços do ouro em Python: cotações ao vivo com requests, tratamento de erros e novas tentativas, cache e gráficos de séries do pandas com matplotlib.
API de preços do ouro em Python: o que você precisa
Este tutorial mostra como usar uma API de preços do ouro em Python, da primeira requisição até um cliente com cache e um gráfico dos últimos dez meses. Ele usa a MetalAPI, uma API REST em JSON para metais preciosos, metais industriais, energia, moedas e cripto, junto com três bibliotecas conhecidas: requests para HTTP, pandas para a série temporal e matplotlib para o gráfico. Todos os trechos rodam no Python 3.9 ou mais recente.
Crie uma conta grátis e crie uma chave de API em API keys no painel. As chaves começam com mk_live_ e são exibidas uma única vez, então copie a sua direto para uma variável de ambiente. O plano Free não exige cartão de crédito e inclui 100 requisições por mês com atualização diária dos preços, o suficiente para tudo o que está neste artigo.
pip install requests pandas matplotlib
# macOS e Linux
export METALAPI_KEY="mk_live_your_key_here"
# Windows PowerShell
$env:METALAPI_KEY = "mk_live_your_key_here"Leia a chave com os.environ["METALAPI_KEY"], que falha de forma explícita quando a variável não existe, em vez de colá-la no script. Se você prefere um arquivo .env, o pacote python-dotenv o carrega com uma única chamada a load_dotenv(). Adicione .env ao .gitignore para que a chave nunca chegue ao seu repositório.
Como obter o preço do ouro em Python
Todo endpoint da MetalAPI é uma requisição GET sob https://metalapi.com/api/v1. A chave pode ir em um parâmetro de consulta api_key, em um cabeçalho Authorization: Bearer ou em um cabeçalho X-API-Key. Use um cabeçalho: URLs completas acabam em logs, e algumas exceções do requests imprimem a URL com a query string. O endpoint /latest retorna os preços atuais, base define a moeda e currencies limita a resposta aos símbolos de que você precisa:
import os
import requests
resp = requests.get(
"https://metalapi.com/api/v1/latest",
params={"base": "USD", "currencies": "XAU"},
headers={"X-API-Key": os.environ["METALAPI_KEY"]},
timeout=10,
)
data = resp.json()
if not data["success"]:
raise SystemExit(f"MetalAPI error {data['error']['code']}: {data['error']['info']}")
price = data["rates"][data["base"] + "XAU"]
print(f"Gold: {price:,.2f} {data['base']} per troy ounce")A resposta tem esta aparência. Os números são os valores de exemplo usados em toda a documentação da MetalAPI, não preços ao vivo:
{
"success": true,
"timestamp": 1792834892,
"date": "2026-10-24",
"base": "USD",
"rates": { "XAU": 0.0002501319, "USDXAU": 3997.89 },
"rate_limit": { "limit": 100, "remaining": 99, "used": 1, "plan": "Free", "...": "..." }
}Com esses valores, o script imprime Gold: 3,997.89 USD per troy ounce. rates["USDXAU"] é o preço de uma onça troy de ouro na moeda base. rates["XAU"] é o seu inverso, onças de ouro por dólar, e lê-lo por engano é o bug mais comum em código de preços do ouro. Montar a chave a partir de data["base"] mantém o script correto quando você muda para base=EUR (chave EURXAU) ou base=INR. Adicione unit=gram ou unit=kilogram para obter preços por grama ou por quilograma. timestamp é o horário dos dados de preço em segundos Unix, e não o horário da sua requisição.
Tratando erros da API de preços do ouro em Python
Uma chamada com falha retorna um status HTTP de erro e um corpo JSON com success: false e um objeto error que contém um code numérico, um type e uma mensagem info. Tome decisões com base no código, nunca no texto da mensagem. Só alguns códigos merecem uma nova tentativa:
104(too_many_requests, HTTP 429): o limite por minuto. Aguarde o número de segundos indicado no cabeçalhoRetry-After.500,503e erros de rede: tente de novo com backoff exponencial.- Todo o resto, como
101ou102(chave ausente ou inválida),105(cota mensal esgotada),106(endpoint fora do seu plano) e os erros de validação2xx: corrija a requisição, a chave ou o plano.
Este pequeno módulo encapsula essa lógica. Ele reutiliza uma única requests.Session, para que as conexões fiquem abertas entre as chamadas, e lança uma exceção que você pode inspecionar:
import os
import time
import requests
API_URL = "https://metalapi.com/api/v1"
session = requests.Session()
session.headers["X-API-Key"] = os.environ["METALAPI_KEY"]
class MetalAPIError(Exception):
def __init__(self, code, type_, info, retry_after=None):
super().__init__(f"MetalAPI {code} ({type_}): {info}")
self.code = code
self.type = type_
self.retry_after = retry_after
def _get(path, params):
r = session.get(f"{API_URL}/{path}", params=params, timeout=10)
try:
data = r.json()
except ValueError: # não é JSON, p. ex. uma página de erro HTML de um proxy
raise MetalAPIError(r.status_code, "http_error", r.reason) from None
if not data.get("success"):
err = data["error"]
retry_after = r.headers.get("Retry-After")
raise MetalAPIError(err["code"], err["type"], err["info"], int(retry_after) if retry_after else None)
return data
def call(path, params=None, retries=3):
"""GET em um endpoint. Repete apenas limites de requisições, erros do servidor e falhas de rede."""
for attempt in range(retries + 1):
try:
return _get(path, params or {})
except (MetalAPIError, requests.RequestException) as exc:
code = getattr(exc, "code", None) # None para erros de rede
if attempt == retries or (code is not None and code != 104 and code < 500):
raise
time.sleep(getattr(exc, "retry_after", None) or 2**attempt)O backoff espera 1, 2 e 4 segundos antes de desistir. Tentar de novo não custa nada aqui, porque requisições rejeitadas com 104 ou com erro do servidor não são cobradas. Erros de validação são cobrados, o que é mais um motivo para nunca repeti-los em loop. O guia de tratamento de erros lista todos os códigos com o respectivo status HTTP.
Cache de preços do ouro para economizar requisições
Cada plano atualiza os preços em um intervalo fixo: uma vez por dia no Free, a cada 30 minutos no Lite, a cada 10 minutos no Standard e a cada 60 segundos no Advanced e no Pro. As atualizações caem em múltiplos desse intervalo contados a partir de 00:00 UTC, então perguntar de novo antes da próxima retorna os mesmos números e gasta cota à toa. Este cache guarda cada resposta até a próxima atualização e serve os últimos preços válidos se a API não puder ser acessada:
import math
import time
import requests
from metalapi import MetalAPIError, call
# Segundos. Free: 86_400, Lite: 3_600 (de hora em hora cabe nas 1.000 requisições), Standard: 600
UPDATE_INTERVAL = 600
_cache = {} # (base, symbols, unit) -> (expires_at, data)
def latest(base="USD", symbols="XAU,XAG", unit="troy_oz"):
key = (base, symbols, unit)
now = time.time()
hit = _cache.get(key)
if hit and now < hit[0]:
return hit[1]
try:
data = call("latest", {"base": base, "currencies": symbols, "unit": unit})
except (MetalAPIError, requests.RequestException):
if not hit:
raise
_cache[key] = (now + 60, hit[1]) # serve os últimos preços válidos e tenta de novo em um minuto
return hit[1]
next_update = (math.floor(now / UPDATE_INTERVAL) + 1) * UPDATE_INTERVAL
_cache[key] = (next_update + 5, data) # expira alguns segundos após a próxima atualização
return datalatest("EUR", "XAU", "gram")["rates"]["EURXAU"] retorna o preço em euros por grama. Peça todos os símbolos de que você precisa em uma só chamada: XAU,XAG,XPT,XPD custa uma requisição, não quatro. Lembre-se de que este cache vive na memória. Um script iniciado pelo cron começa sempre com o cache vazio, então ali o próprio agendamento é o cache: execute-o no máximo uma vez por intervalo de atualização. Para acompanhar a sua cota, chame /usage, que nunca conta para ela.
Carregando uma série temporal do preço do ouro no pandas
Para o histórico, o endpoint /timeframe (alias /timeseries) retorna preços de fechamento diários entre start_date e end_date, indexados por data. Uma requisição cobre até 30 dias no plano Free e 365 dias nos planos pagos, e um intervalo maior falha com o erro 207. Esta função divide qualquer intervalo em blocos permitidos e retorna uma Series do pandas:
from datetime import date, timedelta
import pandas as pd
from metalapi import call
def gold_history(start, end, base="USD", max_days=30): # max_days=365 nos planos pagos
rates = {}
chunk_start = start
while chunk_start <= end:
chunk_end = min(chunk_start + timedelta(days=max_days - 1), end)
data = call("timeframe", {
"start_date": chunk_start.isoformat(),
"end_date": chunk_end.isoformat(),
"currencies": "XAU",
"base": base,
})
rates.update(data["rates"])
chunk_start = chunk_end + timedelta(days=1)
df = pd.DataFrame.from_dict(rates, orient="index")
df.index = pd.to_datetime(df.index)
return df[f"{base}XAU"].sort_index().rename(f"gold_{base.lower()}")
gold = gold_history(date(2026, 1, 1), date(2026, 10, 23))
gold.to_csv("gold_usd.csv")
print(f"{len(gold)} trading days, high {gold.max():,.2f}, low {gold.min():,.2f}")No plano Free, esses dez meses levam 10 requisições. A Series tem uma linha por pregão: fins de semana e feriados do mercado ficam de fora em vez de serem preenchidos, então espere menos linhas do que dias corridos. Se você precisa de um valor para cada dia, gold.asfreq("D").ffill() repete o fechamento de sexta-feira ao longo do fim de semana. A partir daqui valem as ferramentas habituais do pandas, como gold.pct_change() para retornos diários ou gold.rolling(20).mean() para uma média móvel.
Os preços de fechamento passados nunca mudam, então o arquivo CSV é o seu cache para o histórico: carregue-o da próxima vez em vez de solicitar o mesmo intervalo de novo. Para um backfill completo desde 2000 em um banco de dados, veja Preços históricos de ouro e prata via API.
Plotando o preço do ouro com matplotlib
O gráfico lê o arquivo CSV, então redesenhá-lo não custa nenhuma requisição. Ele plota o fechamento diário junto com uma média móvel de 20 dias:
import matplotlib.pyplot as plt
import pandas as pd
gold = pd.read_csv("gold_usd.csv", index_col=0, parse_dates=True)["gold_usd"]
sma20 = gold.rolling(20).mean()
fig, ax = plt.subplots(figsize=(10, 5))
ax.plot(gold.index, gold, linewidth=1, label="Gold, USD per troy oz")
ax.plot(sma20.index, sma20, linewidth=2, label="20-day moving average")
ax.set_title(f"Gold price, {gold.index[0]:%d %b %Y} to {gold.index[-1]:%d %b %Y}")
ax.yaxis.set_major_formatter("{x:,.0f}")
ax.grid(alpha=0.3)
ax.legend()
fig.autofmt_xdate()
fig.tight_layout()
fig.savefig("gold.png", dpi=150)Como o índice contém datas reais, o matplotlib espaça os pontos de acordo com o tempo, e a média móvel começa no 20º pregão. Para fazer o gráfico do ouro em outra moeda, chame gold_history com base="EUR": as chaves das cotações seguem o mesmo padrão, então a função funciona sem alterações.
Próximos passos
Agora você tem o que a maioria das integrações em Python precisa: uma requisição autenticada, tratamento de erros com novas tentativas sensatas, um cache que segue o intervalo de atualização do seu plano e o histórico no pandas. Desenvolva e teste tudo no plano Free. Quando precisar de preços mais recentes, escolha um plano pelo intervalo de atualização e pelo volume: um job que roda a cada 10 minutos consome cerca de 4.500 requisições por mês, o que cabe nas 10.000 do Standard. Compare os planos na página de preços.
Se os preços forem parar em um site, API de preços do ouro em JavaScript mostra como servi-los a partir de uma rota Node.js sem expor a sua chave.
Perguntas frequentes
Existe uma API de preços do ouro gratuita para Python?
Sim. O plano Free da MetalAPI inclui 100 requisições por mês com atualização diária dos preços e não exige cartão de crédito, e todos os trechos deste artigo funcionam nele. Os preços mais recentes são o fechamento do dia anterior, e uma requisição /timeframe cobre até 30 dias. API de preços do ouro gratuita explica o plano gratuito em mais detalhes.
Por que a cotação XAU é um número tão pequeno?
rates["XAU"] é a cotação inversa: onças troy de ouro por uma unidade da moeda base. O preço está na chave com prefixo, rates["USDXAU"] para a base USD ou rates["EURXAU"] para a base EUR. Com os valores de exemplo, 1 ÷ 0,0002501319 resulta novamente em 3.997,89.
Como obter o preço do ouro por grama em Python?
Adicione unit=gram à requisição, por exemplo call("latest", {"base": "EUR", "currencies": "XAU", "unit": "gram"}). rates["EURXAU"] passa então a ser o preço em euros por grama, e a resposta inclui "unit": "gram". Evite unit=ounce: ela é a onça avoirdupois, e não a onça troy em que o ouro é cotado.
Desde quando existe histórico de preços do ouro?
Os preços diários do ouro começam em 2000, e todos os planos podem consultar o histórico completo. Os planos diferem apenas em quantos dias uma requisição /timeframe pode cobrir: 30 no Free e 365 nos planos pagos. Com max_days=365, tudo de 2000 até hoje leva 27 requisições.
Com que frequência um script Python deve chamar a API?
No máximo com a frequência com que o seu plano atualiza os preços: uma vez por dia no Free, após 00:00 UTC; de hora em hora no Lite, porque a cada 30 minutos seriam cerca de 1.490 requisições por mês para uma cota de 1.000; a cada 10 minutos no Standard. Espere preços parados no fim de semana, quando o ouro à vista não é negociado das 17h de sexta às 17h de domingo, horário de Nova York.
Experimente com seus próprios dados
Crie uma conta grátis e faça sua primeira requisição em menos de dois minutos. 100 requisições por mês, sem cartão de crédito.
Continue lendo
Significado de XAU/USD: o que são XAU, XAG, XPT e XPD
O significado de XAU/USD: por que o ouro tem um código de moeda ISO 4217, o que XAG, XPT e XPD representam e como ler a direção das cotações em preços e APIs.
API de preço do ouro grátis: o que 100 requisições por mês fazem
Uma API de preço do ouro grátis, sem exageros: o que o plano Free da MetalAPI inclui, como render 100 requisições por mês com cache e lotes, e quando migrar.
API de preços do ouro em JavaScript: Node.js, TypeScript e Next.js
Obtenha o preço do ouro em JavaScript com uma API: fetch com async/await, tipos TypeScript, uma rota Express ou Next.js com cache e uso seguro da chave de API.