Jurisdictions & geocoding

Every tax in TaxLens hangs off a place. Places form a tree, each has a hierarchical code, and a stay is resolved to one node — either directly by code or by reverse-geocoding coordinates. Learn how the tree is shaped and how a location becomes a tax answer.

What a jurisdiction is

A jurisdiction is any authority that can levy an accommodation tax: a country, a state or province, a county, a city, a borough, or a special district (a tourism improvement district, a convention-center zone). TaxLens stores them as a single global tree — the same tree serves every organization, because the law is the same no matter who is asking.

Each node carries a hierarchical, dash-delimited code that encodes its place in the tree:

  • NL — the Netherlands. A country sits at the root.
  • US-NY-NYC — New York City, inside New York State (US-NY), inside the United States (US).
  • ES-CT-BCN — Barcelona, inside Catalonia (ES-CT), inside Spain (ES).
  • US-NY-NYC-MAN — Manhattan, a sub-city node below New York City.

The code is not cosmetic: it is how rates and rules target a place, and how the engine knows which nodes are ancestors of which. ES-CT-BCN is unambiguously a descendant of ES-CT and ES.

The hierarchy

Because taxes stack additively (see Layered & additive stacking), the tree is the backbone of every calculation. A stay at a leaf inherits every tax authored at any ancestor above it.

A slice of the global jurisdiction tree
USUS-NYUS-TXUS-NY-NYCUS-TX-FTWESES-CTES-CT-BCN
Each node can carry its own tax layers; a stay inherits the whole chain above it.

Ancestor chains

When you calculate for a leaf, the first thing the engine does is resolve its ancestor chain — the ordered path from the country down to the node you asked for. For a Barcelona stay that is [ES, ES-CT, ES-CT-BCN]. Every rate and rule attached to any node on that chain is then a candidate to fire.

You can inspect a chain yourself. This returns the ordered ancestors for a code — exactly the chain the engine walks:

GET/v1/jurisdictions/ES-CT-BCN/ancestors
Sign in to run

The ordered ancestor chain TaxLens walks when calculating tax for Barcelona.

Sign in to run this against the live API. Read-only — nothing is saved.

And this resolves every active rate in force on that chain today — the raw layers, before any booking-specific rule fires:

GET/v1/jurisdictions/ES-CT-BCN/effective-rates
Sign in to run

Every rate effective on the Barcelona chain right now — country VAT plus regional and municipal tourist layers.

Sign in to run this against the live API. Read-only — nothing is saved.

Detail
Rates are time-aware. The chain is fixed by geography, but which rates on it are active depends on the stay date — a future rate change already on file fires for a future stay, and an expired rate does not.

How a location resolves

You give the engine a location in one of two ways. If you already know the node, pass jurisdiction_code directly — no geocoding, instant resolution. If you only have coordinates, pass lat and lng and TaxLens reverse-geocodes them to the most specific matching node.

{
  "jurisdiction_code": "ES-CT-BCN",
  "stay_date": "2026-07-01",
  "nights": 3,
  "nightly_rate": 200,
  "currency": "EUR"
}

For managed properties you rarely do either by hand — a property stores its resolved jurisdiction once at setup, and bookings reference the property. See Add your first property.

Geocoding: coordinates to a node

Reverse-geocoding is a pluggable provider chain. The primary provider is LocationIQ; an always-available open fallback (Nominatim) covers gaps. The fallback fires when the primary returns nothing or a partial answer — a response that is "truthy" but missing a country code still counts as incomplete and falls through, so a half-answer never silently wins.

Reverse-geocode resolution
lat / lnginputLocationIQ (primary)Nominatim (fallback)field walk (most specific →)district→suburb→city→county→state→countryfilter by jurisdiction_typea "city" candidate can't match a state noderesolved node + confidenceUS-NY-NYC-MAN
Provider chain, then a field-by-field walk from the most specific level down to the country.

Once a provider answers, TaxLens walks the result from the most specific level down — city_district → suburb → city → county → state_district → state → subdivision → country — and at each level matches against the tree, filtering by the expected jurisdiction type. That filter is what stops a state-level "New York" candidate from accidentally matching a city named the same. The first valid match wins, and resolution stops there.

Failed lookups are never cached
A transient geocoder outage returns nothing — and TaxLens does not cache that miss. Caching a failure would poison the location forever; instead the next request retries.

Aliases and confidence

Geocoders speak their own place names, which don't always match our internal codes. Each jurisdiction can carry aliases in its metadata that bridge provider names to our tree — for example a provider's "Manhattan" maps to US-NY-NYC-MAN. Aliases are how a free-form provider label becomes a precise internal node.

Every resolved location also carries a confidence signal and a mapping status, so you can tell a clean, high-confidence match from one that landed on a coarser ancestor (e.g. resolved to the country but not the city). In the dashboard this surfaces on each property so you can review low-confidence mappings before they drive live bookings — see Properties.

Tip
When confidence is low or the mapping landed too high in the tree, pass an explicit jurisdiction_code instead of coordinates. A known code is always the most reliable input.

Where to go next

Now that a location resolves to a chain, see how the layers on that chain combine in Layered & additive stacking, what kinds of tax each layer can be in Tax categories, and how to browse the tree visually in the Jurisdictions browser and Coverage map.