Eurostatx

CI Hex version Documentation License

An Elixir client for the Eurostat APIs, the open data services of the statistical office of the European Union, powered by Req and integrated with Explorer.

Eurostatx is not an official Eurostat library. It is an independent open source project, not developed, endorsed or supported by Eurostat. The client published by Eurostat in its GitHub organization is restatapi, for R.

The Eurostat APIs give access to every dataset of the Eurostat database: national accounts (GDP), prices (HICP), population, labour market, international trade (Comext), industry (Prodcom), regional statistics and much more. They are free and do not require an API key.

Eurostatx covers every API described in the Eurostat API guides: Statistics, SDMX 3.0 and 2.1 (data and structure queries), Catalogue, asynchronous and files APIs, for Eurostat, Comext/Prodcom and the other Directorates-General of the European Commission (REDISSTAT). Like Inex, it follows the design of Fred, and it has been cross-checked against restatapi.

Contents

Installation

Add eurostatx to your list of dependencies in mix.exs:

def deps do
  [
    {:eurostatx, "~> 0.1.0"}
  ]
end

Or in Livebook:

Mix.install([{:eurostatx, "~> 0.1.0"}])

Quick Start

Get the annual inflation rate of the euro area and four countries, one column per country, just like Fred.Series.observations_as_data_frame/2:

iex> "prc_hicp_minr"
...> |> Eurostatx.Statistics.data_as_data_frame!(
...>   filters: [geo: ["EA", "DE", "FR", "ES", "IT"], unit: "RCH_A", coicop18: "TOTAL"],
...>   last_periods: 6,
...>   shape: :wide
...> )
...> |> Explorer.DataFrame.print()
+----------------------------------------------------+
|     Explorer DataFrame: [rows: 6, columns: 6]      |
+------------+-------+-------+-------+-------+-------+
|    date    |  EA   |  DE   |  ES   |  FR   |  IT   |
|   <date>   | <f64> | <f64> | <f64> | <f64> | <f64> |
+============+=======+=======+=======+=======+=======+
| 2026-04-01 | 3.0   | 2.9   | 3.5   | 2.5   | 2.8   |
| 2026-05-01 | 3.2   | 2.7   | 3.6   | 2.8   | 3.2   |
| 2026-06-01 | 2.8   | 2.4   | 3.6   | 2.0   | 3.0   |
| …          | …     | …     | …     | …     | …     |
| 2026-08-01 | 3.2   | 2.9   | 4.6   | 2.6   | 3.2   |
| 2026-09-01 | 3.8   | 3.3   | 5.0   | 3.4   | 4.1   |
+------------+-------+-------+-------+-------+-------+

Or get the JSON-stat response, untouched:

iex> Eurostatx.Statistics.data("prc_hicp_minr",
...>   filters: [geo: "EA", unit: "RCH_A", coicop18: "TOTAL"],
...>   last_periods: 2
...> )
{:ok,
 %{
   "label" => "Harmonised index of consumer prices (HICP) - ECOICOP ver.2 - indices and rates of change, monthly data",
   "id" => ["freq", "unit", "coicop18", "geo", "time"],
   "size" => [1, 1, 1, 1, 2],
   "value" => %{"0" => 3.2, "1" => 3.8},
   "status" => %{"1" => "e"},
   "updated" => "2026-10-02T11:00:00+0200",
   ...
 }}

Every endpoint function returns {:ok, result} or {:error, %Eurostatx.Error{}}. The *_as_data_frame functions return {:ok, data_frame} and have bang variants that raise instead, handy for Livebook pipelines.

Finding Data

Datasets are identified by their online data code, shown next to their title in the Eurostat website and Data Browser (prc_hicp_minr, nama_10_gdp, TPS00001...). Every dataset has dimensions (freq, unit, geo...) whose codes come from code lists.

# 1. Search the table of contents
iex> {:ok, results} = Eurostatx.Catalogue.search("consumer prices")
iex> results |> Enum.map(&{&1["code"], &1["title"]}) |> Enum.take(2)
[
  {"ei_cphi_m", "Harmonised index of consumer prices - monthly data"},
  {"prc_hicp_ct", "Harmonised index of consumer prices (HICP) - ECOICOP ver.2 - indices and rates of change at constant tax rates, monthly data"}
]

# 2. Discover the dimensions of a dataset and the codes they use, with their labels
iex> {:ok, dimensions} = Eurostatx.Datasets.values("prc_hicp_minr")
iex> Enum.map(dimensions, &{&1["id"], &1["name"], length(&1["values"])})
[
  {"freq", "Time frequency", 1},
  {"unit", "Unit of measure", 5},
  {"coicop18", "Classification of individual consumption by purpose (COICOP) - 2018", 555},
  {"geo", "Geopolitical entity (reporting)", 46}
]

