Skip to content

Instantly share code, notes, and snippets.

@danperrout
Last active September 19, 2025 20:49
Show Gist options
  • Save danperrout/b27197056fa38d0d669332647ab89d7a to your computer and use it in GitHub Desktop.
Save danperrout/b27197056fa38d0d669332647ab89d7a to your computer and use it in GitHub Desktop.
API Função TESOURODIRETO Google Sheets
/*
* @return Acesse radaropcoes.com Retorna a cotação atual de um título específico do Tesouro Direto.
* API: https://radaropcoes.com/
* Fonte: https://www.tesourodireto.com.br/titulos/precos-e-taxas.htm
**/
function TESOURODIRETO(bondName, argumento="r") {
let srcURL = "https://api.radaropcoes.com/bonds.json";
let jsondata = UrlFetchApp.fetch(srcURL);
let parsedData = JSON.parse(jsondata.getContentText()).response;
for(let bond of parsedData.TrsrBdTradgList) {
let currBondName = bond.TrsrBd.nm;
if (currBondName.toLowerCase() === bondName.toLowerCase())
if(argumento == "r")
return bond.TrsrBd.untrRedVal;
else
return bond.TrsrBd.untrInvstmtVal;
}
throw new Error("Título não encontrado.");
}
@fabiotirapelli
Copy link

fabiotirapelli commented Aug 19, 2025

Segue uma versão usando os CSVs, assim conseguimos os dados de investimento e de resgate:

`function TESOURODIRETO(bondName, argumento, urlCsv) {
bondName = (bondName || "Tesouro IPCA+ 2029").toString().trim();
argumento = (argumento || "ti").toString().trim().toLowerCase();
var urlInvestir = "https://www.tesourodireto.com.br/documents/d/guest/rendimento-investir-csv?download=true";
var urlResgatar = "https://www.tesourodireto.com.br/documents/d/guest/rendimento-resgatar-csv?download=true";
if (!urlCsv || urlCsv === "") {
if (argumento === "ti" || argumento === "ti_txt" || argumento === "i" || argumento === "i_txt" || argumento === "min") {
urlCsv = urlInvestir;
} else if (argumento === "tr" || argumento === "tr_txt" || argumento === "r" || argumento === "r_txt") {
urlCsv = urlResgatar;
} else {
urlCsv = urlInvestir;
}
}

var resp = UrlFetchApp.fetch(urlCsv, { muteHttpExceptions: true, followRedirects: true });
if (resp.getResponseCode() !== 200) {
throw new Error("HTTP " + resp.getResponseCode() + " ao acessar CSV: " + urlCsv);
}

var text = resp.getContentText("UTF-8");

var rows = Utilities.parseCsv(text, ";");
if (!rows || rows.length < 2) throw new Error("CSV vazio ou sem dados.");

var header = rows[0].map(function(s){ return String(s||"").trim(); });

var investirLayout = header.length >= 5 && header[2].toLowerCase().indexOf("investimento") >= 0;

var IDX = {};
if (investirLayout) {
IDX = { titulo:0, taxa:1, minimo:2, puInvest:3, venc:4, puResg:null };
} else {
IDX = { titulo:0, taxa:1, minimo:null, puInvest:null, venc:3, puResg:2 };
}

var lower = bondName.toLowerCase();
var data = rows.slice(1);
var row = data.find(function(r){ return (r[IDX.titulo]||"").toLowerCase() === lower; }) ||
data.find(function(r){ return (r[IDX.titulo]||"").toLowerCase().indexOf(lower) >= 0; });

if (!row) throw new Error("Título não encontrado: " + bondName);

var taxaTxt = row[IDX.taxa] || "";
var vencTxt = row[IDX.venc] || "";
var minimoTxt = (IDX.minimo != null) ? (row[IDX.minimo] || "") : "";
var puInvTxt = (IDX.puInvest != null) ? (row[IDX.puInvest] || "") : "";
var puResTxt = (IDX.puResg != null) ? (row[IDX.puResg] || "") : "";
var taxaNum = extractPercentNumber(taxaTxt);
var minimo = toNumberCurrency(minimoTxt);
var puInv = toNumberCurrency(puInvTxt);
var puRes = toNumberCurrency(puResTxt);

switch (argumento) {
case "ti": return taxaNum;
case "ti_txt": return taxaTxt;
case "i": return puInv;
case "i_txt": return puInvTxt;
case "min": return minimo;
case "tr": return taxaNum;
case "tr_txt": return taxaTxt;
case "r": return puRes;
case "r_txt": return puResTxt;
case "venc": return vencTxt;
case "raw": return JSON.stringify(objectFromRow(header, row));
default: return taxaNum;
}
}

function toNumberCurrency(brStr){
if (!brStr) return NaN;
var s = String(brStr).replace(/[^\d,.-]/g,"").replace(/./g,"").replace(",",".");
var n = Number(s);
return isNaN(n) ? NaN : n;
}
function extractPercentNumber(txt){
if (!txt) return NaN;
var m = String(txt).match(/(-?\d+(?:,\d+)?)\s*%/);
return m ? Number(m[1].replace(",", ".")) : NaN;
}
function objectFromRow(header, row){
var o = {};
for (var i=0;i<header.length;i++) o[header[i]] = row[i];
return o;
}`

