Получить актуальные курсы валют в Power BI — одна из тех задач, которые выглядят завершёнными задолго до того, как это действительно так. Вы вставляете URL API в Получить данные → Веб, появляется таблица курсов, мера пересчитанной выручки загорается, вы публикуете. Через два дня обновление набора данных падает в службе Power BI с сообщением про динамический источник данных — или проходит успешно и тихо пересчитывает каждую историческую операцию по сегодняшнему курсу.
Это руководство проходит весь путь: запрос Power Query на M, возвращающий аккуратную таблицу курсов; шаблон RelativePath, благодаря которому она остаётся обновляемой в службе; функция исторических курсов для пересчёта на дату операции; DAX-мера для динамической валюты отчётности; и арифметика количества запросов, которая определяет, помещается ли ваше расписание обновления в тарифный план API.
Все запросы ниже написаны под документированные структуры ответов API Finexly. Если вы уже читали наше руководство по актуальным курсам валют в Excel, код M покажется знакомым — но Power BI добавляет серверный слой обновления, которого у Excel нет, и именно на нём ломается большинство таких проектов.
Три способа завести курсы в Power BI
| Подход | Обновляется в службе? | Ключ API защищён? | Исторические курсы | Для чего годится |
|---|---|---|---|---|
| Веб-коннектор, URL вставлен в диалог | Часто нет — URL обычно становится динамическим источником | ❌ Ключ остаётся в URL | Нет | Пятиминутная проверка идеи |
Пустой запрос с Web.Contents + RelativePath | ✅ Да | ✅ Через заголовок | ✅ Да | Практически любая рабочая модель |
| Поток данных (или конвейер Fabric), питающий таблицу курсов | ✅ Да, и независимо от обновления отчёта | ✅ Через заголовок | ✅ Да | Несколько отчётов, большие объёмы |
Способ 1: живая таблица курсов на Power Query
Откройте Преобразовать данные → Создать источник → Пустой запрос → Расширенный редактор и вставьте это. Получите по строке на валютную пару с меткой времени получения в UTC.
let
ApiKey = "YOUR_API_KEY",
Base = "USD",
Symbols = "EUR,GBP,JPY,CHF,AUD,CAD,SEK,NZD",
Source = Json.Document(
Web.Contents(
"https://api.finexly.com",
[
RelativePath = "v1/latest",
Query = [ base = Base, symbols = Symbols ],
Headers = [ #"Authorization" = "Bearer " & ApiKey ]
]
)
),
Rates = Record.ToTable( Source[rates] ),
Renamed = Table.RenameColumns( Rates, {{"Name", "Quote"}, {"Value", "Rate"}} ),
AddBase = Table.AddColumn( Renamed, "Base", each Base, type text ),
AddStamp = Table.AddColumn( AddBase, "RetrievedUTC", each DateTimeZone.UtcNow(), type datetimezone ),
Typed = Table.TransformColumnTypes(
AddStamp,
{{"Quote", type text}, {"Rate", type number}}
)
in
TypedВызываемая конечная точка в командной строке выглядит так — стоит один раз выполнить, чтобы увидеть структуру, которую вы разбираете:
curl "https://api.finexly.com/v1/latest?base=USD&symbols=EUR,GBP,JPY" \
-H "Authorization: Bearer YOUR_API_KEY"{
"success": true,
"base": "USD",
"date": "2026-09-01",
"rates": { "EUR": "…", "GBP": "…", "JPY": "…" }
}Почему RelativePath и Query не опциональны
Это самая важная мысль статьи, поэтому у неё отдельный заголовок.
Если собрать URL одной склеенной строкой — "https://api.finexly.com/v1/latest?base=" & Base — Power Query не может определить адрес назначения до момента выполнения запроса. Microsoft классифицирует это как динамический источник данных, а динамические источники в службе Power BI не обновляются — по соображениям безопасности и конфиденциальности. Отчёт прекрасно обновится на вашем ноутбуке и упадёт в тот момент, когда попадёт в расписание.
Передача пути в RelativePath, а параметров в Query — документированное исключение. Тогда Power BI может разрешить единый статический базовый URL — https://api.finexly.com — для проверок учётных данных и конфиденциальности, а переменные части остаются переменными. Отсюда три правила:
- Базовый URL должен быть строковым литералом. Никаких параметров, никакой конкатенации, никаких
&. RelativePath— это фиксированный путь конечной точки."v1/latest", а не"v1/latest?base=USD".- Никогда не склеивайте строки внутри
Query. Передавайте запись пар «имя — значение» и позвольте Power Query выполнить URL-кодирование. Он также экранирует символы, которые иначе сломали бы запрос.
Настройка учётных данных
При первом запуске Power BI спросит, как аутентифицироваться на https://api.finexly.com. Выберите Анонимно. Это кажется неправильным, но это верно: ключ API едет в заголовке Authorization, который вы задали в коде M, а не через хранилище учётных данных Power BI. Вариант Веб-API или Обычная заставит Power BI добавить собственный заголовок, и запрос будет отклонён.
Задайте уровень конфиденциальности Общедоступный или Организация одинаково для всех источников. Несовпадающие уровни — вторая по частоте причина обновления, которое работает в Desktop и падает в службе: Power BI скорее заблокирует запрос, чем рискнёт передать данные одного источника в запрос к другому.
Честная оговорка: ключ теперь хранится открытым текстом внутри запроса. Любой, кто откроет .pbix, прочитает его. Для всего, что уходит за пределы вашей машины, превратите ключ в параметр Power Query, а заполненную версию держите в потоке данных, принадлежащем сервисной учётной записи, — тогда авторы отчётов пользуются таблицей курсов, ни разу не увидев учётные данные.
Способ 2: исторические курсы для пересчёта на дату операции
Живая таблица курсов отвечает на вопрос «сколько сейчас стоит EUR/USD». Она не отвечает на вопрос «какова была наша январская выручка в USD», и использовать её для этого — самая дорогая ошибка во всей теме: пересмотр цифр прошлого квартала из-за того, что модель пересчитала их по новому курсу, — ровно то, что ищут аудиторы. Если ваш отчёт питает что-то, что придётся защищать, прочитайте вместе с этим разделом наше руководство по курсам валют и налоговой отчётности.
Нужна таблица курсов с ключом по дате. Эта функция M оборачивает конечную точку временных рядов и возвращает по строке на дату и валюту:
let
FxHistory = (base as text, symbols as text, startDate as date, endDate as date) as table =>
let
ApiKey = "YOUR_API_KEY",
Source = Json.Document(
Web.Contents(
"https://api.finexly.com",
[
RelativePath = "v1/timeseries",
Query = [
base = base,
symbols = symbols,
start_date = Date.ToText( startDate, [Format = "yyyy-MM-dd", Culture = "en-US"] ),
end_date = Date.ToText( endDate, [Format = "yyyy-MM-dd", Culture = "en-US"] )
],
Headers = [ #"Authorization" = "Bearer " & ApiKey ]
]
)
),
Days = Table.RenameColumns( Record.ToTable( Source[rates] ), {{"Name", "RateDate"}} ),
Expanded = Table.ExpandRecordColumn( Days, "Value", Record.FieldNames( Days{0}[Value] ) ),
Unpivoted = Table.UnpivotOtherColumns( Expanded, {"RateDate"}, "Quote", "Rate" ),
AsDate = Table.TransformColumns(
Unpivoted,
{{"RateDate", each Date.FromText( _, [Format = "yyyy-MM-dd", Culture = "en-US"] ), type date}}
),
AddBase = Table.AddColumn( AsDate, "Base", each base, type text ),
Typed = Table.TransformColumnTypes( AddBase, {{"Quote", type text}, {"Rate", type number}} )
in
Typed
in
FxHistoryДве детали здесь делают настоящую работу:
Culture = "en-US"и вDate.ToText, и вDate.FromText. Без этого машина с немецкой или французской локалью отправит01.09.2026в качествеstart_date, и API его отвергнет — или, хуже, обновление у коллеги даст таблицу другой формы, чем у вас. Локаль — невидимая переменная любого проекта Power Query, который выходит за пределы одной страны.Table.UnpivotOtherColumns. API возвращает даты как ключи записи с вложенной записью валют. Развёртывание в длинный форматRateDate / Base / Quote / Rateдаёт таблицу, которая чисто соединяется с измерением дат и не требует перекройки при добавлении каждой новой валюты.
Вызывайте один раз за загрузку: FxHistory( "USD", "EUR,GBP,JPY", #date(2026,1,1), Date.From( DateTime.LocalNow() ) ).
Поскольку курсы прошедших дат никогда не меняются, эта таблица — хрестоматийный случай для инкрементного обновления: разбейте на разделы по RateDate, обновляйте последние 7 дней, остальное архивируйте. Время обновления перестаёт расти вместе с историей, а количество запросов — тоже.
Не вызывайте API построчно
Приём, убивающий такие модели, — вызов функции курса как пользовательского столбца на таблице фактов. Десять тысяч операций означают десять тысяч HTTP-запросов на каждое обновление, сорокаминутное обновление и счёт за превышение квоты, приходящий до обеда.
Посчитайте заранее. Power BI Pro допускает 8 запланированных обновлений набора данных в сутки; Premium Per User и ёмкость Fabric — 48. Один вызов /v1/latest, покрывающий все нужные валюты, при обновлении по максимуму Pro стоит 8 запросов в день — около 240 в месяц, что уверенно укладывается в бесплатный лимит 1 000. То же расписание с построчной функцией не имеет верхней границы. Даже на максимуме PPU в 48 обновлений один консолидированный вызов даёт примерно 1 440 запросов в месяц — это небольшой платный план, а не построчная катастрофа. Наши тарифные планы перечисляют пороги, если нужно посчитать точно.
Когда курсы нужны более чем одному отчёту, перенесите запрос в поток данных. Поток вызывает API по собственному расписанию и материализует результат; каждый нижестоящий набор данных читает из хранилища, а не бьёт по API снова. Пять отчётов на одном потоке дают один комплект запросов, а не пять.
Пересчёт сумм: слияние в Power Query или DAX-мера
Есть два законных места для пересчёта, и они отвечают на разные вопросы.
Вариант A: слияние в Power Query
Если валюта отчётности фиксирована — всё показывается в USD, и точка — выполняйте соединение на загрузке.
- Загрузите таблицу фактов в Power Query.
- Объединить запросы с таблицей курсов, сопоставляя по коду валюты и дате. Удерживайте
Ctrlи выбирайте столбцы в одинаковом порядке в обеих таблицах. - Разверните только столбец
Rateи задайте тип Фиксированный десятичный. - Выделите
AmountиRate, затем Добавление столбца → Стандартный → Умножить. - Отключите загрузку таблицы курсов, если больше ничто на неё не ссылается.
Это быстро, материализуется один раз и не поддаётся срезам. Последнее и есть плата за скорость.
Вариант B: DAX-мера с выбираемой валютой отчётности
Если пользователям нужно переключать весь отчёт между USD, EUR и GBP, пересчёт должен происходить во время запроса. Добавьте несвязанную таблицу Reporting Currency с одним столбцом кодов ISO, положите её в срез и напишите:
Revenue (Reporting Currency) =
VAR ReportingCurrency = SELECTEDVALUE( 'Reporting Currency'[Code], "USD" )
RETURN
SUMX (
'Sales',
VAR TxCurrency = 'Sales'[CurrencyCode]
VAR TxDate = 'Sales'[OrderDate]
VAR Rate =
CALCULATE (
MAX ( 'FX Rates'[Rate] ),
REMOVEFILTERS ( 'FX Rates' ),
'FX Rates'[Base] = TxCurrency,
'FX Rates'[Quote] = ReportingCurrency,
'FX Rates'[RateDate] = TxDate
)
RETURN 'Sales'[Amount] * Rate
)Три момента заслуживают внимания:
- Строки
VAR— несущие. ЗахватTxCurrencyиTxDateв переменные доCALCULATEпривязывает их к текущей строкеSUMX. Прямая ссылка на столбцы внутри аргументов фильтра приглашает ошибку перехода контекста, дающую правдоподобные, но неверные итоги. REMOVEFILTERS( 'FX Rates' )не даёт входящему фильтру на таблице курсов сузить поиск.- Отсутствующий курс возвращает
BLANK(), аAmount * BLANK()— этоBLANK(). Именно такое поведение и нужно. Строка, которая тихо откатывается к непересчитанной сумме, — это строка, раздувающая итог ровно на величину курса.
Ловушка связей
Не создавайте физическую связь от таблицы фактов к таблице курсов только по коду валюты. В таблице с ключом по дате код валюты не уникален, поэтому Power BI предложит связь «многие ко многим», а «многие ко многим» с двунаправленной фильтрацией с удовольствием размножит ваши строки и умножит выручку. Либо используйте составное слияние в Power Query, либо держите таблицу курсов несвязанной и ищите курсы в DAX, как показано выше.
Пять ошибок, дающих неверные числа без сообщения об ошибке
- Склеенные URL. Обновляется в Desktop, падает в службе с «Этот набор данных содержит динамический источник данных». Лечится
RelativePathиQuery. - Перевёрнутые пары.
base=USD&symbols=EURвозвращает USD→EUR. Если таблица фактов хранит суммы в EUR, нужна обратная величина. Проверьте одну известную пару вручную, прежде чем доверять хоть какому-то итогу. Наш справочник ISO 4217 поможет, если непонятно, какой код с какой стороны. - Сегодняшний курс на исторических строках. Число меняется при каждом обновлении, и никто не замечает, пока кто-нибудь не сравнит две выгрузки одного отчёта.
- Даты и десятичные в локальном формате. Запятая как десятичный разделитель превращает
1,0842в текст,Table.TransformColumnTypesвозвращает ошибку, которую модель считает пустым значением, и затронутые строки исчезают из итогов. - Округление не на том шаге. Округляйте один раз, при отображении, после умножения. Округление курса до четырёх знаков перед умножением на семизначную сумму даёт заметное расхождение с бухгалтерской системой.
Часто задаваемые вопросы
Может ли Power BI обновлять курсы валют автоматически?
Да. Опубликованному набору данных можно задать до 8 обновлений в сутки на Power BI Pro и 48 в сутки на Premium Per User или ёмкости Fabric. Конечная точка XMLA в Premium позволяет внешним инструментам запускать обновления за пределами этих лимитов. Самому запросу курсов ничего особенного не нужно, кроме того чтобы он был построен на Web.Contents + RelativePath — тогда служба его примет.
Как безопасно передать ключ API в Power BI?
Поместите его в запись Headers функции Web.Contents как Authorization = "Bearer " & ApiKey и выберите аутентификацию Анонимно, когда Power BI спросит. Так ключ не попадёт ни в URL, ни в логи прокси, ни в историю браузера. Это не шифрует ключ внутри .pbix — для общих моделей держите ключ в потоке данных, принадлежащем сервисной учётной записи, а отчёты пусть читают материализованную таблицу.
Почему набор данных пишет «Этот набор данных содержит динамический источник данных»?
Потому что URL собирается в коде, и Power BI не может проверить адрес до выполнения запроса, поэтому отказывается обновлять его в службе. Перестройте вызов на статический базовый URL плюс опции RelativePath и Query, опубликуйте заново и повторно введите учётные данные для базового URL.
Как пересчитать суммы по курсу на дату операции?
Загрузите таблицу курсов с ключом по дате из конечной точки временных рядов и сопоставляйте по валюте и дате — либо составным слиянием в Power Query, либо поиском через CALCULATE внутри SUMX, как показано выше. Никогда не соединяйте только по валюте: вы молча возьмёте ту строку курса, которая случайно окажется первой при сортировке.
Есть ли бесплатный валютный API, работающий с Power BI?
Да. Бесплатный тариф бесплатного валютного API Finexly включает 1 000 запросов в месяц без кредитной карты, чего с большим запасом хватает набору данных на лицензии Pro, обновляющемуся восемь раз в сутки. Если вы сравниваете поставщиков по лимитам обновления, глубине истории или охвату валют, сравните валютные API, прежде чем встраивать один из них в модель, которую будете поддерживать годами.
Начните с Finexly
Готовы добавить актуальные курсы валют в свои отчёты Power BI? Получите бесплатный ключ API Finexly — без кредитной карты. Начните с 1 000 бесплатных запросов в месяц и переходите на больший тариф по мере роста. Курсы в реальном времени и исторические данные по более чем 170 валютам из одного REST API, который ведёт себя одинаково, вызываете вы его из Power Query, Python или платёжного сервиса.
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 →