API reference · v1

API Documentation

One call per stay taxes every night under its own date's rules — per-night rates, guests and a per-authority breakdown included.

  • Base URL: https://yadozei.jp/v1
  • JSON
  • CORS enabled
  • 66 areas
  • Data 2026.10
  • Dates in JST
  • Rates per night, excl. consumption tax
Contents

Quickstart

  1. Start without a key

    Without a key you can make 100 calls a day and 10 a minute from each IP address (everything except POST /v1/resolve and GET /v1/data).

    Keyless and keyed calls alike are subject to our Terms of Service, which form part of the contract.

  2. Make your first request

    2 guests, 2 nights in Tokyo at ¥15,000 per person per night, before consumption tax:

    Request
    cURL
    curl -X POST https://yadozei.jp/v1/calculate \
      -H 'content-type: application/json' \
      -d '{"areaId":"tokyo","ratePerNight":15000,"date":"2026-12-01","nights":2,"guests":2}'
    JavaScript
    const res = await fetch("https://yadozei.jp/v1/calculate", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({
        "areaId": "tokyo",
        "ratePerNight": 15000,
        "date": "2026-12-01",
        "nights": 2,
        "guests": 2
      }),
    });
    const data = await res.json();
    if (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);
    Python
    import requests
    
    res = requests.post(
        "https://yadozei.jp/v1/calculate",
        json={"areaId": "tokyo", "ratePerNight": 15000, "date": "2026-12-01", "nights": 2, "guests": 2},
        timeout=10,
    )
    data = res.json()
    if not res.ok:
        raise RuntimeError(f"{res.status_code} {data['error']['code']}: {data['error']['message']}")
    Response 200
    {
      "areaId": "tokyo",
      "areaName": {
        "en": "Tokyo",
        "ja": "東京都"
      },
      "currency": "JPY",
      "dateUsed": "2026-12-01",
      "basis": "per_person",
      "perNightTax": 200,
      "breakdown": [
        {
          "authority": {
            "en": "Tokyo Metropolitan Government",
            "ja": "東京都"
          },
          "amount": 200,
          "type": "fixed"
        }
      ],
      "nights": 2,
      "guests": 2,
      "nightly": [
        {
          "date": "2026-12-01",
          "ratePerNight": 15000,
          "perNightTax": 200,
          "breakdown": [
            {
              "authority": {
                "en": "Tokyo Metropolitan Government",
                "ja": "東京都"
              },
              "amount": 200,
              "type": "fixed"
            }
          ]
        },
        {
          "date": "2026-12-02",
          "ratePerNight": 15000,
          "perNightTax": 200,
          "breakdown": [
            {
              "authority": {
                "en": "Tokyo Metropolitan Government",
                "ja": "東京都"
              },
              "amount": 200,
              "type": "fixed"
            }
          ]
        }
      ],
      "stayTotal": {
        "perPerson": 400,
        "total": 800,
        "breakdown": [
          {
            "authority": {
              "en": "Tokyo Metropolitan Government",
              "ja": "東京都"
            },
            "amount": 800,
            "type": "fixed"
          }
        ]
      }
    }

    The whole stay's tax is stayTotal.total — ¥800 here.

  3. Get an API key

    Use an API key in production. Contact us with your system's name and expected monthly requests; we issue a key (yz_…) with your quote. Send it in the X-API-Key header.

What the API does not do

  • Exemptions (school trips and the like) are not applied automatically. Leave exempt guests out of guests, or calculate them separately.
  • Guests paying different rates, or occupancy that changes mid-stay, need one call per group.
  • Informational only, not tax advice. Confirm final amounts with each municipality's official source.

Try it

Sends a real request to the live API. No key needed.

Rates

Keyless and keyed calls alike are subject to our Terms of Service, which form part of the contract.

Each send uses one call of your IP's free allowance (100 a day, 10 a minute, shared with your other anonymous calls).

Request
cURL
curl -X POST https://yadozei.jp/v1/calculate \
  -H 'content-type: application/json' \
  -d '{"areaId":"tokyo","ratePerNight":15000,"nights":2,"guests":2}'
JavaScript
const res = await fetch("https://yadozei.jp/v1/calculate", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({
    "areaId": "tokyo",
    "ratePerNight": 15000,
    "nights": 2,
    "guests": 2
  }),
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);
Python
import requests

res = requests.post(
    "https://yadozei.jp/v1/calculate",
    json={"areaId": "tokyo", "ratePerNight": 15000, "nights": 2, "guests": 2},
    timeout=10,
)
data = res.json()
if not res.ok:
    raise RuntimeError(f"{res.status_code} {data['error']['code']}: {data['error']['message']}")

Press “Send request” to see the API's response here.

Authentication

Send your key in the X-API-Key header. Keys start with yz_ and are shown once, when issued.

Request
cURL
curl https://yadozei.jp/v1/areas/tokyo \
  -H "x-api-key: $YADOZEI_API_KEY"
JavaScript
const res = await fetch("https://yadozei.jp/v1/areas/tokyo", {
  headers: { "x-api-key": process.env.YADOZEI_API_KEY },
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);
Python
import os
import requests

res = requests.get(
    "https://yadozei.jp/v1/areas/tokyo",
    headers={"x-api-key": os.environ["YADOZEI_API_KEY"]},
    timeout=10,
)
data = res.json()
if not res.ok:
    raise RuntimeError(f"{res.status_code} {data['error']['code']}: {data['error']['message']}")

Keep your key on your server. Never ship it in browser or mobile code: CORS is open, but a published key lets anyone spend your quota.

  • An unknown or revoked key gets 401 unauthorized — it never falls back to anonymous access.
  • Repeated bad keys are throttled per IP at 10 a minute, then get 429.
  • Plans and prices: see Pricing.

Rate limits

PlanPer minuteQuotaResets
Anonymous (no key; per IP, IPv6 per /64)10100 / dayDaily, 00:00 JST
Standard3010,000 / month1st of the month, 00:00 JST
Pro60100,000 / month1st of the month, 00:00 JST
Vendor601,000,000 / month1st of the month, 00:00 JST
  • One allowance covers every /v1 endpoint. POST /v1/resolve and GET /v1/data need a paid plan.
  • A POST /v1/resolve batch counts as one request, however many addresses it holds.
  • Per-minute limits reset on the clock minute.
  • Every request that passes the key check counts — including ones that then fail validation (400). Requests refused by the daily or monthly quota count too; requests refused by the per-minute limit don't.
  • A key over its quota gets 429 until the reset.
  • Retry-After and error.retryAfter count down to the first limit hit. If you wait and get 429 again, a longer quota is used up — follow that response's value.

Reading your remaining quota

Every response that passed the key check (successes, and the 400, 404 and 413 after it) carries these headers: today's allowance without a key, this month's with one. Per-minute limits aren't included; exceeding one returns 429 with Retry-After.

HeaderMeaning
X-RateLimit-LimitThe quota: requests per day without a key, per month with one.
X-RateLimit-RemainingRequests left in the window, this one counted.
X-RateLimit-ResetSeconds until the window resets: JST midnight, or the 1st of the month for keys.

Errors

Errors share one envelope. Branch on error.code; message is for people.

404
{
  "error": {
    "code": "unknown_area",
    "message": "Unknown area: Kyoto",
    "suggestions": [
      "kyoto"
    ]
  }
}
StatusCodeWhenWhat to do
400invalid_jsonThe body is not valid JSONSend a valid JSON body with content-type: application/json.
400validation_errorThe body or query failed validation; message names the first problemCheck the parameter table's types and ranges. Unknown keys and null values are rejected.
401unauthorizedThe X-API-Key is unknown or revokedCheck the key. A bad key never falls back to anonymous access.
403forbiddenThe endpoint needs a paid-plan keySend a paid-plan key in X-API-Key.
404unknown_area+ suggestionsThe area id does not existPick an id from suggestions (up to 3 candidates) or from GET /v1/areas.
413payload_too_largeThe body is over its limit: 4 KB for /v1/calculate, 128 KB for /v1/resolveDrop extra keys (a full year of nightlyRates fits). Split /v1/resolve addresses across several calls.
429rate_limited+ retryAfterA per-minute limit or the daily/monthly quota was exceededWait Retry-After seconds, then retry. A second 429 means a longer quota is used up.
503unhealthy+ dataVersion/healthz only: the database is not writableA temporary fault; check again shortly.

Responses outside the table

  • CORS preflight (OPTIONS) answers 204.
  • An unsupported method gets 405 with an empty body.
  • An unexpected failure can return a 500 that may not be JSON; retry with backoff.
  • The X-Data-Version header comes with the API's JSON responses, not with these.

Data versions & change feed

Every JSON response carries an X-Data-Version header naming the dataset it used (readable from browser fetch too). Rules are dated and scheduled rules are already in the data, so a future date calculates under the rule that will apply.

Keeping in sync (needs a key)

  1. Store the highest seq you have processed (start from 0).
  2. With your key, call GET /v1/changes?since_seq=<seq>. You get every entry with a greater seq, oldest first.
  3. Refresh each entry's areaIds via GET /v1/areas/{id} (or /v1/data on a paid plan), then store its seq.

Anonymous calls return only the newest 5 matching entries — a preview. Syncing from them will skip changes.

Prefer email? Subscribe to rule-change alerts.

Endpoints

POST /v1/calculate

Key optional#

Calculate the accommodation tax for one stay. Each night is taxed under the rules in force on its own date; you get per-night results, the stay total and a breakdown by taxing authority.

Parameters

NameTypeRequiredDescription
X-API-KeyheaderstringoptionalYour API key. Omit it to call anonymously on the free per-IP allowance.
areaIdbodystringrequiredArea id from GET /v1/areas (e.g. tokyo, kyoto, onna). An unknown id returns 404 unknown_area with suggestions.
ratePerNightbodyintegerone ofTaxable charge for one night in JPY, excluding consumption tax: per person — or per room/unit when the applied basis is per_unit — room only, service charge included. Applied to every night. Never send a tax-inclusive price.0–10,000,000
nightlyRatesbodyinteger[]one ofOne rate per night in JPY, excluding consumption tax, in the same unit as ratePerNight: nightlyRates[0] is the night of the check-in date, nightlyRates[i] the night of date + i days; the number of nights is the array's length. Each night is taxed on its own, so send the real rates, not an average.1–365 items, each 0–10,000,000
datebodystring (YYYY-MM-DD)optionalCheck-in date (the first night), YYYY-MM-DD.2020-01-01 – 2030-12-31 · Default: today in Japan (JST)
nightsbodyintegeroptionalNumber of nights. If you also send nightlyRates, it must equal the array's length.1–365 · Default: 1, or the length of nightlyRates
guestsbodyintegeroptionalNumber of guests. Multiplies the total when the applied basis is per_person.1–100 · Default: 1
basisbodystringoptionalPricing basis. Only read for variable areas (see Pricing basis); ignored everywhere else.per_person | per_unit · Default: per_unit

Unknown keys are rejected. Optional fields may be omitted, but not sent as null. The body may be up to 4 KB.

Request

Request
cURL
curl -X POST https://yadozei.jp/v1/calculate \
  -H 'content-type: application/json' \
  -d '{"areaId":"tokyo","ratePerNight":15000,"date":"2026-12-01","nights":2,"guests":2}'
JavaScript
const res = await fetch("https://yadozei.jp/v1/calculate", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({
    "areaId": "tokyo",
    "ratePerNight": 15000,
    "date": "2026-12-01",
    "nights": 2,
    "guests": 2
  }),
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);
Python
import requests

res = requests.post(
    "https://yadozei.jp/v1/calculate",
    json={"areaId": "tokyo", "ratePerNight": 15000, "date": "2026-12-01", "nights": 2, "guests": 2},
    timeout=10,
)
data = res.json()
if not res.ok:
    raise RuntimeError(f"{res.status_code} {data['error']['code']}: {data['error']['message']}")

Response

200 OK
{
  "areaId": "tokyo",
  "areaName": {
    "en": "Tokyo",
    "ja": "東京都"
  },
  "currency": "JPY",
  "dateUsed": "2026-12-01",
  "basis": "per_person",
  "perNightTax": 200,
  "breakdown": [
    {
      "authority": {
        "en": "Tokyo Metropolitan Government",
        "ja": "東京都"
      },
      "amount": 200,
      "type": "fixed"
    }
  ],
  "nights": 2,
  "guests": 2,
  "nightly": [
    {
      "date": "2026-12-01",
      "ratePerNight": 15000,
      "perNightTax": 200,
      "breakdown": [
        {
          "authority": {
            "en": "Tokyo Metropolitan Government",
            "ja": "東京都"
          },
          "amount": 200,
          "type": "fixed"
        }
      ]
    },
    {
      "date": "2026-12-02",
      "ratePerNight": 15000,
      "perNightTax": 200,
      "breakdown": [
        {
          "authority": {
            "en": "Tokyo Metropolitan Government",
            "ja": "東京都"
          },
          "amount": 200,
          "type": "fixed"
        }
      ]
    }
  ],
  "stayTotal": {
    "perPerson": 400,
    "total": 800,
    "breakdown": [
      {
        "authority": {
          "en": "Tokyo Metropolitan Government",
          "ja": "東京都"
        },
        "amount": 800,
        "type": "fixed"
      }
    ]
  }
}

Response fields

FieldTypeDescription
areaIdstringThe area that was calculated.
areaName{ en, ja }The area's name in English and Japanese.
currency"JPY"Always JPY.
dateUsedYYYY-MM-DDThe check-in date: your date, or today in JST when omitted.
basis"per_person" | "per_unit"The applied basis. Under per_unit, amounts are per room/unit.
nightsintegerNumber of nights.
guestsintegerNumber of guests.
nightly[]arrayOne entry per night: date (that night's date), ratePerNight (that night's pre-tax rate), perNightTax (for one person or unit) and breakdown by authority.
stayTotal.perPersonintegerSum over the nights for one person (one unit under per_unit).
stayTotal.totalintegerThe amount to charge; multiplied by guests under per_person.
stayTotal.breakdown[]arrayThe stay total per taxing authority; sums to total. Its type is the last night's rule type — read nightly[].breakdown for each night's method.
perNightTax, breakdowncompatCompatibility: the first night only. Use stayTotal for the whole stay.

Errors: invalid_jsonvalidation_errorunauthorizedunknown_areapayload_too_largerate_limited

GET /v1/areas

Key optional#

List every covered area: id, names, prefecture, administrative level and tax base.

Parameters

NameTypeRequiredDescription
X-API-KeyheaderstringoptionalYour API key. Omit it to call anonymously on the free per-IP allowance.

Request

Request
cURL
curl https://yadozei.jp/v1/areas
JavaScript
const res = await fetch("https://yadozei.jp/v1/areas");
const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);
Python
import requests

res = requests.get(
    "https://yadozei.jp/v1/areas",
    timeout=10,
)
data = res.json()
if not res.ok:
    raise RuntimeError(f"{res.status_code} {data['error']['code']}: {data['error']['message']}")

Response

200 OK
{
  "areas": [
    {
      "id": "tokyo",
      "name": {
        "en": "Tokyo",
        "ja": "東京都"
      },
      "prefecture": {
        "en": "Tokyo",
        "ja": "東京都"
      },
      "level": "prefecture",
      "taxBase": "per_person"
    },
    {
      "id": "osaka",
      "name": {
        "en": "Osaka",
        "ja": "大阪府"
      },
      "prefecture": {
        "en": "Osaka",
        "ja": "大阪府"
      },
      "level": "prefecture",
      "taxBase": "per_person"
    },
    {
      "id": "kyoto",
      "name": {
        "en": "Kyoto",
        "ja": "京都市"
      },
      "prefecture": {
        "en": "Kyoto",
        "ja": "京都府"
      },
      "level": "city",
      "taxBase": "per_person"
    }
  ]
}
3 of 66 shown

Errors: unauthorizedrate_limited

GET /v1/areas/{id}

Key optional#

Get one area's full rules — effective dates, tiers or rate, cap, threshold — plus exemption categories and the official source.