@Nauahbanda
Copy link

bom dia, qual argumento passa para pegar o PU de resgate? obrigado.

@tuliopascoal
Copy link

Segue uma versão usando os CSVs, assim conseguimos os dados de investimento e de resgate:

`function TESOURODIRETO(bondName, argumento, urlCsv) { bondName = (bondName || "Tesouro IPCA+ 2029").toString().trim(); argumento = (argumento || "ti").toString().trim().toLowerCase(); var urlInvestir = "https://www.tesourodireto.com.br/documents/d/guest/rendimento-investir-csv?download=true"; var urlResgatar = "https://www.tesourodireto.com.br/documents/d/guest/rendimento-resgatar-csv?download=true"; if (!urlCsv || urlCsv === "") { if (argumento === "ti" || argumento === "ti_txt" || argumento === "i" || argumento === "i_txt" || argumento === "min") { urlCsv = urlInvestir; } else if (argumento === "tr" || argumento === "tr_txt" || argumento === "r" || argumento === "r_txt") { urlCsv = urlResgatar; } else { urlCsv = urlInvestir; } }

var resp = UrlFetchApp.fetch(urlCsv, { muteHttpExceptions: true, followRedirects: true }); if (resp.getResponseCode() !== 200) { throw new Error("HTTP " + resp.getResponseCode() + " ao acessar CSV: " + urlCsv); }

var text = resp.getContentText("UTF-8");

var rows = Utilities.parseCsv(text, ";"); if (!rows || rows.length < 2) throw new Error("CSV vazio ou sem dados.");

var header = rows[0].map(function(s){ return String(s||"").trim(); });

var investirLayout = header.length >= 5 && header[2].toLowerCase().indexOf("investimento") >= 0;

var IDX = {}; if (investirLayout) { IDX = { titulo:0, taxa:1, minimo:2, puInvest:3, venc:4, puResg:null }; } else { IDX = { titulo:0, taxa:1, minimo:null, puInvest:null, venc:3, puResg:2 }; }

var lower = bondName.toLowerCase(); var data = rows.slice(1); var row = data.find(function(r){ return (r[IDX.titulo]||"").toLowerCase() === lower; }) || data.find(function(r){ return (r[IDX.titulo]||"").toLowerCase().indexOf(lower) >= 0; });

if (!row) throw new Error("Título não encontrado: " + bondName);

var taxaTxt = row[IDX.taxa] || ""; var vencTxt = row[IDX.venc] || ""; var minimoTxt = (IDX.minimo != null) ? (row[IDX.minimo] || "") : ""; var puInvTxt = (IDX.puInvest != null) ? (row[IDX.puInvest] || "") : ""; var puResTxt = (IDX.puResg != null) ? (row[IDX.puResg] || "") : ""; var taxaNum = extractPercentNumber(taxaTxt); var minimo = toNumberCurrency(minimoTxt); var puInv = toNumberCurrency(puInvTxt); var puRes = toNumberCurrency(puResTxt);

switch (argumento) { case "ti": return taxaNum; case "ti_txt": return taxaTxt; case "i": return puInv; case "i_txt": return puInvTxt; case "min": return minimo; case "tr": return taxaNum; case "tr_txt": return taxaTxt; case "r": return puRes; case "r_txt": return puResTxt; case "venc": return vencTxt; case "raw": return JSON.stringify(objectFromRow(header, row)); default: return taxaNum; } }

function toNumberCurrency(brStr){ if (!brStr) return NaN; var s = String(brStr).replace(/[^\d,.-]/g,"").replace(/./g,"").replace(",","."); var n = Number(s); return isNaN(n) ? NaN : n; } function extractPercentNumber(txt){ if (!txt) return NaN; var m = String(txt).match(/(-?\d+(?:,\d+)?)\s*%/); return m ? Number(m[1].replace(",", ".")) : NaN; } function objectFromRow(header, row){ var o = {}; for (var i=0;i<header.length;i++) o[header[i]] = row[i]; return o; }`

Perfeito! Muito obrigado!!

@jcimagem
Copy link

Testei buscando PU com o parâmetro r e retorna valor zerado. Mas se peço com o parâmetro r_txt vêm o valor de resgate em formato texto. Deve ter um erro na função to NumberCurrency mas não sei identificar.

@jcimagem
Copy link

Mudei a função toNumberCurrency e resolveu o retorno do PU em forma de valor (parâmetro r):

function toNumberCurrency(brStr){
if (!brStr) return NaN;
var s = brStr.replace("R$", "").replace(/./g, "").replace(",", ".");
var n = parseFloat(s);
return isNaN(n) ? NaN : n;
}

