методы API
Срез по территориям, поиск рядов и показателей, значения одного ряда, происхождение цифр и справочники - чтобы знать, что подставлять в параметры. У каждого метода - готовый пример на Python, R и Julia.
Во всех примерах токен передаётся заголовком Authorization: Bearer;
подходит и X-API-Key. Тарифы и квоты - на отдельной странице.
GET /api/v1/data - срез по территориям
Отдаёт один показатель сразу по многим территориям: «цены на пиво по всем регионам» - это один запрос, а не восемьдесят пять. Квота считается в значениях, поэтому срез стоит ровно столько, сколько в нём точек.
Параметры
| Параметр | По умолчанию | Что задаёт |
|---|---|---|
dataset | обязателен | набор: fedstat/indicator_31448 или просто код набора |
geo | region | уровень территории или перечень кодов через запятую |
class | все | код продукта, несколько через запятую, либо all |
measure | все | вид показателя (у ИПЦ - вид индекса) |
transformation | все | преобразование: level, pct_yoy, level_ytd |
freq | все | частота: M, Q, A, W |
unit | все | единица измерения |
start, end | вся история | границы периода; месяц допустим: 2024-01 |
format | json | json или csv |
labels | 0 | 1 - добавить название продукта рядом с его кодом |
bom | 0 | только для CSV: 1 добавляет BOM для Excel на Windows |
limit | 100 | сколько рядов вернуть за раз, максимум 300 |
offset | 0 | с какого ряда начать страницу |
Не переданное измерение означает «без фильтра»: вернутся все его значения, какие есть в наборе. Измерения, которых у набора нет, в ответе не появляются.
Уровни территории
geo | Что вернётся | Территорий |
|---|---|---|
country | Россия целиком | 1 |
district | федеральные округа | 13 с историческими |
macroregion | экономические районы (прежнее деление) | 11 |
region | субъекты федерации | до 85 |
city | города и районы внутри субъектов | сотни |
all | все уровни сразу | - |
46000000000,RU-50 | только перечисленные территории | по списку |
Коды принимаются в обоих видах: исходный ОКАТО и нормализованный
(RU-46). Полный перечень - в справочнике территорий.
Продукты
В наборе обычно не один продукт, а сотни: в ценах на продовольствие их 875.
Параметр class работает так же, как geo: один код, список через запятую
или всё сразу.
class | Что вернётся |
|---|---|
3203 | один продукт |
3203,7802 | только перечисленные, порядок не важен |
all | все продукты набора |
| не передан | то же, что all |
Отсюда берётся «все цены по Москве одним запросом»:
geo=45000000000&class=all. Ряды отдаются страницами по limit, всего их
series_total.
Коды и их названия - в справочнике на сайте и методом
/api/v1/reference/codes в программе.
Россия приходит двумя рядами
«Российская Федерация» и «Российская Федерация без учёта новых субъектов (с 01.01.2023)» -
оба на уровне country.
Это не дубль, а две методики счёта. Различить их можно по полю geo_label,
выбор между ними за вами.
Ответ в JSON
Ответ состоит из двух частей: series - описания рядов,
observations - плоский список значений. Части соединяются по series_key.
Такая форма разворачивается в таблицу «период на территории» одной строкой кода и не требует
разбирать многоэтажную шапку исходных выгрузок.
{
"series": [
{ "series_key": "s1_z74wcnnmmefy4tbn",
"series_ref": "fedstat/indicator_31448/AVG_PRICE.45000000000.M.RUB.3203",
"title": "Пиво, л - Средняя цена · Город Москва",
"title_raw": "AVG_PRICE | Пиво, л | Город Москва",
"geo": "45000000000", "geo_canonical": "RU-45",
"geo_level": "region", "geo_label": "Москва",
"indicator": "AVG_PRICE", "classification": "3203",
"measure": null, "transformation": null,
"unit": "RUB", "freq": "M",
"last_period": "2025-06-01", "status": "active",
"provider_code": "fedstat", "dataset_code": "indicator_31448" }
],
"observations": [
{ "series_key": "s1_z74wcnnmmefy4tbn", "period": "2025-05-01",
"value": 206.42, "as_of": "2025-05-01" },
{ "series_key": "s1_z74wcnnmmefy4tbn", "period": "2025-06-01",
"value": 211.13, "as_of": "2025-06-01" }
],
"dataset": { "status": "fresh", "latest_period": "2026-07", "freq": "M",
"sla_months": 3, "lag_months": 2, "overdue_by": 0 },
"series_count": 1, "observation_count": 2, "series_total": 1,
"offset": 0, "has_more": false, "next_offset": null
}
| Поле ряда | Значение |
|---|---|
series_key | идентификатор ряда; с ним работают observations и evidence |
series_ref | читаемый адрес того же ряда: провайдер, набор, значения измерений |
title | название ряда в том же виде, что на сайте: товар, показатель, территория |
title_raw | складское название: код показателя и части через |, как в исходной выгрузке |
indicator | код показателя внутри набора |
classification | код продукта или раздела классификатора |
measure, transformation | коды вида показателя и преобразования; null, если у набора такого измерения нет |
unit, freq | коды единицы измерения и частоты |
geo, geo_canonical | код территории в ОКАТО и нормализованный код |
geo_level, geo_label | уровень территории и её название |
last_period | последний период, за который у ряда есть данные - по всей истории, а не по запрошенному окну |
continues_as, continues_from | ключ ряда, под которым та же история идёт дальше или шла раньше после смены кода (643 → 643004.АГ); null, если разрыва нет |
status | active - ряд выходит вместе с набором; stalled - перестал публиковаться |
provider_code, dataset_code | источник и набор, из которых пришёл ряд |
| Поле значения | Значение |
|---|---|
series_key | к какому ряду относится строка |
period | первый день периода: месяц - первое число, квартал - 1 января, 1 апреля, 1 июля или 1 октября, год - 1 января |
value | значение, число |
as_of | дата публикации именно этой оценки |
| Поле ответа | Значение |
|---|---|
dataset | выходит ли НАБОР: fresh, overdue (должен был выйти и не вышел), archival (закрытая эра), unknown; рядом - ожидаемый срок и просрочка в месяцах |
series_count, observation_count | сколько рядов и значений в этой странице |
series_total | сколько рядов подходит под фильтры целиком |
offset, has_more, next_offset | с какого ряда начата страница, есть ли ещё, с чего начинать следующую |
У недельных наборов period - дата регистрации цен из
бюллетеня Росстата. Источник публикует номер недели («33 неделя 2026»), а дату к нему мы берём из
справочника привязок; недели, до которой справочник ещё не дошёл, датируется шагом в семь дней от
последней известной - такая точка на сайте подписана одним номером, без даты.
Публикуется ли ряд ещё
Ряд может исчезнуть по-разному, и это разные новости. Товар сняли с
наблюдения, а набор выходит как обычно - это status: stalled у ряда. Встал весь
набор - это status: overdue в блоке dataset. По одному признаку их не
различить, поэтому признака два.
Меряются они тоже по-разному. Набор считается от сегодняшнего дня: для недельных данных выпуск ждём в пределах месяца, для месячных - трёх, для годовых - до двадцати. Ряд считается не от «сегодня», а от того, чем набор закончился: отстал от своего набора больше чем на три месяца (недельные и дневные), четыре (месячные), девять (квартальные) или восемнадцать (годовые) - значит, перестал выходить. По календарю мерить ряд нельзя: набор сам выходит с задержкой, и снятыми выглядели бы все ряды сразу после публикации.
Это тот же счёт, по которому на странице ряда стоит пометка «не обновляется»: сайт и API отвечают на этот вопрос одинаково.
last_period считается по всей истории набора, а не по
запрошенному окну. Иначе ряд, оборвавшийся в 2019 году, в срезе за последний год выглядел бы как
живой: у обоих в окне пусто.
Ровная линия - не то же самое, что снятый ряд
Тарифы на метро, электроэнергию и воду не двигаются месяцами, а ряд при этом живой и
выходит каждую неделю: у него status: active.
И наоборот, stalled - не приговор: наблюдение по товару иногда возобновляется
спустя годы. Поэтому рядом всегда стоит last_period - судите по нему.
На каждый период приходит одно значение - последняя опубликованная оценка.
Статистику уточняют задним числом, и по as_of видно, какая версия цифры у вас на
руках. Все версии - в методе evidence.
Разбор ответа JSON: соединить описания рядов со значениями и развернуть в таблицу «период на территории»
import pandas as pd
import requests
TOKEN = "rst_ваш_токен"
resp = requests.get(
"https://rustata.ru/api/v1/data",
params={"dataset": "fedstat/indicator_31448", "class": "3203",
"geo": "region", "start": "2024-01", "labels": 1},
headers={"Authorization": f"Bearer {TOKEN}"},
timeout=60,
)
resp.raise_for_status()
payload = resp.json()
series = pd.DataFrame(payload["series"]).set_index("series_key")
obs = pd.DataFrame(payload["observations"])
# описание ряда подставляется к каждому значению по series_key
obs = obs.join(series[["classification", "class_label", "geo", "geo_label", "unit"]],
on="series_key")
obs["period"] = pd.to_datetime(obs["period"])
table = obs.pivot(index="period", columns="geo_label", values="value")
print(table.tail())
# у тарифов без месячной квоты этого заголовка нет, поэтому .get, а не []
print("остаток квоты:", resp.headers.get("X-Quota-Values-Remaining", "без ограничения"))
library(httr)
library(jsonlite)
library(dplyr)
library(tidyr)
# Windows: если консоль печатает кириллицу как <U+0411><U+0435>…, дело в локали сессии,
# а не в данных - сами строки целы. Лечится одной строкой:
if (.Platform$OS.type == "windows") Sys.setlocale("LC_CTYPE", "Russian_Russia.utf8")
token <- "rst_ваш_токен"
resp <- GET(
"https://rustata.ru/api/v1/data",
query = list(dataset = "fedstat/indicator_31448", class = "3203",
geo = "region", start = "2024-01", labels = 1),
add_headers(Authorization = paste("Bearer", token))
)
stop_for_status(resp)
# fromJSON отдаёт обе части готовыми data.frame, соединять их по series_key
payload <- fromJSON(content(resp, "text", encoding = "UTF-8"))
obs <- left_join(payload$observations,
payload$series[, c("series_key", "geo_label", "class_label", "unit")],
by = "series_key")
table <- pivot_wider(obs, id_cols = period, names_from = geo_label, values_from = value)
head(table)
using HTTP, JSON3, DataFrames
token = "rst_ваш_токен"
resp = HTTP.get("https://rustata.ru/api/v1/data";
query = Dict("dataset" => "fedstat/indicator_31448", "class" => "3203",
"geo" => "region", "start" => "2024-01", "labels" => 1),
headers = ["Authorization" => "Bearer $token"])
payload = JSON3.read(resp.body)
series = DataFrame(payload.series)
obs = DataFrame(payload.observations)
obs = leftjoin(obs, series[:, [:series_key, :geo_label, :class_label, :unit]], on = :series_key)
table = unstack(obs, :period, :geo_label, :value)
first(table, 5)
Ответ в CSV
При format=csv приходят те же данные одной длинной (tidy)
таблицей: строка на значение, разделитель ;, кодировка UTF-8 без BOM. В строке стоит
полная идентичность ряда - series_key, коды всех измерений набора, территория, -
поэтому каждая колонка сразу годится для группировки и соединения, и доставать что-либо из
series_key не нужно.
series_key;class;freq;unit;geo;geo_label;geo_level;period;value;as_of;last_period;status
s1_kk3htnlek6p3y3v7;3203;M;RUB;46000000000;Московская область;region;2025-05-01;184.37;2025-05-01;2025-06-01;active
s1_kk3htnlek6p3y3v7;3203;M;RUB;46000000000;Московская область;region;2025-06-01;190.21;2025-06-01;2025-06-01;active
s1_z74wcnnmmefy4tbn;3203;M;RUB;45000000000;Москва;region;2025-05-01;206.42;2025-05-01;2025-06-01;active
Колонки измерений зависят от набора: выводятся только те, что у него есть
(class, measure, transformation, freq,
unit). Дальше всегда идут geo, geo_label,
geo_level, period, value, as_of, а в конце -
last_period и status. Две последние приписаны именно в конец: кто читает
по именам колонок, ничего не заметит, а кто по порядку - найдёт прежние колонки на прежних
местах.
UTF-8 без BOM - это то, что ждут pandas, readr и CSV.jl. Excel на Windows,
наоборот, без BOM показывает кириллицу кракозябрами: для него добавьте &bom=1.
Названия рядом с кодами: labels=1
По умолчанию в ответе только коды. Код устойчив и годится для соединения таблиц, а формулировку источник меняет от выпуска к выпуску, и join по названию разваливается.
Флаг labels=1 добавляет название продукта рядом с его кодом. В
CSV это колонка class_label сразу после class:
GET /api/v1/data?dataset=fedstat/indicator_31448&class=3203&geo=45000000000&format=csv
series_key;class;freq;unit;geo;geo_label;geo_level;period;value;as_of;last_period;status
s1_z74wcnnmmefy4tbn;3203;M;RUB;45000000000;Москва;region;2025-05-01;206.42;2025-05-01
GET /api/v1/data?dataset=fedstat/indicator_31448&class=3203&geo=45000000000&format=csv&labels=1
series_key;class;class_label;freq;unit;geo;geo_label;geo_level;period;value;as_of;last_period;status
s1_z74wcnnmmefy4tbn;3203;Пиво, л;M;RUB;45000000000;Москва;region;2025-05-01;206.42;2025-05-01
В JSON тот же флаг добавляет ряду поле class_label:
{ "series_key": "s1_z74wcnnmmefy4tbn", "classification": "3203",
"class_label": "Пиво, л", "geo": "45000000000", "geo_label": "Москва", … }
Если у набора нет классификации или название кода неизвестно,
class_label приходит пустым: строка не пропадает и форму не меняет. Название
территории (geo_label) приходит всегда - территория общая для всех наборов, и её
название устойчиво.
Тот же срез через CSV: все цены по Москве, с названиями продуктов
import io
import pandas as pd
import requests
TOKEN = "rst_ваш_токен"
resp = requests.get(
"https://rustata.ru/api/v1/data",
params={"dataset": "fedstat/indicator_31448", "class": "all",
"geo": "45000000000", "start": "2024-01", "format": "csv", "labels": 1},
headers={"Authorization": f"Bearer {TOKEN}"},
timeout=120,
)
resp.raise_for_status()
df = pd.read_csv(io.StringIO(resp.text), sep=";", parse_dates=["period", "as_of"])
table = df.pivot(index="period", columns="class_label", values="value")
print(table.tail())
library(httr)
library(readr)
library(tidyr)
token <- "rst_ваш_токен"
resp <- GET(
"https://rustata.ru/api/v1/data",
query = list(dataset = "fedstat/indicator_31448", class = "all",
geo = "45000000000", start = "2024-01", format = "csv", labels = 1),
add_headers(Authorization = paste("Bearer", token))
)
stop_for_status(resp)
# content(resp, "raw") + locale(): readr сам разбирает UTF-8, без промежуточной строки
df <- read_delim(content(resp, "raw"), delim = ";",
locale = locale(encoding = "UTF-8"), show_col_types = FALSE)
table <- pivot_wider(df, id_cols = period, names_from = class_label, values_from = value)
head(table)
using HTTP, CSV, DataFrames
token = "rst_ваш_токен"
resp = HTTP.get("https://rustata.ru/api/v1/data";
query = Dict("dataset" => "fedstat/indicator_31448", "class" => "all",
"geo" => "45000000000", "start" => "2024-01",
"format" => "csv", "labels" => 1),
headers = ["Authorization" => "Bearer $token"])
df = CSV.read(IOBuffer(resp.body), DataFrame; delim = ';')
table = unstack(df, :period, :class_label, :value)
first(table, 5)
GET /api/v1/series/search - поиск рядов
Ищет ряды по строке и возвращает их series_key - с ним потом
берутся значения и происхождение. Нужен, когда код продукта заранее неизвестен.
Работает тот же поиск, что и на сайте: словоформы, синонимы и ранжирование - национальные ряды выше региональных, снятые с наблюдения ниже действующих. Поэтому запрос словом, которого нет в заголовке ряда («инфляция»), всё равно находит нужное, а выдача совпадает с тем, что показывает строка поиска на витрине.
| Параметр | Значение |
|---|---|
q | строка поиска; можно не указывать, если задан отбор |
limit | сколько рядов вернуть, 1..200, по умолчанию 20 |
provider | источник: fedstat, rosstat_files, cbr, roskazna, showdata |
dataset | набор: fedstat/indicator_31448 или просто код набора |
geo | территория: RU-45 или код ОКАТО |
freq | частота: M, Q, A, W |
class, transformation | код продукта и преобразование |
Несколько значений пишутся через запятую и означают «любое из»; разные
измерения складываются по И. Имена те же, что у /api/v1/data, -
отбор, однажды записанный, переносится из метода в метод без переписывания.
Строка поиска при этом не обязательна: «все ряды набора с месячной частотой»
это dataset=fedstat/indicator_31448&freq=M без q.
Найти ряд по названию и получить его ключ
import requests
TOKEN = "rst_ваш_токен"
resp = requests.get(
"https://rustata.ru/api/v1/series/search",
params={"q": "бензин аи-92", "limit": 5},
headers={"Authorization": f"Bearer {TOKEN}"}, timeout=30,
)
resp.raise_for_status()
for item in resp.json()["items"]:
print(item["series_key"], "|", item["title"])
library(httr)
library(jsonlite)
token <- "rst_ваш_токен"
resp <- GET("https://rustata.ru/api/v1/series/search",
query = list(q = "бензин аи-92", limit = 5),
add_headers(Authorization = paste("Bearer", token)))
stop_for_status(resp)
items <- fromJSON(content(resp, "text", encoding = "UTF-8"))$items
items[, c("series_key", "title")]
using HTTP, JSON3
token = "rst_ваш_токен"
resp = HTTP.get("https://rustata.ru/api/v1/series/search";
query = Dict("q" => "бензин аи-92", "limit" => 5),
headers = ["Authorization" => "Bearer $token"])
for item in JSON3.read(resp.body).items
println(item.series_key, " | ", item.title)
end
GET /api/v1/series/{series_key}/observations
Значения одного ряда. Нужен, когда ключ уже известен: получен поиском или
взят из адреса страницы ряда. Период задаётся start и end - годом,
месяцем или датой; без них приходят свежие limit значений (500 по умолчанию, до 5000).
Значения лежат в поле items, а не observations: последнее - у
среза.
Для разового скачивания метод не нужен - на странице ряда есть кнопка «скачать CSV», она работает без токена.
История одного ряда с начала 2024 года
import pandas as pd
import requests
TOKEN = "rst_ваш_токен"
KEY = "s1_mydzoqiytm4xqjq3"
resp = requests.get(
f"https://rustata.ru/api/v1/series/{KEY}/observations",
params={"start": "2024-01"},
headers={"Authorization": f"Bearer {TOKEN}"}, timeout=30,
)
resp.raise_for_status()
df = pd.DataFrame(resp.json()["items"])
df["period"] = pd.to_datetime(df["period"])
print(df.set_index("period")["value"].tail())
library(httr)
library(jsonlite)
token <- "rst_ваш_токен"
key <- "s1_mydzoqiytm4xqjq3"
resp <- GET(paste0("https://rustata.ru/api/v1/series/", key, "/observations"),
query = list(start = "2024-01"),
add_headers(Authorization = paste("Bearer", token)))
stop_for_status(resp)
obs <- fromJSON(content(resp, "text", encoding = "UTF-8"))$items
tail(obs[, c("period", "value")])
using HTTP, JSON3, DataFrames
token = "rst_ваш_токен"
key = "s1_mydzoqiytm4xqjq3"
resp = HTTP.get("https://rustata.ru/api/v1/series/$key/observations";
query = Dict("start" => "2024-01"),
headers = ["Authorization" => "Bearer $token"])
obs = DataFrame(JSON3.read(resp.body).items)
last(obs, 5)
GET /api/v1/series/{series_key}/evidence
Происхождение значений: из какого файла источника они пришли, каким релизом опубликованы, каким прогоном загружены. Этим ответ на вопрос «откуда цифра» отличается от «скачал где-то в интернете».
Откуда взялись значения ряда
import requests
TOKEN = "rst_ваш_токен"
KEY = "s1_mydzoqiytm4xqjq3"
resp = requests.get(
f"https://rustata.ru/api/v1/series/{KEY}/evidence",
headers={"Authorization": f"Bearer {TOKEN}"}, timeout=30,
)
resp.raise_for_status()
for release in resp.json()["releases"][:3]:
print(release["published_at"], "|", release["source_file"])
library(httr)
library(jsonlite)
token <- "rst_ваш_токен"
key <- "s1_mydzoqiytm4xqjq3"
resp <- GET(paste0("https://rustata.ru/api/v1/series/", key, "/evidence"),
add_headers(Authorization = paste("Bearer", token)))
stop_for_status(resp)
releases <- fromJSON(content(resp, "text", encoding = "UTF-8"))$releases
head(releases[, c("published_at", "source_file")], 3)
using HTTP, JSON3
token = "rst_ваш_токен"
key = "s1_mydzoqiytm4xqjq3"
resp = HTTP.get("https://rustata.ru/api/v1/series/$key/evidence";
headers = ["Authorization" => "Bearer $token"])
for release in JSON3.read(resp.body).releases[1:min(3, end)]
println(release.published_at, " | ", release.source_file)
end
Показатели: /api/v1/concepts/search и /api/v1/concepts/{id}
Поиск по показателям, а не по отдельным рядам, и карточка показателя со сводкой: сколько рядов, по каким территориям, какие преобразования. Удобно, когда нужно понять, что вообще есть по теме, прежде чем забирать данные.
Найти показатель и посмотреть его сводку
import requests
TOKEN = "rst_ваш_токен"
headers = {"Authorization": f"Bearer {TOKEN}"}
found = requests.get("https://rustata.ru/api/v1/concepts/search",
params={"q": "молоко", "limit": 3},
headers=headers, timeout=30).json()
for concept in found["items"]:
print(concept["concept_id"], "|", concept["label"], "|", concept["series_count"], "рядов")
first_id = found["items"][0]["concept_id"]
card = requests.get(f"https://rustata.ru/api/v1/concepts/{first_id}",
headers=headers, timeout=30).json()
print(card["label"], "- территорий:", card.get("geo_count"))
library(httr)
library(jsonlite)
token <- "rst_ваш_токен"
auth <- add_headers(Authorization = paste("Bearer", token))
found <- fromJSON(content(GET("https://rustata.ru/api/v1/concepts/search",
query = list(q = "молоко", limit = 3), auth),
"text", encoding = "UTF-8"))
found$items[, c("concept_id", "label", "series_count")]
card <- fromJSON(content(GET(paste0("https://rustata.ru/api/v1/concepts/",
found$items$concept_id[1]), auth),
"text", encoding = "UTF-8"))
card$label
using HTTP, JSON3
token = "rst_ваш_токен"
auth = ["Authorization" => "Bearer $token"]
found = JSON3.read(HTTP.get("https://rustata.ru/api/v1/concepts/search";
query = Dict("q" => "молоко", "limit" => 3), headers = auth).body)
for concept in found.items
println(concept.concept_id, " | ", concept.label, " | ", concept.series_count)
end
card = JSON3.read(HTTP.get("https://rustata.ru/api/v1/concepts/$(found.items[1].concept_id)";
headers = auth).body)
println(card.label)
GET /api/v1/reference/* - справочники
Три списка, из которых берутся значения параметров: наборы для
dataset, территории для geo, коды продуктов для class. На
сайте они есть страницами, но программе нужен ответ, а не страница - иначе коды переписывают
с экрана руками.
| Метод | Что отдаёт |
|---|---|
/api/v1/reference/datasets | наборы: код, название, частота, сколько рядов и территорий, за какой период есть данные, выходит ли набор (status) |
/api/v1/reference/geo | территории: код, название, уровень. Параметр level сужает до одного уровня |
/api/v1/reference/codes | коды продуктов одного набора, с последним периодом и пометкой stalled у снятых с наблюдения. Параметр dataset обязателен |
/api/v1/reference/methodology | оговорки набора: охват (полный круг или малые предприятия), исключения, классификатор, пересматриваются ли значения |
/api/v1/reference/related | связи наборов: другие эры показателя и соседние наборы - подробнее или с длиннее историей. Поле splice отвечает, можно ли сшивать: false - нельзя, у эр разошлась методология |
/api/v1/reference/units | единицы измерения: код, название, коды ОКЕИ с множителем mult, сколько рядов и наборов. Одна величина приходит несколькими кодами: 168 - тонна, 169 - тысяча тонн |
/api/v1/reference/continuity | объявленные разрывы: где ряд продолжается под другим кодом (643 → 643004.АГ) |
Пометка снятых кодов стоит именно здесь, потому что здесь код и выбирают: без неё его берут из списка вслепую, строят запрос и получают график, оборвавшийся восемь лет назад.
Оговорки и связи не обязательно спрашивать отдельно: ответ
/api/v1/series/{key}/observations несёт их рядом с числами - series
(единица и её название, частота, территория, множитель, пересматривается ли),
notes (пометки именно этого ряда) и related (эры и соседние наборы с
полем splice). Раньше ответ был голым, и единицу приходилось искать вторым запросом -
а без неё значение не значит ничего.
Все принимают format=csv и bom=1 - справочник
чаще всего нужен таблицей, чтобы положить рядом со своими данными и сматчить. Квоту в значениях
они не расходуют: это метаданные, а не наблюдения.
Общего справочника кодов нет намеренно
У каждого набора свой классификатор: код 3203 в ценах и в производстве означает
разные вещи.
Объединённый список подталкивал бы подставить код не в тот набор - и запрос вернул бы данные, только не те, что ожидались.
Найти набор, взять его коды и выгрузить нужные продукты
import pandas as pd
import requests
TOKEN = "rst_ваш_токен"
headers = {"Authorization": f"Bearer {TOKEN}"}
BASE = "https://rustata.ru/api/v1"
codes = pd.DataFrame(requests.get(f"{BASE}/reference/codes",
params={"dataset": "fedstat/indicator_31448"},
headers=headers, timeout=60).json()["items"])
beer = codes[codes["name"].str.contains("Пиво", case=False)]
print(beer[["class", "name"]])
# коды списком - один запрос вместо запроса на каждый продукт
data = requests.get(f"{BASE}/data",
params={"dataset": "fedstat/indicator_31448",
"class": ",".join(beer["class"]),
"geo": "region", "start": "2024-01"},
headers=headers, timeout=120).json()
print(data["series_total"], "рядов")
library(httr)
library(jsonlite)
token <- "rst_ваш_токен"
auth <- add_headers(Authorization = paste("Bearer", token))
base <- "https://rustata.ru/api/v1"
codes <- fromJSON(content(GET(paste0(base, "/reference/codes"),
query = list(dataset = "fedstat/indicator_31448"), auth),
"text", encoding = "UTF-8"))$items
beer <- codes[grepl("Пиво", codes$name, ignore.case = TRUE), ]
beer[, c("class", "name")]
data <- fromJSON(content(GET(paste0(base, "/data"),
query = list(dataset = "fedstat/indicator_31448",
class = paste(beer$class, collapse = ","),
geo = "region", start = "2024-01"), auth),
"text", encoding = "UTF-8"))
data$series_total
using HTTP, JSON3, DataFrames
token = "rst_ваш_токен"
auth = ["Authorization" => "Bearer $token"]
base = "https://rustata.ru/api/v1"
codes = DataFrame(JSON3.read(HTTP.get("$base/reference/codes";
query = Dict("dataset" => "fedstat/indicator_31448"), headers = auth).body).items)
beer = filter(row -> occursin("Пиво", row.name), codes)
data = JSON3.read(HTTP.get("$base/data";
query = Dict("dataset" => "fedstat/indicator_31448",
"class" => join(beer.class, ","),
"geo" => "region", "start" => "2024-01"), headers = auth).body)
println(data.series_total, " рядов")
GET /api/v1/updates - что нового
Лента загрузок: день, набор, до какого периода есть данные, сколько выпусков и сколько точек пересмотрено. Отвечает на «что нового» одним запросом - иначе приходится опрашивать справочник наборов целиком и сравнивать с тем, что клиент запомнил сам.
Пересмотр СТАРЫХ периодов по latest_period не виден вовсе: он
там не меняется. Поэтому в ленте есть отдельная колонка с числом пересмотренных точек.
Пересматривает ли источник значения этого набора вообще - поле
revised в справочнике наборов и в блоке dataset
ответа /api/v1/data. У индексов потребительских цен и у
зарегистрированных цен стоит false: цена периода публикуется один раз и не
уточняется, искать в ней правки бессмысленно.
Правила, общие для всех методов
Постраничная выдача
За один раз приходит 100 рядов, параметром limit
можно взять до 300. Сколько подходит всего, видно в series_total, а
next_offset подсказывает, с чего начать следующую страницу.
Постранично идут ряды, а не значения: каждый ряд приходит целиком по времени, склеивать разрезанную историю не нужно.
Если срез выбирает больше 60 000 значений, ответ не придёт -
вернётся SLICE_TOO_BIG с подсказкой сузить запрос.
Кэширование
Ответы отдаются с ETag. Передайте его обратно в
If-None-Match - если данные не менялись, придёт 304 без тела, и квота
не израсходуется. На регулярных обновлениях это экономит и время, и лимит.
Ошибки
Ошибка приходит одинаково устроенным объектом: код, понятное сообщение и подсказка, что делать.
{ "error": { "code": "SLICE_TOO_BIG",
"message": "Запрос выбирает 1 200 000 значений, максимум 200 000",
"hint": "Сузьте период или список территорий" } }
| Код | HTTP | Когда |
|---|---|---|
UNAUTHORIZED | 401 | токен не передан или отозван |
RATE_LIMITED | 429 | исчерпана квота тарифа |
SLICE_TOO_BIG | 400 | запрос выбирает слишком много данных |
NOT_FOUND | 404 | под фильтры ничего не подошло |
INTERNAL | 500 | ошибка на нашей стороне |
Интерактивный справочник
Запрос можно выполнить своим токеном прямо в браузере - Swagger. Машиночитаемое описание - openapi.json.