Expat Living Italy
Server Details
Sourced data on Italy's ~7,900 comuni for people relocating or retiring there.
- Status
- Healthy
- Uptime
- 100.0% over 22 days
- Last Tested
- Transport
- Streamable HTTP · MCP 2025-11-25
- URL
TDQS
Scored across 11 tools
Most tools target distinct resources/grains: articles (list_articles/get_article), comuni (search_comuni/get_comune/find_comuni), provinces (get_province), nationalities (get_nationality), datasets (list_datasets/get_coverage), and tax regimes (list_tax_regimes/check_tax_eligibility). The main soft spots are find_comuni vs search_comuni (both about comuni, though descriptions split filter-by-attribute vs name-to-code lookup) and the three tax-regime entry points, which could momentarily confuse selection.
All tools use a consistent snake_case verb_noun pattern (list_*, get_*, find_*, search_*, check_*), so the convention is uniform and readable. The only minor deviation is that near-synonymous prefixes (find_comuni vs search_comuni) are used for related comuni operations, slightly reducing predictability.
Eleven tools sit squarely in the well-scoped 3–15 range for a domain covering comune/province statistics, tax regimes, datasets and articles. Each tool maps to a distinct grain or lifecycle step (discover, look up, profile, cite) with no obvious filler.
The surface covers the core workflow well: name-to-code lookup, full comune and province profiles, attribute filtering, citizenship concentration, tax-regime checks, dataset coverage, and article access. Minor gaps exist (no explicit region-grain profile tool and no direct comparison/multi-place endpoint), but region filtering via find_comuni largely covers these.
Available Tools
11 toolscheck_tax_eligibilityDoes a comune meet a regime's geographic criteria?ARead-onlyIdempotentInspect
For one regime slug (from list_tax_regimes) and one 6-digit ISTAT code (from search_comuni): qualifies, does_not_qualify or not_assessable, with a one-sentence reason to quote. not_assessable is never a no. This is about the place only; say so, and never present it as whether a person qualifies or as tax advice.
| Name | Required | Description | Default |
|---|---|---|---|
| slug | Yes | A regime slug from /tax/regimes, e.g. "regime-7-percent-pensioners". | |
| comune | Yes | 6-digit ISTAT comune code. Find it with /comuni?q=. |
Output Schema
| Name | Required | Description |
|---|---|---|
| place | Yes | |
| reason | Yes | One sentence on why, safe to quote. |
| regime | Yes | |
| headroom | No | Residents between this comune and the ceiling, when it qualifies. |
| disclaimer | Yes | |
| population | Yes | |
| attribution | Yes | |
| maxPopulation | No | |
| placesNotPeople | Yes | |
| ruleIsIncomplete | Yes | |
| geographicEligibility | Yes | qualifies: the comune meets the regime's verified geographic criteria. does_not_qualify: it doesn't, and the rule has no unverified route that could admit it. not_assessable: no geographic answer can be given (see reason). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, idempotentHint=true and openWorldHint=false, so safety is covered. The description adds interpretation context that annotations cannot: the three-valued result set, the instruction to quote the one-sentence reason, and the critical caveat that not_assessable is never a no — which prevents a downstream false negative. It stops short of describing error cases or the shape of the returned object, so a 4 rather than a 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two dense sentences with zero filler: the first covers inputs and outputs, the second covers the framing constraint. The most consequential caveat (not_assessable is never a no) sits early, so nothing important is buried.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return structure need not be explained, yet the description still supplies the interpretation rules an agent needs (three-valued outcome, quote the reason, do not over-read not_assessable). Combined with the explicit scope limitation, nothing required to call or correctly report this tool is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and both parameters are fully documented there (slug from /tax/regimes, 6-digit ISTAT code found via /comuni?q=). The description restates the same sourcing guidance rather than adding format or constraint detail beyond the schema, so the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names a specific verb and resource (checks whether one comune meets one regime's geographic criteria) and enumerates the exact outcome set: qualifies, does_not_qualify, not_assessable. It also names the sibling tools that supply each input (list_tax_regimes, search_comuni), so the agent can place it precisely among the eleven siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It states the required inputs and their provenance (one slug from list_tax_regimes, one 6-digit ISTAT code from search_comuni), and explicitly bounds when NOT to use its result: it is about the place only, must be described as such, and must never be presented as a person's eligibility or as tax advice. That is explicit when/when-not guidance rather than implied usage.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
find_comuniFind comuni by criteriaARead-onlyIdempotentInspect
Which Italian comuni match: region, province, population range, purchase-price and rent ranges (OMI), population change 2019–2025, and/or the verified comuni of a tax regime. Paged (max 100) and sortable. A comune with no price, rent or population-change figure is left out of the filter on it and COUNTED in missingData — never treated as cheap, free or stable; say how many were left out. The taxRegime filter lists only comuni on the regime's verified routes: a comune missing from it is NOT a 'no' (the response says so in taxRegime.note). Rows carry plain numbers; their source and vintage are in figures. Use search_comuni to turn a town name into a code, get_comune for one town's full profile.
| Name | Required | Description | Default |
|---|---|---|---|
| q | No | Only comuni whose name contains this (case- and accent-insensitive). | |
| sort | No | Sort key. A comune without a figure for the sort key always sorts last. | population |
| limit | No | Rows per page, 1–100. Default 25. | |
| order | No | Default: desc for population and populationChange, asc for name, purchasePrice and rent. | |
| offset | No | Rows to skip, for the next page. | |
| region | No | A region name or slug, e.g. "Abruzzo" or "valle-d-aosta". | |
| province | No | A province's two-letter code ("AQ") or slug ("l-aquila"). | |
| taxRegime | No | A verified geographic regime slug from /tax/regimes. Lists only comuni on the regime's verified routes; a comune missing from the list is NOT a "no" (the rule may admit comuni by routes not yet encoded). | |
| maxPopulation | No | Residents, inclusive. | |
| minPopulation | No | Residents, inclusive. | |
| maxRentEurM2Month | No | Rent in EUR/m2 per month, inclusive. A comune with no rent figure is left out. | |
| minRentEurM2Month | No | Rent in EUR/m2 per month, inclusive. A comune with no rent figure is left out. | |
| maxPurchasePriceEurM2 | No | Purchase price in EUR/m2, inclusive. A comune with no price is left out, never treated as cheap. | |
| minPurchasePriceEurM2 | No | Purchase price in EUR/m2, inclusive. A comune with no price is left out, never treated as cheap. | |
| maxPopulationChangePct | No | Population change 2019 to 2025, in percent, inclusive. | |
| minPopulationChangePct | No | Population change 2019 to 2025, in percent, inclusive. A negative value means decline. |
Output Schema
| Name | Required | Description |
|---|---|---|
| limit | Yes | |
| total | Yes | Comuni matching every filter, before paging. |
| offset | Yes | |
| applied | Yes | The filters and sort as understood, with region and province resolved to their canonical names. |
| figures | Yes | |
| hasMore | Yes | |
| results | Yes | |
| returned | Yes | |
| taxRegime | No | Present only when taxRegime was filtered on. |
| nextOffset | Yes | Pass as offset for the next page; null on the last page. |
| attribution | Yes | |
| missingData | Yes | One entry per figure you filtered on: how many comuni were left out for having no figure. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the readOnly/idempotent annotations by disclosing the null-handling contract: comuni lacking a price, rent or population-change figure are excluded from that filter and counted in `missingData`, never treated as cheap/free/stable. It also explains that taxRegime lists only verified routes and that absence is not a 'no'. This is exactly the behavioral context annotations cannot convey.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the filter list, then the critical caveats, then sibling routing. Dense and essentially waste-free, though the repeated null-handling explanation for price/rent is slightly verbose given the schema already states it per-parameter.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need no explanation, and the description still adds the `missingData` and `taxRegime.note` semantics an agent needs to interpret results correctly. For a 16-parameter, zero-required query tool, nothing material is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
With 100% schema coverage the baseline is 3, but the description adds output-side meaning: rows carry plain numbers whose source and vintage live in `figures`, and paging caps at 100. It largely mirrors the schema's own inclusive/exclusion notes, so it earns credit above baseline without being indispensable.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a precise verb+resource: 'Which Italian comuni match' followed by the exact filterable dimensions (region, province, population, OMI price/rent ranges, population change, tax regime). An agent can immediately tell this is a multi-criteria filtered query and distinguish it from get_comune and search_comuni.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes the agent to alternatives: 'Use search_comuni to turn a town name into a code, get_comune for one town's full profile.' It also states the intended usage pattern for paging/sorting and warns that the taxRegime filter is not exhaustive, so a missing comune must not be read as a negative.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_articleRead one articleARead-onlyIdempotentInspect
One article's full text as Markdown, with its places, datasets and a ready-made citation. Quote figures from the dataset pages it cites, which carry their vintage. Content is free to use provided you credit Expat Living Italy and link to the article.
| Name | Required | Description | Default |
|---|---|---|---|
| slug | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| url | Yes | |
| body | Yes | The article as Markdown, blocks flattened. |
| deck | Yes | |
| slug | Yes | |
| title | Yes | |
| author | Yes | |
| places | Yes | |
| status | Yes | |
| topics | Yes | |
| updated | Yes | |
| datasets | Yes | |
| published | Yes | |
| attribution | Yes | |
| markdownUrl | Yes | The whole article as Markdown, with its citation at both ends. |
| supersededBy | No | Slug of the newer article that replaces this one. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover readOnly/idempotent/closed-world, and the description adds real behavioral context beyond them: the return is Markdown, it bundles places, datasets and a citation, figures should be sourced from the cited dataset pages with their vintage, and reuse is permitted only with credit plus a link. That licensing constraint is genuinely useful and not captured by any annotation.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, front-loaded with what is returned, then sourcing advice, then licensing. Dense and mostly waste-free, though the licensing sentence is arguably peripheral to tool selection.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return structure need not be restated, and annotations carry the safety profile. The definition is nearly complete for a one-parameter read tool; the only real omission is how an agent obtains a valid slug and what happens on an unknown one.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0% and the description never mentions the sole parameter 'slug' — not its format, not where to obtain it (presumably from list_articles or search). With a required parameter carrying no semantics anywhere, the description does not compensate for the gap.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names the specific resource and the exact payload: one article's full text as Markdown, plus its places, datasets and a citation. 'One article's...' implicitly separates it from the listing sibling, but it never names list_articles or another sibling explicitly, so differentiation is left to inference.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives usage guidance for the content ('quote figures from the dataset pages it cites') and the attribution obligation ('credit Expat Living Italy and link to the article'), but says nothing about when to call this tool versus list_articles, or how to obtain a valid slug. Usage is implied rather than stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_comuneGet one comune's profileARead-onlyIdempotentInspect
One Italian comune's population, foreign residents by citizenship, property purchase/rent price, price-to-rent, verified tax-regime eligibility, its province's rollup, seismicZone (the official zona sismica 1-4: a legal classification, not a forecast and not a verdict on any building; quote it with its label and say so — a comune that straddles zones has no single zone, never round it; the zone has nothing to do with tax eligibility, and is not the 7% regime’s “seismic” routes, which depend on the 2009 and 2016-17 earthquake areas), layers (climate, air quality, crime, healthcare quality, age, population change and every other Italy-map layer) and provinceWellbeing (ISTAT BES indicators, polarity-aware percentiles) — looked up by 6-digit ISTAT code. Each layer's grain and measuredFor say whose number it is: a province or region value is not a measurement of this comune — say so when you quote it. Every field is wrapped in a provenance envelope (source, vintage, extractedOn, license); repeat the vintage when you state the number, and if a value is null, say why rather than treating it as zero.
| Name | Required | Description | Default |
|---|---|---|---|
| istatCode | Yes | 6-digit ISTAT comune code, e.g. 075029. |
Output Schema
| Name | Required | Description |
|---|---|---|
| place | Yes | |
| layers | Yes | Every other map layer's value for this place, at the grain its source publishes. A null value means the source has no figure for this place (see `reason`). |
| articles | No | Published articles about this comune — the reporting behind the numbers. Credit required to reuse. |
| population | Yes | |
| attribution | Yes | |
| seismicZone | Yes | The comune's official seismic zone (zona sismica). A legal classification, not a forecast and not a verdict on any building. value is null, never a default zone, when the Department's table has no row for this comune. |
| provinceRollup | Yes | |
| priceToRentYears | Yes | Years of rent that equal the purchase price. Not a yield, an ROI, or a payback period. |
| provinceWellbeing | Yes | ISTAT BES well-being indicators for this comune's province — province-level, not a comune measurement. |
| purchasePriceEurM2 | Yes | |
| rentPriceEurM2Month | Yes | |
| taxRegimeEligibility | Yes | Verified regimes only — "placesNotPeople": geographic eligibility, not personal advice. |
| totalForeignResidents | Yes | Sum of foreignResidentsByCitizenship: only the citizenships this site tracks, so a floor, not every foreign resident — Moroccan, Chinese and Indian residents, among others, are not counted. For every foreign resident, see foreignResidentsAllCitizenships. null when the citizenship dataset has no row for this comune. |
| foreignResidentsByCitizenship | Yes | Counts by ISO-2 citizenship code. Citizenship, not birthplace — excludes foreign-born Italian citizens. value is null, never a zero-filled object, when ISTAT's citizenship dataset has no row at all for this comune. |
| foreignResidentsAllCitizenships | Yes | Residents holding any foreign citizenship: ISTAT's own total, not limited to the citizenships tracked in foreignResidentsByCitizenship. Citizenship, not birthplace. value is null, never 0, when ISTAT sends no total for this comune. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare readOnly/idempotent/closed-world; the description goes far beyond, disclosing the provenance envelope (source, vintage, extractedOn, license), null semantics ('say why rather than treating it as zero'), and grain/measuredFor semantics warning that province or region values are not measurements of this comune. It also flags that seismicZone is a legal classification with no bearing on tax eligibility — substantial behavioral context an agent could not infer from structured fields.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
It is front-loaded with the resource, but the body is a single ~200-word run-on sentence built from stacked em-dashes and nested parentheticals, most notably a sprawling seismicZone aside. Field-level caveats of that length would be better placed in the output schema, and the wall-of-text format hurts scannability.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite an output schema existing (so return values need not be explained), the description supplies the interpretation rules an agent needs to quote the data responsibly — grain/measuredFor attribution, vintage repetition, null handling, and the seismicZone caveat. Nothing required to call or correctly report this tool is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and there is only one parameter, so the schema already documents istatCode with format and example. The description only restates 'looked up by 6-digit ISTAT code', adding no syntax or edge-case detail beyond the schema. Baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names the exact resource (one Italian comune's profile) and enumerates the returned content domains — population, foreign residents, property prices, tax-regime eligibility, seismicZone, layers, provinceWellbeing — plus the lookup key. An agent can distinguish this from get_province, find_comuni, and search_comuni without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied by the 'looked up by 6-digit ISTAT code' framing, which positions it as the single-record fetch after discovery via search_comuni/find_comuni, but no sibling is named and no when-not condition is given (e.g. use get_province for province rollups). The description spends its guidance budget on interpretation rules rather than tool routing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_coverageWhat is missing, by datasetARead-onlyIdempotentInspect
For every dataset: how many places it covers, out of how many at its grain, plus the source's own coverage caveat. Pass dataset (a slug from list_datasets) to list the places that dataset lacks, with the gaps by region. Use it before telling anyone data does not exist for a place, and to explain a null. Missing means no figure, never a zero. Keys a source publishes that match no current place (a superseded province code) are reported and never mapped onto a current place. Drafts are listed as not served, without counts.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Missing places per page when dataset is given, 1–100. Default 50. | |
| offset | No | Missing places to skip, for the next page. | |
| dataset | No | A dataset slug from /datasets. Omit it for the summary of every dataset; pass it to list the places that dataset lacks. |
Output Schema
| Name | Required | Description |
|---|---|---|
| datasets | Yes | |
| attribution | Yes | |
| missingPlaces | No | Present only when `dataset` was given and it has missing places. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the readOnly/idempotent annotations, it discloses substantive semantics: missing means no figure, never a zero; unmatched source keys are reported but never mapped onto a current place; drafts are listed as not served without counts. These are exactly the behaviors an agent must know to interpret results correctly.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
It is front-loaded with the core behavior and every sentence carries semantics (mode selection, safety-of-interpretation rules, edge-case handling). It is dense and runs long, but no sentence is padding, so it stops short of a 5 only for sheer length.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present, the description need not document return structure, and it covers the remaining gaps: both operating modes, pagination context via the schema, and interpretation caveats. An agent has everything needed to call and read this tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so limit/offset are already documented; the baseline is 3. The description still adds real meaning by explaining what passing vs. omitting `dataset` yields (summary of every dataset vs. that dataset's missing places by region), which the schema only paraphrases.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a concrete verb+resource: it reports, per dataset, how many places it covers out of how many at its grain, plus the source's coverage caveat. It also clearly distinguishes the two modes (summary vs. gap listing) from siblings like list_datasets, from which the slug is drawn.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives an explicit trigger ('Use it before telling anyone data does not exist for a place, and to explain a null'), names list_datasets as where the `dataset` slug comes from, and specifies which mode applies when the parameter is omitted versus passed. Nothing about when to reach for this tool is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_nationalityWhere one nationality lives in ItalyARead-onlyIdempotentInspect
For one tracked citizenship (ISO-2 code such as US, GB, DE): the comuni, provinces and regions where its holders concentrate, with counts and shares. It is CITIZENSHIP, not birthplace — foreign-born Italian citizens are not counted — and it carries small-number caveats: the comune ranking skips places under 1,000 residents by default and flags rows under 30 residents (smallNumber). A comune with no ISTAT row is left out, not counted as zero, so totals are floors. State those caveats when you quote a ranking; they are in the result's caveats. Province and comune lists are different levels of detail — say which.
| Name | Required | Description | Default |
|---|---|---|---|
| cc | Yes | ISO-2 citizenship code of one of the citizenships this site tracks, e.g. "US". | |
| limit | No | How many comuni and provinces to list, 1–50. Default 10. | |
| rankBy | No | share: residents with this citizenship as a fraction of the comune's population. count: absolute numbers. | share |
| minPopulation | No | Smallest comune, in residents, that may appear in the comune ranking. Default 1,000: in a village of sixty, four foreign residents is not a finding. Lowering it lets tiny places top the ranking. |
Output Schema
| Name | Required | Description |
|---|---|---|
| caveats | Yes | Read these before quoting a number; they travel with the data. |
| byRegion | Yes | |
| topComuni | Yes | |
| attribution | Yes | |
| nationality | Yes | |
| topProvinces | Yes | Province grain: a different level of detail from the comuni above. Sums only the comuni ISTAT has a row for. |
| nationalTotal | Yes | Residents with this citizenship in the comuni ISTAT reports on; `coverage` is how many comuni that is. |
| citizenshipNotBirthplace | Yes | Every count here is by CITIZENSHIP, not country of birth: it leaves out foreign-born people who hold Italian citizenship. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare read-only/idempotent/closed-world; the description adds substantial non-obvious behavior: comuni under 1,000 residents are skipped by default, rows under 30 residents are flagged via `smallNumber`, and comuni without an ISTAT row are omitted so totals are floors, not zeros. This is exactly the data-quality disclosure an agent needs to interpret results honestly.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the core definition and keeps every sentence information-bearing. It is dense and a couple of long clauses stack multiple ideas, but there is no redundant filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present, return-value explanation is unnecessary, and the description covers the remaining burden: caveat propagation, level-of-detail distinction, and the small-number/floors semantics. An agent can call this correctly and report results responsibly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3. The description adds reasoning beyond the schema — explaining why minPopulation defaults to 1,000 and that lowering it lets tiny places top the ranking, and tying the `smallNumber` flag to the ranking logic — which meaningfully clarifies parameter effects.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource (nationality concentration across comuni/provinces/regions) and pins the key semantic: tracked citizenship by ISO-2 code, with counts and shares. The 'CITIZENSHIP, not birthplace' clarification sharply distinguishes it from the general geographic siblings like get_comune and find_comuni.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives clear operational context — state the small-number caveats when quoting a ranking, and say whether province or comune level is being cited. However it never names an alternative tool or states when to prefer this over a sibling, so routing guidance is implied rather than explicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_provinceGet one province's profileARead-onlyIdempotentInspect
One Italian province (two-letter code such as AQ, or its slug — a comune's provinceCode): population, OMI prices, foreign residents by citizenship (citizenship, not birthplace), ALL ISTAT BES well-being indicators with polarity-aware percentiles among provinces (no composite score), every province- and region-grain map layer, seismicZones (how many of its comuni sit in each official seismic zone — a count of comuni, never an average, since a province has no zone of its own), and how much of the province meets each verified tax regime (places, not people). Sources use different province lists — Sardinia was reorganized in 2026 — so a section a source lacks is null with a reason and listed in missingSections: report it as missing, never substitute another province's figure.
| Name | Required | Description | Default |
|---|---|---|---|
| code | Yes | A province's two-letter code, e.g. "AQ", or its slug, e.g. "l-aquila". Find it in a comune's `provinceCode`. |
Output Schema
| Name | Required | Description |
|---|---|---|
| layers | Yes | Every province- and region-grain map layer for this province, each saying whose number it is. |
| prices | Yes | |
| province | Yes | |
| wellbeing | Yes | |
| population | Yes | Sum of the province's comuni. |
| taxRegimes | Yes | Verified regimes with at least one qualifying comune here: how much of the province meets the geographic criteria. Places, not people; a regime absent here is not a no. |
| attribution | Yes | |
| seismicZones | Yes | How this province's comuni are spread across the official seismic zones: a count per zone, never an average. A province has no zone of its own. |
| missingSections | Yes | Every part of this profile a source has no figure for, with why. Sources use different province lists (Sardinia was reorganized in 2026): a section a source lacks is listed here as missing, never filled in from another province. |
| placesNotPeople | Yes | |
| foreignResidentsByCitizenship | Yes | Citizenship, not birthplace — excludes foreign-born Italian citizens — and only the citizenships this site tracks, so a floor on foreign residents. Summed over the comuni ISTAT has a row for: `coverage` says how many that is, and a comune with no row adds nothing rather than a zero. |
| foreignResidentsAllCitizenships | Yes | Residents holding any foreign citizenship: ISTAT's own total, not limited to the citizenships tracked in foreignResidentsByCitizenship. Citizenship, not birthplace. Summed over the comuni ISTAT has a total for: `coverage` says how many that is, and a comune with no total adds nothing rather than a zero. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations cover safety (readOnly, idempotent, non-open-world), and the description adds substantial domain behavior the annotations cannot: polarity-aware percentiles with no composite score, seismicZones as a count of comuni rather than an average, tax regimes as places not people, foreign residents by citizenship not birthplace, and null-with-reason missingSections due to differing source province lists.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Content is largely justified, but it is delivered as one very long, densely parenthetical run-on sentence, which hampers parseability. It is front-loaded with the resource, yet the mid-sentence clarifications could be broken out for readability.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a single-parameter read tool with an output schema, the description fully covers data caveats an agent needs: source-list divergence (Sardinia's 2026 reorganization), null-with-reason missing sections, and the correct handling of missing data. Nothing material for correct invocation is absent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the single parameter is fully documented in the schema (two-letter code or slug, e.g. "AQ"/"l-aquila", from a comune's provinceCode). The description largely restates this, so it adds little meaning beyond the schema — the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (get) and resource (one Italian province's profile) and then enumerates its exact contents — population, OMI prices, foreign residents, ISTAT BES indicators, map layers, seismicZones, tax-regime coverage. The province-grain scope and its emphasis on province-level aggregation (as opposed to comune-level tools like get_comune) make it distinguishable from siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives clear context for use: it explains how to find the `code` (a comune's `provinceCode`) and gives an explicit behavioral directive — report a missing section as missing, never substitute another province's figure. It stops short of naming alternative tools or stating explicit when-not-to-use conditions, so it is not a full 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_articlesList Expat Living Italy's articlesARead-onlyIdempotentInspect
Dated, sourced reporting on moving to and retiring in Italy, newest first. Filter by topic, by place (a comune's ISTAT code) or by dataset slug. Each entry has its URL, a Markdown URL for the full text, and the places and datasets it cites. Content is free to use provided you credit Expat Living Italy and link to the article.
| Name | Required | Description | Default |
|---|---|---|---|
| place | No | A comune's 6-digit ISTAT code (or a province sigla / region slug). | |
| topic | No | One of: tax, residency-visas, costs-housing, healthcare, places, daily-life, data-method. | |
| dataset | No | A dataset slug from GET /datasets. | |
| include | No | Pass `archived` to include archived articles, flagged by `status`. |
Output Schema
| Name | Required | Description |
|---|---|---|
| articles | Yes | |
| attribution | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already establish readOnly/idempotent/no-open-world, so the safety profile is covered. The description adds real value beyond that: newest-first ordering, the shape of each entry (URL, Markdown URL, cited places/datasets), and licensing/attribution terms that an agent must relay to a user.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three tightly written sentences with zero filler, and the core purpose and ordering are front-loaded before the filtering and licensing details. Every sentence carries information an agent needs.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return-value explanation is not required, yet the description still summarizes entry shape and adds licensing context. It is nearly complete, with the only gap being the unmentioned 'include=archived' filter.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents all four parameters, including the enum and the ISTAT-code format. The description restates the topic/place/dataset filters but adds no syntax or format detail, and it omits the 'include=archived' parameter entirely, leaving the baseline 3.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (List) plus resource (articles) and narrows scope to 'moving to and retiring in Italy' with explicit ordering ('newest first'). This clearly separates it from the get_article sibling, which returns a single article, without needing to open either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Names the three filtering axes (topic, place, dataset), which implies how to narrow a listing, but never states when to use this versus get_article or the other list_* siblings, and gives no exclusions or prerequisites. Usage is inferable but not spelled out.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_datasetsList every dataset behind expatliving.itARead-onlyIdempotentInspect
The catalog: each dataset's source institution, vintage (the period it describes), geographic grain, license, the expatliving.it page that presents it, and whether it is servable (false = still an unverified draft; never quote its values). Use it to say what data exists and where a reader can see it.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| datasets | Yes | |
| attribution | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover the safety profile (readOnlyHint, openWorldHint=false, idempotentHint), so the bar is lower, yet the description adds real behavioral context: the `servable` flag semantics with the explicit instruction "false = still an unverified draft; never quote its values." That is a meaningful caveat an agent could not derive from structured fields alone. It stops short of 5 only because nothing is said about scope or staleness of the catalog itself.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, front-loaded with the resource, no filler. The field enumeration is somewhat redundant given an output schema exists, but it is compact and the critical servability warning is placed where it will be read. Slightly long but every clause carries information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no parameters and a published output schema, the description does not need to explain return values, yet it helpfully surfaces the one field whose semantics matter operationally (servable). The intended use case is stated. Nothing essential to correct invocation is missing, though guidance on pairing it with the coverage/article siblings would make it fully complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so per the calibration the baseline is 4 and the schema (100% coverage, empty properties) fully carries the parameter burden. The description correctly adds nothing about arguments that do not exist.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with "The catalog" and enumerates exactly what each entry contains (source institution, vintage, geographic grain, license, presenting page, servability), so the resource is unambiguous. It is distinguishable from the tax/comuni/article siblings, which share no scope with it. The verb is implicit rather than stated, keeping it just short of a 5.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
"Use it to say what data exists and where a reader can see it" gives an intended use case, so usage is implied rather than absent. However, it offers no when-not conditions, no prerequisites, and no reference to any alternative tool for related questions (e.g. coverage or article lookups). Adequate but thin guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_tax_regimesList the Italian tax regimes this site encodesARead-onlyIdempotentInspect
Each regime's status (verified or draft), whether it depends on place (geographic) or on the person, its legislation, population ceiling and how many comuni meet its verified criteria. Places, not people: not tax advice.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| regimes | Yes | |
| disclaimer | Yes | |
| attribution | Yes | |
| placesNotPeople | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, idempotentHint=true and openWorldHint=false, so the safety profile is covered. The description adds a scope disclaimer ('Places, not people: not tax advice'), which is useful context, but says nothing about return size, ordering, or filtering behavior. Modest added value over the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, no filler, with the field enumeration up front and the scope disclaimer last. The first sentence is a dense list but each item earns its place by previewing what the agent will receive.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present, the description need not explain return values, and annotations cover the safety profile; a zero-parameter list tool has little else to disclose. The only gap is the absence of explicit routing guidance versus check_tax_eligibility, which would make it fully complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so per the rubric the baseline is 4. The field enumeration in the description is about output content rather than parameters and largely duplicates the output schema, but nothing is missing on the input side.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description enumerates what each regime carries (status, place- vs person-dependence, legislation, population ceiling, comuni count), which makes clear it returns a list of Italian tax regimes. It never states the verb 'list' or 'returns' explicitly, relying on the name/title to carry that, but the resource is unambiguous. The closing 'Places, not people' implicitly distinguishes it from check_tax_eligibility.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no explicit when-to-use or when-not guidance. 'Places, not people: not tax advice' is a soft scope cue that hints the agent should reach for check_tax_eligibility when the query is about a person, but it is left implicit rather than naming the alternative or a condition. Adequate but thin.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_comuniFind a comune by nameARead-onlyIdempotentInspect
Look up Italian comuni by name (case- and accent-insensitive, partial names work) and get each one's 6-digit ISTAT code, province and region — the code get_comune needs. Returns identifiers only, no figures. Several comuni share a name; check the province before choosing.
| Name | Required | Description | Default |
|---|---|---|---|
| q | Yes | A comune name or part of one, accents optional — e.g. "Introdacqua" or "reggio". | |
| limit | No | How many matches to return, 1–25. Default 10. |
Output Schema
| Name | Required | Description |
|---|---|---|
| query | Yes | |
| results | Yes | Best matches first: exact name, then names starting with the query, then names containing it. Several Italian comuni share a name — check the province before picking one. |
| truncated | Yes | True when more comuni matched than `limit` allowed — narrow the query. |
| attribution | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover the safety profile (readOnly, idempotent, closed-world), so the description's job is to add matching and output semantics — and it does: case- and accent-insensitive, partial names accepted, identifiers returned with no figures. The duplicate-name ambiguity warning is genuinely useful behavioral context not derivable from the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three tight sentences, front-loaded with the core action, then output, then the ambiguity caveat. No filler, and the ordering matches how an agent would need the information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return structure needn't be spelled out, yet the description still flags that only identifiers come back ('no figures') and warns about name collisions — exactly the decision-relevant facts for a fuzzy search tool. Nothing needed to call it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so both 'q' (accents optional, partial names) and 'limit' (1–25, default 10) are already fully documented in the schema. The description reinforces the partial-name/accent behavior but adds no syntax or constraints the schema lacks, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource (look up Italian comuni by name) and goes further to name the payload (6-digit ISTAT code, province, region) and the downstream tool that consumes it (get_comune). An agent can distinguish this from get_comune and find_comuni without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives clear context for when to reach for it — when you need the code that get_comune requires — and adds a genuine usage caveat: 'Several comuni share a name; check the province before choosing.' However, it never distinguishes itself from the sibling find_comuni, whose name suggests overlapping behavior, leaving that ambiguity unresolved.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
2 tool updates
- Changed
get_comune2 fields changed- added
Output schema / properties / seismicZoneAdded value: +{ + "additionalProperties": false, + "description": "The comune's official seismic zone (zona sismica). A legal classification, not a forecast and not a verdict on any building. value is null, never a default zone, when the Department's table has no row for this comune.", + "properties": { + "coverage": { + "additionalProperties": false, + "description": "How many of Italy's places this dataset covers at all, e.g. 7587 of 7894 comuni.", + "properties": { + "in": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "of": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "in", + "of" + ], + "type": "object" + }, + "extractedOn": { + "description": "ISO date this value was extracted from the source.", + "type": "string" + }, + "license": { + "type": "string" + }, + "method": { + "description": "URL to the methodology page for this dataset.", + "type": "string" + }, + "reason": { + "description": "Present only when value is null: why this place has no figure.", + "type": "string" + }, + "source": { + "type": "string" + }, + "unit": { + "type": "string" + }, + "value": { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "hazardClass": { + "anyOf": [ + { + "maximum": 4, + "minimum": 1, + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "The map layer's value: 5 minus the zone, so 4 is the highest hazard (official zone 1). null when split." + }, + "label": { + "description": "The Department of Civil Protection's label as published: \"3\", a regional sub-zone such as \"2B\" or \"3S\", or a combined label such as \"2A-3A-3B\".", + "type": "string" + }, + "officialZone": { + "anyOf": [ + { + "maximum": 4, + "minimum": 1, + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "The national zone, 1 (highest hazard) to 4 (lowest). null when the comune straddles zones." + }, + "reading": { + "description": "What the classification is and is not. A legal classification used for planning and building control — not a forecast, and not an assessment of any building.", + "type": "string" + }, + "zonesInvolved": { + "description": "Every national zone the label names; more than one means the comune straddles zones.", + "items": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "type": "array" + } + }, + "required": [ + "officialZone", + "label", + "hazardClass", + "zonesInvolved", + "reading" + ], + "type": "object" + }, + { + "type": "null" + } + ] + }, + "vintage": { + "description": "The period the value describes, e.g. \"2025H2\" or \"2025\" — not when it was fetched.", + "type": "string" + } + }, + "required": [ + "value", + "unit", + "source", + "vintage", + "extractedOn", + "method", + "license" + ], + "type": "object" +} - changed
Output schema / requiredPrevious value: -[ - "place", - "population", - "foreignResidentsByCitizenship", - "totalForeignResidents", - "foreignResidentsAllCitizenships", - "purchasePriceEurM2", - "rentPriceEurM2Month", - "priceToRentYears", - "taxRegimeEligibility", - "provinceRollup", - "layers", - "provinceWellbeing", - "attribution" -]New value: +[ + "place", + "population", + "foreignResidentsByCitizenship", + "totalForeignResidents", + "foreignResidentsAllCitizenships", + "purchasePriceEurM2", + "rentPriceEurM2Month", + "priceToRentYears", + "taxRegimeEligibility", + "provinceRollup", + "seismicZone", + "layers", + "provinceWellbeing", + "attribution" +]
- Changed
get_province2 fields changed- added
Output schema / properties / seismicZonesAdded value: +{ + "additionalProperties": false, + "description": "How this province's comuni are spread across the official seismic zones: a count per zone, never an average. A province has no zone of its own.", + "properties": { + "coverage": { + "additionalProperties": false, + "description": "How many of Italy's places this dataset covers at all, e.g. 7587 of 7894 comuni.", + "properties": { + "in": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "of": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "in", + "of" + ], + "type": "object" + }, + "extractedOn": { + "description": "ISO date this value was extracted from the source.", + "type": "string" + }, + "license": { + "type": "string" + }, + "method": { + "description": "URL to the methodology page for this dataset.", + "type": "string" + }, + "reason": { + "description": "Present only when value is null: why this place has no figure.", + "type": "string" + }, + "source": { + "type": "string" + }, + "unit": { + "type": "string" + }, + "value": { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "comuniByOfficialZone": { + "additionalProperties": false, + "description": "Comuni in each official zone (zone1 is the highest hazard). Split comuni are not counted here.", + "properties": { + "zone1": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "zone2": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "zone3": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "zone4": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "zone1", + "zone2", + "zone3", + "zone4" + ], + "type": "object" + }, + "comuniWithoutClassification": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "reading": { + "type": "string" + }, + "straddlingZones": { + "description": "Comuni that straddle zones and so have no single zone; named, not rounded to one.", + "items": { + "additionalProperties": false, + "properties": { + "label": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "name", + "label" + ], + "type": "object" + }, + "type": "array" + }, + "totalComuni": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "comuniByOfficialZone", + "straddlingZones", + "comuniWithoutClassification", + "totalComuni", + "reading" + ], + "type": "object" + }, + { + "type": "null" + } + ] + }, + "vintage": { + "description": "The period the value describes, e.g. \"2025H2\" or \"2025\" — not when it was fetched.", + "type": "string" + } + }, + "required": [ + "value", + "unit", + "source", + "vintage", + "extractedOn", + "method", + "license" + ], + "type": "object" +} - changed
Output schema / requiredPrevious value: -[ - "province", - "population", - "prices", - "foreignResidentsByCitizenship", - "foreignResidentsAllCitizenships", - "wellbeing", - "layers", - "taxRegimes", - "missingSections", - "placesNotPeople", - "attribution" -]New value: +[ + "province", + "population", + "prices", + "foreignResidentsByCitizenship", + "foreignResidentsAllCitizenships", + "wellbeing", + "seismicZones", + "layers", + "taxRegimes", + "missingSections", + "placesNotPeople", + "attribution" +]
3 tool updates
- Changed
get_comune2 fields changed- changed
Output schema / properties / foreignResidentsAllCitizenships / descriptionPrevious value: -"Residents holding any foreign citizenship: ISTAT's own total, not limited to the 25 citizenships tracked in foreignResidentsByCitizenship. Citizenship, not birthplace. value is null, never 0, when ISTAT sends no total for this comune."New value: +"Residents holding any foreign citizenship: ISTAT's own total, not limited to the citizenships tracked in foreignResidentsByCitizenship. Citizenship, not birthplace. value is null, never 0, when ISTAT sends no total for this comune." - changed
Output schema / properties / totalForeignResidents / descriptionPrevious value: -"Sum of foreignResidentsByCitizenship: only the citizenships this site tracks (25), so a floor, not every foreign resident — Romanian, Albanian and Moroccan residents, among others, are not counted. For every foreign resident, see foreignResidentsAllCitizenships. null when the citizenship dataset has no row for this comune."New value: +"Sum of foreignResidentsByCitizenship: only the citizenships this site tracks, so a floor, not every foreign resident — Moroccan, Chinese and Indian residents, among others, are not counted. For every foreign resident, see foreignResidentsAllCitizenships. null when the citizenship dataset has no row for this comune."
- Changed
get_nationality1 field changed- changed
Input schema / properties / cc / descriptionPrevious value: -"ISO-2 citizenship code of one of the 25 citizenships this site tracks, e.g. \"US\"."New value: +"ISO-2 citizenship code of one of the citizenships this site tracks, e.g. \"US\"."
- Changed
get_province2 fields changed- changed
Output schema / properties / foreignResidentsAllCitizenships / descriptionPrevious value: -"Residents holding any foreign citizenship: ISTAT's own total, not limited to the 25 citizenships tracked in foreignResidentsByCitizenship. Citizenship, not birthplace. Summed over the comuni ISTAT has a total for: `coverage` says how many that is, and a comune with no total adds nothing rather than a zero."New value: +"Residents holding any foreign citizenship: ISTAT's own total, not limited to the citizenships tracked in foreignResidentsByCitizenship. Citizenship, not birthplace. Summed over the comuni ISTAT has a total for: `coverage` says how many that is, and a comune with no total adds nothing rather than a zero." - changed
Output schema / properties / foreignResidentsByCitizenship / properties / value / anyOfPrevious value: -[ - { - "items": { - "additionalProperties": false, - "properties": { - "code": { - "description": "ISO-2 citizenship code.", - "type": "string" - }, - "count": { - "maximum": 9007199254740991, - "minimum": -9007199254740991, - "type": "integer" - }, - "shareOfPopulation": { - "type": "number" - } - }, - "required": [ - "code", - "count", - "shareOfPopulation" - ], - "type": "object" - }, - "maxItems": 40, - "type": "array" - }, - { - "type": "null" - } -]New value: +[ + { + "items": { + "additionalProperties": false, + "properties": { + "code": { + "description": "ISO-2 citizenship code.", + "type": "string" + }, + "count": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "shareOfPopulation": { + "type": "number" + } + }, + "required": [ + "code", + "count", + "shareOfPopulation" + ], + "type": "object" + }, + "maxItems": 100, + "type": "array" + }, + { + "type": "null" + } +]
2 tool updates
- Changed
get_comune3 fields changed- added
Output schema / properties / foreignResidentsAllCitizenshipsAdded value: +{ + "additionalProperties": false, + "description": "Residents holding any foreign citizenship: ISTAT's own total, not limited to the 25 citizenships tracked in foreignResidentsByCitizenship. Citizenship, not birthplace. value is null, never 0, when ISTAT sends no total for this comune.", + "properties": { + "coverage": { + "additionalProperties": false, + "description": "How many of Italy's places this dataset covers at all, e.g. 7587 of 7894 comuni.", + "properties": { + "in": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "of": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "in", + "of" + ], + "type": "object" + }, + "extractedOn": { + "description": "ISO date this value was extracted from the source.", + "type": "string" + }, + "license": { + "type": "string" + }, + "method": { + "description": "URL to the methodology page for this dataset.", + "type": "string" + }, + "reason": { + "description": "Present only when value is null: why this place has no figure.", + "type": "string" + }, + "source": { + "type": "string" + }, + "unit": { + "type": "string" + }, + "value": { + "anyOf": [ + { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + { + "type": "null" + } + ] + }, + "vintage": { + "description": "The period the value describes, e.g. \"2025H2\" or \"2025\" — not when it was fetched.", + "type": "string" + } + }, + "required": [ + "value", + "unit", + "source", + "vintage", + "extractedOn", + "method", + "license" + ], + "type": "object" +} - changed
Output schema / properties / totalForeignResidents / descriptionPrevious value: -"Sum of foreignResidentsByCitizenship: only the citizenships this site tracks (25), so a floor, not every foreign resident — Romanian, Albanian and Moroccan residents, among others, are not counted. null when the citizenship dataset has no row for this comune."New value: +"Sum of foreignResidentsByCitizenship: only the citizenships this site tracks (25), so a floor, not every foreign resident — Romanian, Albanian and Moroccan residents, among others, are not counted. For every foreign resident, see foreignResidentsAllCitizenships. null when the citizenship dataset has no row for this comune." - changed
Output schema / requiredPrevious value: -[ - "place", - "population", - "foreignResidentsByCitizenship", - "totalForeignResidents", - "purchasePriceEurM2", - "rentPriceEurM2Month", - "priceToRentYears", - "taxRegimeEligibility", - "provinceRollup", - "layers", - "provinceWellbeing", - "attribution" -]New value: +[ + "place", + "population", + "foreignResidentsByCitizenship", + "totalForeignResidents", + "foreignResidentsAllCitizenships", + "purchasePriceEurM2", + "rentPriceEurM2Month", + "priceToRentYears", + "taxRegimeEligibility", + "provinceRollup", + "layers", + "provinceWellbeing", + "attribution" +]
- Changed
get_province2 fields changed- added
Output schema / properties / foreignResidentsAllCitizenshipsAdded value: +{ + "additionalProperties": false, + "description": "Residents holding any foreign citizenship: ISTAT's own total, not limited to the 25 citizenships tracked in foreignResidentsByCitizenship. Citizenship, not birthplace. Summed over the comuni ISTAT has a total for: `coverage` says how many that is, and a comune with no total adds nothing rather than a zero.", + "properties": { + "coverage": { + "additionalProperties": false, + "description": "How many of Italy's places this dataset covers at all, e.g. 7587 of 7894 comuni.", + "properties": { + "in": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "of": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "in", + "of" + ], + "type": "object" + }, + "extractedOn": { + "description": "ISO date this value was extracted from the source.", + "type": "string" + }, + "license": { + "type": "string" + }, + "method": { + "description": "URL to the methodology page for this dataset.", + "type": "string" + }, + "reason": { + "description": "Present only when value is null: why this place has no figure.", + "type": "string" + }, + "source": { + "type": "string" + }, + "unit": { + "type": "string" + }, + "value": { + "anyOf": [ + { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + { + "type": "null" + } + ] + }, + "vintage": { + "description": "The period the value describes, e.g. \"2025H2\" or \"2025\" — not when it was fetched.", + "type": "string" + } + }, + "required": [ + "value", + "unit", + "source", + "vintage", + "extractedOn", + "method", + "license" + ], + "type": "object" +} - changed
Output schema / requiredPrevious value: -[ - "province", - "population", - "prices", - "foreignResidentsByCitizenship", - "wellbeing", - "layers", - "taxRegimes", - "missingSections", - "placesNotPeople", - "attribution" -]New value: +[ + "province", + "population", + "prices", + "foreignResidentsByCitizenship", + "foreignResidentsAllCitizenships", + "wellbeing", + "layers", + "taxRegimes", + "missingSections", + "placesNotPeople", + "attribution" +]
8 tool updates
- Changed
check_tax_eligibility1 field changed- changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "attribution": { + "additionalProperties": false, + "properties": { + "citeAs": { + "description": "A ready-made citation line.", + "type": "string" + }, + "page": { + "description": "The expatliving.it page these figures come from — link it when you quote them.", + "type": "string" + }, + "publisher": { + "type": "string" + }, + "request": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "publisher", + "url", + "citeAs", + "request" + ], + "type": "object" + }, + "disclaimer": { + "type": "string" + }, + "geographicEligibility": { + "description": "qualifies: the comune meets the regime's verified geographic criteria. does_not_qualify: it doesn't, and the rule has no unverified route that could admit it. not_assessable: no geographic answer can be given (see reason).", + "enum": [ + "qualifies", + "does_not_qualify", + "not_assessable" + ], + "type": "string" + }, + "headroom": { + "description": "Residents between this comune and the ceiling, when it qualifies.", + "type": "number" + }, + "maxPopulation": { + "type": "number" + }, + "place": { + "additionalProperties": false, + "properties": { + "istatCode": { + "description": "6-digit ISTAT comune code, e.g. 075029.", + "type": "string" + }, + "lat": { + "type": "number" + }, + "lng": { + "type": "number" + }, + "name": { + "type": "string" + }, + "page": { + "description": "This comune's profile page on expatliving.it — link it when you quote any figure below.", + "type": "string" + }, + "province": { + "type": "string" + }, + "provinceCode": { + "type": "string" + }, + "provinceSlug": { + "type": "string" + }, + "region": { + "type": "string" + }, + "regionSlug": { + "type": "string" + }, + "slug": { + "description": "Stable URL slug for /data/comune/{slug} — never changes, even if a comune merges.", + "type": "string" + } + }, + "required": [ + "istatCode", + "slug", + "name", + "province", + "provinceCode", + "provinceSlug", + "region", + "regionSlug", + "lat", + "lng", + "page" + ], + "type": "object" + }, + "placesNotPeople": { + "const": true, + "type": "boolean" + }, + "population": { + "additionalProperties": false, + "properties": { + "coverage": { + "additionalProperties": false, + "description": "How many of Italy's places this dataset covers at all, e.g. 7587 of 7894 comuni.", + "properties": { + "in": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "of": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "in", + "of" + ], + "type": "object" + }, + "extractedOn": { + "description": "ISO date this value was extracted from the source.", + "type": "string" + }, + "license": { + "type": "string" + }, + "method": { + "description": "URL to the methodology page for this dataset.", + "type": "string" + }, + "reason": { + "description": "Present only when value is null: why this place has no figure.", + "type": "string" + }, + "source": { + "type": "string" + }, + "unit": { + "type": "string" + }, + "value": { + "type": [ + "number", + "null" + ] + }, + "vintage": { + "description": "The period the value describes, e.g. \"2025H2\" or \"2025\" — not when it was fetched.", + "type": "string" + } + }, + "required": [ + "value", + "unit", + "source", + "vintage", + "extractedOn", + "method", + "license" + ], + "type": "object" + }, + "reason": { + "description": "One sentence on why, safe to quote.", + "type": "string" + }, + "regime": { + "additionalProperties": false, + "properties": { + "criteria": { + "description": "geographic: some comuni qualify and others don't. person: turns on who you are, not where you live; no per-comune answer exists.", + "enum": [ + "geographic", + "person" + ], + "type": "string" + }, + "label": { + "type": "string" + }, + "page": { + "description": "The regime's page on expatliving.it.", + "type": "string" + }, + "slug": { + "description": "The regime's id, e.g. \"regime-7-percent-pensioners\".", + "type": "string" + }, + "status": { + "description": "draft: encoded but not yet checked against the legislation. A draft regime's eligibility is never served.", + "enum": [ + "verified", + "draft" + ], + "type": "string" + } + }, + "required": [ + "slug", + "label", + "status", + "criteria", + "page" + ], + "type": "object" + }, + "ruleIsIncomplete": { + "type": "boolean" + } + }, + "required": [ + "regime", + "place", + "geographicEligibility", + "reason", + "population", + "ruleIsIncomplete", + "placesNotPeople", + "disclaimer", + "attribution" + ], + "type": "object" +}
- Changed
get_article1 field changed- changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "attribution": { + "additionalProperties": false, + "properties": { + "citeAs": { + "description": "A ready-made citation line, with the article's canonical URL.", + "type": "string" + }, + "creditText": { + "type": "string" + }, + "license": { + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "name", + "url" + ], + "type": "object" + }, + "required": { + "const": true, + "description": "Credit with a link is the condition of free use (CC BY 4.0).", + "type": "boolean" + }, + "requirement": { + "type": "string" + } + }, + "required": [ + "required", + "license", + "creditText", + "citeAs", + "requirement" + ], + "type": "object" + }, + "author": { + "type": "string" + }, + "body": { + "description": "The article as Markdown, blocks flattened.", + "type": "string" + }, + "datasets": { + "items": { + "additionalProperties": false, + "properties": { + "coverage": { + "description": "How much of the place set the data covers, as the article states it.", + "type": "string" + }, + "slug": { + "description": "The dataset's slug in GET /datasets, when the page is one of them.", + "type": "string" + }, + "title": { + "type": "string" + }, + "url": { + "type": "string" + }, + "vintage": { + "type": "string" + } + }, + "required": [ + "title", + "url" + ], + "type": "object" + }, + "type": "array" + }, + "deck": { + "type": "string" + }, + "markdownUrl": { + "description": "The whole article as Markdown, with its citation at both ends.", + "type": "string" + }, + "places": { + "items": { + "additionalProperties": false, + "properties": { + "code": { + "description": "ISTAT code for a comune, sigla for a province, slug for a region.", + "type": "string" + }, + "grain": { + "enum": [ + "comune", + "province", + "region" + ], + "type": "string" + }, + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "grain", + "code", + "name", + "url" + ], + "type": "object" + }, + "type": "array" + }, + "published": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "status": { + "enum": [ + "published", + "archived" + ], + "type": "string" + }, + "supersededBy": { + "description": "Slug of the newer article that replaces this one.", + "type": "string" + }, + "title": { + "type": "string" + }, + "topics": { + "items": { + "type": "string" + }, + "type": "array" + }, + "updated": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "slug", + "title", + "deck", + "author", + "published", + "updated", + "status", + "topics", + "url", + "markdownUrl", + "places", + "datasets", + "body", + "attribution" + ], + "type": "object" +}
- Changed
get_comune1 field changed- changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "articles": { + "description": "Published articles about this comune — the reporting behind the numbers. Credit required to reuse.", + "items": { + "additionalProperties": false, + "properties": { + "markdownUrl": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "title": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "slug", + "title", + "url", + "markdownUrl" + ], + "type": "object" + }, + "type": "array" + }, + "attribution": { + "additionalProperties": false, + "properties": { + "citeAs": { + "description": "A ready-made citation line.", + "type": "string" + }, + "page": { + "description": "The expatliving.it page these figures come from — link it when you quote them.", + "type": "string" + }, + "publisher": { + "type": "string" + }, + "request": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "publisher", + "url", + "citeAs", + "request" + ], + "type": "object" + }, + "foreignResidentsByCitizenship": { + "additionalProperties": false, + "description": "Counts by ISO-2 citizenship code. Citizenship, not birthplace — excludes foreign-born Italian citizens. value is null, never a zero-filled object, when ISTAT's citizenship dataset has no row at all for this comune.", + "properties": { + "coverage": { + "additionalProperties": false, + "description": "How many of Italy's places this dataset covers at all, e.g. 7587 of 7894 comuni.", + "properties": { + "in": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "of": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "in", + "of" + ], + "type": "object" + }, + "extractedOn": { + "description": "ISO date this value was extracted from the source.", + "type": "string" + }, + "license": { + "type": "string" + }, + "method": { + "description": "URL to the methodology page for this dataset.", + "type": "string" + }, + "reason": { + "description": "Present only when value is null: why this place has no figure.", + "type": "string" + }, + "source": { + "type": "string" + }, + "unit": { + "type": "string" + }, + "value": { + "anyOf": [ + { + "additionalProperties": { + "type": "number" + }, + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + { + "type": "null" + } + ] + }, + "vintage": { + "description": "The period the value describes, e.g. \"2025H2\" or \"2025\" — not when it was fetched.", + "type": "string" + } + }, + "required": [ + "value", + "unit", + "source", + "vintage", + "extractedOn", + "method", + "license" + ], + "type": "object" + }, + "layers": { + "description": "Every other map layer's value for this place, at the grain its source publishes. A null value means the source has no figure for this place (see `reason`).", + "items": { + "additionalProperties": false, + "properties": { + "coverage": { + "additionalProperties": false, + "description": "How many of Italy's places this dataset covers at all, e.g. 7587 of 7894 comuni.", + "properties": { + "in": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "of": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "in", + "of" + ], + "type": "object" + }, + "download": { + "description": "The whole dataset, as published JSON (every place, with its meta block).", + "type": "string" + }, + "extractedOn": { + "description": "ISO date this value was extracted from the source.", + "type": "string" + }, + "grain": { + "description": "The level this value is measured at. Only \"comune\" is a measurement of this comune itself.", + "enum": [ + "comune", + "province", + "region" + ], + "type": "string" + }, + "label": { + "type": "string" + }, + "license": { + "type": "string" + }, + "measuredFor": { + "description": "The place the value describes, e.g. \"L'Aquila (province)\".", + "type": "string" + }, + "method": { + "description": "URL to the methodology page for this dataset.", + "type": "string" + }, + "note": { + "description": "The source's own coverage caveat, where it has one.", + "type": "string" + }, + "page": { + "description": "The map page for this layer on expatliving.it — link readers here.", + "type": "string" + }, + "reason": { + "description": "Present only when value is null: why this place has no figure.", + "type": "string" + }, + "slug": { + "description": "The dataset slug, as in GET /datasets.", + "type": "string" + }, + "source": { + "type": "string" + }, + "unit": { + "type": "string" + }, + "value": { + "type": [ + "number", + "null" + ] + }, + "vintage": { + "description": "The period the value describes, e.g. \"2025H2\" or \"2025\" — not when it was fetched.", + "type": "string" + } + }, + "required": [ + "value", + "unit", + "source", + "vintage", + "extractedOn", + "method", + "license", + "slug", + "label", + "grain", + "measuredFor", + "page", + "download" + ], + "type": "object" + }, + "maxItems": 100, + "type": "array" + }, + "place": { + "additionalProperties": false, + "properties": { + "istatCode": { + "description": "6-digit ISTAT comune code, e.g. 075029.", + "type": "string" + }, + "lat": { + "type": "number" + }, + "lng": { + "type": "number" + }, + "name": { + "type": "string" + }, + "page": { + "description": "This comune's profile page on expatliving.it — link it when you quote any figure below.", + "type": "string" + }, + "province": { + "type": "string" + }, + "provinceCode": { + "type": "string" + }, + "provinceSlug": { + "type": "string" + }, + "region": { + "type": "string" + }, + "regionSlug": { + "type": "string" + }, + "slug": { + "description": "Stable URL slug for /data/comune/{slug} — never changes, even if a comune merges.", + "type": "string" + } + }, + "required": [ + "istatCode", + "slug", + "name", + "province", + "provinceCode", + "provinceSlug", + "region", + "regionSlug", + "lat", + "lng", + "page" + ], + "type": "object" + }, + "population": { + "additionalProperties": false, + "properties": { + "coverage": { + "additionalProperties": false, + "description": "How many of Italy's places this dataset covers at all, e.g. 7587 of 7894 comuni.", + "properties": { + "in": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "of": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "in", + "of" + ], + "type": "object" + }, + "extractedOn": { + "description": "ISO date this value was extracted from the source.", + "type": "string" + }, + "license": { + "type": "string" + }, + "method": { + "description": "URL to the methodology page for this dataset.", + "type": "string" + }, + "reason": { + "description": "Present only when value is null: why this place has no figure.", + "type": "string" + }, + "source": { + "type": "string" + }, + "unit": { + "type": "string" + }, + "value": { + "anyOf": [ + { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + { + "type": "null" + } + ] + }, + "vintage": { + "description": "The period the value describes, e.g. \"2025H2\" or \"2025\" — not when it was fetched.", + "type": "string" + } + }, + "required": [ + "value", + "unit", + "source", + "vintage", + "extractedOn", + "method", + "license" + ], + "type": "object" + }, + "priceToRentYears": { + "additionalProperties": false, + "description": "Years of rent that equal the purchase price. Not a yield, an ROI, or a payback period.", + "properties": { + "coverage": { + "additionalProperties": false, + "description": "How many of Italy's places this dataset covers at all, e.g. 7587 of 7894 comuni.", + "properties": { + "in": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "of": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "in", + "of" + ], + "type": "object" + }, + "extractedOn": { + "description": "ISO date this value was extracted from the source.", + "type": "string" + }, + "license": { + "type": "string" + }, + "method": { + "description": "URL to the methodology page for this dataset.", + "type": "string" + }, + "reason": { + "description": "Present only when value is null: why this place has no figure.", + "type": "string" + }, + "source": { + "type": "string" + }, + "unit": { + "type": "string" + }, + "value": { + "type": [ + "number", + "null" + ] + }, + "vintage": { + "description": "The period the value describes, e.g. \"2025H2\" or \"2025\" — not when it was fetched.", + "type": "string" + } + }, + "required": [ + "value", + "unit", + "source", + "vintage", + "extractedOn", + "method", + "license" + ], + "type": "object" + }, + "provinceRollup": { + "additionalProperties": false, + "properties": { + "code": { + "type": "string" + }, + "name": { + "type": "string" + }, + "purchasePriceEurM2": { + "additionalProperties": false, + "properties": { + "coverage": { + "additionalProperties": false, + "description": "How many of Italy's places this dataset covers at all, e.g. 7587 of 7894 comuni.", + "properties": { + "in": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "of": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "in", + "of" + ], + "type": "object" + }, + "extractedOn": { + "description": "ISO date this value was extracted from the source.", + "type": "string" + }, + "license": { + "type": "string" + }, + "method": { + "description": "URL to the methodology page for this dataset.", + "type": "string" + }, + "reason": { + "description": "Present only when value is null: why this place has no figure.", + "type": "string" + }, + "source": { + "type": "string" + }, + "unit": { + "type": "string" + }, + "value": { + "type": [ + "number", + "null" + ] + }, + "vintage": { + "description": "The period the value describes, e.g. \"2025H2\" or \"2025\" — not when it was fetched.", + "type": "string" + } + }, + "required": [ + "value", + "unit", + "source", + "vintage", + "extractedOn", + "method", + "license" + ], + "type": "object" + }, + "rentPriceEurM2Month": { + "additionalProperties": false, + "properties": { + "coverage": { + "additionalProperties": false, + "description": "How many of Italy's places this dataset covers at all, e.g. 7587 of 7894 comuni.", + "properties": { + "in": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "of": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "in", + "of" + ], + "type": "object" + }, + "extractedOn": { + "description": "ISO date this value was extracted from the source.", + "type": "string" + }, + "license": { + "type": "string" + }, + "method": { + "description": "URL to the methodology page for this dataset.", + "type": "string" + }, + "reason": { + "description": "Present only when value is null: why this place has no figure.", + "type": "string" + }, + "source": { + "type": "string" + }, + "unit": { + "type": "string" + }, + "value": { + "type": [ + "number", + "null" + ] + }, + "vintage": { + "description": "The period the value describes, e.g. \"2025H2\" or \"2025\" — not when it was fetched.", + "type": "string" + } + }, + "required": [ + "value", + "unit", + "source", + "vintage", + "extractedOn", + "method", + "license" + ], + "type": "object" + }, + "slug": { + "type": "string" + } + }, + "required": [ + "name", + "code", + "slug", + "purchasePriceEurM2", + "rentPriceEurM2Month" + ], + "type": "object" + }, + "provinceWellbeing": { + "additionalProperties": false, + "description": "ISTAT BES well-being indicators for this comune's province — province-level, not a comune measurement.", + "properties": { + "coverage": { + "additionalProperties": false, + "description": "How many of Italy's places this dataset covers at all, e.g. 7587 of 7894 comuni.", + "properties": { + "in": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "of": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "in", + "of" + ], + "type": "object" + }, + "extractedOn": { + "description": "ISO date this value was extracted from the source.", + "type": "string" + }, + "grain": { + "const": "province", + "type": "string" + }, + "license": { + "type": "string" + }, + "measuredFor": { + "type": "string" + }, + "method": { + "description": "URL to the methodology page for this dataset.", + "type": "string" + }, + "page": { + "type": "string" + }, + "reason": { + "description": "Present only when value is null: why this place has no figure.", + "type": "string" + }, + "source": { + "type": "string" + }, + "unit": { + "type": "string" + }, + "value": { + "anyOf": [ + { + "items": { + "additionalProperties": false, + "properties": { + "code": { + "type": "string" + }, + "domain": { + "type": "string" + }, + "label": { + "type": "string" + }, + "percentile": { + "description": "0–100 rank among Italy's provinces, polarity-aware: higher is always better, whichever way the raw value runs.", + "type": "number" + }, + "polarity": { + "description": "\"high\": a higher raw value is better; \"low\": a lower one is.", + "enum": [ + "high", + "low" + ], + "type": "string" + }, + "unit": { + "type": "string" + }, + "value": { + "description": "The raw indicator value.", + "type": "number" + }, + "year": { + "description": "The year this observation describes — indicators update on different cycles.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "code", + "domain", + "label", + "unit", + "polarity", + "value", + "year", + "percentile" + ], + "type": "object" + }, + "maxItems": 80, + "type": "array" + }, + { + "type": "null" + } + ] + }, + "vintage": { + "description": "The period the value describes, e.g. \"2025H2\" or \"2025\" — not when it was fetched.", + "type": "string" + } + }, + "required": [ + "value", + "unit", + "source", + "vintage", + "extractedOn", + "method", + "license", + "grain", + "measuredFor", + "page" + ], + "type": "object" + }, + "purchasePriceEurM2": { + "additionalProperties": false, + "properties": { + "coverage": { + "additionalProperties": false, + "description": "How many of Italy's places this dataset covers at all, e.g. 7587 of 7894 comuni.", + "properties": { + "in": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "of": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "in", + "of" + ], + "type": "object" + }, + "extractedOn": { + "description": "ISO date this value was extracted from the source.", + "type": "string" + }, + "license": { + "type": "string" + }, + "method": { + "description": "URL to the methodology page for this dataset.", + "type": "string" + }, + "reason": { + "description": "Present only when value is null: why this place has no figure.", + "type": "string" + }, + "source": { + "type": "string" + }, + "unit": { + "type": "string" + }, + "value": { + "type": [ + "number", + "null" + ] + }, + "vintage": { + "description": "The period the value describes, e.g. \"2025H2\" or \"2025\" — not when it was fetched.", + "type": "string" + } + }, + "required": [ + "value", + "unit", + "source", + "vintage", + "extractedOn", + "method", + "license" + ], + "type": "object" + }, + "rentPriceEurM2Month": { + "additionalProperties": false, + "properties": { + "coverage": { + "additionalProperties": false, + "description": "How many of Italy's places this dataset covers at all, e.g. 7587 of 7894 comuni.", + "properties": { + "in": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "of": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + } + }, + "required": [ + "in", + "of" + ], + "type": "object" + }, + "extractedOn": { + "description": "ISO date this value was extracted from the source.", + "type": "string" + }, + "license": { + "type": "string" + }, + "method": { + "description": "URL to the methodology page for this dataset.", + "type": "string" + }, + "reason": { + "description": "Present only when value is null: why this place has no figure.", + "type": "string" + }, + "source": { + "type": "string" + }, + "unit": { + "type": "string" + }, + "value": { + "type": [ + "number", + "null" + ] + }, + "vintage": { + "description": "The period the value describes, e.g. \"2025H2\" or \"2025\" — not when it was fetched.", + "type": "string" + } + }, + "required": [ + "value", + "unit", + "source", + "vintage", + "extractedOn", + "method", + "license" + ], + "type": "object" + }, + "taxRegimeEligibility": { + "description": "Verified regimes only — \"placesNotPeople\": geographic eligibility, not personal advice.", + "items": { + "additionalProperties": false, + "properties": { + "headroom": { + "description": "Residents between this comune and the ceiling.", + "type": "number" + }, + "href": { + "description": "Path to this regime's own page on expatliving.it.", + "type": "string" + }, + "label": { + "type": "string" + }, + "maxPopulation": { + "description": "The regime's population ceiling, when it has one.", + "type": "number" + }, + "ruleIsIncomplete": { + "description": "True when the rule admits comuni by routes not yet verified — silence about OTHER regimes proves nothing.", + "type": "boolean" + }, + "slug": { + "type": "string" + } + }, + "required": [ + "slug", + "label", + "href", + "ruleIsIncomplete" + ], + "type": "object" + }, + "type": "array" + }, + "totalForeignResidents": { + "anyOf": [ + { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Sum of foreignResidentsByCitizenship: only the citizenships this site tracks (25), so a floor, not every foreign resident — Romanian, Albanian and Moroccan residents, among others, are not counted. null when the citizenship dataset has no row for this comune." + } + }, + "required": [ + "place", + "population", + "foreignResidentsByCitizenship", + "totalForeignResidents", + "purchasePriceEurM2", + "rentPriceEurM2Month", + "priceToRentYears", + "taxRegimeEligibility", + "provinceRollup", + "layers", + "provinceWellbeing", + "attribution" + ], + "type": "object" +}
- Changed
get_nationality1 field changed- changed
Output schema / properties / topComuni / properties / value / anyOfPrevious value: -[ - { - "items": { - "additionalProperties": false, - "properties": { - "count": { - "maximum": 9007199254740991, - "minimum": -9007199254740991, - "type": "integer" - }, - "istatCode": { - "type": "string" - }, - "name": { - "type": "string" - }, - "page": { - "type": "string" - }, - "population": { - "maximum": 9007199254740991, - "minimum": -9007199254740991, - "type": "integer" - }, - "province": { - "type": "string" - }, - "provinceCode": { - "type": "string" - }, - "rank": { - "maximum": 9007199254740991, - "minimum": -9007199254740991, - "type": "integer" - }, - "region": { - "type": "string" - }, - "shareOfPopulation": { - "description": "A fraction, 0–1.", - "type": "number" - }, - "smallNumber": { - "description": "True when fewer than 50 residents hold this citizenship here: the share rests on a handful of households.", - "type": "boolean" - } - }, - "required": [ - "rank", - "istatCode", - "name", - "province", - "provinceCode", - "region", - "population", - "count", - "shareOfPopulation", - "smallNumber", - "page" - ], - "type": "object" - }, - "maxItems": 50, - "type": "array" - }, - { - "type": "null" - } -]New value: +[ + { + "items": { + "additionalProperties": false, + "properties": { + "count": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "istatCode": { + "type": "string" + }, + "name": { + "type": "string" + }, + "page": { + "type": "string" + }, + "population": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "province": { + "type": "string" + }, + "provinceCode": { + "type": "string" + }, + "rank": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "region": { + "type": "string" + }, + "shareOfPopulation": { + "description": "A fraction, 0–1.", + "type": "number" + }, + "smallNumber": { + "description": "True when fewer than 30 residents hold this citizenship here: the share rests on a handful of households.", + "type": "boolean" + } + }, + "required": [ + "rank", + "istatCode", + "name", + "province", + "provinceCode", + "region", + "population", + "count", + "shareOfPopulation", + "smallNumber", + "page" + ], + "type": "object" + }, + "maxItems": 50, + "type": "array" + }, + { + "type": "null" + } +]
- Changed
list_articles1 field changed- changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "articles": { + "items": { + "additionalProperties": false, + "properties": { + "author": { + "type": "string" + }, + "datasets": { + "items": { + "additionalProperties": false, + "properties": { + "coverage": { + "description": "How much of the place set the data covers, as the article states it.", + "type": "string" + }, + "slug": { + "description": "The dataset's slug in GET /datasets, when the page is one of them.", + "type": "string" + }, + "title": { + "type": "string" + }, + "url": { + "type": "string" + }, + "vintage": { + "type": "string" + } + }, + "required": [ + "title", + "url" + ], + "type": "object" + }, + "type": "array" + }, + "deck": { + "type": "string" + }, + "markdownUrl": { + "description": "The whole article as Markdown, with its citation at both ends.", + "type": "string" + }, + "places": { + "items": { + "additionalProperties": false, + "properties": { + "code": { + "description": "ISTAT code for a comune, sigla for a province, slug for a region.", + "type": "string" + }, + "grain": { + "enum": [ + "comune", + "province", + "region" + ], + "type": "string" + }, + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "grain", + "code", + "name", + "url" + ], + "type": "object" + }, + "type": "array" + }, + "published": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "status": { + "enum": [ + "published", + "archived" + ], + "type": "string" + }, + "supersededBy": { + "description": "Slug of the newer article that replaces this one.", + "type": "string" + }, + "title": { + "type": "string" + }, + "topics": { + "items": { + "type": "string" + }, + "type": "array" + }, + "updated": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "slug", + "title", + "deck", + "author", + "published", + "updated", + "status", + "topics", + "url", + "markdownUrl", + "places", + "datasets" + ], + "type": "object" + }, + "type": "array" + }, + "attribution": { + "additionalProperties": false, + "properties": { + "citeAs": { + "description": "Per article: \"“<title>” by <author>, Expat Living Italy, <date>. <url>\".", + "type": "string" + }, + "creditText": { + "type": "string" + }, + "license": { + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "name", + "url" + ], + "type": "object" + }, + "required": { + "const": true, + "description": "Credit with a link is the condition of free use (CC BY 4.0).", + "type": "boolean" + }, + "requirement": { + "type": "string" + } + }, + "required": [ + "required", + "license", + "creditText", + "requirement", + "citeAs" + ], + "type": "object" + } + }, + "required": [ + "articles", + "attribution" + ], + "type": "object" +}
- Changed
list_datasets1 field changed- changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "attribution": { + "additionalProperties": false, + "properties": { + "citeAs": { + "description": "A ready-made citation line.", + "type": "string" + }, + "page": { + "description": "The expatliving.it page these figures come from — link it when you quote them.", + "type": "string" + }, + "publisher": { + "type": "string" + }, + "request": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "publisher", + "url", + "citeAs", + "request" + ], + "type": "object" + }, + "datasets": { + "items": { + "additionalProperties": false, + "properties": { + "download": { + "description": "The whole dataset as published JSON: a meta block (provenance, unit, grain) and every place's value.", + "type": "string" + }, + "extractedOn": { + "description": "ISO date this dataset was extracted from the source.", + "type": "string" + }, + "grain": { + "description": "The geographic level the source publishes at. NUTS 2 and NUTS 3 are Eurostat/OECD statistical regions — close to, but not the same as, Italy's regions and provinces. Never present a coarser-grain value as if it were measured for one comune.", + "enum": [ + "comune", + "province", + "region", + "nuts2", + "nuts3", + "national" + ], + "type": "string" + }, + "label": { + "type": "string" + }, + "license": { + "type": "string" + }, + "page": { + "description": "The human-readable page for this dataset on expatliving.it — link readers here.", + "type": "string" + }, + "servable": { + "description": "False while a hand-curated dataset is still \"draft\" (unverified against its legislation). A non-servable dataset returns no values anywhere in this API.", + "type": "boolean" + }, + "slug": { + "type": "string" + }, + "source": { + "description": "The institution to cite, e.g. \"ISTAT\" or \"Eurostat\".", + "type": "string" + }, + "sourceUrl": { + "description": "Where this dataset was fetched from upstream.", + "type": "string" + }, + "vintage": { + "description": "The period the data describes, e.g. \"2025H2\", \"2024\" or \"2015–2024\" — not when it was fetched.", + "type": "string" + } + }, + "required": [ + "slug", + "label", + "source", + "sourceUrl", + "vintage", + "extractedOn", + "license", + "grain", + "servable" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "datasets", + "attribution" + ], + "type": "object" +}
- Changed
list_tax_regimes1 field changed- changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "attribution": { + "additionalProperties": false, + "properties": { + "citeAs": { + "description": "A ready-made citation line.", + "type": "string" + }, + "page": { + "description": "The expatliving.it page these figures come from — link it when you quote them.", + "type": "string" + }, + "publisher": { + "type": "string" + }, + "request": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "publisher", + "url", + "citeAs", + "request" + ], + "type": "object" + }, + "disclaimer": { + "type": "string" + }, + "placesNotPeople": { + "const": true, + "type": "boolean" + }, + "regimes": { + "items": { + "additionalProperties": false, + "properties": { + "criteria": { + "description": "geographic: some comuni qualify and others don't. person: turns on who you are, not where you live; no per-comune answer exists.", + "enum": [ + "geographic", + "person" + ], + "type": "string" + }, + "knownIncompleteness": { + "description": "Ways the encoded rule is narrower than the law. A comune missing from the set is not proof it doesn't qualify while this is non-empty.", + "items": { + "type": "string" + }, + "type": "array" + }, + "label": { + "type": "string" + }, + "maxPopulation": { + "description": "The population ceiling, for a geographic regime that has one.", + "type": "number" + }, + "page": { + "description": "The regime's page on expatliving.it.", + "type": "string" + }, + "qualifyingComuni": { + "description": "How many comuni meet the verified geographic criteria. Only for a verified geographic regime.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "shortLabel": { + "type": "string" + }, + "slug": { + "description": "The regime's id, e.g. \"regime-7-percent-pensioners\".", + "type": "string" + }, + "sourceLegislation": { + "items": { + "additionalProperties": false, + "properties": { + "cite": { + "type": "string" + }, + "title": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "cite", + "title", + "url" + ], + "type": "object" + }, + "type": "array" + }, + "status": { + "description": "draft: encoded but not yet checked against the legislation. A draft regime's eligibility is never served.", + "enum": [ + "verified", + "draft" + ], + "type": "string" + }, + "summary": { + "type": "string" + }, + "validFrom": { + "type": "string" + } + }, + "required": [ + "slug", + "label", + "shortLabel", + "status", + "criteria", + "page", + "summary", + "validFrom", + "sourceLegislation", + "knownIncompleteness" + ], + "type": "object" + }, + "maxItems": 50, + "type": "array" + } + }, + "required": [ + "regimes", + "placesNotPeople", + "disclaimer", + "attribution" + ], + "type": "object" +}
- Changed
search_comuni1 field changed- changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "attribution": { + "additionalProperties": false, + "properties": { + "citeAs": { + "description": "A ready-made citation line.", + "type": "string" + }, + "page": { + "description": "The expatliving.it page these figures come from — link it when you quote them.", + "type": "string" + }, + "publisher": { + "type": "string" + }, + "request": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "publisher", + "url", + "citeAs", + "request" + ], + "type": "object" + }, + "query": { + "type": "string" + }, + "results": { + "description": "Best matches first: exact name, then names starting with the query, then names containing it. Several Italian comuni share a name — check the province before picking one.", + "items": { + "additionalProperties": false, + "properties": { + "istatCode": { + "description": "6-digit ISTAT comune code — pass this to GET /comuni/{istatCode} or get_comune.", + "type": "string" + }, + "name": { + "type": "string" + }, + "page": { + "description": "The comune's profile page on expatliving.it.", + "type": "string" + }, + "province": { + "type": "string" + }, + "provinceCode": { + "type": "string" + }, + "region": { + "type": "string" + } + }, + "required": [ + "istatCode", + "name", + "province", + "provinceCode", + "region", + "page" + ], + "type": "object" + }, + "maxItems": 25, + "type": "array" + }, + "truncated": { + "description": "True when more comuni matched than `limit` allowed — narrow the query.", + "type": "boolean" + } + }, + "required": [ + "query", + "results", + "truncated", + "attribution" + ], + "type": "object" +}
2 tool updates
- Added
get_coverage - Added
get_nationality
2 tool updates
- Added
find_comuni - Added
get_province
2 tool updates
- Added
check_tax_eligibility - Added
list_tax_regimes
2 tool updates
- Added
get_article - Added
list_articles
3 tool updates
- First observed
get_comune - First observed
list_datasets - First observed
search_comuni
Related MCP Connectors
Italian cadastral parcels, geocoding, OMI valuations, risk and demographic data. 85M parcels.
Read-only migration data: cities, visa routes, entry rules, costs, safety, climate, with sources.
Dati turistici ISTAT di ogni comune italiano: presenze, arrivi, posti letto, popolazione.
Data by municipality near Barcelona and Madrid: €/m², IBI, rent, minutes. Every figure sourced
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceEnables querying a comprehensive data lake of Italian civic open data, returning cited facts about Italian municipalities across domains like environment, education, finance, and governance.1-
- AlicenseNot gradedqualityBmaintenanceEnables querying Italian national statistics (ISTAT) data through natural language questions, with tools for accessing demographic, economic, and social indicators.31 npmMIT
- AlicenseNot gradedqualityCmaintenanceEnables read-only lookup and comparison of sourced, dated migration data — 39 countries and 27,000+ cities, visa and residence routes, passport entry rules, cost of living, salaries, crime, air quality and climate. Every fact returned includes its value, unit, geographic grain, observation date, source and grade, with unknown values left explicit rather than estimated.MIT

io.github.stucchi/italyofficial
AlicenseNot gradedqualityDmaintenanceMCP server exposing Italian open data (municipalities, provinces, regions, postal codes, coordinates, geographic data) through simple, developer-friendly tools.1MIT
Glama MCP Gateway
Add one secure layer between your agents and this server.