Skip to content
Octroi

Resolve a place

Name a place by jurisdiction id or LAU code, and get the charges in force there on a date, with their rates, exemptions and evidence.

resolve(release, place, on) says which charges apply to a place on a date and what their rates are. It takes no stay and no money: use it to show a hotel its taxes, or to check a place before you quote.

import { resolve } from "./calculator";

const answer = resolve(
  release,
  { lau: { country: "es", code: "08019", year: 2024 }, type: "hotel", class: "4" },
  "2026-11-12",
);

quote takes the same place, so everything on this page about places holds for quotes too.

The place

A place has one of two forms.

By jurisdiction id

FieldTypeMeaning
jurisdictionstringA jurisdiction id from the country file, such as es.barcelona. Its first two letters are the country.
typestring, optionalThe property type, from the place's classification, such as hotel.
classstring, optionalThe property class within that type, such as 4.

By LAU code

LAU codes are Eurostat's codes for local administrative units, roughly one per municipality. Use them when your own data already has them.

FieldTypeMeaning
lau.countrystringThe country id, such as es.
lau.codestringThe LAU code, such as 08019 for Barcelona. Up to 20 letters, digits, dots, underscores or hyphens.
lau.yearintegerThe LAU edition the code is from. It must be the country's LAU year, which the manifest gives per country as lauYear: 2024 in every country today.
withinstring, optionalA jurisdiction id the place is in. See Codes the release doesn't list.
type, classstring, optionalAs above.

Type and class

Some rates depend on the kind of property. The most local jurisdiction in the place's chain that has a classification sets the types and classes the place can take. A type or class it doesn't have throws type_not_in_classification or class_not_in_classification.

Leaving them out is not an error. A charge whose rate depends on them comes back as an unknown, type_unknown or class_unknown, and the rest is still answered.

Codes the release doesn't list

A country file lists its municipalities with their LAU codes. A listed code gives that municipality, and within, if given, must be in its chain.

An unlisted code is a municipality the release has nothing on. The calculator places it at within when you give one, which must then be a country or a region, or at the country otherwise. Charges levied by the country or that region still apply. Each level that levies charges in the country and has nothing researched in the chain comes back as a jurisdiction_not_researched unknown. Madrid, 28079, is unlisted today; Coverage and unknowns shows its quote.

The answer

Top level

FieldMeaning
releaseThe id of the release the answer comes from.
onThe date asked about.
place.jurisdictionsThe place's chain, from the country down: regions, any associations that hold the municipality, the municipality, a zone.
place.type, place.classThe type and class, when the request gave them.
chargesThe charge versions in force on on, ordered by charge id.
upcomingThe versions that come into force after on, ordered by charge id, then date.
unknownsWhat the release can't say for this place on that date. See Coverage and unknowns.

A charge

FieldMeaning
charge, versionThe charge id and the version: the date it came into force.
kind, name, nameEnAs on a quote line.
validFrom, validToThe dates the version is in force. No validTo means no end date is known.
ratesThe rates that match the place, each with its amount, its exemptions and, when it holds only part of the year, its season.
inPriceWhether the law counts the charge as part of the room price.
vatOnChargeBpsVAT on the charge itself, in basis points, where the law puts VAT on it.
roundingHow the law rounds the charge, where it says.
payer, collectedByAs on a quote line.
evidenceThe ids of the sources behind the version and its rates.

amount.basis says how the rate is levied: person_night with amountMinor, for example, is an amount per liable guest per night. A rate the release has found but can't price has the basis unpriced, and the answer lists it in unknowns as unpriced_rule.

Barcelona, by LAU code

The request at the top of this page. The answer, trimmed: the first charge is cut and upcoming is shortened to each version's charge, start and end date.

{
  "release": "2026-10-11",
  "on": "2026-11-12",
  "place": {
    "jurisdictions": ["es", "es.catalunya", "es.barcelona"],
    "type": "hotel",
    "class": "4"
  },
  "charges": [
    {
      "charge": "es.catalunya.tourist-tax",
      "version": "2026-04-01",
      "kind": "city_tax",
      "name": "Impost sobre les estades en establiments turístics",
      "nameEn": "Tax on stays in tourist establishments",
      "validFrom": "2026-04-01",
      "validTo": "2027-03-31",
      "rates": [
        {
          "amount": { "basis": "person_night", "amountMinor": 340 },
          "exemptions": [
            {
              "id": "aged_16_or_under",
              "kind": "age_band",
              "maxAge": 16,
              "factorBps": 0,
              "evidence": ["ev_ac687c6c38ae608e"]
            },
            {
              "id": "after_7_nights",
              "kind": "night_cap",
              "limit": { "nights": 7 },
              "scope": "stay",
              "factorBps": 0,
              "evidence": ["ev_67e0918d37a6bdb0", "ev_e79e909f95bd4a78"]
            }
          ]
        }
      ],
      "inPrice": false,
      "payer": "guest",
      "collectedBy": ["operator"],
      "evidence": ["ev_3e52b0f296a9686f", "ev_8616466e3a257bc8", "ev_0b8eb3499237089f", "ev_c873a3f06bb483bd"]
    }
  ],
  "upcoming": [
    ["es.catalunya.municipal-surcharge", "2027-04-01", "2028-03-31"],
    ["es.catalunya.municipal-surcharge", "2028-04-01", "2029-03-31"],
    ["es.catalunya.municipal-surcharge", "2029-04-01", null],
    ["es.catalunya.tourist-tax", "2027-04-01", null]
  ],
  "unknowns": []
}

The first charge, cut above, is Barcelona's municipal surcharge, es.catalunya.municipal-surcharge: €5.00 a person a night, with the same two exemptions. In the real answer each upcoming entry is a full charge, like those in charges.

Errors

resolve throws a TaxDataError as quote does, with a code and a pointer:

CodeMeaning
request_invalidThe place or the date doesn't match the schema.
country_unknownThe place's country isn't in the loaded release.
jurisdiction_unknownNo such jurisdiction in the country, for jurisdiction or for within.
lau_year_mismatchlau.year isn't the country's LAU year.
place_within_mismatchwithin is in another country, isn't in the place's chain, or, for an unlisted code, isn't a country or region.
type_not_in_classificationThe type isn't one the place's classification has.
class_not_in_classificationThe class isn't one the place's classification has for that type.

A LAU year of 2021 for a Spanish code, for example, throws lau_year_mismatch at /place/lau/year.

On this page