catastrogps
Official Python client for the Catastro GPS API: cadastral parcels from 26 European cadastres plus the foral cadastres of the Basque Country and Navarre with one API key.
- Look up a parcel by its official cadastral reference or by coordinates, with the country detected for you.
- Turn a postal address into parcels in Spain and 26 more countries, ranked by confidence.
- Import every unit of a Spanish building (dwellings, shops, garages) with paging and free
304refreshes. - Get the parcel outline (GeoJSON or a
[lat, lng]ring), and KML / GPX / DXF exports. - Relief, Natura 2000 and climate (Copernicus DEM, EEA, ERA5-Land) and ground motion (Copernicus EGMS, mm/year) for any covered country.
- Solar, agriculture, market, score, value history and comparison for Spain, Portugal, France, Italy and Germany.
- Quota, prepaid balance and per-minute limit of every response, typed exceptions for every API code.
- Two small dependencies (
httpx,typing_extensions), typed, Python 3.9+.
Free tier: 250 calls a month, forever. Failed lookups are not charged. Get a key at parcelgps.com/developers.
Install
pip install catastrogps
Quick start
from catastrogps import CatastroGPS
client = CatastroGPS("pk_live_your_key_here")
parcel = client.parcels.get("9872023VH5797S0001WX")
print(parcel["municipio"], parcel.get("superficieParcela"), parcel["latitud"], parcel["longitud"])
CatastroGPS() with no arguments reads CATASTROGPS_API_KEY from the environment. Use it as a context manager to close the connection pool:
with CatastroGPS() as client:
...
Methods
| Method | Endpoint | Countries |
|---|---|---|
parcels.get(ref, country=) |
GET /api/catastro/{ref} |
All references (see coverage) |
parcels.at_point(lat, lng, country=) |
GET /api/search/coordinates |
All, including HR, SE and UK (Scotland) |
search_address_candidates(q, street=, number=, municipality=, postcode=, country=, limit=) |
GET /api/search/address/candidates |
Spain (with Basque Country and Navarre) and 26 more; not SE or HR |
parcels.find_by_address(text) |
POST /api/search/address/parse |
Spain, central Catastro, live (prefer the candidates search) |
get_units(ref14, country=, previous=), iter_unit_pages |
GET /api/catastro/{ref14}/units |
ES, PV, NA |
parcels.geometry(ref, country=) |
GET /api/catastro/{ref}/polygon |
All but UK |
parcels.terrain(ref, country=, lat=, lng=) |
GET /api/catastro/{ref}/terrain |
All; lat/lng required outside ES, PV, NA, PT, FR, IT, DE |
parcels.ground_motion(ref, country=, lat=, lng=) |
GET /api/catastro/{ref}/ground-motion |
Same as terrain, inside EGMS coverage |
parcels.solar, parcels.agriculture, parcels.score |
/solar, /agro, /score |
ES, PV, NA, PT, FR, IT, DE |
parcels.market(ref, country=) |
/market |
Figures in FR, IT and DE (NRW); a note without figures elsewhere |
parcels.value_history(ref, country=) |
/value-history |
ES, PV, NA, PT, FR, IT, DE, AT |
parcels.compare([(ref, country), ...]) |
POST /api/catastro/compare |
2 or 3 parcels in ES, PT, FR, IT, DE |
export.file(ref, "kml" | "gpx" | "dxf", country=) |
GET /api/export/{format} |
Where a geometry exists |
resolve(text, hint=) |
GET /api/resolve |
Free, no quota: which country and kind a text is |
Examples
in_warsaw = client.parcels.at_point(52.2297, 21.0122)
in_warsaw["referenciaCatastral"], in_warsaw.get("pais")
found = client.search_address_candidates("Damrak 1, 1012 LG Amsterdam", country="NL")
found["candidatos"][0]["refCatastral"], found["candidatos"][0]["confianza"]
terrain = client.parcels.terrain("10194A00110004")
terrain["relief"]["slope"]["class"], terrain["protected_areas"]["natura2000"], terrain.get("climate")
brussels = client.parcels.terrain("21004C0123/00", country="BE", lat=50.848139, lng=4.353613)
motion = client.parcels.ground_motion("0745901TG4304N0002KH")
motion["status"], motion.get("ground_motion", {}).get("vertical", {}).get("mean_mm_year")
comparison = client.parcels.compare([("9872023VH5797S0001WX", "ES"), ("750560000AB0001", "FR")])
kml_bytes = client.export.file("9872023VH5797S0001WX", "kml")
guess = client.resolve("05102200100005")
guess["candidates"]
terrain and ground_motion carry a source.attribution string that must be shown next to the data. A ground motion answer with status: "no_data" and reason: "no_reflectors" means the satellite had nothing stable to measure (fields, forest, water): there is no figure, not a zero.
Responses are the API's data object as a dict, with the field names the API uses (refCatastral, municipio, superficieParcela…). See the API reference and the OpenAPI spec at https://api.parcelgps.com/api/openapi.json.
Import a building (comunidad de propietarios, Spain)
Address, then finca (14-character reference), then every unit with use, area, participation coefficient, stair, floor and door.
found = client.search_address_candidates("Avenida José Rodríguez de la Borbolla Camoyán 10, Dos Hermanas")
finca = found["candidatos"][0]
comunidad = client.get_units(finca["refCatastral"], country=finca["pais"])
comunidad["totalUnidadesFinca"]
for u in comunidad["unidades"]:
print(u["refCatastral"], u["uso"], u["superficie"], u.get("participacion"), u["escalera"], u["planta"], u["puerta"])
comunidad["dataSource"], comunidad["attribution"]
refreshed = client.get_units(finca["refCatastral"], previous=comunidad)
refreshed["changed"]
get_unitsfollowsnextCursorfor you (up to 200 units per page).iter_unit_pages(ref)yields page by page.- Cost: one quota unit per unit served in each page, minimum one per page; a page is cut to the quota you have left. Pass the previous result as
previous=to re-import: unchanged pages come back as304 Not Modified, cost nothing and are reused;changedtells you if anything moved. - Basque Country and Navarre (
country="PV"or"NA") are served from our copy of each foral cadastre: pass the foral reference of any unit or the finca key inrefCatastral. The foral open data carry no participation coefficient. - Organisations with a per-finca cap agreed by contract (Startup and Growth) pay at most that cap per finca and period: see
client.last_quota["finca_cap"]({"cap", "used"}), also in each page'squota.
Quota, prepaid balance and limits
client.last_quota is read from the headers of the last response; conditional_request also returns the quota of that exact response.
| Key | Header | Meaning |
|---|---|---|
plan, limit, remaining, resets_at |
X-Quota-Tier, X-Quota-Limit, X-Quota-Remaining, X-Quota-Reset |
Monthly quota of your plan, after this response |
overage: balance, unit_price, remaining_units |
X-Quota-Overage, X-Quota-Balance, X-Quota-Overage-Unit-Price, X-Quota-Overage-Remaining |
Prepaid pay-as-you-go balance in euros, used once the monthly quota is spent |
rate_limit: limit, remaining, reset_seconds |
X-RateLimit-* |
Per-minute burst limit of your key (Free 10, Developer 60, Startup 120, Growth 300) |
finca_cap: cap, used |
X-Quota-Finca-Cap, X-Quota-Finca-Cap-Used |
Only on /units, only with a contract cap |
When the monthly quota is spent and there is no prepaid balance left, the API answers 429 KEY_AUTH_004 (never 402): the client raises QuotaExceededError with the quota of that response, and prepaid_balance_empty is true when your organisation has the prepaid overage on (top up at https://parcelgps.com/app/developer). Only 2xx responses spend quota; 300, 304, 4xx and 5xx are free, and so are 2xx answers without data.
Errors
Every error is a CatastroGPSError with status, code and details:
from catastrogps import (
AmbiguousReferenceError,
CoverageError,
NotFoundError,
PlaceNameError,
QuotaExceededError,
)
try:
client.parcels.get("05102200100005")
except AmbiguousReferenceError as error:
client.parcels.get("05102200100005", country=error.candidates[0]["country"])
except PlaceNameError as error:
print("A place name, not a reference", error.location)
except CoverageError as error:
print(error.country, error.feature, error.supported_countries)
except NotFoundError as error:
print(error.message)
except QuotaExceededError as error:
print("Quota spent", error.quota, error.prepaid_balance_empty)
| Exception | API code / status |
|---|---|
AmbiguousReferenceError |
300 CNV_AMBIGUOUS: bare digits of several countries; candidates lists them |
CoverageError |
422 CNV_COVERAGE: country not covered, or an analysis outside its countries |
PlaceNameError (a ValidationError) |
422 CNV_PLACE_NAME: a place name, with the geocoded location when found |
ValidationError |
400 VALIDATION_ERROR, other 422 |
AuthenticationError / ForbiddenError |
401 KEY_AUTH_001..003, 403 |
NotFoundError |
404 NOT_FOUND |
QuotaExceededError |
429 KEY_AUTH_004 |
RateLimitError |
429 KEY_RATE_002, RATE_LIMIT_EXCEEDED; retry_after |
ServiceUnavailableError |
503 SERVICE_UNAVAILABLE; retry_after |
ServerError, TimeoutError, NetworkError |
5xx, client timeout, network failure |
Timeouts, network errors, per-minute 429s and 502/503/504 are retried up to max_retries times (default 2) with exponential backoff, waiting Retry-After when the API sends it (up to 60 s). An exhausted monthly quota is never retried.
Options
| Argument | Default | |
|---|---|---|
api_key |
CATASTROGPS_API_KEY env var |
Required |
base_url |
https://api.catastrogps.es |
https://api.parcelgps.com is the same API |
timeout |
30.0 seconds |
Official cadastres can be slow |
max_retries |
2 |
|
http_client |
a new httpx.Client |
Inject your own for proxies or tests (httpx.MockTransport) |
Coverage
Measured against production on 1 October 2026. 26 European cadastres plus the Basque Country and Navarre; Croatia, Sweden and the United Kingdom are partial sources, listed for honesty.
| Code | Country / region | Reference | Coordinates | Address | Notes |
|---|---|---|---|---|---|
ES |
Spain | Yes | Yes | Yes | Units per building |
PV · NA |
Basque Country · Navarre | Yes | Yes | With country="ES" |
Foral cadastres; units per building |
FR · IT |
France · Italy | Yes | Yes | Yes | |
DE |
Germany | Partial | Partial | Partial | 15 of 16 Länder: no Bavaria |
PT |
Portugal | Yes | Partial | Partial | The cadastre does not cover Lisbon, Porto or Coimbra |
AT NL BE PL CZ DK NO FI EE |
Austria, Netherlands, Belgium, Poland, Czechia, Denmark, Norway, Finland, Estonia | Yes | Yes | Yes | Belgium is slow (3-15 s) |
CH |
Switzerland | Yes | Partial | Yes | Not in cantons that publish no parcels (e.g. Vaud) |
LT SI SK BG LU LI IS CY |
Lithuania, Slovenia, Slovakia, Bulgaria, Luxembourg, Liechtenstein, Iceland, Cyprus | Yes | Yes | Yes | |
GR · LV |
Greece · Latvia | With the country set | Yes | Yes | |
IE |
Ireland | With country="IE" |
Partial | Partial | Numeric SP_ID |
SE |
Sweden | Agricultural blocks | Agricultural blocks | No | Not property units |
HR |
Croatia | No | ARKOD only | No | Agricultural parcels of the Paying Agency, not the cadastre |
UK |
United Kingdom | No | Scotland | Scotland |
Not covered: Hungary, Romania and the rest (CoverageError). Terrain and ground motion work wherever a parcel outline exists, with lat/lng outside the seven analysis countries. Data comes from each official source, so availability follows theirs.
Pricing
| Plan | Price | Calls / month | Prepaid overage per 1,000 |
|---|---|---|---|
| Free | €0, forever | 250 | €10 |
| Developer | €19 / month | 5,000 | €5 |
| Startup | €49 / month | 15,000 | €4 |
| Growth | €99 / month | 50,000 | €2.50 |
Overage prices are net (VAT apart) and are charged from a prepaid balance you top up in the portal; with the balance at zero the API answers 429, never debt.
The same key works with the JavaScript SDK (npm install catastrogps), the Go SDK and the MCP server for AI agents (catastro-gps-mcp).
License
MIT
Metadata
Release files for catastrogps 1.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| catastrogps-1.3.0.tar.gz | 19.3 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| catastrogps-1.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 35.2 kB
Release files / catastrogps-1.3.0.tar.gz
| Download URL | catastrogps-1.3.0.tar.gz |
|---|---|
| Size | 19.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
faa5d6d087f2bbdfaefe8c2b73f7ce4f2fb26edbeb2940af7b251aa3da02ebe0
|
|
BLAKE2b-256 checksum How to use checksums |
9e81e6bb0094bfa1b99089dd4b65c3ba9ae85f05e5f92392e7c53775220be086
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.4
|
Release files / catastrogps-1.3.0-py3-none-any.whl
| Download URL | catastrogps-1.3.0-py3-none-any.whl |
|---|---|
| Size | 15.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
73cc985d4f14c4d92f3cbbcf9ed81935e1335025ff61a326a3ad915c37e10353
|
|
BLAKE2b-256 checksum How to use checksums |
31df07f68725d410b9a2a34c8d76f9bb1fb4b1bedf84cf672f91bf2414ff4c90
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.4
|
Security for catastrogps 1.3.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.