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
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/resolveandGET /v1/data).Keyless and keyed calls alike are subject to our Terms of Service, which form part of the contract.
Make your first request
2 guests, 2 nights in Tokyo at ¥15,000 per person per night, before consumption tax:
cURLcurl -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}'JavaScriptconst 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}`);Pythonimport 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']}"){ "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.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 theX-API-Keyheader.
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.
curl -X POST https://yadozei.jp/v1/calculate \
-H 'content-type: application/json' \
-d '{"areaId":"tokyo","ratePerNight":15000,"nights":2,"guests":2}'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}`);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.
curl https://yadozei.jp/v1/areas/tokyo \
-H "x-api-key: $YADOZEI_API_KEY"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}`);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
| Plan | Per minute | Quota | Resets |
|---|---|---|---|
| Anonymous (no key; per IP, IPv6 per /64) | 10 | 100 / day | Daily, 00:00 JST |
| Standard | 30 | 10,000 / month | 1st of the month, 00:00 JST |
| Pro | 60 | 100,000 / month | 1st of the month, 00:00 JST |
| Vendor | 60 | 1,000,000 / month | 1st of the month, 00:00 JST |
- One allowance covers every
/v1endpoint.POST /v1/resolveandGET /v1/dataneed a paid plan. - A
POST /v1/resolvebatch 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-Afteranderror.retryAftercount 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.
| Header | Meaning |
|---|---|
X-RateLimit-Limit | The quota: requests per day without a key, per month with one. |
X-RateLimit-Remaining | Requests left in the window, this one counted. |
X-RateLimit-Reset | Seconds 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.
{
"error": {
"code": "unknown_area",
"message": "Unknown area: Kyoto",
"suggestions": [
"kyoto"
]
}
}| Status | Code | When | What to do |
|---|---|---|---|
| 400 | invalid_json | The body is not valid JSON | Send a valid JSON body with content-type: application/json. |
| 400 | validation_error | The body or query failed validation; message names the first problem | Check the parameter table's types and ranges. Unknown keys and null values are rejected. |
| 401 | unauthorized | The X-API-Key is unknown or revoked | Check the key. A bad key never falls back to anonymous access. |
| 403 | forbidden | The endpoint needs a paid-plan key | Send a paid-plan key in X-API-Key. |
| 404 | unknown_area | The area id does not exist | Pick an id from suggestions (up to 3 candidates) or from GET /v1/areas. |
| 413 | payload_too_large | The body is over its limit: 4 KB for /v1/calculate, 128 KB for /v1/resolve | Drop extra keys (a full year of nightlyRates fits). Split /v1/resolve addresses across several calls. |
| 429 | rate_limited | A per-minute limit or the daily/monthly quota was exceeded | Wait Retry-After seconds, then retry. A second 429 means a longer quota is used up. |
| 503 | unhealthy | /healthz only: the database is not writable | A temporary fault; check again shortly. |
Responses outside the table
- CORS preflight (
OPTIONS) answers204. - An unsupported method gets
405with an empty body. - An unexpected failure can return a
500that may not be JSON; retry with backoff. - The
X-Data-Versionheader 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)
- Store the highest
seqyou have processed (start from 0). - With your key, call
GET /v1/changes?since_seq=<seq>. You get every entry with a greaterseq, oldest first. - Refresh each entry's
areaIdsviaGET /v1/areas/{id}(or/v1/dataon a paid plan), then store itsseq.
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
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
| Name | Type | Required | Description |
|---|---|---|---|
X-API-Keyheader | string | optional | Your API key. Omit it to call anonymously on the free per-IP allowance. |
areaIdbody | string | required | Area id from GET /v1/areas (e.g. tokyo, kyoto, onna). An unknown id returns 404 unknown_area with suggestions. |
ratePerNightbody | integer | one of | Taxable 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. |
nightlyRatesbody | integer[] | one of | One 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. |
datebody | string (YYYY-MM-DD) | optional | Check-in date (the first night), YYYY-MM-DD. |
nightsbody | integer | optional | Number of nights. If you also send nightlyRates, it must equal the array's length. |
guestsbody | integer | optional | Number of guests. Multiplies the total when the applied basis is per_person. |
basisbody | string | optional | Pricing basis. Only read for variable areas (see Pricing basis); ignored everywhere else. |
Unknown keys are rejected. Optional fields may be omitted, but not sent as null. The body may be up to 4 KB.
Request
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}'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}`);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
{
"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
| Field | Type | Description |
|---|---|---|
areaId | string | The area that was calculated. |
areaName | { en, ja } | The area's name in English and Japanese. |
currency | "JPY" | Always JPY. |
dateUsed | YYYY-MM-DD | The 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. |
nights | integer | Number of nights. |
guests | integer | Number of guests. |
nightly[] | array | One 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.perPerson | integer | Sum over the nights for one person (one unit under per_unit). |
stayTotal.total | integer | The amount to charge; multiplied by guests under per_person. |
stayTotal.breakdown[] | array | The 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, breakdown | compat | Compatibility: the first night only. Use stayTotal for the whole stay. |
Errors: invalid_jsonvalidation_errorunauthorizedunknown_areapayload_too_largerate_limited
List every covered area: id, names, prefecture, administrative level and tax base.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
X-API-Keyheader | string | optional | Your API key. Omit it to call anonymously on the free per-IP allowance. |
Request
curl https://yadozei.jp/v1/areasconst 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}`);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
{
"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"
}
]
}Errors: unauthorizedrate_limited
Get one area's full rules — effective dates, tiers or rate, cap, threshold — plus exemption categories and the official source.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
X-API-Keyheader | string | optional | Your API key. Omit it to call anonymously on the free per-IP allowance. |
idpath | string | required | Area id. |
Request
curl https://yadozei.jp/v1/areas/tokyoconst 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}`);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
{
"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"
}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
| Name | Type | Required | Description |
|---|---|---|---|
X-API-Keyheader | string | optional | Your API key. Omit it to call anonymously on the free per-IP allowance. |
addressquery | string | one of | The address, starting with the prefecture (e.g. 沖縄県国頭郡恩納村…). A postal code, a leading 日本, full-width characters and spaces are fine. |
prefecturequery | string | one of | Instead of address: the prefecture's full name (e.g. 沖縄県), sent with municipality. |
municipalityquery | string | one of | Instead of address: the municipality (e.g. 国頭郡恩納村 or 那覇市). |
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
curl -G https://yadozei.jp/v1/resolve \
--data-urlencode 'address=沖縄県国頭郡恩納村字恩納2451'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}`);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
{
"input": "沖縄県国頭郡恩納村字恩納2451",
"areaId": "onna",
"match": "municipal",
"areaName": {
"ja": "恩納村",
"en": "Onna"
},
"prefecture": "沖縄県",
"municipality": "恩納村"
}Response fields
| Field | Type | Description |
|---|---|---|
input | string | The address as you sent it; for prefecture + municipality, the two joined. |
areaId | string | null | The 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. |
prefecture | string | The prefecture found, in Japanese; absent when none was. |
municipality | string | null | The matched municipal area's name; null when no municipal area matched. |
message | string | unresolved only: what the address is missing. |
match values
| Value | Meaning |
|---|---|
municipal | A municipality with its own tax. areaId is that municipality's area, which already includes any prefectural tax charged there (沖縄県国頭郡恩納村 → onna). |
prefecture | The prefecture-wide tax: the municipality has no area of its own (沖縄県那覇市 → okinawa_other, 東京都新宿区 → tokyo). |
no_tax | No accommodation tax: the prefecture has no prefecture-wide tax and the municipality none of its own (京都府宇治市). areaId is null. |
unresolved | Could 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. |
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
| Name | Type | Required | Description |
|---|---|---|---|
X-API-Keyheader | string | required | A paid-plan API key (required). |
addressesbody | string[] | required | The addresses, each written as for GET's address. |
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
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"]}'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}`);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
{
"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
List dataset changes after since_seq, oldest first. Syncing needs an API key — anonymous callers see only the newest 5.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
X-API-Keyheader | string | optional | Your API key. Omit it to call anonymously on the free per-IP allowance. |
since_seqquery | integer | optional | Returns entries with a seq greater than this. Store the highest seq you have processed and pass it back. |
Request
curl 'https://yadozei.jp/v1/changes?since_seq=5' \
-H "x-api-key: $YADOZEI_API_KEY"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}`);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
{
"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"
]
}
]
}Get the full dataset — every area with all its rules — and the data version in one call. Paid plans only.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
X-API-Keyheader | string | required | A paid-plan API key (required). |
Request
curl https://yadozei.jp/v1/data \
-H "x-api-key: $YADOZEI_API_KEY"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}`);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
{
"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"
}
]
}Service health and the data version. Not rate-limited, so use it for monitoring.
Request
curl https://yadozei.jp/healthzconst 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}`);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
{
"status": "ok",
"dataVersion": "2026.10",
"db": "ok"
}Errors: unhealthy
Concepts
Rates: per night, before consumption tax
- Rates are per night.
ratePerNightapplies to every night;nightlyRateslists one rate per date from the first night on (nightlyRates[0]is the night of the check-indate,nightlyRates[i]the night ofdate+ 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_unitapplies (see Pricing basis).
Sending a tax-inclusive price: Kyoto, one night
| Rate sent | Tax |
|---|---|
| ¥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
| Night | Date | Rate (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
curl -X POST https://yadozei.jp/v1/calculate \
-H 'content-type: application/json' \
-d '{"areaId":"kyoto","nightlyRates":[18000,22000,51000],"date":"2026-12-01"}'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}`);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']}")| Night | Date | Rate (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
| Night | Date | Rate (excl. tax) | Method | Tax |
|---|---|---|---|---|
| Night 1 | ¥20,000 | fixed | ¥200 | |
| Night 2 | ¥20,000 | percentage | ¥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.
| taxBase | Meaning | Areas |
|---|---|---|
per_person | Taxed per person per night; the total is multiplied by guests. | 64 areas |
per_unit | Taxed per room/unit; guests are not multiplied, and rates are per room/unit. | none currently |
variable | Depends 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
datemeans today in JST — not UTC. - The first night is
date, the check-in; night n falls ondate+ (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 (
effectiveFromin/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.