Parameters

NameTypeRequiredDescription
X-API-KeyheaderstringoptionalYour API key. Omit it to call anonymously on the free per-IP allowance.
idpathstringrequiredArea id.

Request

Request
cURL
curl https://yadozei.jp/v1/areas/tokyo
JavaScript
const res = await fetch("https://yadozei.jp/v1/areas/tokyo");
const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);
Python
import requests

res = requests.get(
    "https://yadozei.jp/v1/areas/tokyo",
    timeout=10,
)
data = res.json()
if not res.ok:
    raise RuntimeError(f"{res.status_code} {data['error']['code']}: {data['error']['message']}")

Response

200 OK
{
  "id": "tokyo",
  "name": {
    "en": "Tokyo",
    "ja": "東京都"
  },
  "prefecture": {
    "en": "Tokyo",
    "ja": "東京都"
  },
  "level": "prefecture",
  "taxBase": "per_person",
  "rules": [
    {
      "authority": {
        "en": "Tokyo Metropolitan Government",
        "ja": "東京都"
      },
      "effectiveFrom": "2002-10-01",
      "effectiveUntil": "2027-03-31",
      "type": "fixed",
      "tiers": [
        {
          "min": 0,
          "max": 9999,
          "amount": 0
        },
        {
          "min": 10000,
          "max": 14999,
          "amount": 100
        },
        {
          "min": 15000,
          "max": null,
          "amount": 200
        }
      ]
    },
    {
      "authority": {
        "en": "Tokyo Metropolitan Government",
        "ja": "東京都"
      },
      "effectiveFrom": "2027-04-01",
      "effectiveUntil": null,
      "type": "percentage",
      "rate": 0.03,
      "cap": null,
      "threshold": 13000
    }
  ],
  "exemptions": [
    "school_trips"
  ],
  "notes": "Revision consented by the Minister of Internal Affairs 2026-06-30, in effect from 2027-04-01: flat 3% of the nightly charge (no cap), exempt threshold raised to stays under 13,000 yen per person per night, and hostels (kan'i-shukusho) / minpaku added to the scope (their operators register as collection agents from 2026-07-01).",
  "source": "https://www.tax.metro.tokyo.lg.jp/kazei/leisure/shuk"
}

