One typed core, three surfaces: a REST API with an OpenAPI 3.1 spec, a remote MCP server, and an agent-readable layer. All three read the same data as the site itself — no upstream call happens in a request path, and every value carries its source, vintage and extraction date. Missing data comes back asnull with a reason, never a zero or an imputed average.
Base URL: https://www.expatliving.it/api/v1. Spec: /api/v1/openapi.json (OpenAPI 3.1, importable unmodified as a Custom GPT Action).
GET /comuni?q={name} — find a comune's 6-digit ISTAT code by name (accents and case optional, partial names work, up to 25 matches). Several comuni share a name; check the province.GET /comuni?region=&province=&minPopulation=&maxPurchasePriceEurM2=&taxRegime=… — find comuni by criteria: region, province, population, purchase price, rent, population change 2019–2025 and the verified comuni of a tax regime. Paged (up to 100) and sortable. A comune with no price (or rent, or population-change) figure is left out of that filter and counted in missingData, never treated as cheap. taxRegime lists only comuni on the regime’s verified routes: one missing from the list is not a no.GET /comuni/{istatCode} — one comune: population, foreign residents by citizenship, property purchase and rent price, price-to-rent, verified tax-regime eligibility, a province rollup, seismicZone (the official zona sismica 1–4: a legal classification, not a forecast, and unrelated to tax eligibility), layers (every Italy-map layer — climate, air quality, crime, healthcare quality, age, population change — each with the level it is measured at) and provinceWellbeing (ISTAT BES indicators, polarity-aware percentiles).GET /province/{code} — one province (two-letter code such as AQ): population, OMI prices, foreign residents by citizenship, every ISTAT BES indicator with polarity-aware percentiles, seismicZones (how many of its comuni sit in each official seismic zone, a count and never an average), every province- and region-level layer, and how much of it meets each verified tax regime. Where a source lacks the province (Sardinia’s provinces were reorganized in 2026, and sources differ) the section is null and listed in missingSections, never borrowed from another province.GET /nationalities/{cc} — where one of the tracked citizenships (US, GB, DE…) concentrates, by comune, province and region. Citizenship, not birthplace. Small numbers are flagged, the comune ranking skips places under 1,000 residents by default, and a comune with no ISTAT row is left out rather than counted as zero. The caveats are in the payload.GET /meta/coverage — for every dataset, how many places it covers out of how many, and the source’s own caveat; ?dataset= lists the places that dataset lacks. Missing means no figure, never a zero.GET /datasets — every dataset behind the site: source, source URL, vintage (the period it describes), geographic grain, license, its page on expatliving.it, a download link to the full published JSON, and whether it is servable.GET /tax/regimes — the tax regimes the site encodes: verified or draft, geographic or person-level, legislation, population ceiling and how many comuni meet the verified criteria.GET /tax/regimes/{slug}/eligibility?comune={istatCode} — whether one comune meets a regime’s geographic criteria: qualifies, does_not_qualify or not_assessable (never a no), with a reason. About the place, not the person; not tax advice.curl "https://www.expatliving.it/api/v1/comuni?q=Introdacqua"
curl https://www.expatliving.it/api/v1/comuni/066048
curl https://www.expatliving.it/api/v1/datasets
curl "https://www.expatliving.it/api/v1/tax/regimes/regime-7-percent-pensioners/eligibility?comune=066048"Anonymous, no key required, and CORS-enabled for calls from a browser. Responses are cached for a day at the edge — the data changes on an ETL run, not per request.
Streamable HTTP at https://www.expatliving.it/api/v1/mcp. Read-only, no auth. Nine tools, each generated from the same schema as its REST endpoint above, so the two can never drift apart: search_comuni, find_comuni, get_comune, get_province, get_nationality, get_coverage, list_datasets, list_tax_regimes and check_tax_eligibility.
Claude Code:
claude mcp add --transport http expatliving-data https://www.expatliving.it/api/v1/mcpClaude and ChatGPT apps: add the URL above as a custom connector. Any other client that takes a JSON config:
{
"mcpServers": {
"expatliving-data": {
"url": "https://www.expatliving.it/api/v1/mcp"
}
}
}/llms.txt — what this site is, every dataset it holds with its vintage and page, how to answer from it, and where the API and MCP endpoints are.
Every comune-, province- and region-level dataset on the Italy map is served per comune. The Eurostat and OECD regional figures (NUTS 2/3) are listed by GET /datasets with their page and full-file download, but not resolved per comune, and the MEF tax-rate files stay out until they are verified. Province, nationality and tax-eligibility endpoints are next.
Free to use. Please credit "Expat Living Italy (expatliving.it)" and link the page the figures come from whenever you quote them. Every response carries an attribution block — citeAs is a ready-made citation line and page the page to link — and every MCP tool result opens with the same credit line.
null means the data doesn't exist for that place — read its reason; never substitute a zero or an average.servable: false is an unverified draft — don't quote it.