Endpoints
Three of them. All GET, all JSON, all requiring a key.
/api/data/latest.jsonCurrent quarter for every district and neighbourhood — 83 areas, a few kilobytes. This is the one to poll if you want a number on a dashboard.
/api/data/latest.json{
"name": "Barcelona rent and sale prices … — current quarter",
"publisher": {"name": "...", "url": "..."},
"source": {"rent": "...", "sale": "..."},
"license": "https://www.barcelona-rent.fyi/data/licence",
"periods": {
"neighbourhoodRent": "2026-Q1", "neighbourhoodSale": "2026-Q1",
"districtRent": "2026-Q1", "districtSale": "2026-Q1"
},
"units": {
"rentEurPerSqmMonth": "euros per square metre per month",
"salePriceEurPerSqm": "euros per square metre of built area",
"grossYieldPct": "annual rent as a percentage of purchase price"
},
"areas": [
{
"type": "district",
"code": 1,
"name": "Ciutat Vella",
"url": "https://www.barcelona-rent.fyi/district/ciutat-vella",
"rentEurPerSqmMonth": 18.41,
"salePriceEurPerSqm": 4647.32,
"grossYieldPct": 4.75
}
]
}| Field | Type | Description |
|---|---|---|
| areas[].type | string | Either "district" or "neighbourhood". 10 of the first, 73 of the second. |
| areas[].code | integer | The official Barcelona statistical code. District 1–10; neighbourhood 1–73. Codes are stable across quarters, names are the thing that changes. |
| areas[].rentEurPerSqmMonth | number | null | Null where the quarter has too few registered contracts to publish. |
| areas[].salePriceEurPerSqm | number | null | Same, for registered sales. |
| areas[].grossYieldPct | number | null | Omitted rather than guessed when rent and sale describe different quarters — check `periods` before comparing across the two. |
/api/data/history.jsonEvery observation behind the site, back to 2000 for district rent. Around a megabyte. Fetch it once and keep it — it changes quarterly.
/api/data/history.jsonAbout a megabyte — the response below is truncated for display.
Observations are positional tuples rather than objects: repeating six key names across nine thousand rows would triple the payload for nothing. The columns key tells you the order.
{
"columns": {
"neighbourhoodSeries": ["neighbourhoodCode", "year", "quarter", "value"],
"districtSeries": ["districtCode", "year", "quarter", "value"],
"citySeries": ["year", "quarter", "value"]
},
"coverage": {
"neighbourhoodRent": {"from": "2014-Q1", "to": "2026-Q1", "quarters": 49, "rows": 3347},
"districtRent": {"from": "2000-Q1", "to": "2026-Q1", "quarters": 105, "rows": 1050},
"cityRent": {"from": "2000-Q1", "to": "2026-Q1", "quarters": 105, "rows": 105}
},
"neighbourhoods": [{"code": 1, "name": "el Raval", "districtCode": 1, "url": "..."}],
"districts": [{"code": 1, "name": "Ciutat Vella", "url": "..."}],
"series": {
"neighbourhoodRent": [[71, 2014, 1, 9.54]],
"districtRent": [[1, 2000, 1, 5]],
"cityRent": [[2000, 1, 5.73]]
}
}| Field | Type | Description |
|---|---|---|
| series.neighbourhoodRent | array | [code, year, quarter, €/m²/month]. 2014 Q1 onward — before that the Generalitat published districts only. |
| series.districtRent | array | [code, year, quarter, €/m²/month]. 2000 Q1 onward, the longest series here. |
| series.cityRent | array | [year, quarter, €/m²/month]. Barcelona as a whole — the row the Generalitat publishes, which is not the mean of the districts. |
| series.neighbourhoodSale | array | [code, year, quarter, €/m² built]. 2013 Q4 onward. |
| series.districtSale | array | [code, year, quarter, €/m² built]. 2013 Q4 onward. |
| series.citySale | array | [year, quarter, €/m² built]. 2013 Q4 onward. |
/api/keys/statusWhat your key is and what it has spent today. Does not count against your quota.
/api/keys/status{"tier":"free","daily_limit":500,"used_today":1,"remaining":499,"linked_to_account":false}/api/keysIssues a key. Takes an optional JSON body with label. Three keys per address per day; the fourth gets a 429.
What there isn’t
No query parameters, no pagination, no filtering by area or date. Two endpoints that each return everything is the whole surface, because the whole dataset fits in a megabyte — filtering it is a line of code on your side and a cache miss on ours. If that stops being true, this page changes before the endpoints do.
NextRate limits →