E adicionei o 4º parâmetro na função principal (updt):
function TESOURODIRETO(bondName, argumento, urlCsv, updt) {

updt é 1 célula com caixa de seleção e quando clico atualiza.

@lturbino-jpg
Copy link

Mudei a função toNumberCurrency e resolveu o retorno do PU em forma de valor (parâmetro r):

function toNumberCurrency(brStr){ if (!brStr) return NaN; var s = brStr.replace("R$", "").replace(/./g, "").replace(",", "."); var n = parseFloat(s); return isNaN(n) ? NaN : n; }

E adicionei o 4º parâmetro na função principal (updt): function TESOURODIRETO(bondName, argumento, urlCsv, updt) {

updt é 1 célula com caixa de seleção e quando clico atualiza.

Continuou não funcionando aqui para o valor, ajustei a célula conforme abaixo e funcionou:

=SUBSTITUTE(TesouroDireto(H173; "r_txt");"R$ ";"")+0

@thiagochaim
Copy link

Boa tarde pessoal,
Estou tentando acompanhar todas as mudanças, mas tenho pouquíssimo conhecimento com o código.
Gostaria de saber se algum de vocês poderia postar o código completo que esteja funcionando no App Script do google depois da atualização do site do tesouro. A única informação que preciso atualmente é o valor de resgate dos títulos. Eu estava usando um script que criava a função TESOURODIRETO(), não sei se é esse que estão usando aqui.

Se for preciso, posso compartilhar o código.

Se puderem ajudar, agradeço.

@WadjoResende
Copy link

WadjoResende commented Aug 25, 2025

Boa tarde pessoal, Estou tentando acompanhar todas as mudanças, mas tenho pouquíssimo conhecimento com o código. Gostaria de saber se algum de vocês poderia postar o código completo que esteja funcionando no App Script do google depois da atualização do site do tesouro. A única informação que preciso atualmente é o valor de resgate dos títulos. Eu estava usando um script que criava a função TESOURODIRETO(), não sei se é esse que estão usando aqui.

Se for preciso, posso compartilhar o código.

Se puderem ajudar, agradeço.

O código que está funcionando é o que o fabiotirapelli postou.
Basta você substituir o código que estava usando pelo código dele e substituir a fórmula na célula que você quer ver o valor de regate pela fórmula abaixo sugerida pelo lturbino-jpg (A4 é a célula que contém o nome do título):

=SUBSTITUTE(TESOURODIRETO(A4; "r_txt");"R$ ";"")+0

Vou colocar aqui o código completo sugerido pelo fabiotirapelli. É só copiar e substituir o código que você estava usando (sem os apóstrofos do início e final):

`function TESOURODIRETO(bondName, argumento, urlCsv) {
bondName = (bondName || "Tesouro IPCA+ 2029").toString().trim();
argumento = (argumento || "ti").toString().trim().toLowerCase();
var urlInvestir = "https://www.tesourodireto.com.br/documents/d/guest/rendimento-investir-csv?download=true";
var urlResgatar = "https://www.tesourodireto.com.br/documents/d/guest/rendimento-resgatar-csv?download=true";
if (!urlCsv || urlCsv === "") {
if (argumento === "ti" || argumento === "ti_txt" || argumento === "i" || argumento === "i_txt" || argumento === "min") {
urlCsv = urlInvestir;
} else if (argumento === "tr" || argumento === "tr_txt" || argumento === "r" || argumento === "r_txt") {
urlCsv = urlResgatar;
} else {
urlCsv = urlInvestir;
}
}

var resp = UrlFetchApp.fetch(urlCsv, { muteHttpExceptions: true, followRedirects: true });
if (resp.getResponseCode() !== 200) {
throw new Error("HTTP " + resp.getResponseCode() + " ao acessar CSV: " + urlCsv);
}

var text = resp.getContentText("UTF-8");

var rows = Utilities.parseCsv(text, ";");
if (!rows || rows.length < 2) throw new Error("CSV vazio ou sem dados.");

var header = rows[0].map(function(s){ return String(s||"").trim(); });

var investirLayout = header.length >= 5 && header[2].toLowerCase().indexOf("investimento") >= 0;

var IDX = {};
if (investirLayout) {
IDX = { titulo:0, taxa:1, minimo:2, puInvest:3, venc:4, puResg:null };
} else {
IDX = { titulo:0, taxa:1, minimo:null, puInvest:null, venc:3, puResg:2 };
}

var lower = bondName.toLowerCase();
var data = rows.slice(1);
var row = data.find(function(r){ return (r[IDX.titulo]||"").toLowerCase() === lower; }) ||
data.find(function(r){ return (r[IDX.titulo]||"").toLowerCase().indexOf(lower) >= 0; });

if (!row) throw new Error("Título não encontrado: " + bondName);

var taxaTxt = row[IDX.taxa] || "";
var vencTxt = row[IDX.venc] || "";
var minimoTxt = (IDX.minimo != null) ? (row[IDX.minimo] || "") : "";
var puInvTxt = (IDX.puInvest != null) ? (row[IDX.puInvest] || "") : "";
var puResTxt = (IDX.puResg != null) ? (row[IDX.puResg] || "") : "";
var taxaNum = extractPercentNumber(taxaTxt);
var minimo = toNumberCurrency(minimoTxt);
var puInv = toNumberCurrency(puInvTxt);
var puRes = toNumberCurrency(puResTxt);

switch (argumento) {
case "ti": return taxaNum;
case "ti_txt": return taxaTxt;
case "i": return puInv;
case "i_txt": return puInvTxt;
case "min": return minimo;
case "tr": return taxaNum;
case "tr_txt": return taxaTxt;
case "r": return puRes;
case "r_txt": return puResTxt;
case "venc": return vencTxt;
case "raw": return JSON.stringify(objectFromRow(header, row));
default: return taxaNum;
}
}

function toNumberCurrency(brStr){
if (!brStr) return NaN;
var s = String(brStr).replace(/[^\d,.-]/g,"").replace(/./g,"").replace(",",".");
var n = Number(s);
return isNaN(n) ? NaN : n;
}
function extractPercentNumber(txt){
if (!txt) return NaN;
var m = String(txt).match(/(-?\d+(?:,\d+)?)\s*%/);
return m ? Number(m[1].replace(",", ".")) : NaN;
}
function objectFromRow(header, row){
var o = {};
for (var i=0;i<header.length;i++) o[header[i]] = row[i];
return o;
}`

@thiagochaim
Copy link

@WadjoResende Muito obrigado. Funcionou perfeitamente.

@gwstavo
Copy link

gwstavo commented Sep 4, 2025

Não funcionou para mim. Copiei o código e a fórmula mas não consegui. Será que estou fazendo algo errado?
@thiagochaim @WadjoResende

@tuliopascoal
Copy link

@gwstavo , talvez seja porque desde hoje o código mais atual que estava funcionando está retornando 403.
O TD deve ter bloqueado os acessos novamente...

Alguém tem alguma novidade?

@thiagochaim
Copy link

Não funcionou para mim. Copiei o código e a fórmula mas não consegui. Será que estou fazendo algo errado? @thiagochaim @WadjoResende

Realmente parou de funcionar novamente.
Que tristeza.

@andretc-cit
Copy link

Vish, tá retornando 403 da url "https://www.tesourodireto.com.br/documents/d/guest/rendimento-resgatar-csv?download=true";, talvez tenham limitado

@tuliopascoal
Copy link

Sim, está fora desde meados da quarta/quinta-feira.
Alguém já descobriu uma forma diferente?

@gabrielgasp
Copy link

Ajustei uma API que eu uso para consumir os CSVs como fonte de dados. Estou deixando ela pública para quem tiver interesse.

Observações

  1. Essa API foi feita para meu uso pessoal, atendendo às minhas próprias necessidades. Não pretendo implementar modificações sob demanda. Está disponível gratuitamente as is.
  2. Como os dados vêm diretamente dos CSVs, não há garantia de que a API continuará funcionando indefinidamente.
  3. O campo investable foi adicionado por mim para indicar se o título ainda está disponível para investimento atualmente na plataforma do Tesouro.
  4. O campo updated_at representa a última vez que a api conseguiu baixar os CSVs para atualizar as informações. Isso não garante que os CSVs na fonte estejam atualizados, pois isso foge do meu controle.
  5. A aplicação tenta atualizar as informações a cada 15 minutos, de segunda a sexta-feira, entre 9h e 19h.

Exemplos de uso

GET https://tesouro.gabrielgaspar.com.br/bonds

{
  "bonds": [
    {
      "name": "Tesouro Selic 2028",
      "investable": true,
      "annual_investment_rate": "SELIC + 0,05%",
      "unitary_investment_value": 17284.36,
      "minimum_investment_amount": 172.84,
      "annual_redemption_rate": "SELIC + 0,06%",
      "unitary_redemption_value": 17280.11,
      "maturity": "01/03/2028"
    },
    {
      "name": "Tesouro Prefixado 2029",
      "investable": false,
      "annual_investment_rate": "",
      "unitary_investment_value": 0,
      "minimum_investment_amount": 0,
      "annual_redemption_rate": "13,33%",
      "unitary_redemption_value": 662.55,
      "maturity": "01/01/2029"
    },
    {
      "name": "Tesouro IPCA+ 2029",
      "investable": true,
      "annual_investment_rate": "IPCA + 7,76%",
      "unitary_investment_value": 3453.16,
      "minimum_investment_amount": 34.53,
      "annual_redemption_rate": "IPCA + 7,88%",
      "unitary_redemption_value": 3439.16,
      "maturity": "15/05/2029"
    },
    ...
  ],
  "updated_at": "2025-09-06T00:11:23-03:00"
}
GET https://tesouro.gabrielgaspar.com.br/bonds/{bondName}
Exemplo: GET https://tesouro.gabrielgaspar.com.br/bonds/tesouro-selic-2028  
(obs: espaços no nome do título são substituídos por hífens)

{
  "bond": {
    "name": "Tesouro Selic 2028",
    "investable": true,
    "annual_investment_rate": "SELIC + 0,05%",
    "unitary_investment_value": 17284.36,
    "minimum_investment_amount": 172.84,
    "annual_redemption_rate": "SELIC + 0,06%",
    "unitary_redemption_value": 17280.11,
    "maturity": "01/03/2028"
  },
  "updated_at": "2025-09-06T00:11:23-03:00"
}
// Exemplo em Google Apps Script
// =GET_TITULO_TESOURO("tesouro-selic-2028")

function GET_TITULO_TESOURO(bondName) {
  // bondName é o nome do título. Ex: tesouro-selic-2028
  const srcURL = `https://tesouro.gabrielgaspar.com.br/bonds/${bondName}`;
  const jsondata = UrlFetchApp.fetch(srcURL);

  if (jsondata.getResponseCode() !== 200) {
    throw new Error("Erro ao buscar título");
  } 

  const parsedData = JSON.parse(jsondata.getContentText());

  // parsedData contém todas as chaves mostradas no exemplo acima.
  // Aqui, por exemplo, retorno o valor unitário de investimento.
  return parsedData.bond.unitary_investment_value;
}

@gabrielgasp
Copy link

gabrielgasp commented Sep 6, 2025

Pra quem quer tentar uma implementação própria pra puxar os dados, a dica que eu posso dar é tentar achar uma forma de fazer a requisição que simule um browser. Pelos meus testes, quando você tenta fazer um fetch direto ou até mesmo um wget, eles conseguem detectar e bloquear (403), mas quando eles acham que o acesso está vindo de um browser tipo chrome eles deixa passar na maioria das vezes.

Essa api ai de cima eu implementei com Golang usando uma lib que consegue fazer o request impersonando o Chrome. Por enquanto tá dando certo.

@afalcaoneto88
Copy link

afalcaoneto88 commented Sep 6, 2025

Consegui puxar o valor (do IPCA+ 2040) no Google Sheets usando:
=VALUE(SUBSTITUTE(REGEXREPLACE(IMPORTXML("https://taxas-tesouro.com/resgatar/tesouro-ipca+-2040/"; "(//span[contains(.,'R$')])[1]");"[^\d.,]";"");".";","))

@hdillax
Copy link

hdillax commented Sep 8, 2025

Pra quem quer tentar uma implementação própria pra puxar os dados, a dica que eu posso dar é tentar achar uma forma de fazer a requisição que simule um browser. Pelos meus testes, quando você tenta fazer um fetch direto ou até mesmo um wget, eles conseguem detectar e bloquear (403), mas quando eles acham que o acesso está vindo de um browser tipo chrome eles deixa passar na maioria das vezes.

Essa api ai de cima eu implementei com Golang usando uma lib que consegue fazer o request impersonando o Chrome. Por enquanto tá dando certo.

Excelente, meu caro! Muito obrigado

@lturbino-jpg
Copy link

Consegui puxar o valor (do IPCA+ 2040) no Google Sheets usando: =VALUE(SUBSTITUTE(REGEXREPLACE(IMPORTXML("https://taxas-tesouro.com/resgatar/tesouro-ipca+-2040/"; "(//span[contains(.,'R$')])[1]");"[^\d.,]";"");".";","))

Funcionou aqui para pegar o valor do título, obrigado.

Alguém saberia informar como pegar a taxa?

@lturbino-jpg
Copy link

consegui pegar a taxa assim

=VALUE(SUBSTITUTE(REGEXREPLACE(IMPORTXML("https://taxas-tesouro.com/resgatar/tesouro-ipca+-2035/"; "(//span[contains(.,'%')])[1]");"[^\d.,]";"");".";""))/100

minha dúvida é qual a frequencia com que o site taxas-tesouro,com atualiza, pois depende desta atualização. hoje 8/set está indicando que a última atualização foi em 4/set

@thiagochaim
Copy link

Pra quem quer tentar uma implementação própria pra puxar os dados, a dica que eu posso dar é tentar achar uma forma de fazer a requisição que simule um browser. Pelos meus testes, quando você tenta fazer um fetch direto ou até mesmo um wget, eles conseguem detectar e bloquear (403), mas quando eles acham que o acesso está vindo de um browser tipo chrome eles deixa passar na maioria das vezes.

Essa api ai de cima eu implementei com Golang usando uma lib que consegue fazer o request impersonando o Chrome. Por enquanto tá dando certo.

Quão difícil é isso para alguém que não tem esse conhecimento técnico todo?
Eu estou frustrado com toda esse mudança do site do Tesouro Direto pois o script que eu usava funcionou muito bem por muitos anos.

Ficarei muito grato se puder me dar orientações ou referências para que eu consiga implementar isso na minha planilha.

Sei que estou pedindo muito e entenderei caso não consiga.

Muito obrigado por toda informação compartilhada até agora.

@andreluistosato
Copy link

andreluistosato commented Sep 8, 2025 via email

@ramonlucas
Copy link

ramonlucas commented Sep 8, 2025

Pra quem quer tentar uma implementação própria pra puxar os dados, a dica que eu posso dar é tentar achar uma forma de fazer a requisição que simule um browser. Pelos meus testes, quando você tenta fazer um fetch direto ou até mesmo um wget, eles conseguem detectar e bloquear (403), mas quando eles acham que o acesso está vindo de um browser tipo chrome eles deixa passar na maioria das vezes.

Essa api ai de cima eu implementei com Golang usando uma lib que consegue fazer o request impersonando o Chrome. Por enquanto tá dando certo.

Sempre uso o CURL por padrão em minhas requisições e não consegui nem por reza, mesmo simulando o browser, montando o header bonitinho... nada!

Quando tentei com o WGET funcionou.

wget --compression=auto --header='Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7' --header='Accept-Language: en-US,en;q=0.9,pt-BR;q=0.8,pt;q=0.7,nl;q=0.6' --header='Priority: u=0, i' --header='Sec-Ch-Ua: "Not)A;Brand";v="8", "Chromium";v="138", "Google Chrome";v="138"' --header='Sec-Ch-Ua-Mobile: ?0' --header='Sec-Ch-Ua-Platform: "macOS"' --header='Sec-Fetch-Dest: document' --header='Sec-Fetch-Mode: navigate' --header='Sec-Fetch-Site: none' --header='Sec-Fetch-User: ?1' --header='Upgrade-Insecure-Requests: 1' --header='User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36' -O rendimento.csv 'https://www.tesourodireto.com.br/documents/d/guest/rendimento-resgatar-csv?download=true'

@thiagochaim
Copy link

Thiago, se quiser usar, essa é a função que criei na minha Google Sheets, ela deixa cacheado também por um tempo, pra não ficar batendo direto na api (estou usando a api do gabriel hahaha), só adicionar um script com o código abaixo, aí você chama na célula =TESOURODIRETO("nome do titulo") /* * @return Retorna a cotação atual de um título específico do Tesouro Direto. / var cache = CacheService.getScriptCache(); function TESOURODIRETO(bondName) { var tesouro = getCachedTesouro(); if (!tesouro) { Logger.log("No cached tesouro info found"); updateTesouroCache(); } tesouro = getCachedTesouro(); console.log(tesouro); for (let bond of tesouro.bonds) { console.log(bond); if (bond.name.toLowerCase() === bondName.toLowerCase()) return bond.unitary_redemption_value; } return 0; } function getCachedTesouro() { var cached = cache.get("tesouro"); if (cached) { try { var tesouro = JSON.parse(cached); return tesouro; } catch (e) { Logger.log("Error while parsing tesouro from cache"); } } } function updateTesouroCache() { Logger.log("Updating tesouro cached information"); var url = "https://tesouro.gabrielgaspar.com.br/bonds"; var response = UrlFetchApp.fetch(url); console.log(JSON.parse(response.getContentText())); var content = response.getContentText(); try { var data = JSON.parse(content); cache.put("tesouro", JSON.stringify(data),21600); } catch (e) { Logger.log("Error while parsing response from tesouro: " + content); } } https://about.me/andreluistosato?promo=email_sig&utm_source=product&utm_medium=email_sig&utm_campaign=edit_panel&utm_content=thumb André Luis Tosato Cruz about.me/andreluistosato https://about.me/andreluistosato?promo=email_sig&utm_source=product&utm_medium=email_sig&utm_campaign=edit_panel&utm_content=thumb

On Mon, Sep 8, 2025 at 12:11 PM Thiago Chaim @.
> wrote: @.*** commented on this gist. ------------------------------ Pra quem quer tentar uma implementação própria pra puxar os dados, a dica que eu posso dar é tentar achar uma forma de fazer a requisição que simule um browser. Pelos meus testes, quando você tenta fazer um fetch direto ou até mesmo um wget, eles conseguem detectar e bloquear (403), mas quando eles acham que o acesso está vindo de um browser tipo chrome eles deixa passar na maioria das vezes. Essa api ai de cima eu implementei com Golang usando uma lib https://github.com/imroc/req que consegue fazer o request impersonando o Chrome. Por enquanto tá dando certo. Quão difícil é isso para alguém que não tem esse conhecimento técnico todo? Eu estou frustrado com toda esse mudança do site do Tesouro Direto pois o script que eu usava funcionou muito bem por muitos anos. Ficarei muito grato se puder me dar orientações ou referências para que eu consiga implementar isso na minha planilha. Sei que estou pedindo muito e entenderei caso não consiga. Muito obrigado por toda informação compartilhada até agora. — Reply to this email directly, view it on GitHub https://gist.github.com/danperrout/b27197056fa38d0d669332647ab89d7a#gistcomment-5751425 or unsubscribe https://github.com/notifications/unsubscribe-auth/AAF2NKNMPBNGPRMTWCG2VMT3RWMBVBFHORZGSZ3HMVZKMY3SMVQXIZNMON2WE2TFMN2F65DZOBS2WR3JON2EG33NNVSW45FGORXXA2LDOOIYFJDUPFYGLJDHNFZXJJLWMFWHKZNJGEYDIMZVHA3DIM5KMF2HI4TJMJ2XIZLTSKBKK5TBNR2WLJDUOJ2WLJDOMFWWLO3UNBZGKYLEL5YGC4TUNFRWS4DBNZ2F6YLDORUXM2LUPGBKK5TBNR2WLJDHNFZXJJDOMFWWLK3UNBZGKYLEL52HS4DF . You are receiving this email because you are subscribed to this thread. Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub .

Sua solução funcionou lindamente.

Muito obrigado. Vocês são muito feras.

@tuliopascoal
Copy link

Thiago, se quiser usar, essa é a função que criei na minha Google Sheets, ela deixa cacheado também por um tempo, pra não ficar batendo direto na api (estou usando a api do gabriel hahaha), só adicionar um script com o código abaixo, aí você chama na célula =TESOURODIRETO("nome do titulo") /* * @return Retorna a cotação atual de um título específico do Tesouro Direto. / var cache = CacheService.getScriptCache(); function TESOURODIRETO(bondName) { var tesouro = getCachedTesouro(); if (!tesouro) { Logger.log("No cached tesouro info found"); updateTesouroCache(); } tesouro = getCachedTesouro(); console.log(tesouro); for (let bond of tesouro.bonds) { console.log(bond); if (bond.name.toLowerCase() === bondName.toLowerCase()) return bond.unitary_redemption_value; } return 0; } function getCachedTesouro() { var cached = cache.get("tesouro"); if (cached) { try { var tesouro = JSON.parse(cached); return tesouro; } catch (e) { Logger.log("Error while parsing tesouro from cache"); } } } function updateTesouroCache() { Logger.log("Updating tesouro cached information"); var url = "https://tesouro.gabrielgaspar.com.br/bonds"; var response = UrlFetchApp.fetch(url); console.log(JSON.parse(response.getContentText())); var content = response.getContentText(); try { var data = JSON.parse(content); cache.put("tesouro", JSON.stringify(data),21600); } catch (e) { Logger.log("Error while parsing response from tesouro: " + content); } } https://about.me/andreluistosato?promo=email_sig&utm_source=product&utm_medium=email_sig&utm_campaign=edit_panel&utm_content=thumb André Luis Tosato Cruz about.me/andreluistosato https://about.me/andreluistosato?promo=email_sig&utm_source=product&utm_medium=email_sig&utm_campaign=edit_panel&utm_content=thumb

On Mon, Sep 8, 2025 at 12:11 PM Thiago Chaim @.
> wrote: @.*** commented on this gist. ------------------------------ Pra quem quer tentar uma implementação própria pra puxar os dados, a dica que eu posso dar é tentar achar uma forma de fazer a requisição que simule um browser. Pelos meus testes, quando você tenta fazer um fetch direto ou até mesmo um wget, eles conseguem detectar e bloquear (403), mas quando eles acham que o acesso está vindo de um browser tipo chrome eles deixa passar na maioria das vezes. Essa api ai de cima eu implementei com Golang usando uma lib https://github.com/imroc/req que consegue fazer o request impersonando o Chrome. Por enquanto tá dando certo. Quão difícil é isso para alguém que não tem esse conhecimento técnico todo? Eu estou frustrado com toda esse mudança do site do Tesouro Direto pois o script que eu usava funcionou muito bem por muitos anos. Ficarei muito grato se puder me dar orientações ou referências para que eu consiga implementar isso na minha planilha. Sei que estou pedindo muito e entenderei caso não consiga. Muito obrigado por toda informação compartilhada até agora. — Reply to this email directly, view it on GitHub https://gist.github.com/danperrout/b27197056fa38d0d669332647ab89d7a#gistcomment-5751425 or unsubscribe https://github.com/notifications/unsubscribe-auth/AAF2NKNMPBNGPRMTWCG2VMT3RWMBVBFHORZGSZ3HMVZKMY3SMVQXIZNMON2WE2TFMN2F65DZOBS2WR3JON2EG33NNVSW45FGORXXA2LDOOIYFJDUPFYGLJDHNFZXJJLWMFWHKZNJGEYDIMZVHA3DIM5KMF2HI4TJMJ2XIZLTSKBKK5TBNR2WLJDUOJ2WLJDOMFWWLO3UNBZGKYLEL5YGC4TUNFRWS4DBNZ2F6YLDORUXM2LUPGBKK5TBNR2WLJDHNFZXJJDOMFWWLK3UNBZGKYLEL52HS4DF . You are receiving this email because you are subscribed to this thread. Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub .

Enormemente agradecido meu caro @andreluistosato.
Fiz alguma pequenas adaptacoes consegui ter o funcionamento normal que tinha antes.
Grato!!

@fabiodesss
Copy link

Eu normalmente não costumo criar contas em fóruns, mas fiz questão de criar uma aqui apenas para agradecer ao colega @gabrielgasp pela disponibilização da API. Muito obrigado mesmo, sua ajuda foi fundamental para a resolução do problema. Fazia dias que eu vinha quebrando a cabeça tentando encontrar uma solução, e sua contribuição veio em ótima hora.

@kleitonramires
Copy link

@andreluistosato agradeço por ter compartilhado seu conhecimento. Resolveu aqui. Vida longa a você.

@nozeedd
Copy link

nozeedd commented Sep 11, 2025

Era o que eu precisava. Um tanto básico, peguei a API do @gabrielgasp e importei pelo Excel > Power Query
Converter a Lista para tabela, Renomear coluna, Expandir para diversas colunas.

let Source = Json.Document(Web.Contents("https://tesouro.gabrielgaspar.com.br/bonds")), bonds1 = Source[bonds], #"Converted to Table" = Table.FromList(bonds1, Splitter.SplitByNothing(), null, null, ExtraValues.Error), #"Renamed Columns" = Table.RenameColumns(#"Converted to Table",{{"Column1", "TD"}}), #"Expanded TD" = Table.ExpandRecordColumn(#"Renamed Columns", "TD", {"name", "investable", "annual_investment_rate", "unitary_investment_value", "minimum_investment_amount", "annual_redemption_rate", "unitary_redemption_value", "maturity"}, {"TD.name", "TD.investable", "TD.annual_investment_rate", "TD.unitary_investment_value", "TD.minimum_investment_amount", "TD.annual_redemption_rate", "TD.unitary_redemption_value", "TD.maturity"}) in #"Expanded TD"

@diegodemorais
Copy link

Pessoal, funcionou perfeitamente! Muitíssimo obrigado @gabrielgasp
Fiz um pequeno ajuste pra retornar somente o Juros pós-fixado, sem o texto "Selic" ou "IPCA".

Assim:

function GET_TITULO_TESOURO(bondName) {
  const srcURL = `https://tesouro.gabrielgaspar.com.br/bonds/${bondName}`;
  const jsondata = UrlFetchApp.fetch(srcURL);

  if (jsondata.getResponseCode() !== 200) {
    throw new Error("Erro ao buscar título");
  } 

  const parsedData = JSON.parse(jsondata.getContentText());
  const rate = parsedData.bond.annual_investment_rate; // Ex: "SELIC + 0,05%"

  // Extrai o primeiro número com sinal de % da string
  const match = rate.match(/-?\d+,\d+%/);

  return match ? match[0] : "";
}

Abraços!

@mathsebastiany
Copy link

mathsebastiany commented Sep 19, 2025

Fala, pessoal!
Fiz adaptações para incluir algumas funções que eu uso na minha planilha e vou disponibilizar para vocês aqui:
txcompra(bondName) → taxa de compra (annual_investment_rate)
txvenda(bondName) → taxa de venda (annual_redemption_rate)
pucompra(bondName) → preço de compra (unitary_investment_value)
puvenda(bondName) → preço de venda (unitary_redemption_value)

function txcompra(bondName) {
  var tesouro = getCachedTesouro();
  if (!tesouro) updateTesouroCache();
  tesouro = getCachedTesouro();

  for (let bond of tesouro.bonds) {
    if (bond.name.toLowerCase() === bondName.toLowerCase()) {
      return extrairTaxaNumerica(bond.annual_investment_rate);
    }
  }

  return "Título não encontrado";
}

function txvenda(bondName) {
  var tesouro = getCachedTesouro();
  if (!tesouro) updateTesouroCache();
  tesouro = getCachedTesouro();

  for (let bond of tesouro.bonds) {
    if (bond.name.toLowerCase() === bondName.toLowerCase()) {
      return extrairTaxaNumerica(bond.annual_redemption_rate);
    }
  }

  return "Título não encontrado";
}

/**
 * Extrai apenas a parte numérica da taxa"
 */
function extrairTaxaNumerica(taxa) {
  if (!taxa) return "";
  var partes = taxa.split("+");
  return partes.length > 1 ? partes[1].trim() : taxa.trim();
}

function puvenda(bondName) {
  var tesouro = getCachedTesouro();

  if (!tesouro) {
    Logger.log("No cached tesouro info found");
    updateTesouroCache();
  }

  tesouro = getCachedTesouro();
  console.log(tesouro);

  for (let bond of tesouro.bonds) {
    console.log(bond);
    if (bond.name.toLowerCase() === bondName.toLowerCase())
      return bond.unitary_redemption_value;
  }

  return 0;
}

function pucompra(bondName) {
  var tesouro = getCachedTesouro();

  if (!tesouro) {
    Logger.log("No cached tesouro info found");
    updateTesouroCache();
  }

  tesouro = getCachedTesouro();
  console.log(tesouro);

  for (let bond of tesouro.bonds) {
    console.log(bond);
    if (bond.name.toLowerCase() === bondName.toLowerCase())
      return bond.unitary_investment_value;
  }

  return 0;
}

/**
 * Função para buscar o tesouro armazenado em cache.
 * @return {Object} O objeto do tesouro armazenado.
 */
function getCachedTesouro() {
  var cached = cache.get("tesouro");
  if (cached) {
    try {
      var tesouro = JSON.parse(cached);
      return tesouro;
    } catch (e) {
      Logger.log("Error while parsing tesouro from cache");
    }
  }
  return null;
}

/**
 * Função para atualizar o cache com as informações mais recentes do Tesouro Direto.
 */
function updateTesouroCache() {
  Logger.log("Updating tesouro cached information");

  var url = "https://tesouro.gabrielgaspar.com.br/bonds";
  var response = UrlFetchApp.fetch(url);
  console.log(JSON.parse(response.getContentText()));
  var content = response.getContentText();
  try {
    var data = JSON.parse(content);
    cache.put("tesouro", JSON.stringify(data), 21600);
  } catch (e) {
    Logger.log("Error while parsing response from tesouro: " + content);
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment