Data · due diligence

Dataset due diligence

Due-diligence pack for the Spanish coastal new-build listing book and the observation ledger published with it. Listing counts are computed from the book on this deployment. The price-history range is read from price_snapshots when a database client is configured. The published open-data file is reported separately and is not used to fill a failed database read. A figure that cannot be computed is not available.

Generated 2026-10-26T09:32:36.124Z · Health checked 2026-10-26T09:32:36.124Z

JSONSample CSVMethodology
Listings
2,051

Current listing book

Towns
96

5 provinces mapped

Price history
2026-08-05 – 2026-10-26

Live price_snapshots

Published ledger
2026-10-26

v2026-10-26

01

Dataset overview

Avena Spanish Coastal New-Build Market Observations The book on this deployment holds 2,051 listings across 96 location strings and 9 costa labels. Feed artifact time: 2026-10-26T06:52:07.350Z.

Open-dataset town file, recomputed now from this book with the same k≥5 rule: 67 towns. The published manifest reports 67 towns and 2,051 live listings at 2026-10-26T06:52:56.385Z.

Rows with no country field are counted as unset. GET /api/v1/properties defaults a missing country to ES. This pack does not apply that default to the count.

Country on the rowListings
(country not set on the listing row)2,051
02

Coverage

Provinces

Parsed from the location string with the province list used for the Spain statistics file. A location that does not match is unmapped. Unmapped locations: none.

ProvinceTownsListings
Málaga18861
Alicante50822
Murcia24327
Granada16
Cádiz335

Costas

CostaListings
Costa del Sol861
Costa Blanca South487
Costa Calida275
Costa Blanca North249
Costa Blanca South - Inland76
Costa Calida - Inland52
Costa del Sol (Cadiz)35
Costa Blanca North - Inland10
Costa Tropical6

Property types

TypeListings
Apartment886
Penthouse398
Villa391
Townhouse197
Bungalow173
Studio6

Developments and promoters

Distinct development_ref values on the current book: 756. Rows with none: 0. A development reference identifies a project in the feed. It is not a company name.

Promoter names: not available. The listing feed does not include promoter or developer company names. The value stored as developer is a source label written by the capture from the region (Costa del Sol versus the other regions). It is not a promoter directory.

Source labels on the current book, grouped from the developer field. These are not promoter names.

Source labelListings
Via Xavia Estate1,149
Via Avena Estate902

Towns

LocationProvinceCostaListings
Estepona, MálagaMálagaCosta del Sol230
Mijas, MálagaMálagaCosta del Sol187
Torrevieja, AlicanteAlicanteCosta Blanca South118
Fuengirola, MálagaMálagaCosta del Sol100
Pilar de La Horadada, AlicanteAlicanteCosta Blanca South90
Marbella, MálagaMálagaCosta del Sol84
Los Alcazares, MurciaMurciaCosta Calida77
Finestrat, AlicanteAlicanteCosta Blanca North59
Guardamar del Segura, AlicanteAlicanteCosta Blanca South54
Casares, MálagaMálagaCosta del Sol52
San Pedro del Pinatar, MurciaMurciaCosta Calida52
Torre Pacheco, MurciaMurciaCosta Calida48
Benijofar, AlicanteAlicanteCosta Blanca South41
Manilva, MálagaMálagaCosta del Sol40
Benahavís, MálagaMálagaCosta del Sol36
San Miguel de Salinas, AlicanteAlicanteCosta Blanca South32
Pinoso, AlicanteAlicanteCosta Blanca South - Inland31
Villajoyosa, AlicanteAlicanteCosta Blanca North31
Algorfa, AlicanteAlicanteCosta Blanca South30
Polop, AlicanteAlicanteCosta Blanca North27
Torremolinos, MálagaMálagaCosta del Sol27
Rojales, AlicanteAlicanteCosta Blanca South25
Málaga, MálagaMálagaCosta del Sol24
Orihuela Costa, AlicanteAlicanteCosta Blanca South22
Baños y Mendigo, MurciaMurciaCosta Calida21
Benalmádena, MálagaMálagaCosta del Sol21
San Javier, MurciaMurciaCosta Calida19
Vélez Málaga, MálagaMálagaCosta del Sol19
San Roque, CádizCádizCosta del Sol (Cadiz)18
Alicante, AlicanteAlicanteCosta Blanca North16
Calpe, AlicanteAlicanteCosta Blanca North16
Orihuela, AlicanteAlicanteCosta Blanca South16
Fuente Álamo, MurciaMurciaCosta Calida - Inland14
Aguilas, MurciaMurciaCosta Calida13
Jávea Xàbia, AlicanteAlicanteCosta Blanca North13
la Nucia, AlicanteAlicanteCosta Blanca North13
rincon de la victoria, MálagaMálagaCosta del Sol13
Alhama De Murcia, MurciaMurciaCosta Calida - Inland12
El Verger, AlicanteAlicanteCosta Blanca North12
Hondón de las Nieves, AlicanteAlicanteCosta Blanca South - Inland12
La Linea De La Concepcion, CádizCádizCosta del Sol (Cadiz)12
Benissa, AlicanteAlicanteCosta Blanca North11
El Campello, AlicanteAlicanteCosta Blanca North11
Cartagena, MurciaMurciaCosta Calida10
Ciudad Quesada, AlicanteAlicanteCosta Blanca South10
Denia, AlicanteAlicanteCosta Blanca North10
Dolores, AlicanteAlicanteCosta Blanca South10
La Manga Club, MurciaMurciaCosta Calida10
La Manga del Mar Menor, MurciaMurciaCosta Calida10
San Pedro De Alcantara, MálagaMálagaCosta del Sol10
Santa Pola, AlicanteAlicanteCosta Blanca South10
Mazarron, MurciaMurciaCosta Calida - Inland9
Mutxamel, AlicanteAlicanteCosta Blanca North9
Aspe, AlicanteAlicanteCosta Blanca South - Inland8
Cox, AlicanteAlicanteCosta Blanca South - Inland7
Moraira_Teulada, AlicanteAlicanteCosta Blanca North7
Puerto de Mazarron, MurciaMurciaCosta Calida7
San Fulgencio, AlicanteAlicanteCosta Blanca South7
Almuñecar, GranadaGranadaCosta Tropical6
Bigastro, AlicanteAlicanteCosta Blanca South6
La Romana, AlicanteAlicanteCosta Blanca South - Inland6
Ojen, MálagaMálagaCosta del Sol6
Torrox, MálagaMálagaCosta del Sol6
Catral, AlicanteAlicanteCosta Blanca South - Inland5
La Marina, AlicanteAlicanteCosta Blanca South5
Monforte del Cid, AlicanteAlicanteCosta Blanca North - Inland5
Sotogrande, CádizCádizCosta del Sol (Cadiz)5
Alfas del Pi, AlicanteAlicanteCosta Blanca North4
Altea, AlicanteAlicanteCosta Blanca North4
Avileses, MurciaMurciaCosta Calida - Inland4
Benidorm, AlicanteAlicanteCosta Blanca North4
Daya Nueva, AlicanteAlicanteCosta Blanca South4
Lorca, MurciaMurciaCosta Calida - Inland4
Los Montesinos, AlicanteAlicanteCosta Blanca South4
Penaguila, AlicanteAlicanteCosta Blanca North - Inland4
Calasparra, MurciaMurciaCosta Calida - Inland3
Istán, MálagaMálagaCosta del Sol3
Jacarilla, AlicanteAlicanteCosta Blanca South - Inland3
Yecla, MurciaMurciaCosta Calida - Inland3
Alhaurín De La Torre, MálagaMálagaCosta del Sol2
Benejúzar, AlicanteAlicanteCosta Blanca South - Inland2
Cabo de Palos, MurciaMurciaCosta Calida2
Fortuna, MurciaMurciaCosta Calida - Inland2
Gran Alacant, AlicanteAlicanteCosta Blanca South2
La Union, MurciaMurciaCosta Calida2
Santiago de la Ribera, MurciaMurciaCosta Calida2
Almoradi, AlicanteAlicanteCosta Blanca South - Inland1
Benitachell, AlicanteAlicanteCosta Blanca North1
Formentera del Segura, AlicanteAlicanteCosta Blanca South1
Lliber, AlicanteAlicanteCosta Blanca North - Inland1
Los Nietos, MurciaMurciaCosta Calida1
Los Urrutias, MurciaMurciaCosta Calida1
Nerja, MálagaMálagaCosta del Sol1
Rafal, AlicanteAlicanteCosta Blanca South - Inland1
San Juan Alicante, AlicanteAlicanteCosta Blanca North1
Sucina, MurciaMurciaCosta Calida - Inland1

Price history

Source: price_snapshots. First date: 2026-08-05. Latest date: 2026-10-26. Observed days: 76. published movement ledger; its first and latest dates match the live price_snapshots endpoints

Published ledger on this deployment: 2026-08-05 to 2026-10-26, 76 observation days, 764 moves, 328 tombstones (45 still listed, 283 currently absent). Ledger gaps (public/open-data/movement-ledger.json): 2026-10-14 to 2026-10-20 (7 days)

03

Data dictionary

Fields on the open observation files, on GET /api/v1/properties (default and format=apip), on GET /api/v1/dataset-export, and on the aggregate payload of GET /api/v1/open-dataset. Other routes are not this dataset. Their index is /api/openapi.json.

Open dataset (public/open-data and the avena-data market/ mirror)

towns.csv / towns.json

town

string · required

Town name, the text before the comma in the listing location. Towns with fewer than 5 live listings are omitted from this file.

Example: Estepona

costa

string · required

Costa label from the listing book for that town.

Example: Costa del Sol

n_listings

integer · listings · required

Live listings in the town at the time the file was built.

Example: 230

median_price_eur

number · EUR · required

Median asking price of the live listings in the town.

Example: 724,500

median_pm2_eur

number · EUR/m² · required

Median asking price per square metre.

Example: 6,793

p25_pm2_eur

number · EUR/m² · required

25th percentile of asking price per square metre.

Example: 5,475

p75_pm2_eur

number · EUR/m² · required

75th percentile of asking price per square metre.

Example: 8,896

median_built_m2

number · m² · required

Median built area of the live listings in the town.

Example: 102

villa_share_pct

number · percent · required

Share of listings whose type is Villa, Townhouse, or Bungalow.

Example: 13

median_beach_km

number · km · nullable

Median beach distance. Null when the town has no beach-distance values.

Example: 1.2

movement-ledger.csv / movement-ledger.json

date

date · YYYY-MM-DD · required

Observation day. Days with no snapshot are absent, not filled with zeros.

Example: 2026-08-05

listings_observed

integer · listings · required

Distinct listing refs observed that day.

Example: 1,990

price_increases

integer · listings · required

Verified asking-price increases observed that day. Unit changes are not included.

Example: 0

price_reductions

integer · listings · required

Verified asking-price reductions observed that day. Unit changes are not included.

Example: 0

median_abs_move_pct

number · percent · nullable

Median absolute size of the verified moves that day. Null when the day had no counted move.

Example: null

delistings

integer · listings · required

Listing refs recorded as leaving the feed that day. A delisting is not a registered sale.

Example: 0

relistings

integer · listings · required

Delisted refs observed listed again that day.

Example: 0

moves.csv / moves.json

avn_id

string · required

Listing ref the move was observed on. The column name is historical; the value is the feed ref.

Example: N6155

date

date · YYYY-MM-DD · required

Day the new asking price was observed.

Example: 2026-08-06

prev_date

date · YYYY-MM-DD · required

Day the previous asking price of the same ref was observed. Not necessarily the previous calendar day.

Example: 2026-08-05

prev_price_eur

number · EUR · required

Previous asking price.

Example: 270,631

new_price_eur

number · EUR · required

Newly observed asking price.

Example: 285,631

change_pct

number · percent · required

Change from the previous observed asking price to the new one, in percent.

Example: 5.54

town

string · nullable

Town stored on the newer snapshot. Null when the snapshot had none.

Example: La Manga del Mar Menor

type

string · nullable

Property type stored on the newer snapshot. Null when the snapshot had none.

Example: Townhouse

tombstones.csv / tombstones.json

avn_id

string · required

Listing ref that left the observed feed. The value is the feed ref.

Example: N9263

town

string · nullable

Town on the delisting row.

Example: Pinoso

region

string · nullable

Region code on the delisting row.

Example: cb-south

type

string · nullable

Property type on the delisting row.

Example: Villa

beds

number · bedrooms · nullable

Bedrooms on the delisting row.

Example: null

built_m2

number · m² · nullable

Built area on the delisting row.

Example: null

last_price_eur

number · EUR · nullable

Last asking price observed before the ref left the feed.

Example: 385,000

last_pm2_eur

number · EUR/m² · nullable

Last asking price per square metre observed before the ref left the feed.

Example: 3,667

last_seen_date

date · YYYY-MM-DD · nullable

Last day the ref was observed in the feed.

Example: 2026-08-06

relisted_on

date · YYYY-MM-DD · nullable

First later day the ref was observed listed again. Null when it has not returned. The tombstone row is kept either way.

Example: null

still_listed

boolean · required

True when the ref is present on the latest observation day. Those rows are not evidence of absorption.

Example: false

dataset.json

name

string · required

Dataset name written by the open-dataset generator.

Example: Avena Spanish Coastal New-Build Market Observations

version

string · required

File version, the generation date prefixed with v. Not the Zenodo deposit.

Example: v2026-10-26

schema_version

integer · required

Schema version of the observation files. 2 added relisted_on, relistings, and the split between delistings that returned and delistings still absent.

Example: 2

generated_at

timestamp · ISO-8601 · required

When this manifest was generated.

Example: 2026-10-26T06:52:56.385Z

license

string · required

License string written on the manifest.

Example: CC-BY-4.0

attribution

string · required

Attribution string written on the manifest.

Example: Avena Terminal (avenaterminal.com)

observation_ledger.first_date

date · YYYY-MM-DD · required

First observation day in the published ledger.

Example: 2026-08-05

observation_ledger.latest_date

date · YYYY-MM-DD · required

Latest observation day in the published ledger.

Example: 2026-10-26

observation_ledger.observation_days

integer · days · required

Count of observation days in the file. Not a calendar span.

Example: 76

observation_ledger.refs_observed

integer · listings · required

Distinct refs present in the snapshots used to build the file.

Example: 2,343

observation_ledger.moves_recorded

integer · moves · required

Rows in moves.csv.

Example: 764

observation_ledger.delistings_recorded

integer · listings · required

Rows in tombstones.csv, including refs later observed again.

Example: 328

observation_ledger.relistings_recorded

integer · listings · required

Tombstoned refs with a relisted_on date.

Example: 50

observation_ledger.delistings_still_listed

integer · listings · required

Tombstoned refs present again on the latest observation day.

Example: 45

observation_ledger.delistings_currently_absent

integer · listings · required

Tombstoned refs absent on the latest observation day.

Example: 283

cross_section.live_listings

integer · listings · required

Listing-book size when the file was built. It can differ from the book on a later deploy.

Example: 2,051

cross_section.towns_published

integer · towns · required

Towns in towns.csv.

Example: 67

cross_section.town_k_threshold

integer · listings · required

Minimum live listings for a town to be published in towns.csv.

Example: 5

zenodo_snapshot.doi

string · required

DOI of an earlier one-off snapshot. The manifest note says the current dataset version is not that deposit.

Example: 10.5281/zenodo.19520064

zenodo_snapshot.published

date · YYYY-MM-DD · required

Publication date of the Zenodo snapshot named on the manifest.

Example: 2026-04-11

zenodo_snapshot.listings

integer · listings · required

Listing count of that Zenodo snapshot, as written on the manifest. Not the current book.

Example: 500

zenodo_snapshot.note

string · required

The generator's own note on what the Zenodo DOI is and is not.

Example: Earlier one-off snapshot. This dataset version is not deposited on Zenodo.

GET /api/v1/properties

properties[]

ref

string · nullable

Feed listing reference. Omitted from JSON when the listing has none.

Example: N9739

name

string · required

Listing title stored on the book.

Example: Modern New Build Villas in Finestrat with Private Pool Near Benidorm

developer

string · required

Source label written by the capture, not a promoter company name. See coverage.

Example: Via Xavia Estate

town

string · required

Location string as stored, normally "town, province".

Example: Finestrat, Alicante

region

string · required

Costa name when the listing has one, otherwise the region code.

Example: Costa Blanca North

type

string · required

Property type stored on the listing.

Example: Villa

price

number · EUR · required

Asking price. Not a registered transaction price.

Example: 550,000

price_to

number · EUR · required

Upper asking price when the feed has a range. Equals price when it does not.

Example: 550,000

pm2

number · EUR/m² · nullable

Asking price per square metre of the area used for the listing.

Example: 5,140

mm2

number · EUR/m² · required

Area benchmark price per square metre stored on the listing. A model input, not a transaction index.

Example: 6,466

built_m2

number · m² · required

Built area stored on the listing.

Example: 107

plot_m2

number · m² · nullable

Plot area. Null when the feed has none.

Example: 300

bedrooms

number · bedrooms · required

Bedroom count stored on the listing.

Example: 3

bathrooms

number · bathrooms · required

Bathroom count stored on the listing.

Example: 3

beach_km

number · km · nullable

Beach distance stored on the listing.

Example: 5

status

string · required

Build status stored on the listing.

Example: under-construction

completion

string · required

Completion quarter stored on the listing, or TBA when the feed has no delivery date.

Example: 2026-Q4

score

number · 0–100 · nullable

Avena Score computed when the book is loaded. Not a read of score_history. Null when the listing was not scored.

Example: 79

score_confidence

number · 0–1 · required

Deterministic adversarial confidence computed with the response. The route passes energy as null into that calculation.

Example: 0.94

score_confidence_codes

string[] · required

Reason codes from the same confidence calculation. Empty when none apply.

Example: ["missing_energy"]

score_flagged_for_review

boolean · required

True when that confidence is below the review threshold in the confidence module.

Example: false

yield_gross

number · percent · nullable

Gross yield estimate computed with the score. Null when the listing has no yield.

Example: 8.9

yield_net

number · percent · nullable

Net yield estimate computed with the score.

Example: 5

yield_annual

number · EUR · nullable

Gross annual rent estimate behind the yield.

Example: 49,000

url

string · required

URL stored on the listing. It is not guaranteed to be an http listing page.

Example: https://www.xaviaestate.com/search-property/province_4/city_65/page_1/order_ddesc/

image

string · nullable

First image URL stored on the listing.

Example: https://fotos15.apinmo.com/7515/28857817/16-1.jpg

lat

number · degrees · nullable

Latitude stored on the listing.

Example: 38.558160418991896

lng

number · degrees · nullable

Longitude stored on the listing.

Example: -0.20115097434858767

country

string · ISO 3166-1 alpha-2 · required

Country code. The route writes ES when the listing has none.

Example: ES

country_name

string · required

Country name. The route writes Spain when the listing has none.

Example: Spain

currency

string · ISO 4217 · required

Currency code. The route writes EUR when the listing has none.

Example: EUR

source_portal

string · nullable

Portal name stored on the listing, when the book has one.

Example: null

last_synced

timestamp · ISO-8601 · nullable

Last sync timestamp stored on the listing, when the book has one.

Example: null

response

total

integer · listings · required

Listings matching the filters, before paging. The example is the unfiltered book.

Example: 2,051

limit

integer · listings · required

Page size. Default 20. The route caps it at 50.

Example: 20

offset

integer · listings · required

Paging offset. Default 0.

Example: 0

count

integer · listings · required

Listings in this page.

Example: 20

avg_price

number · EUR · required

Mean asking price of the page, not of the whole filtered set.

Example: 472,712

avg_score

number · 0–100 · required

Mean score of scored listings on the page. The route rounds the mean of an empty page to 0.

Example: 77

GET /api/v1/properties?format=apip

property

id

string · required

Identifier derived from the feed ref by deriveAPIPId. Not the feed ref itself. The default payload's ref is the feed ref.

Example: ES-COST-1931

deal_score

integer · 0–100 · required

Rounded score, clamped to 0–100. A listing with no score is emitted as 0.

Example: 53

yield_gross

number · percent · nullable

Gross yield, rounded to 2 decimals. Omitted when the listing has no yield.

Example: 2.4

developer_rating

string · nullable

Present in the APIP schema. toAPIP always sets it undefined, so the JSON omits it. Not populated from this book.

Example: null

market_regime

string · nullable

Present in the APIP schema. toAPIP always sets it undefined, so the JSON omits it.

Example: null

liquidity_score

integer · 0–100 · nullable

Present in the APIP schema. toAPIP always sets it undefined, so the JSON omits it.

Example: null

carbon_rating

string · nullable

Copied from the listing energy letter when that letter is A–G. No separate carbon figure is computed. Omitted otherwise.

Example: B

apci_at_listing

number · nullable

Present in the APIP schema. toAPIP always sets it undefined, so the JSON omits it.

Example: null

location.town

string · required

Text before the comma in the location string.

Example: Estepona

location.region

string · required

Costa name, or the region code when the listing has no costa.

Example: Costa del Sol

location.country

string · ISO 3166-1 alpha-2 · required

Country code. ES when the listing has none.

Example: ES

location.coordinates.lat

number · degrees · nullable

Latitude. The coordinates object is omitted unless both lat and lng are present.

Example: 36.42236862288461

location.coordinates.lng

number · degrees · nullable

Longitude. Omitted with coordinates when either value is missing.

Example: -5.201588722366235

location.beach_km

number · km · nullable

Beach distance. Omitted when the listing has none.

Example: 2.2

pricing.asking_price

number · EUR · required

Asking price.

Example: 560,000

pricing.price_per_m2

number · EUR/m² · nullable

Asking price per square metre. Omitted when absent or zero.

Example: 7,778

pricing.market_reference

number · EUR/m² · nullable

The listing benchmark price per square metre (mm2). Omitted when absent or zero.

Example: 8,687

pricing.currency

string · ISO 4217 · required

Currency. EUR when the listing has none.

Example: EUR

pricing.raw_price_local

number · local currency · nullable

Pre-conversion price. Omitted when the listing has none. This book's rows do not carry it.

Example: null

specifications.built_m2

number · m² · nullable

Built area. Omitted when absent or zero.

Example: 72

specifications.bedrooms

number · bedrooms · nullable

Bedrooms. Omitted when null.

Example: 3

specifications.bathrooms

number · bathrooms · nullable

Bathrooms. Omitted when null.

Example: 2

specifications.pool

string · nullable

Mapped to private, communal, or none. Other stored values are omitted.

Example: communal

specifications.energy

string · nullable

Energy letter A–G, or pending when a value is present but not a letter. Omitted when the listing has none.

Example: B

specifications.status

string · nullable

Mapped to off_plan, under_construction, key_ready, or resale. Unrecognised status text is omitted.

Example: under_construction

source.portal

string · nullable

Portal stored on the listing. The source object is omitted when portal, source ref, last sync, and URL are all empty.

Example: null

source.source_ref

string · nullable

Source reference stored on the listing.

Example: null

source.last_synced

timestamp · ISO-8601 · nullable

Last sync timestamp stored on the listing.

Example: null

source.url

string · nullable

URL stored on the listing.

Example: mailto:henrik@xaviaestate.com

GET /api/v1/dataset-export

row

ref

string · required

Feed ref, or the development ref when the feed ref is missing. Empty string when both are missing.

Example: SP1931

type

string · required

Property type.

Example: Apartment

town

string · required

Location string as stored.

Example: Estepona, Málaga

region

string · required

Region code, not the costa name. The properties API region field prefers the costa name.

Example: costa-del-sol

price

number · EUR · required

Asking price.

Example: 560,000

price_m2

number · EUR/m² · nullable

Asking price per square metre.

Example: 7,778

score

number · 0–100 · nullable

Score computed when the book is loaded.

Example: 53

yield_gross

number · percent · nullable

Gross yield estimate.

Example: 2.4

beds

number · bedrooms · required

Bedroom count.

Example: 3

baths

number · bathrooms · required

Bathroom count.

Example: 2

m2

number · m² · required

Built area.

Example: 72

beach_km

number · km · nullable

Beach distance.

Example: 2.2

developer

string · required

Source label, not a promoter name.

Example: Via Avena Estate

status

string · required

Build status.

Example: under-construction

energy

string · nullable

Energy rating stored on the listing.

Example: B

pool

string · nullable

Pool value stored on the listing.

Example: communal

source

string · required

Constant attribution string written by this route on every row.

Example: Avena Terminal (avenaterminal.com)

doi

string · required

Constant DOI string written by this route on every row. The open-dataset manifest describes that DOI as an earlier snapshot, not the current observation file.

Example: 10.5281/zenodo.19520064

GET /api/v1/open-dataset

response

version

string · required

Literal version written by this route. It is not the version in /open-data/dataset.json.

Example: 1.0.0

license

string · required

License URL written by this route.

Example: https://creativecommons.org/licenses/by/4.0/

data.town_statistics[]

town

string · required

Full location string from the listing book, including the province. Not the town-only name used in towns.csv.

Example: Estepona, Málaga

count

integer · listings · required

Listings with that location string.

Example: 230

avg_price

number · EUR · required

Mean asking price, rounded. The route emits 0 when the input list is empty.

Example: 989,440

avg_yield

number · percent · required

Mean gross yield, one decimal. 0 when the location has no yield estimates.

Example: 2

avg_score

number · 0–100 · required

Mean score, rounded. 0 when the location has no scores.

Example: 51

avg_pm2

number · EUR/m² · required

Mean asking price per square metre, rounded.

Example: 7,903

source

string · required

Constant attribution written on every aggregate row.

Example: Avena Terminal (avenaterminal.com)

license

string · required

Constant license string written on every aggregate row.

Example: CC BY 4.0

data.costa_statistics[]

costa

string · required

Costa label from the listing book.

Example: Costa del Sol

count

integer · listings · required

Listings in that costa.

Example: 861

avg_price

number · EUR · required

Mean asking price in the costa, rounded.

Example: 1,006,990

avg_yield

number · percent · required

Mean gross yield in the costa. 0 when none of the listings have a yield.

Example: 2

avg_score

number · 0–100 · required

Mean score in the costa. 0 when none of the listings have a score.

Example: 50

avg_pm2

number · EUR/m² · required

Mean asking price per square metre in the costa, rounded.

Example: 7,925

source

string · required

Constant attribution written on every aggregate row.

Example: Avena Terminal (avenaterminal.com)

license

string · required

Constant license string written on every aggregate row.

Example: CC BY 4.0

data.property_type_distribution[]

type

string · required

Property type.

Example: Apartment

count

integer · listings · required

Listings of that type.

Example: 886

pct

number · percent · required

Share of the book, one decimal.

Example: 43.2

data.price_band_distribution[]

band

string · required

Asking-price band label defined in the route.

Example: Under €100k

count

integer · listings · required

Listings in that band. The top of each band is exclusive.

Example: 1

data.score_distribution[]

band

string · required

Score band label defined in the route.

Example: 90–100 (Exceptional)

count

integer · listings · required

Scored listings in that band.

Example: 0

data.yield_distribution[]

band

string · required

Gross-yield band label defined in the route.

Example: Over 8%

count

integer · listings · required

Listings whose gross yield falls in that band.

Example: 65

data.index_values

apci_composite

number · required

A single number this route computes from the book averages. It is not the APCI series published at /avena-index.

Example: 22.7

avg_investment_score

number · 0–100 · required

Mean score of scored listings, one decimal.

Example: 49.9

avg_price

number · EUR · required

Mean asking price of the book, rounded.

Example: 723,589

avg_yield

number · percent · required

Mean gross yield of listings that have one, two decimals.

Example: 3.58

avg_pm2

number · EUR/m² · required

Mean asking price per square metre, rounded.

Example: 6,340

total_properties

integer · listings · required

Listings in the book used to build the response.

Example: 2,051

scored_properties

integer · listings · required

Listings that have a score.

Example: 2,051

date

date · YYYY-MM-DD · required

UTC date of the request, not the feed generation date.

Example: 2026-10-26

04

Update cadence and lineage

Last-observed values come from /api/health where that payload has them. A clock time the health payload does not carry is not available.

Feed refresh

Refreshes the listing book in public/data.json from the daily capture and rebuilds the open-data observation files. After the book is deployed it calls /api/cron/pricing-history and /api/cron/scribe.

Cadence: GitHub Actions feed-refresh.yml at 01:37 UTC and 05:10 UTC, plus workflow_dispatch.
Source: .github/workflows/feed-refresh.yml
Last observed: 2026-10-26T06:52:07.350Z
feed-meta.json generated_at, from the /api/health feed check. This is the capture artifact time, not the GitHub run start.
Health status: ok
Health detail: deployed feed is 3h old

Daily price capture

Writes price_snapshots for the current UTC day from the deployed listing book and diffs asking prices against the previous trusted day. Same-unit checks are applied there.

Cadence: Vercel cron /api/cron/pricing-history at 02:20 UTC and 14:30 UTC. feed-refresh.yml also calls that route after the new book is deployed. The cron can run before that deploy.
Source: vercel.json
Last observed: 2026-10-26
Latest price_snapshots.snapshot_date from the /api/health capture check. A date, not the clock time of the job. The job clock time is not in /api/health.
Health status: ok
Health detail: today's capture (2026-10-26) is in

Capture backstop

Checks whether today's capture is missing and, only if it is, dispatches feed-refresh. It does not reconstruct an earlier day that was never stored.

Cadence: GitHub Actions capture-backstop.yml at 07:00 UTC and 09:00 UTC.
Source: .github/workflows/capture-backstop.yml
Last observed: not available
The run timestamp is not in /api/health. That payload only carries the latest completed-run conclusion.
Health status: unknown
Health detail: Workflows check from /api/health (feed-refresh, capture-backstop, and tests together): GitHub Actions status could not be read

Spain official benchmark

Rebuilds data/es-official.json from INE housing price index table 79563 and the Ministerio de Vivienda y Agenda Urbana appraisal file. Separate from the listing history. The Registradores series is recorded as not ingested when the portal refuses the job.

Cadence: GitHub Actions es-official-refresh.yml on Wednesday at 04:40 UTC.
Source: .github/workflows/es-official-refresh.yml
Last observed: 2026-10-24T23:22:40.336Z
data/es-official.json generated_at on this deployment. /api/health does not watch es-official-refresh.yml, so the GitHub run timestamp is not available.
Health status: not available
Health detail: not available
05

Data quality and known gaps

The published observation ledger begins 2026-08-05, the day price capture was pointed at the live feed. Earlier internal tables repeated a frozen snapshot and are excluded. Snapshots before that date are not in the open dataset.

The sell-through monitor records this gap: no snapshots on these dates after a feed outage. Listings last seen the day before the gap and absent the day after left on an unknown date inside the gap and are flagged as exits in that gap. The ledger gaps below are computed from the published movement ledger on this deployment, which can show the same gap and any further gap.

  • 2026-10-14 to 2026-10-20

A developer often advertises several units of one building under a single listing reference and changes which unit it shows. A change in asking price counts as a price move only when it is the same unit: same property type, bedrooms and bathrooms, and built and plot size within 2%. When the specification changed, the row is recorded as a unit change and excluded from move counts. A move of 25% or more also needs full specs on both sides, the same photo set, a new EUR/m² within 0.5–2× the area benchmark, and must not return exactly to an asking price the listing showed in the previous 365 days; otherwise it is marked unverified and not counted. Every observation is kept. The verdict is stored on the row.

Snapshot rows carry move_status from 2026-10-24. moves.csv drops a change only when that status is unit_changed or unverified. A row with no status is not dropped. History from before that date was not reclassified.

Excluded

  • Registered transaction prices. Spanish unit-level sale prices are not in this dataset. Figures are asking prices.
  • Days with no capture, including the declared gap. Those days are absent. They are not filled.
  • Snapshots before 2026-08-05.
  • Unit changes and unverified large moves, from moves.csv and from move counts. The snapshot row is kept.
  • Towns with fewer than 5 live listings, from towns.csv only. They remain in the listing book and on the listings API.
  • Listing descriptions, image files, listing URLs, and project or promoter names, from the open observation files. The current book at /data.json still contains description text and image URLs.
  • Promoter company names. They are not in the feed.
  • The resale market and the separate Spain official statistics file. This book is new-build listings from the daily capture.

Notes on the published manifest

  • Prices are ASKING prices observed in listings. Spanish transaction prices are not public at unit level; nothing here is a registered sale price.
  • The observation ledger begins 2026-08-05. Earlier internal tables exist but repeated a frozen snapshot rather than recording observations, and are excluded on principle.
  • A delisting means the unit left the observed feed — strong evidence of absorption, not a notarised sale.
  • 50 of the 328 delisted units were later observed listed again (relisted_on). Of those, 45 are on the market as of the latest observation day (still_listed=true) and are NOT evidence of absorption; the rest returned and left again. 283 of the 328 tombstoned units are absent today. Tombstones are never deleted — a withdrawal followed by a return is itself an observation, and one no public source records.
  • Cross-sectional aggregates cover only towns with at least 5 live listings.
  • No listing descriptions, images, URLs, or project/developer names are redistributed — only numeric observations and derived statistics.
  • AVM accuracy, measured nightly against the whole book, is published at https://avenaterminal.com/model-stats.json (in-sample, caveats included).
06

Point-in-time and backfill

Asking-price history is a calendar of observations. A stored day is the book captured for that UTC date. Missed days stay missed. Scores on the live API are computed at read time and are not that history.

price_snapshots is written for the current UTC day, one row per listing ref and date. A later capture on the same UTC day upserts that day's row, so the same day can be replaced. The job does not write earlier dates. score rows in score_history are not rewritten: the scribe upsert ignores a row that already exists for that ref and date.

A calendar day that was not captured is not backfilled. The capture backstop only dispatches a fresh capture when today is missing. It cannot invent the book for a day that has already passed without a snapshot.

score_history is written by /api/cron/scribe. The Vercel cron is 03:05 UTC (5 3 * * *), and feed-refresh.yml calls the same route after a deploy. Existing rows are not updated. Rows dated before 2026-08-21 can be filed one day after the book they describe. The route dates rows from the feed file after that seam and does not rewrite the earlier rows.

score, yield, and score_confidence on GET /api/v1/properties are computed when the response is built. They are not a read of score_history. A methodology change changes the live score without rewriting stored history.

The open dataset is rebuilt from snapshots on or after 2026-08-05 and from the listing book at build time. Rebuilding it does not create rows for dates that have no snapshot. A same-day replacement of today's snapshot is what a later build of that same day will contain.

Scribe last run: not available. /api/health does not report the scribe job, so its last-run timestamp is not available.

07

Delivery

/api/v1/properties

GET · JSON · API key required. A request with no key is rejected.

Listing page, default 20 rows, cap 50. format=apip returns the APIP shape of the same page. Pricing on request. See /license and /access.

/api/v1/dataset-export

GET · JSON. CSV with ?format=csv. JSONL wrappers with ?format=kaggle and ?format=huggingface. · No key on this route.

Column subset of the current listing book. The kaggle and huggingface query values change the file shape. They do not mean this response was fetched from those sites.

/api/v1/open-dataset

GET · JSON · No key.

Aggregate counts for the current book. No listing rows.

/open-data/dataset.json

GET · JSON and CSV · No key.

Observation files: dataset.json, towns.csv, towns.json, movement-ledger.csv, movement-ledger.json, moves.csv, moves.json, tombstones.csv, tombstones.json.

https://github.com/HenrikKolstad/avena-data

GET · git, market/ directory · Public repository.

Mirror of the observation files. /api/health compares market/dataset.json. Latest mirror date from this health read: 2026-10-25.

/data.json

GET · JSON · No key.

Current listing book used by the site, including description text and image URLs that the open observation files omit. It is the current cross-section, not the price history.

/api/v1/due-diligence/sample

GET · CSV · No key.

First 25 data rows of public/open-data/moves.csv, unchanged.

/spain/official

GET · HTML. Source file data/es-official.json. · No key.

Weekly official benchmark. Not the listing history.

https://huggingface.co/datasets/AVENATERMINAL/spain-new-build-properties-2026

GET · Hugging Face dataset · Public page.

feed-refresh.yml uploads the open-data files here when HF_TOKEN is set, and leaves the existing copy when it is not. /api/health does not report this copy. Freshness is not available.

Request access · Download the sample CSV

08

Licensing and sourcing

The observation manifest on this deployment says license CC-BY-4.0 and attribution "Avena Terminal (avenaterminal.com)". The license page states that aggregate datasets, research papers, and training data published by Avena Terminal are under CC BY 4.0, with attribution to Avena Terminal (avenaterminal.com).

Earlier one-off snapshot. This dataset version is not deposited on Zenodo.

The keyed listings API is covered by the API section of /license. This pack does not restate tiers. Commercial terms: Pricing on request. License · Pricing on request

  • Listings are Avena's daily capture of agency-marketed new-build asking prices.
  • Prices in the listing book and the observation files are asking prices. A delisting means the ref left the observed feed. It is not a notarised sale.
  • The observation ledger used for the open dataset starts on 2026-08-05. Earlier repeated snapshots are excluded.
  • The Spain official file is a separate weekly pull from INE table 79563 and the housing ministry appraisal file. Its omissions, including sources the job could not read, are in that file.
  • Earlier one-off snapshot. This dataset version is not deposited on Zenodo.
09

Not available

These values were not computed for this response. The reason is the one recorded at generation time.

  • coverage.feed.promoter_names — The listing feed does not include promoter or developer company names. The value stored as developer is a source label written by the capture from the region (Costa del Sol versus the other regions). It is not a promoter directory.
  • pipeline.capture-backstop.last_observed — The run timestamp is not in /api/health. That payload only carries the latest completed-run conclusion.
  • pipeline.es-official.health_status — /api/health did not return a status for this step.
  • point_in_time.scribe_last_run — /api/health does not report the scribe job, so its last-run timestamp is not available.
  • pipeline.price-capture.run_clock_time — /api/health reports the latest snapshot date, not the clock time the capture job ran.
  • pipeline.capture-backstop.run_clock_time — /api/health reports the workflow conclusion, not the run timestamp.
  • pipeline.es-official.github_run_timestamp — /api/health does not watch es-official-refresh.yml. A generated_at on data/es-official.json is the artifact on this deployment, not a GitHub run timestamp.
  • delivery.huggingface.freshness — Not in /api/health. The feed workflow uploads this copy only when a token is set.