Uma tarefa só, dois ambientes: montar uma carteira com os tickers de teste
PETR4, VALE3, ITUB4 e MGLU3 e imprimir preço, variação, moeda e proventos dos
últimos 12 meses. A função de planilha e o script Python compartilham as
mesmas colunas (ticker, nome, preco, variacao_pct, moeda,
dividendos_12m), então você troca de ambiente sem reescrever nada.
A chamada é a mesma do guia de integração:
GET /api/v2/stocks/quote?symbols=PETR4 com
Authorization: Bearer BRAPI_TOKEN. Esses quatro tickers respondem sem token
no sandbox. O exemplo faz uma requisição por ativo porque o plano gratuito
aceita 1 ativo por requisição; os planos startup e pro podem juntar até 10 e
20 ativos na mesma chamada. Guarde o token no ambiente, nunca no código.
Dividendos e JCP ficam em /api/v2/stocks/dividends
e o plano gratuito recebe HTTP 403 (FEATURE_NOT_AVAILABLE) nessa rota. Com
plano startup ou superior a chamada passa. O exemplo soma os eventos de
cashDividends pagos nos últimos 12 meses com startDate e endDate; sem
acesso, ele imprime uma mensagem clara e deixa a coluna dividendos_12m sem
dados.
Salve o bloco abaixo como carteira.py, rode pip install requests e
BRAPI_TOKEN=... python carteira.py. O token vem do ambiente e nunca
aparece no código nem nos logs:
# Carteira: preço, variação e proventos dos mesmos tickers do exemplo de planilha.
# Requer: pip install requests. Cada ativo usa duas requisições.
import datetime
import os
import sys
import requests
QUOTE_URL = "https://brapi.dev/api/v2/stocks/quote"
DIVIDENDS_URL = "https://brapi.dev/api/v2/stocks/dividends"
TICKERS = ["PETR4", "VALE3", "ITUB4", "MGLU3"]
COLUMNS = ["ticker", "nome", "preco", "variacao_pct", "moeda", "dividendos_12m"]
END_DATE = datetime.date.today()
START_DATE = END_DATE - datetime.timedelta(days=365)
def fetch_quote(token, ticker):
"""Retorna uma linha da carteira nas colunas compartilhadas."""
response = requests.get(
QUOTE_URL,
params={"symbols": ticker},
headers={"Authorization": f"Bearer {token}"},
timeout=10,
)
if response.status_code == 401:
sys.exit("Token ausente ou inválido. Copie a chave no painel da brapi.")
if response.status_code == 429:
sys.exit("Limite de requisições atingido. Aguarde ou reduza a frequência.")
response.raise_for_status()
results = response.json().get("results", [])
data = (results[0].get("data") or {}) if results else {}
return {
"ticker": data.get("symbol", ticker),
"nome": data.get("shortName"),
"preco": data.get("regularMarketPrice"),
"variacao_pct": data.get("regularMarketChangePercent"),
"moeda": data.get("currency"),
}
def fetch_dividends(token, rows):
"""Soma os proventos dos últimos 12 meses em cada linha.
Requer o plano startup; sem acesso, a coluna fica sem dados."""
for row in rows:
response = requests.get(
DIVIDENDS_URL,
params={"symbols": row["ticker"],
"startDate": START_DATE.isoformat(),
"endDate": END_DATE.isoformat()},
headers={"Authorization": f"Bearer {token}"},
timeout=10,
)
if response.status_code == 403:
print("Dividendos exigem o plano startup; coluna sem dados.")
return rows
if response.status_code == 429:
print("Limite de requisições atingido; coluna sem dados.")
return rows
response.raise_for_status()
results = response.json().get("results", [])
cash = ((results[0].get("data") or {}).get("cashDividends") or []) if results else []
row["dividendos_12m"] = round(
sum(float(event["rate"]) for event in cash), 2
)
return rows
def format_value(row, column):
value = row.get(column)
return "sem dados" if value is None else value
token = os.environ.get("BRAPI_TOKEN")
if not token:
sys.exit("Defina BRAPI_TOKEN no ambiente antes de rodar.")
rows = [fetch_quote(token, ticker) for ticker in TICKERS]
rows = fetch_dividends(token, rows)
for row in rows:
print(", ".join(f"{column}={format_value(row, column)}" for column in COLUMNS))
# Um ativo por requisição mantém o exemplo válido no plano gratuito.
# Nos planos startup e pro, junte até 10 ou 20 tickers por chamada.
# Proventos pagos exigem o plano startup: https://brapi.dev/docs/acoes/dividendosCole as três funções em Extensões > Apps Script, salve, e em
Configurações do projeto > Propriedades do script adicione BRAPI_TOKEN
com o valor do painel. Depois, com os tickers em A2:A5, use:
=BRAPI_CARTEIRA(A2:A5)/**
* Preenche as colunas da carteira para um intervalo de tickers.
* Mesma tarefa do exemplo Python: uma requisição por ativo em cada endpoint.
* Uso (tickers em A2:A5):
* =BRAPI_CARTEIRA(A2:A5)
* O token vem da propriedade do script BRAPI_TOKEN
* (Propriedades do projeto > Propriedades do script), nunca do código.
* @param {Range} tickers - Intervalo com os códigos dos ativos (coluna).
* @return {Array<Array>} Linhas com ticker, nome, preco, variacao_pct,
* moeda e dividendos_12m. A coluna de proventos fica vazia quando o
* plano não libera dividendos.
* @customfunction
*/
function BRAPI_CARTEIRA(tickers) {
if (!tickers) return [['Erro: informe o intervalo de tickers']];
var token = PropertiesService.getScriptProperties()
.getProperty('BRAPI_TOKEN');
if (!token) {
return [['Erro: defina BRAPI_TOKEN nas propriedades do script']];
}
var lista = [];
for (var i = 0; i < tickers.length; i++) {
var t = String(tickers[i][0] || '').toUpperCase().trim();
if (t) lista.push(t);
}
if (lista.length === 0) return [['Erro: nenhum ticker no intervalo']];
var header = ['ticker', 'nome', 'preco', 'variacao_pct', 'moeda', 'dividendos_12m'];
var rows = [];
var dividendsAllowed = true;
for (var j = 0; j < lista.length; j++) {
var quote = BRAPI_COTACAO_(token, lista[j]);
if (quote.error) return [[quote.error]];
var row = quote.row || [lista[j], 'sem dados', null, null, null];
var total = null;
if (dividendsAllowed) {
var dividends = BRAPI_PROVENTOS_(token, lista[j]);
if (dividends.blocked) dividendsAllowed = false;
else total = dividends.total;
}
row.push(total);
rows.push(row);
}
return [header].concat(rows);
}
/**
* Busca a cotação de um ativo. Devolve {row: [...]} ou {error: 'mensagem'}.
* @param {string} token - Token da API brapi.dev.
* @param {string} ticker - Código do ativo.
*/
function BRAPI_COTACAO_(token, ticker) {
var url = 'https://brapi.dev/api/v2/stocks/quote' +
'?symbols=' + encodeURIComponent(ticker);
var response = UrlFetchApp.fetch(url, {
muteHttpExceptions: true,
headers: { Authorization: 'Bearer ' + token },
});
var status = response.getResponseCode();
if (status === 401) {
return { error: 'Erro 401: token ausente ou inválido' };
}
if (status === 429) {
return { error: 'Erro 429: limite de requisições atingido; aguarde' };
}
if (status !== 200) return { error: 'Erro HTTP ' + status };
var results = JSON.parse(response.getContentText()).results || [];
var data = (results[0] && results[0].data) || {};
if (data.regularMarketPrice == null) {
return { row: [ticker, 'sem dados', null, null, null] };
}
return {
row: [
data.symbol || ticker,
data.shortName,
data.regularMarketPrice,
data.regularMarketChangePercent,
data.currency,
],
};
}
/**
* Soma os proventos dos últimos 12 meses de um ativo.
* Devolve {total} ou {blocked: true} quando a rota não está liberada.
* @param {string} token - Token da API brapi.dev.
* @param {string} ticker - Código do ativo.
*/
function BRAPI_PROVENTOS_(token, ticker) {
var until = new Date();
var since = new Date(until.getTime() - 365 * 24 * 60 * 60 * 1000);
var url = 'https://brapi.dev/api/v2/stocks/dividends' +
'?symbols=' + encodeURIComponent(ticker) +
'&startDate=' + since.toISOString().slice(0, 10) +
'&endDate=' + until.toISOString().slice(0, 10);
var response = UrlFetchApp.fetch(url, {
muteHttpExceptions: true,
headers: { Authorization: 'Bearer ' + token },
});
var status = response.getResponseCode();
if (status === 403 || status === 429) {
console.log('Dividendos indisponíveis (' + status + '); coluna sem dados.');
return { blocked: true };
}
if (status !== 200) return { total: null };
var results = JSON.parse(response.getContentText()).results || [];
var data = (results[0] && results[0].data) || {};
var cash = data.cashDividends || [];
var total = 0;
for (var i = 0; i < cash.length; i++) {
total += Number(cash[i].rate) || 0;
}
return { total: Math.round(total * 100) / 100 };
}
// Um ativo por requisição mantém o exemplo válido no plano gratuito.
// Nos planos startup e pro, junte até 10 ou 20 tickers por chamada.
// Proventos pagos exigem o plano startup: https://brapi.dev/docs/acoes/dividendosTickers sem cotação recebem a linha sem dados ou células vazias em vez de
derrubar a tabela. Funções @customfunction recalculam com a planilha; para
controlar a frequência, encapsule a chamada em um menu personalizado com
UrlFetchApp.fetch conforme o guia do Sheets.
Sandbox é só o primeiro passo. Para colocar a carteira real no ar:
BRAPI_TOKEN (variável de ambiente no Python, propriedade do script no
Sheets). Nunca no código nem no repositório.