The EODHD Real Estate Data API delivers residential property price statistics for more than sixty countries, sourced from the Bank for International Settlements. It covers two BIS datasets: Selected Property Prices, a harmonised headline index that is comparable across countries, and Detailed Property Prices, a deeper set of national series broken down by dwelling type, region, and vintage. The data suits macro and real-estate analysts, quant researchers, and anyone tracking housing markets across countries on a consistent basis.
Four endpoints share the same authentication and the same JSON response envelope: a country list, the Selected Property Prices series for a country, the Detailed Property Prices observations for a country, and the catalogue of detailed series available for a country. Selected Property Prices are quarterly, with periods expressed as year and quarter, for example 2020-Q1. Detailed Property Prices carry their own frequency per series, which may be monthly, quarterly, half-yearly, or annual.
Overview
Every endpoint is reached over HTTPS with a GET request and authenticated by an api_token query parameter, the same token used across the EODHD API. Requests return a JSON object with three top-level keys: data, the array of records; meta, which carries the total row count, the active page window, and dataset provenance such as source and base year; and links, which holds the pagination link to the next page. Results are paged with page[offset] and page[limit], where the default page size is 50 rows and the maximum is 500. Add fmt=csv to any endpoint to receive the same data as a CSV download.
All endpoints live under a common base path. Country codes are two-letter ISO codes and are case insensitive; lowercase input is accepted and normalised to uppercase.
https://eodhd.com/api/real-estate/{code}?api_token=YOUR_TOKEN
Country Coverage
Before requesting price data, list the countries that are available and check which datasets each one carries. Some countries have Selected Property Prices only, some have Detailed Property Prices only, and most have both. A handful of entries are BIS aggregates that group several economies rather than a single country.
Country List
Returns every available country with two boolean flags indicating which datasets are present. Use these flags to decide whether to call the Selected Property Prices endpoint, the Detailed Property Prices endpoint, or both for a given country.
https://eodhd.com/api/real-estate/countries?api_token=YOUR_TOKEN
Method GET. Authentication by api_token. Pagination by page[offset] and page[limit]. Response is a JSON envelope with data, meta, and links.
Parameters
api_token
string
required
page[limit]
integer
optional
page[offset]
integer
optional
Request Example
https://eodhd.com/api/real-estate/countries?api_token=YOUR_TOKEN
curl --location "https://eodhd.com/api/real-estate/countries?api_token=YOUR_TOKEN&fmt=json"
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://eodhd.com/api/real-estate/countries?api_token=YOUR_TOKEN&fmt=json',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'GET',
));
$data = curl_exec($curl);
curl_close($curl);
try {
$data = json_decode($data, true, 512, JSON_THROW_ON_ERROR);
var_dump($data);
} catch (Exception $e) {
echo 'Error. '.$e->getMessage();
}
import requests
url = f'https://eodhd.com/api/real-estate/countries?api_token=YOUR_TOKEN&fmt=json'
data = requests.get(url).json()
print(data)
library(httr)
library(jsonlite)
url <- 'https://eodhd.com/api/real-estate/countries?api_token=YOUR_TOKEN&fmt=json'
response <- GET(url)
if (http_type(response) == "application/json") {
content <- content(response, "text", encoding = "UTF-8")
cat(content)
} else {
cat("Error while receiving data\n")
}
Try it now (it's free)!
How to use it (YouTube)
Response Example
{
"data": [
{ "code": "US", "name": "United States", "has_spp": true, "has_dpp": true },
{ "code": "AE", "name": "United Arab Emirates", "has_spp": false, "has_dpp": true }
],
"meta": { "total": 64, "offset": 0, "limit": 50 },
"links": { "next": "https://eodhd.com/api/real-estate/countries?fmt=json&page[offset]=50&page[limit]=50" }
}
Response Fields
| Field | Type | Description |
|---|---|---|
| code | string | Two-letter country code, or a BIS aggregate code |
| name | string | Country name |
| has_spp | boolean | True when Selected Property Prices are available for this country |
| has_dpp | boolean | True when Detailed Property Prices are available for this country |
Selected Property Prices
Selected Property Prices are the BIS headline residential property price series, harmonised so that levels and growth rates are comparable across countries. Each observation is available as a nominal value and as a real value, and as a price index or a year-on-year percentage change. Index values are rebased to a common base year, reported in the meta block.
Property Price Series
Returns the Selected Property Prices observations for one country. Without filters the response includes every combination of type and metric. Narrow the result with filter[type] and filter[metric], and restrict the period range with filter[from] and filter[to].
https://eodhd.com/api/real-estate/{code}?api_token=YOUR_TOKEN
Method GET. Authentication by api_token. Pagination by page[offset] and page[limit]. Response is a JSON envelope with data, meta, and links.
Parameters
api_token
string
required
filter[type]
enum
optional
filter[metric]
enum
optional
filter[from]
string
optional
filter[to]
string
optional
page[limit]
integer
optional
page[offset]
integer
optional
Request Example
https://eodhd.com/api/real-estate/US?api_token=YOUR_TOKEN&filter[type]=nominal&filter[metric]=index&filter[from]=2020-Q1&filter[to]=2024-Q4
curl --location "https://eodhd.com/api/real-estate/US?api_token=YOUR_TOKEN&filter[type]=nominal&filter[metric]=index&filter[from]=2020-Q1&filter[to]=2024-Q4&fmt=json"
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://eodhd.com/api/real-estate/US?api_token=YOUR_TOKEN&filter[type]=nominal&filter[metric]=index&filter[from]=2020-Q1&filter[to]=2024-Q4&fmt=json',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'GET',
));
$data = curl_exec($curl);
curl_close($curl);
try {
$data = json_decode($data, true, 512, JSON_THROW_ON_ERROR);
var_dump($data);
} catch (Exception $e) {
echo 'Error. '.$e->getMessage();
}
import requests
url = f'https://eodhd.com/api/real-estate/US?api_token=YOUR_TOKEN&filter[type]=nominal&filter[metric]=index&filter[from]=2020-Q1&filter[to]=2024-Q4&fmt=json'
data = requests.get(url).json()
print(data)
library(httr)
library(jsonlite)
url <- 'https://eodhd.com/api/real-estate/US?api_token=YOUR_TOKEN&filter[type]=nominal&filter[metric]=index&filter[from]=2020-Q1&filter[to]=2024-Q4&fmt=json'
response <- GET(url)
if (http_type(response) == "application/json") {
content <- content(response, "text", encoding = "UTF-8")
cat(content)
} else {
cat("Error while receiving data\n")
}
Try it now (it's free)!
How to use it (YouTube)
Response Example
{
"data": [
{ "period": "2020-Q1", "value": 158.4799, "type": "nominal", "metric": "index" },
{ "period": "2020-Q2", "value": 159.8064, "type": "nominal", "metric": "index" }
],
"meta": {
"country_code": "US",
"country_name": "United States",
"type": "nominal",
"metric": "index",
"base_year": "2010 = 100",
"frequency": "quarterly",
"source": "BIS",
"total": 4,
"from": "2020-Q1",
"to": "2024-Q4",
"offset": 0,
"limit": 50
},
"links": { "next": null }
}
Response Fields
| Field | Type | Description |
|---|---|---|
| period | string | Observation quarter, format YYYY-Qn |
| value | number | Index level when metric is index, or percentage change when metric is yoy |
| type | string | Price basis, nominal or real |
| metric | string | Measure, index or yoy |
Detailed Property Prices
Detailed Property Prices are the fuller set of national series that BIS collects from member central banks and statistical offices. Unlike the harmonised Selected Property Prices, the detailed series vary by country in what they cover: different dwelling types, regions, and index vintages. Each series is described along several BIS dimensions, given both as codes and as human-readable labels. Query the series catalogue first to see which combinations exist for a country and read their titles, then request the observations.
Detailed Observations
Returns the Detailed Property Prices observations for one country. Each row carries the BIS dimension codes that identify which series it belongs to, each with a matching human-readable label, alongside the unit of measure. Narrow the result with the dimension filters below.
https://eodhd.com/api/real-estate/{code}/detailed?api_token=YOUR_TOKEN
Method GET. Authentication by api_token. Pagination by page[offset] and page[limit]. Response is a JSON envelope with data, meta, and links.
Parameters
api_token
string
required
filter[property_type]
string
optional
filter[area]
string
optional
filter[vintage]
string
optional
filter[freq]
string
optional
filter[from]
string
optional
filter[to]
string
optional
page[limit]
integer
optional
page[offset]
integer
optional
Request Example
https://eodhd.com/api/real-estate/US/detailed?api_token=YOUR_TOKEN&filter[property_type]=1
curl --location "https://eodhd.com/api/real-estate/US/detailed?api_token=YOUR_TOKEN&filter[property_type]=1&fmt=json"
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://eodhd.com/api/real-estate/US/detailed?api_token=YOUR_TOKEN&filter[property_type]=1&fmt=json',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'GET',
));
$data = curl_exec($curl);
curl_close($curl);
try {
$data = json_decode($data, true, 512, JSON_THROW_ON_ERROR);
var_dump($data);
} catch (Exception $e) {
echo 'Error. '.$e->getMessage();
}
import requests
url = f'https://eodhd.com/api/real-estate/US/detailed?api_token=YOUR_TOKEN&filter[property_type]=1&fmt=json'
data = requests.get(url).json()
print(data)
library(httr)
library(jsonlite)
url <- 'https://eodhd.com/api/real-estate/US/detailed?api_token=YOUR_TOKEN&filter[property_type]=1&fmt=json'
response <- GET(url)
if (http_type(response) == "application/json") {
content <- content(response, "text", encoding = "UTF-8")
cat(content)
} else {
cat("Error while receiving data\n")
}
Try it now (it's free)!
How to use it (YouTube)
Response Example
{
"data": [
{
"period": "1975-Q4",
"value": 22.481,
"frequency": "Q",
"covered_area": "0",
"covered_area_label": "Whole country",
"property_type": "1",
"property_type_label": "All types of dwellings",
"vintage": "1",
"vintage_label": "Existing",
"unit_measure": "426: Index, 2000 Jan = 100",
"unit_measure_label": null
}
],
"meta": {
"country_code": "US",
"source": "BIS",
"dataset": "DPP",
"total": 202,
"offset": 0,
"limit": 50,
"filters": { "property_type": "1" }
},
"links": { "next": "https://eodhd.com/api/real-estate/US/detailed?filter%5Bproperty_type%5D=1&page%5Boffset%5D=50&page%5Blimit%5D=50" }
}
Response Fields
| Field | Type | Description |
|---|---|---|
| period | string | Observation period. Format follows the series frequency, for example 2020-Q1 or 2020-01 |
| value | number | Observation value, in the units given by unit_measure |
| frequency | string | Observation frequency, one of Q, A, M, or H |
| covered_area | string | BIS covered-area dimension code |
| covered_area_label | string | Human-readable label for covered_area, for example Whole country |
| property_type | string | BIS property-type dimension code |
| property_type_label | string | Human-readable label for property_type, for example All types of dwellings |
| vintage | string | BIS vintage dimension code |
| vintage_label | string | Human-readable label for vintage, for example Existing |
| unit_measure | string | Unit of measure and index base, for example 426: Index, 2000 Jan = 100 |
| unit_measure_label | string | Human-readable label for the unit of measure, null when the raw unit is already descriptive |
Series Catalogue
Returns the catalogue of Detailed Property Prices series available for one country. Each entry describes a series along the BIS dimensions and gives a human-readable title. Use this endpoint to interpret the dimension codes returned by the detailed observations endpoint, and to decide which combination to filter for.
https://eodhd.com/api/real-estate/{code}/detailed/series?api_token=YOUR_TOKEN
Method GET. Authentication by api_token. Response is a JSON envelope with data and meta.
Parameters
api_token
string
required
Request Example
https://eodhd.com/api/real-estate/US/detailed/series?api_token=YOUR_TOKEN
curl --location "https://eodhd.com/api/real-estate/US/detailed/series?api_token=YOUR_TOKEN&fmt=json"
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://eodhd.com/api/real-estate/US/detailed/series?api_token=YOUR_TOKEN&fmt=json',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'GET',
));
$data = curl_exec($curl);
curl_close($curl);
try {
$data = json_decode($data, true, 512, JSON_THROW_ON_ERROR);
var_dump($data);
} catch (Exception $e) {
echo 'Error. '.$e->getMessage();
}
import requests
url = f'https://eodhd.com/api/real-estate/US/detailed/series?api_token=YOUR_TOKEN&fmt=json'
data = requests.get(url).json()
print(data)
library(httr)
library(jsonlite)
url <- 'https://eodhd.com/api/real-estate/US/detailed/series?api_token=YOUR_TOKEN&fmt=json'
response <- GET(url)
if (http_type(response) == "application/json") {
content <- content(response, "text", encoding = "UTF-8")
cat(content)
} else {
cat("Error while receiving data\n")
}
Try it now (it's free)!
How to use it (YouTube)
Response Example
{
"data": [
{
"covered_area": "0",
"covered_area_label": "Whole country",
"property_type": "1",
"property_type_label": "All types of dwellings",
"vintage": "1",
"vintage_label": "Existing",
"compiling_org": "2",
"priced_unit": "0",
"seasonal_adj": "1",
"unit_measure": "426: Index, 2000 Jan = 100",
"unit_measure_label": null,
"title": "United States - Residential property price index, existing dwellings, whole country"
}
],
"meta": { "country_code": "US", "total": 5 }
}
Response Fields
| Field | Type | Description |
|---|---|---|
| covered_area | string | BIS covered-area dimension code |
| covered_area_label | string | Human-readable label for covered_area |
| property_type | string | BIS property-type dimension code |
| property_type_label | string | Human-readable label for property_type |
| vintage | string | BIS vintage dimension code |
| vintage_label | string | Human-readable label for vintage |
| compiling_org | string | BIS compiling-organisation dimension code |
| priced_unit | string | BIS priced-unit dimension code |
| seasonal_adj | string | Seasonal adjustment dimension code |
| unit_measure | string | Unit of measure and index base |
| unit_measure_label | string | Human-readable label for the unit of measure, null when the raw unit is already descriptive |
| title | string | Human-readable description of the series |
Pagination and CSV
The data endpoints are paged with page[offset] and page[limit]. The default page size is 50 rows and the maximum is 500; a larger page[limit] returns 422. The meta block reports the total row count and the active window, and links.next holds the URL for the following page, or null on the last page.
Add fmt=csv to any endpoint to receive the same data as a CSV file instead of JSON. The CSV is UTF-8 with a byte-order mark and is served as an attachment, and values are protected against spreadsheet formula injection. The column headers match the JSON field names.
https://eodhd.com/api/real-estate/US?api_token=YOUR_TOKEN&filter%5Btype%5D=nominal&filter%5Bmetric%5D=index&fmt=csv
Response Codes
| Code | Meaning |
|---|---|
| 200 | Success. Response body carries data, meta, and where applicable links |
| 401 | Missing or invalid api_token |
| 403 | Token is valid but the plan does not include access to this dataset |
| 404 | Unknown country code. Codes are case insensitive and are normalised to uppercase |
| 405 | Method not allowed. Only GET is supported |
| 422 | Invalid parameter — a malformed period, page[limit] above 500, an end period before the start period, or an out-of-range filter value |
Attribution and Licensing
All property price data is sourced from the Bank for International Settlements. When redistributing or displaying this data, cite the source as shown below.
| Source | Required attribution |
|---|---|
| BIS | Source: BIS residential property price statistics, Bank for International Settlements (www.bis.org) |
Methodology and Limitations
- Selected Property Prices are quarterly, with periods expressed as year and quarter, for example 2020-Q1, and filter[from] and filter[to] use this same format. Detailed Property Prices carry their own frequency per series, which may be monthly, quarterly, half-yearly, or annual, so their periods and filter bounds follow that frequency.
- Selected Property Prices are harmonised and comparable across countries. Index values are rebased to a common base year, reported in the meta base_year field; a year-on-year metric is also provided. Both a nominal and a real basis are available.
- Detailed Property Prices are national series and are not harmonised across countries. Their coverage, dwelling type, and index base differ by country, so compare like with like within a single country. Each observation carries BIS dimension codes with a matching human-readable label field; the series catalogue lists every combination available for a country.
- Not every country carries both datasets. Check the has_spp and has_dpp flags from the country list before requesting data, and note that some entries are BIS aggregates covering several economies.
- Country codes are case insensitive and are normalised to uppercase; an unknown code returns 404. The page[limit] maximum is 500. Filter keys and values are validated, and an unknown filter key or an out-of-range value returns 422.