Errors: unauthorizedunknown_arearate_limited

GET /v1/resolve

Key optional#

Find a property's tax area (areaId) from its address: its municipality's own tax, the prefecture-wide tax, or no accommodation tax at all — decided from the prefecture and the municipality.

Parameters

NameTypeRequiredDescription
X-API-KeyheaderstringoptionalYour API key. Omit it to call anonymously on the free per-IP allowance.
addressquerystringone ofThe address, starting with the prefecture (e.g. 沖縄県国頭郡恩納村…). A postal code, a leading 日本, full-width characters and spaces are fine.up to 200 characters
prefecturequerystringone ofInstead of address: the prefecture's full name (e.g. 沖縄県), sent with municipality.up to 200 characters
municipalityquerystringone ofInstead of address: the municipality (e.g. 国頭郡恩納村 or 那覇市).up to 200 characters

Send address, or prefecture with municipality — not both. Start the address with the prefecture: without one, only a municipality name that is unique in the data can be matched.

Request

Request
cURL
curl -G https://yadozei.jp/v1/resolve \
  --data-urlencode 'address=沖縄県国頭郡恩納村字恩納2451'
JavaScript
const res = await fetch("https://yadozei.jp/v1/resolve?" + new URLSearchParams({"address":"沖縄県国頭郡恩納村字恩納2451"}));
const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);
Python
import requests

res = requests.get(
    "https://yadozei.jp/v1/resolve",
    params={"address": "沖縄県国頭郡恩納村字恩納2451"},
    timeout=10,
)
data = res.json()
if not res.ok:
    raise RuntimeError(f"{res.status_code} {data['error']['code']}: {data['error']['message']}")

Response

200 OK
{
  "input": "沖縄県国頭郡恩納村字恩納2451",
  "areaId": "onna",
  "match": "municipal",
  "areaName": {
    "ja": "恩納村",
    "en": "Onna"
  },
  "prefecture": "沖縄県",
  "municipality": "恩納村"
}

Response fields

FieldTypeDescription
inputstringThe address as you sent it; for prefecture + municipality, the two joined.
areaIdstring | nullThe area id to send to POST /v1/calculate; null when there is no tax or the address could not be resolved.
match"municipal" | "prefecture" | "no_tax" | "unresolved"How the address matched (table below).
areaName{ en, ja }The area's name; only when areaId is set.
prefecturestringThe prefecture found, in Japanese; absent when none was.
municipalitystring | nullThe matched municipal area's name; null when no municipal area matched.
messagestringunresolved only: what the address is missing.

match values

ValueMeaning
municipalA municipality with its own tax. areaId is that municipality's area, which already includes any prefectural tax charged there (沖縄県国頭郡恩納村 → onna).
prefectureThe prefecture-wide tax: the municipality has no area of its own (沖縄県那覇市 → okinawa_other, 東京都新宿区 → tokyo).
no_taxNo accommodation tax: the prefecture has no prefecture-wide tax and the municipality none of its own (京都府宇治市). areaId is null.
unresolvedCould not tell: no prefecture was found (without one, only a municipality name that is unique in the data resolves), or the municipality is missing where it decides the area. Complete the address as message says.

