Todo guia sobre conversão de moedas no Power BI cai em um de dois campos. O campo DAX mostra uma medida elegante e assume silenciosamente que já existe uma tabela ExchangeRate no seu modelo. O campo das APIs mostra um trecho de Power Query que funciona lindamente no Power BI Desktop e falha no momento em que você publica, porque a atualização agendada o rejeita.
Este guia cobre as duas metades, nessa ordem: como puxar taxas de câmbio ao vivo para o Power BI a partir de uma API REST de um jeito que sobrevive à publicação no serviço Power BI, como modelar essas taxas para que seus números sejam defensáveis, e como converter valores no momento da importação ou no momento da consulta, conforme o que o seu relatório realmente precisa.
Todos os exemplos de Power Query e DAX abaixo foram escritos com base nos formatos de resposta documentados da API da Finexly.
Primeiro decida qual problema de conversão você tem
"Conversão de moedas no Power BI" são três problemas de engenharia diferentes usando o mesmo nome, e escolher o errado é o erro mais caro deste artigo inteiro.
- Muitas moedas de origem, uma moeda de relatório. Sua tabela de vendas tem linhas em EUR, GBP e JPY e o CFO quer um único número em USD. Converta na importação. A taxa é uma propriedade da transação, não do relatório.
- Uma moeda de origem, muitas moedas de relatório. Tudo é armazenado em USD e os usuários escolhem a moeda de exibição em uma segmentação. Converta na consulta, com DAX. Pré-calcular todas as moedas é impraticável.
- Muitas moedas de origem, muitas de relatório. Normalize para uma única moeda pivô na importação e depois aplique o caso 2 por cima. Não tente resolver isso em um passo só.
A regra prática que modeladores experientes repetem, e que vale repetir de novo: aplique a conversão o mais cedo que você conseguir. Cada conversão empurrada para o momento da consulta cobra seu preço em cada visual, cada mudança de filtro, cada clique de segmentação.
Puxando taxas ao vivo para o Power Query do jeito certo
Comece pela tabela de taxas. A chamada mais eficiente é uma consulta multipar, para obter todas as moedas que interessam em uma única requisição em vez de uma requisição por moeda.
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://api.finexly.com/v1/convert?q=USD_EUR,USD_GBP,USD_JPY"{
"USD_EUR": { "rate": 0.9215 },
"USD_GBP": { "rate": 0.7892 }
}Aqui está o equivalente como consulta M do Power Query. Crie uma consulta em branco (Página Inicial → Nova Fonte → Consulta em Branco → Editor Avançado) e cole:
let
ApiKey = "YOUR_API_KEY",
Pairs = "USD_EUR,USD_GBP,USD_JPY,USD_CAD,USD_AUD,USD_CHF",
Source = Json.Document(
Web.Contents(
"https://api.finexly.com",
[
RelativePath = "v1/convert",
Query = [ q = Pairs ],
Headers = [ #"Authorization" = "Bearer " & ApiKey ]
]
)
),
ToTable = Record.ToTable(Source),
Expanded = Table.ExpandRecordColumn(ToTable, "Value", {"rate"}, {"Rate"}),
Split = Table.SplitColumn(
Expanded, "Name",
Splitter.SplitTextByDelimiter("_", QuoteStyle.None),
{"BaseCurrency", "Currency"}
),
Typed = Table.TransformColumnTypes(
Split,
{{"BaseCurrency", type text}, {"Currency", type text}, {"Rate", type number}}
),
Stamped = Table.AddColumn(Typed, "RetrievedAt", each DateTimeZone.UtcNow(), type datetimezone)
in
StampedChame-a de FxRates. Você obtém uma tabela de quatro colunas — BaseCurrency, Currency, Rate, RetrievedAt — pronta para uso em relacionamentos e medidas.
O erro de Web.Contents que quebra a atualização agendada
Repare no que a consulta acima não faz: ela nunca concatena os parâmetros dentro da string da URL. Esta é a razão mais comum para um relatório de conversão de moedas funcionar no desktop e morrer na nuvem.
Se você escrever isto:
// Do NOT do this
Source = Json.Document(
Web.Contents("https://api.finexly.com/v1/convert?q=" & Pairs)
)…o Power BI Desktop atualizará sem reclamar, e o serviço Power BI recusará com "Este conjunto de dados inclui uma fonte de dados dinâmica. Não há suporte para atualização." O serviço precisa resolver uma URL base estática em tempo de análise para conseguir anexar credenciais a ela. Passar as partes variáveis por RelativePath e Query dá exatamente isso: a base continua sendo https://api.finexly.com e tudo o que é dinâmico vive nas opções.
O mesmo vale para qualquer consulta que monte uma URL a partir de um parâmetro, uma data ou um valor de outra tabela. Se você levar apenas uma coisa deste artigo, leve RelativePath.
Lidando com a chave de API sem deixá-la no código
ApiKey = "YOUR_API_KEY" embutido serve para um teste de cinco minutos e é errado para qualquer coisa compartilhada. O código M de um modelo semântico é visível para qualquer pessoa com permissão de build sobre ele.
Duas opções que funcionam:
- Um parâmetro do Power Query (Página Inicial → Gerenciar Parâmetros), referenciado como
ApiKey = KeyParam. Ele continua armazenado com o modelo, mas fica centralizado, é fácil de rotacionar e pode ser sobrescrito por ambiente com pipelines de implantação. - O tipo de credencial Web API. No serviço Power BI, vá em Configurações do modelo semântico → Credenciais da fonte de dados → Editar credenciais e escolha Web API, informando a chave ali. O serviço passa a injetar o cabeçalho
Authorizationpor conta própria e você remove a opçãoHeadersdo seu M por completo. Assim o segredo fica fora da definição do modelo.
Seja qual for a escolha, use uma chave restrita a relatórios e rotacione quando alguém sair do time. Se a chave acabar na query string da URL em vez de em um cabeçalho, ela pode aparecer em logs de acesso de proxies e servidores — a documentação da Finexly alerta sobre isso, e vale para qualquer provedor.
Construa uma dimensão de moeda, não só uma lista de taxas
Uma tabela de taxas sozinha dá números. Ela não dá rótulos corretos, casas decimais corretas nem uma segmentação que ordene com sentido. Puxe a lista de moedas como dimensão própria:
let
ApiKey = "YOUR_API_KEY",
Source = Json.Document(
Web.Contents(
"https://api.finexly.com",
[
RelativePath = "v1/currencies",
Headers = [ #"Authorization" = "Bearer " & ApiKey ]
]
)
),
ToTable = Table.FromList(Source, Splitter.SplitByNothing(), {"Currency"}),
Typed = Table.TransformColumnTypes(ToTable, {{"Currency", type text}})
in
TypedDepois adicione as duas colunas que o Power BI não consegue inferir para você:
MinorUnits— o número de casas decimais que a moeda realmente usa. JPY tem 0, KWD tem 3, a maioria tem 2. Formatar um total em ienes com duas casas é um erro de correção visível num relatório financeiro, e arredondar no passo errado agrava tudo. O guia de arredondamento de moedas mostra por onde o erro entra.FormatString— por exemplo"\€#,0.00","\¥#,0". Você vai precisar disso para as cadeias de formato dinâmicas mais adiante.
Ambas seguem o padrão ISO 4217, e não algo que o Power BI conheça nativamente; a referência ISO 4217 tem a tabela completa. Marque essa tabela como dimensão, relacione-a a FxRates[Currency] de um para muitos e use ela — não a tabela de taxas — como fonte da sua segmentação.
Caminho A: converter na importação (rápido, sem graça, correto)
Para o cenário 1 — muitas moedas entrando, uma saindo — faça o trabalho no Power Query e deixe o modelo guardar um único número limpo.
- Carregue sua consulta de transações no Power Query.
- Página Inicial → Mesclar Consultas, ligando
Transactions[Currency]aFxRates[Currency](externa à esquerda). - Expanda a coluna mesclada e mantenha
Rate. - Adicionar Coluna → Coluna Personalizada:
= if [Currency] = "USD" then [Amount]
else if [Rate] = null then null
else [Amount] / [Rate]Repare no ramo null. Uma junção externa à esquerda contra uma tabela de taxas sem determinada moeda produz null, e null na aritmética do Power Query produz null silenciosamente em vez de erro — o que vira um branco no visual e um total discretamente baixo demais. Torne a lacuna explícita para poder filtrá-la e enxergá-la.
Repare também na divisão. USD_EUR = 0.9215 significa que um USD compra 0,9215 EUR, então converter um valor em EUR para USD divide. Converter um valor em USD para EUR multiplica. Inverter isso é o segundo bug mais comum em relatórios multimoeda e, com taxas perto de 1,0, é quase invisível — um erro de 3% num número EUR/USD parece diferença de arredondamento até alguém conciliar.
Caminho B: converter na consulta com DAX
Para o cenário 2 — uma moeda armazenada e uma moeda de exibição escolhida pelo usuário — a conversão tem que acontecer numa medida.
A versão ingênua faz um LOOKUPVALUE por linha e fica lenta a partir de algumas centenas de milhares de linhas. Agregue primeiro, converta uma vez:
Sales (Reporting Currency) =
VAR SelectedCurrency = SELECTEDVALUE ( Currency[Currency], "USD" )
VAR Rate =
CALCULATE (
SELECTEDVALUE ( FxRates[Rate] ),
FxRates[Currency] = SelectedCurrency
)
VAR Result =
IF (
SelectedCurrency = "USD",
[Sales Amount],
[Sales Amount] * Rate
)
RETURN
IF ( ISBLANK ( Rate ) && SelectedCurrency <> "USD", BLANK (), Result )Dois detalhes que importam mais do que parecem:
SELECTEDVALUEcom valor padrão. Sem o fallback"USD", a medida devolve branco sempre que nenhuma moeda estiver selecionada — que é justamente o estado em que o relatório abre.- A proteção explícita de branco. Se uma moeda não tem taxa, devolva branco de propósito em vez de deixar
[Sales Amount] * BLANK()devolver zero. Um zero num cartão de receita é uma mentira; um branco é uma lacuna visível.
Quando a taxa varia ao longo do tempo
A medida acima usa uma única taxa atual para todo o conjunto de dados. Isso está correto para "quanto valeria hoje a receita do ano passado" e errado para quase todo o resto. Se sua tabela de taxas tem uma linha por moeda por dia, agrupe por data antes de converter:
Sales (Historical Rates) =
SUMX (
VALUES ( 'Date'[Date] ),
VAR DayRate =
CALCULATE (
SELECTEDVALUE ( FxRates[Rate] ),
FxRates[Currency] = SELECTEDVALUE ( Currency[Currency], "USD" )
)
RETURN
[Sales Amount] * DayRate
)Iterar sobre VALUES('Date'[Date]) em vez da tabela fato mantém o iterador pequeno — dias, não transações.
Cadeias de formato dinâmicas
Um número convertido com prefixo $ fixo é pior do que nenhum símbolo. No Power BI, defina o Formato da medida como Dinâmico e forneça uma expressão:
SELECTEDVALUE ( Currency[FormatString], "#,0.00" )Agora o cartão que mostra ¥ mostra ¥, com zero casas decimais e sem uma segunda medida. Isso exigia grupos de cálculo no Analysis Services; as cadeias de formato dinâmicas para medidas trouxeram o recurso para o próprio Power BI.
Qual taxa você deveria estar usando de verdade?
Esta é a pergunta que separa um dashboard de um relatório que o financeiro assina, e nenhuma API pode respondê-la por você.
- Taxa spot da data da transação — para registrar transações individuais. Máxima fidelidade, maior tabela de taxas.
- Média mensal ou do período — o padrão para itens da demonstração de resultados tanto sob IAS 21 quanto sob ASC 830. Suaviza a volatilidade intramensal e é o que a maioria das consolidações usa.
- Taxa de fechamento do período — para itens do balanço: caixa, contas a receber, contas a pagar.
- Taxa orçamentária ou de plano — uma taxa fixa mantida o ano todo para que a análise de variações isole o desempenho operacional do movimento cambial.
Um modelo sério costuma precisar de duas ou três dessas lado a lado, como colunas separadas na mesma tabela de taxas (SpotRate, AverageRate, ClosingRate), e não como tabelas distintas. Se o seu relatório alimenta algo que vira declaração, o guia de taxas de câmbio e apuração fiscal trata de qual fonte e qual carimbo de tempo você precisa conseguir defender, e o guia de taxas históricas cobre como obter taxas datadas em vez de taxas ao vivo.
A lacuna do fim de semana que quebra totais em silêncio
O mercado de câmbio fecha. Uma tabela de taxas diária montada a partir de uma API ao vivo não tem sábado, nem domingo, nem 25 de dezembro. Junte uma transação datada de sábado a essa tabela e você recebe null, e null vira branco, e o branco vira um total menor exatamente pelo valor das vendas do fim de semana.
Resolva na tabela de taxas, não na medida. Gere uma lista de datas completa e preencha para baixo:
let
Dates = List.Dates(#date(2026,1,1), Duration.Days(Date.From(DateTime.LocalNow()) - #date(2026,1,1)) + 1, #duration(1,0,0,0)),
DateTable = Table.FromList(Dates, Splitter.SplitByNothing(), {"Date"}),
Typed = Table.TransformColumnTypes(DateTable, {{"Date", type date}}),
Joined = Table.NestedJoin(Typed, {"Date"}, RateHistory, {"Date"}, "r", JoinKind.LeftOuter),
Expanded = Table.ExpandTableColumn(Joined, "r", {"Currency", "Rate"}),
Filled = Table.FillDown(Expanded, {"Currency", "Rate"})
in
FilledTable.FillDown carrega a taxa de sexta-feira pelo fim de semana, que é o tratamento convencional e, mais importante, um tratamento declarado em vez de acidental. Ordene por moeda e data antes de preencher, ou você carregará a taxa da moeda errada pela lacuna.
Se o seu plano não inclui endpoints históricos, dá para construir o histórico para frente: acrescente as taxas de hoje a uma tabela armazenada a cada atualização — um dataflow ou uma tabela do Fabric Lakehouse funcionam bem — e em um trimestre você terá uma série temporal real. Não é retroativo, mas custa uma chamada de API por dia.
Agendamento de atualização e a aritmética da cota
O Power BI Pro permite 8 atualizações agendadas por dia em um modelo semântico; capacidades Premium e Fabric permitem 48. Esse é o número que sua cota de API precisa cobrir, e a conta é mais amigável do que a maioria imagina.
A tabela de taxas acima são duas chamadas por atualização: uma para /v1/currencies e outra para /v1/convert. Logo:
| Cadência de atualização | Atualizações/mês | Chamadas de API/mês | Plano Finexly |
|---|---|---|---|
| 8/dia (máximo do Pro) | ~240 | ~480 | Gratuito (1.000/mês) |
| 48/dia (Fabric, a cada 30 min) | ~1.440 | ~2.880 | Starter |
| 48/dia + histórico horário em dataflow | ~2.160 | ~4.320 | Growth |
O teto de 10 por minuto é o que merece atenção. Se você montar uma consulta que chama /v1/rate uma vez por moeda dentro de um Table.AddColumn, vinte moedas significam vinte chamadas em poucos segundos e uma rajada de respostas 429 no meio da atualização. É exatamente para isso que existe a chamada multipar /v1/convert. Agrupe e faça cache: o guia de cache e tratamento de erros traz padrões de retry com backoff que valem igualmente para uma atualização agendada.
Gateways, Excel e Fabric
Algumas notas de ambiente que economizam uma tarde cada:
- Não é preciso gateway. Uma API REST na nuvem não é uma fonte local, então você não precisa de gateway de dados local para isso. Se a atualização falha e alguém sugere instalar um, quase sempre é o erro de fonte de dados dinâmica disfarçado.
- O Excel usa o mesmo motor. O Power Query no Excel aceita exatamente o M acima. Se o seu público vive em pastas de trabalho e não em dashboards, o guia de taxas ao vivo no Excel cobre
WEBSERVICE,LAMBDAe a matriz de versões; há também um equivalente para Google Sheets. - O Dataflow Gen2 do Fabric é a melhor casa para a tabela de taxas assim que mais de um relatório precisa dela. Aterrisse as taxas uma vez, deixe todos os modelos semânticos lerem a mesma tabela e seu uso de API para de escalar com o número de relatórios.
- Confira contra um número conhecido antes de publicar. Pegue um par no conversor de moedas e compare com o que seu modelo mostra para o mesmo par no mesmo instante. Se divergirem, você tem um problema de direção ou de arredondamento, e é melhor achá-lo agora do que numa reunião de conselho.
Perguntas frequentes
O Power BI consegue converter moedas sem uma API? Consegue, se você fornecer as taxas: uma tabela mantida à mão, uma exportação do sistema financeiro ou uma view de banco de dados. O Power BI não tem fonte de taxas embutida. A API importa quando você precisa de taxas que se atualizam sem alguém lembrar de atualizá-las.
Por que meu relatório de conversão atualiza no Power BI Desktop mas falha no serviço?
Quase sempre pelo erro de fonte de dados dinâmica. Sua chamada Web.Contents monta a URL por concatenação de strings. Mova as partes variáveis para as opções RelativePath e Query para que a URL base fique estática, republique e reinsira as credenciais.
Devo converter moeda no Power Query ou no DAX? Power Query quando o relatório tem uma única moeda de relatório — é mais rápido e mais simples. DAX quando o usuário escolhe a moeda em tempo de execução. Se precisar dos dois, normalize para uma moeda pivô no Power Query e coloque a medida DAX por cima.
Quantas requisições de API uma atualização do Power BI consome?
Duas por atualização, se você agrupar todos os pares numa única chamada /v1/convert. No máximo de 8 atualizações diárias do Power BI Pro, isso dá cerca de 480 requisições por mês, dentro de um plano gratuito. Só fica caro se você chamar a API uma vez por moeda ou uma vez por linha.
Como lidar com fins de semana e feriados numa tabela de taxas diária? Gere uma tabela de datas contínua, faça a junção à esquerda com as taxas, ordene por moeda e data e preencha para baixo. A taxa de sexta-feira se estende pelo fim de semana. O importante é que o tratamento seja deliberado e documentado, e não que as linhas sumam em silêncio.
Qual taxa de câmbio usar para relatórios financeiros? Taxas médias do período para itens da demonstração de resultados e taxas de fechamento para itens do balanço, tanto sob IAS 21 quanto sob ASC 830. Guarde-as como colunas separadas de uma mesma tabela de taxas para que o relatório alterne entre elas sem mudar o modelo.
Pronto para colocar taxas ao vivo por trás dos seus dashboards? Obtenha sua chave gratuita da API Finexly — sem cartão de crédito. Comece com 1.000 requisições por mês, o suficiente para atualizar um workspace Power BI Pro na frequência máxima, e faça upgrade quando precisar de dados históricos ou de uma cadência maior. Se ainda estiver comparando fornecedores, a página de comparação coloca as opções lado a lado.
Explore More
Vlado Grigirov
Senior Currency Markets Analyst & Financial Strategist
Vlado Grigirov is a senior currency markets analyst and financial strategist with over 14 years of experience in foreign exchange markets, cross-border finance, and currency risk management. He has wo...
View full profile →