-
-
Save danperrout/b27197056fa38d0d669332647ab89d7a to your computer and use it in GitHub Desktop.
/* | |
* @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."); | |
} |
Pra mim tbm está fora do ar
Achei, mas tive que mudar "um pouco" o código.
`
/**
- =TESOURODIRETO("Tesouro IPCA+ 2029"; "ti")
- argumentos suportados:
-
- "ti" -> taxa para investir (ex.: "7,02")
-
- "tr" -> taxa para resgatar
-
- "min" -> aporte mínimo (número em R$)
-
- "venc"-> data de vencimento (string ISO)
-
- "tipo"-> U/S/M (sem juros, com juros, mensal - Renda+/Educa+)
-
- "raw" -> objeto completo do título (JSON)
- retrocompat:
-
- "i" -> retorna aporte mínimo (PU não está no novo endpoint)
-
- "r" -> retorna aporte mínimo (PU não está no novo endpoint)
- Precisa buscar em https://www.tesourodireto.com.br/o/rentabilidade/resgatar
*/
function TESOURODIRETO(bondName, argumento) {
bondName = (bondName || "Tesouro IPCA+ 2029").toString().trim();
argumento = (argumento || "ti").toString().trim().toLowerCase();
var url = "https://www.tesourodireto.com.br/o/rentabilidade/investir";
var resp = UrlFetchApp.fetch(url, {
muteHttpExceptions: true,
followRedirects: true,
headers: {
"Accept": "application/json, text/plain, /",
"User-Agent": "Mozilla/5.0 (compatible; AppsScriptFetcher/1.0)",
"Referer": "https://www.tesourodireto.com.br/produtos/dados-sobre-titulos/rendimento-dos-titulos"
}
});
var code = resp.getResponseCode();
if (code !== 200) {
throw new Error("HTTP " + code + " ao acessar o endpoint. Corpo: " + resp.getContentText().slice(0, 300));
}
var text = resp.getContentText("UTF-8");
var data;
try {
data = JSON.parse(text);
} catch (e) {
throw new Error("Falha ao parsear JSON do endpoint: " + e + " | trecho: " + text.slice(0, 300));
}
if (!Array.isArray(data)) {
throw new Error("Formato inesperado: esperado array na raiz.");
}
// Procura o título por nome (case-insensitive); tenta match exato e depois "contém"
var titulo = data.find(function (o) {
return (o.treasuryBondName || "").toLowerCase() === bondName.toLowerCase();
}) || data.find(function (o) {
return (o.treasuryBondName || "").toLowerCase().indexOf(bondName.toLowerCase()) >= 0;
});
if (!titulo) {
throw new Error("Título não encontrado: " + bondName);
}
function parsePercentFromIndexer(str) {
if (!str) return null;
var m = str.match(/([+-]?\d+(?:[.,]\d+)?)\s*%/);
if (!m) return null;
return Number(m[1].replace(",", "."));
}
switch (argumento) {
case "ti":
return parsePercentFromIndexer(titulo.investmentProfitabilityIndexerName);
case "tr":
return parsePercentFromIndexer(titulo.redemptionProfitabilityFeeIndexerName);
case "ti_str":
return titulo.investmentProfitabilityIndexerName || "";
case "tr_str":
return titulo.redemptionProfitabilityFeeIndexerName || "";
case "min":
return Number(titulo.investmentBondMinimumValue);
case "venc":
return titulo.maturityDate || "";
case "tipo":
return titulo.typeReceiptInterest || "";
case "raw":
return JSON.stringify(titulo);
case "i":
case "r":
return (Number(titulo.investmentBondMinimumValue))*100;
default:
return titulo.investmentProfitabilityIndexerName || "";
}
}
`
boa tarde, na verdade preciso pegar o PU e não a taxa, alguém conseguiu depois dessa mudança?
Agora, o PU do resgate só é possível pelo download do CSV no link:
https://www.tesourodireto.com.br/documents/d/guest/rendimento-resgatar-csv
A boa notícia é que, pelo Google Sheets é possível obter o CSV diretamente na planilha sem necessidade de fazer o download.
Basta colocar a seguinte fórmula na primeira célula da planilha:
=IMPORTDATA("https://www.tesourodireto.com.br/documents/d/guest/rendimento-resgatar-csv?download=true")
Nesse caso, a planilha irá atualizar toda vez que for reaberta, ou, para forçar a atualização é só fazer uma pequena alteração na fórmula, da um enter, voltar o valor correto e dar outro enter.
Para o PU de aplicação, é só usar o link do CSV de investir:
=IMPORTDATA("https://www.tesourodireto.com.br/documents/d/guest/rendimento-investir-csv?download=true")
A fórmula IMPORTDATA não faz a divisão de ponto e vírgula, mas aí é só usar a fórmula SPLIT em uma coluna à frente, referenciando as colunas de interesse:
=ARRAYFORMULA(SPLIT(C2:C70; ";"))
Achei, mas tive que mudar "um pouco" o código.
` /**
- =TESOURODIRETO("Tesouro IPCA+ 2029"; "ti")
- argumentos suportados:
- "ti" -> taxa para investir (ex.: "7,02")
- "tr" -> taxa para resgatar
- "min" -> aporte mínimo (número em R$)
- "venc"-> data de vencimento (string ISO)
- "tipo"-> U/S/M (sem juros, com juros, mensal - Renda+/Educa+)
- "raw" -> objeto completo do título (JSON)
- retrocompat:
- "i" -> retorna aporte mínimo (PU não está no novo endpoint)
- "r" -> retorna aporte mínimo (PU não está no novo endpoint)
- Precisa buscar em https://www.tesourodireto.com.br/o/rentabilidade/resgatar
*/
function TESOURODIRETO(bondName, argumento) {
bondName = (bondName || "Tesouro IPCA+ 2029").toString().trim();
argumento = (argumento || "ti").toString().trim().toLowerCase();var url = "https://www.tesourodireto.com.br/o/rentabilidade/investir";
var resp = UrlFetchApp.fetch(url, { muteHttpExceptions: true, followRedirects: true, headers: { "Accept": "application/json, text/plain, /", "User-Agent": "Mozilla/5.0 (compatible; AppsScriptFetcher/1.0)", "Referer": "https://www.tesourodireto.com.br/produtos/dados-sobre-titulos/rendimento-dos-titulos" } });
var code = resp.getResponseCode(); if (code !== 200) { throw new Error("HTTP " + code + " ao acessar o endpoint. Corpo: " + resp.getContentText().slice(0, 300)); }
var text = resp.getContentText("UTF-8"); var data; try { data = JSON.parse(text); } catch (e) { throw new Error("Falha ao parsear JSON do endpoint: " + e + " | trecho: " + text.slice(0, 300)); }
if (!Array.isArray(data)) { throw new Error("Formato inesperado: esperado array na raiz."); }
// Procura o título por nome (case-insensitive); tenta match exato e depois "contém" var titulo = data.find(function (o) { return (o.treasuryBondName || "").toLowerCase() === bondName.toLowerCase(); }) || data.find(function (o) { return (o.treasuryBondName || "").toLowerCase().indexOf(bondName.toLowerCase()) >= 0; });
if (!titulo) { throw new Error("Título não encontrado: " + bondName); }
function parsePercentFromIndexer(str) { if (!str) return null; var m = str.match(/([+-]?\d+(?:[.,]\d+)?)\s*%/); if (!m) return null; return Number(m[1].replace(",", ".")); }
switch (argumento) { case "ti": return parsePercentFromIndexer(titulo.investmentProfitabilityIndexerName); case "tr": return parsePercentFromIndexer(titulo.redemptionProfitabilityFeeIndexerName); case "ti_str": return titulo.investmentProfitabilityIndexerName || ""; case "tr_str": return titulo.redemptionProfitabilityFeeIndexerName || ""; case "min": return Number(titulo.investmentBondMinimumValue); case "venc": return titulo.maturityDate || ""; case "tipo": return titulo.typeReceiptInterest || ""; case "raw": return JSON.stringify(titulo); case "i": case "r": return (Number(titulo.investmentBondMinimumValue))*100; default: return titulo.investmentProfitabilityIndexerName || ""; } }
`
Obrigado por postar essa solução!
Porém, tentei aqui mas só recebo Error 406, vide abaixo.
"
Error
Error: HTTP 406 ao acessar o endpoint. Corpo:
"
Está funcionando normalmente para você?
Grato.
Agora, o PU do resgate só é possível pelo download do CSV no link:
https://www.tesourodireto.com.br/documents/d/guest/rendimento-resgatar-csv
A boa notícia é que, pelo Google Sheets é possível obter o CSV diretamente na planilha sem necessidade de fazer o download. Basta colocar a seguinte fórmula na primeira célula da planilha:
=IMPORTDATA("https://www.tesourodireto.com.br/documents/d/guest/rendimento-resgatar-csv?download=true")
Nesse caso, a planilha irá atualizar toda vez que for reaberta, ou, para forçar a atualização é só fazer uma pequena alteração na fórmula, da um enter, voltar o valor correto e dar outro enter.
Para o PU de aplicação, é só usar o link do CSV de investir: =IMPORTDATA("https://www.tesourodireto.com.br/documents/d/guest/rendimento-investir-csv?download=true")
A fórmula IMPORTDATA não faz a divisão de ponto e vírgula, mas aí é só usar a fórmula SPLIT em uma coluna à frente, referenciando as colunas de interesse: =ARRAYFORMULA(SPLIT(C2:C70; ";"))
Obrigado pela dica.
Existe uma solução similar a esse (i.e., utilizando formulas GoogleSheets) para resgatar o "ti" (taxa para investir)?
Grato.
Agora, o PU do resgate só é possível pelo download do CSV no link:
https://www.tesourodireto.com.br/documents/d/guest/rendimento-resgatar-csv
A boa notícia é que, pelo Google Sheets é possível obter o CSV diretamente na planilha sem necessidade de fazer o download. Basta colocar a seguinte fórmula na primeira célula da planilha:
=IMPORTDATA("https://www.tesourodireto.com.br/documents/d/guest/rendimento-resgatar-csv?download=true")
Nesse caso, a planilha irá atualizar toda vez que for reaberta, ou, para forçar a atualização é só fazer uma pequena alteração na fórmula, da um enter, voltar o valor correto e dar outro enter.
Para o PU de aplicação, é só usar o link do CSV de investir: =IMPORTDATA("https://www.tesourodireto.com.br/documents/d/guest/rendimento-investir-csv?download=true")
A fórmula IMPORTDATA não faz a divisão de ponto e vírgula, mas aí é só usar a fórmula SPLIT em uma coluna à frente, referenciando as colunas de interesse: =ARRAYFORMULA(SPLIT(C2:C70; ";"))Obrigado pela dica. Existe uma solução similar a esse (i.e., utilizando formulas GoogleSheets) para resgatar o "ti" (taxa para investir)?
Grato.
O CSV trás essa informação. Você precisa tratar os dados por causa da formatação de vírgula e ponto e vírgula. Mas, está tudo lá.
É possível usar também uma automação no N8N para já entregar os dados tratados, mas ainda não tive tempo pra fazer isso.
=IMPORTDATA("https://www.tesourodireto.com.br/documents/d/guest/rendimento-resgatar-csv?download=true")
Hmmm, eu testei as duas chamadas (rendimento-resgatar-csv e rendimento-investir-csv), mas em nenhuma das duas consegui localizar o 'ti'.
Por exemplo, para IPCA+ 2029 essas são as duas linhas:
Resgatar:
Tesouro IPCA+ 2029;IPCA + 7 | 82%;R$ 3.434 | 69;15/05/2029
Investir:
Tesouro IPCA+ 2029;IPCA + 7 | 70%;R$ 34 | 48;R$ 3.448 | 90;15/05/2029
Talvez eu esteja sendo leigo, mas não consegui visualizar o 'ti'.
Obrigado.
Adaptei o script do App Script para Google Planilha e está funcionando para meu objetivo de pegar o valor de resgate.
/**
- Retorna informações de um título do Tesouro Direto a partir do CSV oficial de resgate.
- @param {string} bondName – Nome do título, ex.: "Tesouro IPCA+ com Juros Semestrais 2035"
- @param {string} argumento – "Taxa de Investimento" → rendimento anual do título
-
"Valor Unitario de Recompra" → preço unitário de resgate
-
"Vencimento" → data de vencimento
- @return {number|string} – Retorna o valor correspondente ao argumento solicitado
*/
function TESOURODIRETO(bondName, argumento = 'Taxa de Investimento') {
// Acessa o cache do script para evitar múltiplas requisições desnecessárias
const cache = CacheService.getScriptCache();
const cacheKey =${bondName}_${argumento}
; // Chave única para cache
// Tenta obter o resultado do cache
let cachedResult = cache.get(cacheKey);
if (cachedResult) return JSON.parse(cachedResult); // Se existe no cache, retorna imediatamente
// URL do CSV oficial de resgate do Tesouro Direto
const url = 'https://www.tesourodireto.com.br/documents/d/guest/rendimento-resgatar-csv?download=true';
// Faz a requisição e obtém o conteúdo do CSV como texto
const csvText = UrlFetchApp.fetch(url).getContentText('UTF-8');
// Separa o CSV em linhas, removendo espaços extras e linhas vazias
const linhas = csvText.split(/\r?\n/).map(l => l.trim()).filter(l => l);
// Extrai o cabeçalho (primeira linha) e cria um índice para localizar colunas pelo nome
const headers = linhas[0].split(';').map(h => h.trim());
const idx = {};
headers.forEach((h, i) => idx[h] = i);
// Procura a linha que corresponde ao título solicitado
// Faz uma busca aproximada (case-insensitive) para evitar erros de grafia
const linhaTitulo = linhas.slice(1).find(l => {
const t = l.split(';')[idx['Título']].trim().toLowerCase();
return t.includes(bondName.trim().toLowerCase());
});
// Lança erro caso o título não seja encontrado
if (!linhaTitulo) throw new Error('Título não encontrado no CSV: ' + bondName);
// Divide a linha do título em colunas individuais
const colunas = linhaTitulo.split(';');
let resultado;
// Retorna o valor conforme o argumento solicitado
switch (argumento) {
case 'Taxa de Investimento':
// Retorna o rendimento anual do título (string)
resultado = colunas[idx['Rendimento anual do título']].trim();
break;
case 'Valor Unitario de Recompra':
// Retira 'R$', pontos e converte vírgula para ponto antes de transformar em float
resultado = parseFloat(
colunas[idx['Preço unitário de resgate']]
.replace('R$', '')
.replace(/./g, '')
.replace(',', '.')
.trim()
);
break;
case 'Vencimento':
// Retorna a data de vencimento do título
resultado = colunas[idx['Vencimento do Título']].trim();
break;
default:
throw new Error('Argumento inválido para CSV de resgate.');
}
// Salva o resultado no cache por 1 hora (3600 segundos)
cache.put(cacheKey, JSON.stringify(resultado), 3600);
return resultado;
}
// --- Função de teste ---
function teste() {
// Exemplo de retorno da taxa anual do título
const taxa = TESOURODIRETO('Tesouro IPCA+ com Juros Semestrais 2035', 'Taxa de Investimento');
Logger.log('Taxa de Investimento: ' + taxa);
// Exemplo de valor unitário de recompra
const valorRecompra = TESOURODIRETO('Tesouro IPCA+ com Juros Semestrais 2035', 'Valor Unitario de Recompra');
Logger.log('Valor Unitario de Recompra: ' + valorRecompra);
// Exemplo de data de vencimento
const vencimento = TESOURODIRETO('Tesouro IPCA+ com Juros Semestrais 2035', 'Vencimento');
Logger.log('Vencimento: ' + vencimento);
// Exemplo de argumento inválido (mostra que o erro é tratado)
try {
const venc = TESOURODIRETO('Tesouro IPCA+ com Juros Semestrais 2035', 'Vencimentooooooooooo');
Logger.log('Vencimento: ' + venc);
} catch (e) {
Logger.log('Erro esperado: ' + e.message);
}
}
=IMPORTDATA("https://www.tesourodireto.com.br/documents/d/guest/rendimento-resgatar-csv?download=true")
Hmmm, eu testei as duas chamadas (rendimento-resgatar-csv e rendimento-investir-csv), mas em nenhuma das duas consegui localizar o 'ti'. Por exemplo, para IPCA+ 2029 essas são as duas linhas:
Resgatar: Tesouro IPCA+ 2029;IPCA + 7 | 82%;R$ 3.434 | 69;15/05/2029
Investir: Tesouro IPCA+ 2029;IPCA + 7 | 70%;R$ 34 | 48;R$ 3.448 | 90;15/05/2029
Talvez eu esteja sendo leigo, mas não consegui visualizar o 'ti'. Obrigado.
É por causa da separação por vírgulas. Você precisa tratar os dados.
Nos exemplos que você pegou:
Resgatar = R$ 3.434 | 69 = 3.434,69
Investir = R$ 3.448 | 90 = 3.448,90
Coloque a linha =vlookup(****;IMPORTDATA("https://www.tesourodireto.com.br/documents/d/guest/rendimento-investir-csv?download=true";";");4;0)
onde *** é célula com o nome do título na célula do Google Sheets, que já aparece o valor do título.. se mudar o número 4 do final da fórmula, altera-se os dados a serem carregados do csv...
=IMPORTDATA("https://www.tesourodireto.com.br/documents/d/guest/rendimento-resgatar-csv?download=true")
Hmmm, eu testei as duas chamadas (rendimento-resgatar-csv e rendimento-investir-csv), mas em nenhuma das duas consegui localizar o 'ti'. Por exemplo, para IPCA+ 2029 essas são as duas linhas:
Resgatar: Tesouro IPCA+ 2029;IPCA + 7 | 82%;R$ 3.434 | 69;15/05/2029
Investir: Tesouro IPCA+ 2029;IPCA + 7 | 70%;R$ 34 | 48;R$ 3.448 | 90;15/05/2029
Talvez eu esteja sendo leigo, mas não consegui visualizar o 'ti'. Obrigado.É por causa da separação por vírgulas. Você precisa tratar os dados. Nos exemplos que você pegou: Resgatar = R$ 3.434 | 69 = 3.434,69 Investir = R$ 3.448 | 90 = 3.448,90
Obrigado pela resposta.
Talvez eu referenciei a variável errada, mas os valores que eu estou buscando é a taxa de juros paga no momento.
Por exemplo, para Tesouro IPCA+ 2029 é IPCA + 7,70% (18/08/2025).
Mas o resultado do GoogleSheets não parece ser tão preciso (IPCA +7) ao invés de 7,70.
A variável que eu buscava anteriorment era: 'anulInvstmtRate'.
Grato.
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;
}`
bom dia, qual argumento passa para pegar o PU de resgate? obrigado.
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!!
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.
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.
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
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.
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;
}`
@WadjoResende Muito obrigado. Funcionou perfeitamente.
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
@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?
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.
Vish, tá retornando 403 da url "https://www.tesourodireto.com.br/documents/d/guest/rendimento-resgatar-csv?download=true";, talvez tenham limitado
Sim, está fora desde meados da quarta/quinta-feira.
Alguém já descobriu uma forma diferente?
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
- 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.
- Como os dados vêm diretamente dos CSVs, não há garantia de que a API continuará funcionando indefinidamente.
- O campo
investable
foi adicionado por mim para indicar se o título ainda está disponível para investimento atualmente na plataforma do Tesouro. - 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. - 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;
}
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.
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.,]";"");".";","))
Comigo também está fora do ar!
Será que eles descontinuaram esse acesso?