Errors: validation_errorunauthorizedrate_limited

POST /v1/resolve

Paid key required#

Resolve up to 100 addresses in one call, with results in the order sent. One call counts as one request, however many addresses it carries. Paid plans only.

Parameters

NameTypeRequiredDescription
X-API-KeyheaderstringrequiredA paid-plan API key (required).
addressesbodystring[]requiredThe addresses, each written as for GET's address.1–100 items, each up to 200 characters

Unknown keys are rejected. The body may be up to 128 KB. results come back in the order sent, each shaped like the GET /v1/resolve response.

Request

Request
cURL
curl -X POST https://yadozei.jp/v1/resolve \
  -H 'content-type: application/json' \
  -H "x-api-key: $YADOZEI_API_KEY" \
  -d '{"addresses":["沖縄県国頭郡恩納村字恩納2451","沖縄県那覇市泉崎1-2-2","京都府宇治市宇治琵琶33","那覇市泉崎1-2-2"]}'
JavaScript
const res = await fetch("https://yadozei.jp/v1/resolve", {
  method: "POST",
  headers: { "content-type": "application/json", "x-api-key": process.env.YADOZEI_API_KEY },
  body: JSON.stringify({
    "addresses": [
      "沖縄県国頭郡恩納村字恩納2451",
      "沖縄県那覇市泉崎1-2-2",
      "京都府宇治市宇治琵琶33",
      "那覇市泉崎1-2-2"
    ]
  }),
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);
Python
import os
import requests

res = requests.post(
    "https://yadozei.jp/v1/resolve",
    json={"addresses": ["沖縄県国頭郡恩納村字恩納2451", "沖縄県那覇市泉崎1-2-2", "京都府宇治市宇治琵琶33", "那覇市泉崎1-2-2"]},
    headers={"x-api-key": os.environ["YADOZEI_API_KEY"]},
    timeout=10,
)
data = res.json()
if not res.ok:
    raise RuntimeError(f"{res.status_code} {data['error']['code']}: {data['error']['message']}")

Response

200 OK
{
  "results": [
    {
      "input": "沖縄県国頭郡恩納村字恩納2451",
      "areaId": "onna",
      "match": "municipal",
      "areaName": {
        "ja": "恩納村",
        "en": "Onna"
      },
      "prefecture": "沖縄県",
      "municipality": "恩納村"
    },
    {
      "input": "沖縄県那覇市泉崎1-2-2",
      "areaId": "okinawa_other",
      "match": "prefecture",
      "areaName": {
        "ja": "沖縄県(その他地域)",
        "en": "Okinawa Prefecture (other areas)"
      },
      "prefecture": "沖縄県",
      "municipality": null
    },
    {
      "input": "京都府宇治市宇治琵琶33",
      "areaId": null,
      "match": "no_tax",
      "prefecture": "京都府",
      "municipality": null
    },
    {
      "input": "那覇市泉崎1-2-2",
      "areaId": null,
      "match": "unresolved",
      "message": "Could not identify the prefecture: start the address with it (e.g. 沖縄県那覇市…)."
    }
  ]
}

Errors: invalid_jsonvalidation_errorunauthorizedforbiddenpayload_too_largerate_limited

GET /v1/changes

Key optional#

List dataset changes after since_seq, oldest first. Syncing needs an API key — anonymous callers see only the newest 5.

Parameters

NameTypeRequiredDescription
X-API-KeyheaderstringoptionalYour API key. Omit it to call anonymously on the free per-IP allowance.
since_seqqueryintegeroptionalReturns entries with a seq greater than this. Store the highest seq you have processed and pass it back.Default: 0

Request

Request
cURL
curl 'https://yadozei.jp/v1/changes?since_seq=5' \
  -H "x-api-key: $YADOZEI_API_KEY"
JavaScript
const res = await fetch("https://yadozei.jp/v1/changes?since_seq=5", {
  headers: { "x-api-key": process.env.YADOZEI_API_KEY },
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);
Python
import os
import requests

res = requests.get(
    "https://yadozei.jp/v1/changes",
    params={"since_seq": 5},
    headers={"x-api-key": os.environ["YADOZEI_API_KEY"]},
    timeout=10,
)
data = res.json()
if not res.ok:
    raise RuntimeError(f"{res.status_code} {data['error']['code']}: {data['error']['message']}")

Response

200 OK
{
  "changes": [
    {
      "seq": 6,
      "releasedAt": "2026-09-20",
      "dataVersion": "2026.09",
      "areaIds": [
        "kutchan",
        "niseko"
      ],
      "type": "corrected",
      "summary": {
        "en": "Notes clarified, no rate or date change: in Kutchan, and in Niseko from 2026-11-01, the Hokkaido prefectural tiers legally still apply but the town tax is 3% minus the prefectural amount, collected as one 3% charge by the town. The dataset keeps a single 3% town rule, so the breakdown does not split out the prefectural share.",
        "ja": "注記の明確化(税率・施行日に変更なし):倶知安町、および2026年11月1日以降のニセコ町では道税の定額区分が法的に存続するが、町税=3%-道税額として町が3%を一括徴収する。データは単一の3%町ルールを維持し、内訳は道税分を分離しない。"
      },
      "effectiveFrom": null,
      "sources": [
        "https://www.town.kutchan.hokkaido.jp/town_administration/AccommodationTax/3108/",
        "https://www.pref.hokkaido.lg.jp/kz/kkd/191976.html"
      ]
    }
  ]
}
1 of 3 shown

Errors: validation_errorunauthorizedrate_limited

GET /v1/data

Paid key required#

Get the full dataset — every area with all its rules — and the data version in one call. Paid plans only.

Parameters

NameTypeRequiredDescription
X-API-KeyheaderstringrequiredA paid-plan API key (required).

Request

Request
cURL
curl https://yadozei.jp/v1/data \
  -H "x-api-key: $YADOZEI_API_KEY"
JavaScript
const res = await fetch("https://yadozei.jp/v1/data", {
  headers: { "x-api-key": process.env.YADOZEI_API_KEY },
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);
Python
import os
import requests

res = requests.get(
    "https://yadozei.jp/v1/data",
    headers={"x-api-key": os.environ["YADOZEI_API_KEY"]},
    timeout=10,
)
data = res.json()
if not res.ok:
    raise RuntimeError(f"{res.status_code} {data['error']['code']}: {data['error']['message']}")

Response

200 OK
{
  "dataVersion": "2026.10",
  "areas": [
    {
      "id": "tokyo",
      "name": {
        "en": "Tokyo",
        "ja": "東京都"
      },
      "prefecture": {
        "en": "Tokyo",
        "ja": "東京都"
      },
      "level": "prefecture",
      "taxBase": "per_person",
      "rules": [
        {
          "authority": {
            "en": "Tokyo Metropolitan Government",
            "ja": "東京都"
          },
          "effectiveFrom": "2002-10-01",
          "effectiveUntil": "2027-03-31",
          "type": "fixed",
          "tiers": [
            {
              "min": 0,
              "max": 9999,
              "amount": 0
            },
            {
              "min": 10000,
              "max": 14999,
              "amount": 100
            },
            {
              "min": 15000,
              "max": null,
              "amount": 200
            }
          ]
        },
        {
          "authority": {
            "en": "Tokyo Metropolitan Government",
            "ja": "東京都"
          },
          "effectiveFrom": "2027-04-01",
          "effectiveUntil": null,
          "type": "percentage",
          "rate": 0.03,
          "cap": null,
          "threshold": 13000
        }
      ],
      "exemptions": [
        "school_trips"
      ],
      "notes": "Revision consented by the Minister of Internal Affairs 2026-06-30, in effect from 2027-04-01: flat 3% of the nightly charge (no cap), exempt threshold raised to stays under 13,000 yen per person per night, and hostels (kan'i-shukusho) / minpaku added to the scope (their operators register as collection agents from 2026-07-01).",
      "source": "https://www.tax.metro.tokyo.lg.jp/kazei/leisure/shuk"
    }
  ]
}
1 of 66 shown

Errors: unauthorizedforbiddenrate_limited

GET /healthz

No key#

Service health and the data version. Not rate-limited, so use it for monitoring.

Request

Request
cURL
curl https://yadozei.jp/healthz
JavaScript
const res = await fetch("https://yadozei.jp/healthz");
const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);
Python
import requests

res = requests.get(
    "https://yadozei.jp/healthz",
    timeout=10,
)
data = res.json()
if not res.ok:
    raise RuntimeError(f"{res.status_code} {data['error']['code']}: {data['error']['message']}")

Response

200 OK
{
  "status": "ok",
  "dataVersion": "2026.10",
  "db": "ok"
}

Errors: unhealthy

Concepts

Rates: per night, before consumption tax

  • Rates are per night. ratePerNight applies to every night; nightlyRates lists one rate per date from the first night on (nightlyRates[0] is the night of the check-in date, nightlyRates[i] the night of date + i days). Never send a reservation total or an average.
  • Rates exclude Japanese consumption tax (the 10% national and local consumption tax). Leave out meals (room only) and include any service charge.
  • The unit is one person, or one room/unit when per_unit applies (see Pricing basis).

Sending a tax-inclusive price: Kyoto, one night

Rate sentTax
¥19,000 (pre-tax, correct)¥400
¥20,900 (tax-inclusive, wrong)¥1,000

A tax-inclusive price can push a night into a higher tier and overstate the tax. If you only have tax-inclusive prices, back the tax out in integer arithmetic, rounding the way it was added. Float division can land a yen short: Math.floor(6600 / 1.1) is 5,999, which turns a ¥400 night in Kyoto into ¥200.

Night-by-night taxation

Accommodation tax is levied per person per night, so the API taxes each night under the rules in force on its own date and adds them up. A stay across a tax start date or a rate change comes out right in one call.

Across a tax start: Onna, 4 nights from 2027-01-30

NightDateRate (excl. tax)Tax
Night 1¥10,500¥0
Night 2¥10,500¥0
Night 3¥10,500¥200
Night 4¥10,500¥200

The tax starts with the night of 2027-02-01, so the earlier nights are ¥0 — ¥400 in total.

When the rate varies by night: nightlyRates

Request
cURL
curl -X POST https://yadozei.jp/v1/calculate \
  -H 'content-type: application/json' \
  -d '{"areaId":"kyoto","nightlyRates":[18000,22000,51000],"date":"2026-12-01"}'
JavaScript
const res = await fetch("https://yadozei.jp/v1/calculate", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({
    "areaId": "kyoto",
    "nightlyRates": [
      18000,
      22000,
      51000
    ],
    "date": "2026-12-01"
  }),
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);
Python
import requests

res = requests.post(
    "https://yadozei.jp/v1/calculate",
    json={"areaId": "kyoto", "nightlyRates": [18000, 22000, 51000], "date": "2026-12-01"},
    timeout=10,
)
data = res.json()
if not res.ok:
    raise RuntimeError(f"{res.status_code} {data['error']['code']}: {data['error']['message']}")
NightDateRate (excl. tax)Tax
Night 1¥18,000¥400
Night 2¥22,000¥1,000
Night 3¥51,000¥4,000

Each night is taxed in its own rate's tier: ¥5,400 in total. Sending the average (¥30,333) as ratePerNight would give ¥3,000 — which is wrong.

When the rules change mid-stay: Tokyo, 2 nights from 2027-03-31

NightDateRate (excl. tax)MethodTax
Night 1¥20,000fixed¥200
Night 2¥20,000percentage¥600

The method changes by night (fixed → percentage). stayTotal.breakdown's type shows the last night's method, so read nightly[].breakdown for each night.

Pricing basis

Each area's taxBase sets the unit of rates and tax. When per_unit applies, the rate fields and stayTotal.perPerson mean per room/unit.

taxBaseMeaningAreas
per_personTaxed per person per night; the total is multiplied by guests.64 areas
per_unitTaxed per room/unit; guests are not multiplied, and rates are per room/unit.none currently
variableDepends on how the facility prices; pass basis as per_person or per_unit (default per_unit).Kutchan, Yamagata City

Dates and JST

  • All dates are calendar dates in Japan. Omitting date means today in JST — not UTC.
  • The first night is date, the check-in; night n falls on date + (n − 1) days.
  • Accepted check-in dates run from 2020-01-01 to 2030-12-31; later nights may run past it.
  • Each area's data starts at its earliest recorded rule (effectiveFrom in /v1/areas/{id}). A night with no rule returns ¥0 and an empty breakdown — for past dates that does not prove no tax was due.
  • Quota resets are in JST too.

Ready to integrate?

Tell us your system and expected monthly requests — we'll send a quote and your API key.