# 3. Get the data, filtering by dimension code
iex> Eurostatx.Data.get_as_data_frame!("prc_hicp_minr",
...>   filters: [geo: ["DE", "FR"], unit: "RCH_A", coicop18: ["TOTAL", "CP01"]],
...>   start_period: "2025-01"
...> )

Eurostatx.Datasets also lists every dataset with its last update and time coverage (list/1), gives the data structure of a dataset, with its dimensions in key order (structure/2), and the codes actually used by each dimension, including the time periods (availability/2).

Discontinued datasets. When a classification changes, Eurostat publishes new datasets and stops updating the old ones, which keep answering with their historical data. For example the HICP datasets based on COICOP 1999 (prc_hicp_manr, "(1997-2025)") were replaced by the ECOICOP ver.2 ones (prc_hicp_minr). Check the title, "latest_period" and "data_updated_at" in Eurostatx.Datasets.list/1.

Explorer Integration

Eurostatx.DataFrame converts the responses into data frames:

iex> "prc_hicp_minr"
...> |> Eurostatx.Data.get_as_data_frame!(
...>   filters: [geo: ["DE", "FR"], unit: "RCH_A", coicop18: ["TOTAL", "CP01"]],
...>   last_n: 1,
...>   labels: true
...> )
...> |> Explorer.DataFrame.select(["geo_label", "coicop18_label", "time", "value", "flag"])
...> |> Explorer.DataFrame.print()
+----------------------------------------------------------------------------+
|                 Explorer DataFrame: [rows: 4, columns: 5]                  |
+-----------+----------------------------------+----------+-------+----------+
| geo_label |          coicop18_label          |   time   | value |   flag   |
| <string>  |             <string>             | <string> | <f64> | <string> |
+===========+==================================+==========+=======+==========+
| Germany   | Total                            | 2026-09  | 3.3   | e        |
| France    | Total                            | 2026-09  | 3.4   | e        |
| Germany   | Food and non-alcoholic beverages | 2026-08  | 0.2   | nil      |
| France    | Food and non-alcoholic beverages | 2026-08  | 1.1   | nil      |
+-----------+----------------------------------+----------+-------+----------+

JSON-stat (both data APIs), TSV and SDMX-CSV responses can be converted with Eurostatx.DataFrame.from_json_stat/2, from_tsv/1 and from_csv/1, and metadata listings (datasets, table of contents, inventories...) with from_records/1.

API Coverage

Module Function Endpoint
Eurostatx.Statistics data/2 statistics/1.0/data/{dataset} (JSON-stat)
data_as_data_frame/2 same → Explorer.DataFrame
Eurostatx.Data get/2 sdmx/3.0/data/dataflow/{agency}/{dataset}/{version}/{key}, sdmx/2.1/data/{dataset}/{key}
get_as_data_frame/2 same → Explorer.DataFrame
Eurostatx.Datasets list/1 sdmx/2.1/dataflow/{agency}/all
get/2 structure/dataflow/{agency}/{dataset}
structure/2 dataflow with references=children (data structure)
availability/2 dataconstraint (3.0) / contentconstraint (2.1)
values/2 dataflow with references=descendants&detail=referencepartial
Eurostatx.Codelists list/1, get/2, codes/2 codelist/{agency}/{id}/{version}
Eurostatx.Structure get/3 any artefact: dataflow, datastructure, codelist, conceptscheme, constraint, categoryscheme, categorisation, with detail, references and SDMX-ML, JSON or TSV
Eurostatx.Catalogue toc/1, search/2 catalogue/toc/txt, catalogue/toc/xml
toc_schema/1 catalogue/toc/TableOfContent.xsd
rss/1 catalogue/rss/{lang}/statistics-update.rss
dcat/2 catalogue/dcat/{agency}/{FULL,UPDATES}
metabase/1, metabase_as_data_frame/1 catalogue/metabase.txt.gz
Eurostatx.Async status/2, data/2, await/2 1.0/async/status/{key}, 1.0/async/data/{key}
Eurostatx.Files inventory/1 files/inventory?type={data,codelist}
list/2, download/2 files?format=csv&dir=, files?file=

The Eurostat parameters are exposed as idiomatic, validated options:

Eurostatx option Statistics API SDMX 3.0 SDMX 2.1
filters: [geo: ["DE", "FR"]] geo=DE&geo=FR c[geo]=DE,FR key built from the structure
key: "A.CP_MEUR..DE" key (* wildcards) key (+ for several values)
start_period: / end_period: sinceTimePeriod / untilTimePeriod c[TIME_PERIOD]=ge:…+le:… startPeriod / endPeriod
time: [2020, 2022] time=2020&time=2022 c[TIME_PERIOD]=2020,2022 single period only
last_periods: 3 lastTimePeriod
first_n: / last_n: firstNObservations / lastNObservations same
geo_level: :nuts2 geoLevel
language: :fr lang=FR lang (SDMX-CSV labels)
format: :csv, :tsv, :xml... (JSON-stat only) format, formatVersion format
detail: :data_only... attributes, measures detail
labels: :name, last_update_date: true labels, returnLastUpdateDate
return_data: :all returnData returnData
agency: :comext... base URL base URL and agency ID base URL and agency ID

Big Extractions and Fair Use

Eurostat estimates the number of cells of every extraction:

Queued requests are awaited transparently: Eurostatx polls the asynchronous API (SDMX) or repeats the request (Statistics API) until the data is ready or :async_timeout (5 minutes by default) expires. Set async_timeout: 0 to get the request key right away and fetch the data later:

{:error, %Eurostatx.Error{type: :queued, request_id: id}} =
  Eurostatx.Data.get("DS-045409", key: "A.FI.CA..2.SUPPLEMENTARY_QUANTITY", format: :csv, async_timeout: 0)

{:ok, csv} = Eurostatx.Async.await(id, agency: :comext)

Eurostat also queues requests to ensure the fair use of the service, based on the number of concurrent requests and on the requests and extraction cost of the last day, 7 days and 30 days. It asks to make one request at a time: Eurostatx never makes concurrent requests. Datasets are updated twice a day, at 11:00 and 23:00 (Brussels time).

Comext, Prodcom and Other Agencies

Comext and Prodcom datasets (DS- codes) are served by a dedicated endpoint, selected automatically. Full Comext datasets cannot be downloaded: they must be filtered.

The European Commission's Directorates-General for Economic and Financial Affairs (:ecfin), Employment (:empl), Internal Market (:grow) and Taxation (:taxud) publish their data with the same APIs (REDISSTAT). They serve the Statistics API, SDMX 2.1 (the default for them) and the DCAT catalogue:

{:ok, datasets} = Eurostatx.Datasets.list(agency: :empl)
Eurostatx.Statistics.data_as_data_frame!("LMP_EXPME$HR", agency: :empl, last_periods: 1)

Livebook Notebooks

The livebooks/ directory contains interactive Livebook notebooks with VegaLite charts:

Notebook Description
1_getting_started.livemd Statistics API, data frames, wide and long shapes, first charts
2_exploring_datasets.livemd Table of contents, dataset structure, code lists, SDMX queries and formats
3_european_economy.livemd Inflation, unemployment and GDP growth across the EU, regional disparities

Configuration

No configuration is needed. Optionally:

config :eurostatx,
  language: :en,            # Default language of the labels (:en, :fr or :de)
  timeout: 120_000,         # Receive timeout of each request in milliseconds
  async_timeout: 300_000,   # How long to wait for asynchronous responses
  async_interval: 5_000,    # Interval between asynchronous status checks
  req_options: []           # Extra Req options: retries, proxies, test plugs...

Telemetry

Eurostatx emits [:eurostatx, :request, :start | :stop | :exception] telemetry events for every request. The quickest way to see what is going on is the built-in logger:

Eurostatx.Telemetry.Logger.attach()
[info] [eurostatx] GET statistics/1.0/data/prc_hicp_minr - 200 in 241ms (params: [{"lang", "EN"}, {"geo", "EA"}])
[info] [eurostatx] GET sdmx/2.1/data/DS-045409/A.SE.NO..2.SUPPLEMENTARY_QUANTITY - queued in 484ms (request f07392ce-...)
[info] [eurostatx] GET 1.0/async/status/f07392ce-0d11-47eb-9922-809615e9013e - 200 in 54ms

See Eurostatx.Telemetry for the details of each event.

Testing

mix test                          # Unit tests, using real responses recorded from the APIs
mix test --include integration    # Also run the tests against the live APIs
mix test --only restatapi         # Cross-check against restatapi, the R package of Eurostat (requires Docker)

The restatapi suite runs the same queries with restatapi (in a Docker container, see test/restatapi) and with Eurostatx against the live APIs, and compares data values, flags and periods (Statistics API, SDMX 3.0 and 2.1, TSV), the codes and labels of data structures and code lists, and the table of contents.

The CI workflow checks formatting, Credo and Sobelow, and runs the unit tests on Elixir 1.18, 1.19 and 1.20.

Acknowledgements

License

MIT

Artificial Intelligence

Contributions made with the help of AI tools are welcome. However, every issue and pull request must be created and supervised by a human, who is responsible for its content and for following up on the review.

AI coding agents will find instructions about this repository in AGENTS.md.