{"openapi":"3.1.0","info":{"title":"lexrates","version":"1.0.0","summary":"Statutory mileage and allowance figures, with their sources and expiry dates.","description":"Every figure carries the authority that published it, the URL it was read from, the day a\nhuman last verified it, and the day it must be re-read.\n\n**Two time axes.** `on` asks what *applied* on a date. `knownOn` asks what was *published*\nby a date. They differ in both directions: New Zealand’s 2025-26 rates came into force on\n4 June 2026, fourteen months into the year they govern, while Switzerland published its\n2026 figures on 10 September 2025, four months early. A payroll engine reproducing an\nearlier run needs both.\n\n**Tiers.** The open tier is this corpus as it was known and in force twelve months ago:\nany `on` up to that date answers with no key. A date inside the last twelve months, or a\n`knownOn` after the cut, answers 402. For a figure that has not moved the year-old answer\nis also the current one — what the key buys is the confirmation that it has not moved,\nand the alert for when it does.","contact":{"name":"lexrates","email":"info@growingpeas.dev"},"license":{"name":"CC BY 4.0","identifier":"CC-BY-4.0"}},"servers":[{"url":"https://api.lexrates.com","description":"Production"}],"security":[{},{"bearer":[]},{"apiKey":[]}],"tags":[{"name":"catalogue","description":"What exists, and when each figure expires."},{"name":"figures","description":"The periods themselves."},{"name":"compute","description":"Apply a regime to a distance on a date."}],"paths":{"/v1":{"get":{"tags":["catalogue"],"summary":"Service index","description":"Coverage, tier definitions and the list of endpoints.","responses":{"200":{"description":"The index.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceIndex"}}}},"401":{"description":"A credential was presented and cannot be read or is not valid. Omitting it entirely is the open tier; sending an unreadable one is never treated as an anonymous request.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"405":{"description":"The path is right and the method is not. Every endpoint answers GET (and HEAD) only.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/catalogue":{"get":{"tags":["catalogue"],"summary":"Every regime of every domain, with days remaining before its figures expire","parameters":[{"name":"domain","in":"query","description":"Restrict to one domain. Absent, every domain is listed.","schema":{"type":"string","enum":["mileage","per-diem"]}},{"name":"format","in":"query","description":"Response format. `csv` is offered only where the payload is genuinely a table — this route and `/v1/changes`. Everywhere else it is a 400: a statutory figure can be a flat rate, marginal bands, a bracket formula with a fixed part or a pair of day rates across hundreds of destinations, and flattening one into a column is the error this corpus exists to prevent. It is a query parameter and not content negotiation, so a shared cache keys on it without needing `Vary: Accept`.","schema":{"type":"string","enum":["json","csv"]}}],"responses":{"200":{"description":"One row per regime.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CatalogueRow"}}},"text/csv":{"schema":{"type":"string"},"example":"id,domain,country,name,…\r\n"}}},"401":{"description":"A credential was presented and cannot be read or is not valid. Omitting it entirely is the open tier; sending an unreadable one is never treated as an anonymous request.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"405":{"description":"The path is right and the method is not. Every endpoint answers GET (and HEAD) only.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/regimes/{id}":{"get":{"tags":["figures"],"summary":"One regime and its periods","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","enum":["at-kilometergeld","au-ato-cents-per-km","be-indemnite-kilometrique-annuelle","be-indemnite-kilometrique-bosa","be-indemnite-velo","ca-cra-automobile-allowance","ch-berufskosten-fahrzeug","de-entfernungspauschale","de-kilometerpauschale","es-kilometraje-exento","fr-bareme-kilometrique","gb-amap","ie-civil-service-motor-travel","it-rimborso-chilometrico","nl-aftrek-privevervoermiddel","nl-onbelaste-reiskostenvergoeding","nz-ird-kilometre-rates","pt-viatura-propria","us-irs-business","us-irs-charitable","us-irs-medical","us-irs-moving","de-auslandstagegelder","de-verpflegungsmehraufwand"]}},{"$ref":"#/components/parameters/tier"}],"responses":{"200":{"description":"The regime, carrying only the periods of the tier asked for, plus `tier` and `withheldEntries` — how many periods the other tier holds. A `withheldEntries` of 0 on the open tier means the free answer is the whole regime, not a sample.","headers":{"Link":{"description":"On an **anonymous** history answer, the published file carrying the same bytes: `rel=\"alternate\"`. Prefer it — no key, no ceiling, cached. Absent on `tier=current`, and absent for a key holder: their answer is not redacted the way the file is, so the file would hand them `null` where they hold a real end date.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Regime"}}}},"401":{"description":"A credential was presented and cannot be read or is not valid. Omitting it entirely is the open tier; sending an unreadable one is never treated as an anonymous request.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"`tier=current` was asked for without a key.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No such regime.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"405":{"description":"The path is right and the method is not. Every endpoint answers GET (and HEAD) only.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/countries/{code}":{"get":{"tags":["figures"],"summary":"Every regime published in one country","description":"Several regimes can coexist in one country, and which applies is not always the reader’s choice — Belgium publishes two, and the employer picks.","parameters":[{"name":"code","in":"path","required":true,"description":"ISO 3166-1 alpha-2, case-insensitive.","schema":{"type":"string","enum":["AT","AU","BE","CA","CH","DE","ES","FR","GB","IE","IT","NL","NZ","PT","US"]}},{"$ref":"#/components/parameters/tier"}],"responses":{"200":{"description":"The regimes of that country, each split to the tier asked for.","headers":{"Link":{"description":"On an **anonymous** history answer, the published file carrying the same bytes: `rel=\"alternate\"`. Prefer it — no key, no ceiling, cached. Absent on `tier=current`, and absent for a key holder: their answer is not redacted the way the file is, so the file would hand them `null` where they hold a real end date.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Regime"}}}}},"401":{"description":"A credential was presented and cannot be read or is not valid. Omitting it entirely is the open tier; sending an unreadable one is never treated as an anonymous request.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"`tier=current` was asked for without a key.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No regime is published for that country.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"405":{"description":"The path is right and the method is not. Every endpoint answers GET (and HEAD) only.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/compute":{"get":{"tags":["compute"],"summary":"Apply a regime to a distance on a date","description":"Returns the amount and the arithmetic that produced it, step by step, together with the sources behind the figures used.","parameters":[{"name":"regime","in":"query","required":true,"schema":{"type":"string","enum":["at-kilometergeld","au-ato-cents-per-km","be-indemnite-kilometrique-annuelle","be-indemnite-kilometrique-bosa","be-indemnite-velo","ca-cra-automobile-allowance","ch-berufskosten-fahrzeug","de-entfernungspauschale","de-kilometerpauschale","es-kilometraje-exento","fr-bareme-kilometrique","gb-amap","ie-civil-service-motor-travel","it-rimborso-chilometrico","nl-aftrek-privevervoermiddel","nl-onbelaste-reiskostenvergoeding","nz-ird-kilometre-rates","pt-viatura-propria","us-irs-business","us-irs-charitable","us-irs-medical","us-irs-moving"]}},{"name":"vehicleClass","in":"query","required":true,"description":"A class published by that regime. See `/v1/catalogue`.","schema":{"type":"string","enum":["any","bicycle","car","car-3cv","car-4cv","car-5cv","car-6cv","car-7cv-plus","car-van","diesel","electric","engine-1201-1500","engine-1501-plus","engine-up-to-1200","hybrid","moped","motorcycle","motorcycle-1-2cv","motorcycle-151-250","motorcycle-251-600","motorcycle-3-5cv","motorcycle-5cv-plus","motorcycle-601-plus","motorcycle-up-to-150","motorcycle-white-plate","other","own-car","petrol"]}},{"name":"units","in":"query","required":true,"description":"Distance, in the regime’s own unit. Never converted for you.","schema":{"type":"number","minimum":0},"example":6000},{"name":"on","in":"query","description":"The date the travel happened — not the date of the claim. Defaults to today.","schema":{"type":"string","format":"date"},"example":"2025-06-15"},{"name":"knownOn","in":"query","description":"Answer as of what had been published by this date. Use it to reproduce an earlier payroll run exactly, before a retroactive text existed.","schema":{"type":"string","format":"date"},"example":"2025-06-15"},{"name":"modifiers","in":"query","description":"Comma-separated modifier ids, never applied by default. For example `fully-electric` in France, `northern-territories` in Canada.","schema":{"type":"string"}},{"name":"passengers","in":"query","description":"Business passengers carried, where a regime pays a supplement for them.","schema":{"type":"integer","minimum":0}},{"name":"alreadyClaimed","in":"query","description":"Distance already claimed in the same reference year, before this claim. This is not a subtraction you can do from two lookups: a banded scale must be split at the boundary, a non-marginal bracket scale re-prices the whole year when the boundary is crossed, and an annual cap bounds the running total rather than this claim. Returns a `cumulative` block showing the working.","schema":{"type":"number","minimum":0},"example":4800},{"name":"paidPerUnit","in":"query","description":"What the employer actually pays per unit, when it differs from the statutory figure. The statutory figure is a ceiling, not an instruction: the answer splits what was paid into the part within the scale and the excess, and says which of the two treatments applies — exempt from contributions, or a deduction limit.","schema":{"type":"number","minimum":0},"example":0.6},{"name":"use","in":"query","description":"What you are doing with the figures, when a regime serves more than one use. The French scale is both an employee deduction limit and an employer exemption ceiling; the treatment of an excess differs, the arithmetic does not. Defaults to the regime primary purpose when that is a tax mechanism; **required** when it is a kind of journey, since a treatment cannot be derived from where someone went. A use the regime is not accepted for is a 400, and so is omitting it where it is required. `/v1/catalogue` says which regimes those are.","schema":{"type":"string","enum":["employer-reimbursement","tax-deduction"]},"example":"employer-reimbursement"}],"responses":{"200":{"description":"The amount, its arithmetic, and its provenance.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Computation"}}}},"400":{"description":"A parameter is missing or malformed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"A credential was presented and cannot be read or is not valid. Omitting it entirely is the open tier; sending an unreadable one is never treated as an anonymous request.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"The date falls in a period still in force, and no key was sent.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Either no figure is published for that date, or no national figure exists at all (Italy sets the rate per vehicle model). The two are different `type`s.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"405":{"description":"The path is right and the method is not. Every endpoint answers GET (and HEAD) only.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"A spend ceiling was reached. Retry after the window rolls.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/per-diem/compute":{"get":{"tags":["compute"],"summary":"A day of per-diem meal allowance","description":"A different domain and a different arithmetic: no distance, no bands. Meals provided by the employer are deducted as a share of the **full-day** rate, even on a travel day paid at the lower one, and the day is floored at zero rather than owed back.","parameters":[{"name":"regime","in":"query","required":true,"schema":{"type":"string","enum":["de-auslandstagegelder","de-verpflegungsmehraufwand"]}},{"name":"paidPerDay","in":"query","required":false,"description":"What the employer actually pays for the day, when it differs from the published figure. The statutory per-diem is a ceiling, not an instruction: the answer splits what is paid into the part within the scale and the excess. Split against the amount payable **after** meal deductions — providing lunch lowers the ceiling, so paying the printed rate anyway puts part of it above the scale.","schema":{"type":"number","minimum":0}},{"name":"kind","in":"query","required":true,"description":"A whole 24-hour day, or an arrival, departure or short day.","schema":{"type":"string","enum":["full","partial"]}},{"name":"on","in":"query","description":"The day of travel. Defaults to today.","schema":{"type":"string","format":"date"}},{"name":"knownOn","in":"query","schema":{"type":"string","format":"date"}},{"name":"hours","in":"query","description":"Hours of absence. Below the regime minimum nothing is payable — not a prorated amount, nothing.","schema":{"type":"number","minimum":0}},{"name":"meals","in":"query","description":"Comma-separated meals the employer provided, each reducing the day. Only the meals the regime publishes a deduction for are accepted; anything else is a 400 naming them. ⚠️ Each deduction is a share of the FULL day, whatever day is being paid.","schema":{"type":"string","enum":["breakfast","dinner","lunch"]},"example":"breakfast,lunch"},{"name":"use","in":"query","description":"Which question is being asked of the same figures. Germany publishes one set: § 9 Abs. 4a EStG makes them a Werbungskosten deduction, and § 3 Nr. 16 exempts an employer’s reimbursement *by reference to* that deduction. Required whenever `paidPerDay` is sent and the regime’s own purpose is a kind of journey — the answer is otherwise a guess at which mechanism you meant.","schema":{"type":"string","enum":["employer-reimbursement","tax-deduction"]}},{"name":"destination","in":"query","description":"Where the traveller was. Required on a regime that publishes more than one — the German foreign table splits several countries by city, and answering with an arbitrary one would be worse than refusing. A destination the authority names in prose rather than in its table resolves to the figures it points at. One the table omits entirely is a 400 carrying `unlistedDestination`: the authority’s own rule for that case, quoted, for you to apply. It is the value a published entry carries as `destinationId` — the two names differ, and mapping one to the other is on you.","schema":{"type":"string"},"example":"fr-paris"}],"responses":{"200":{"description":"The day, its deductions and the working.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PerDiemComputation"}}}},"400":{"description":"A parameter is missing, malformed, or names another domain. An unknown destination carries `unlistedDestination` when the authority publishes a rule for it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"A credential was presented and cannot be read or is not valid. Omitting it entirely is the open tier; sending an unreadable one is never treated as an anonymous request.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"The date falls in a period still in force, and no key was sent.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No such regime, or no rate in force for that date.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"405":{"description":"The path is right and the method is not. Every endpoint answers GET (and HEAD) only.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/changes":{"get":{"tags":["catalogue"],"summary":"Every date a figure moved","description":"Derived from the corpus: a period boundary is the day something changed. Every move is listed to everyone — a date is not a figure — but `from` and `to` are withheld without a key on any move dated inside the last twelve months, and the row says so with `valuesWithheld`. Changes already published for a period that has not started are current-tier information and need `tier=current` with a key.","parameters":[{"name":"since","in":"query","description":"Only changes effective on or after this date.","schema":{"type":"string","format":"date"}},{"$ref":"#/components/parameters/tier"},{"name":"format","in":"query","description":"Response format. This route and `/v1/catalogue` serve `csv`; everywhere else it is a 400, because those payloads are not tables. A withheld move keeps its `valuesWithheld` column, so an empty cell never reads as \"no new figure\".","schema":{"type":"string","enum":["json","csv"]}}],"responses":{"200":{"description":"Changes, most recent first.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/RateChange"}}},"text/csv":{"schema":{"type":"string"},"example":"date,country,regimeId,regime,…\r\n"}}},"400":{"description":"`since` is not an ISO date.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"A credential was presented and cannot be read or is not valid. Omitting it entirely is the open tier; sending an unreadable one is never treated as an anonymous request.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"`tier=current` was asked for without a key.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"405":{"description":"The path is right and the method is not. Every endpoint answers GET (and HEAD) only.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/alerts":{"get":{"tags":["catalogue"],"summary":"What is about to expire","description":"Sorted by urgency. This is the paid product: knowing before a figure goes stale.","parameters":[{"name":"within","in":"query","description":"Days ahead to look. Defaults to 90.","schema":{"type":"integer","default":90}}],"responses":{"200":{"description":"Silent regimes first — those holding nothing in force today — then regimes due for re-reading, soonest first.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AlertRow"}}}}},"401":{"description":"A credential was presented and cannot be read or is not valid. Omitting it entirely is the open tier; sending an unreadable one is never treated as an anonymous request.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"The alert needs a key.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"405":{"description":"The path is right and the method is not. Every endpoint answers GET (and HEAD) only.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/openapi.json":{"get":{"tags":["catalogue"],"summary":"This document","description":"Generated from the corpus, so the enumerations cannot drift from the data. Import it into Postman, or generate a typed client from it.","responses":{"200":{"description":"The OpenAPI 3.1 description of this API.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"A credential was presented and cannot be read or is not valid. Omitting it entirely is the open tier; sending an unreadable one is never treated as an anonymous request.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"405":{"description":"The path is right and the method is not. Every endpoint answers GET (and HEAD) only.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/health":{"get":{"summary":"Liveness","responses":{"200":{"description":"Alive, and how many regimes this build carries.","content":{"application/json":{"schema":{"type":"object","required":["ok","regimes"],"properties":{"ok":{"type":"boolean"},"regimes":{"type":"integer","minimum":1}}}}}},"401":{"description":"A credential was presented and cannot be read or is not valid. Omitting it entirely is the open tier; sending an unreadable one is never treated as an anonymous request.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"405":{"description":"The path is right and the method is not. Every endpoint answers GET (and HEAD) only.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}}},"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"query","name":"key","description":"Convenient, and it puts your key in every log and cache key that sees the URL. Use the bearer scheme wherever you control the request headers."},"bearer":{"type":"http","scheme":"bearer","description":"The preferred form: the key stays out of URLs, logs and cache keys."}},"parameters":{"tier":{"name":"tier","in":"query","description":"`free` (default) returns the regime as it stood twelve months ago, with any end date reached since reported as `null` — at the cut, none had been published. `current` returns what has begun or been published since, and needs a key.","schema":{"type":"string","enum":["free","current"],"default":"free"}}},"schemas":{"RateComputation":{"type":"object","description":"How a distance becomes an amount. One of flat, banded, bracket, external.","properties":{"kind":{"type":"string","enum":["flat","banded","bracket","external"]},"perUnit":{"type":"number"},"marginal":{"type":"boolean","description":"Banded only: bands split, never re-price."},"bands":{"type":"array","items":{"type":"object","properties":{"upTo":{"type":["number","null"]},"perUnit":{"type":"number"}}}},"breaks":{"type":"array","items":{"type":"number"}},"brackets":{"type":"array","items":{"type":"object","properties":{"perUnit":{"type":"number"},"fixed":{"type":"number"}}}},"reason":{"type":"string","description":"external only: why no national figure exists."},"where":{"type":"string","description":"external only: where the real figure lives."}},"required":["kind"]},"Entry":{"type":"object","properties":{"period":{"type":"object","properties":{"from":{"type":"string","format":"date"},"to":{"type":["string","null"],"format":"date"},"publishedOn":{"type":"string","format":"date","description":"When the figure became KNOWN. Absent unless the source states it."}},"required":["from","to"]},"vehicleClassId":{"type":"string"},"computation":{"$ref":"#/components/schemas/RateComputation"},"cap":{"type":"object","description":"⛔ A distance ceiling and a money ceiling are not interchangeable.","properties":{"maxUnits":{"type":"number"},"maxAmount":{"type":"number"},"note":{"type":"string"}}},"destinationId":{"type":"string","description":"Per-diem: which destination this line is."},"fullDay":{"type":"number"},"partialDay":{"type":"number"},"accommodation":{"type":"number","description":"Published, never added to the day: it is a separate claim."}},"required":["period"]},"Regime":{"type":"object","description":"One regime, its entries reduced to the tier asked for. Mileage and per-diem share this shape and differ in which optional members they carry.","properties":{"id":{"type":"string","enum":["at-kilometergeld","au-ato-cents-per-km","be-indemnite-kilometrique-annuelle","be-indemnite-kilometrique-bosa","be-indemnite-velo","ca-cra-automobile-allowance","ch-berufskosten-fahrzeug","de-entfernungspauschale","de-kilometerpauschale","es-kilometraje-exento","fr-bareme-kilometrique","gb-amap","ie-civil-service-motor-travel","it-rimborso-chilometrico","nl-aftrek-privevervoermiddel","nl-onbelaste-reiskostenvergoeding","nz-ird-kilometre-rates","pt-viatura-propria","us-irs-business","us-irs-charitable","us-irs-medical","us-irs-moving","de-auslandstagegelder","de-verpflegungsmehraufwand"]},"domain":{"type":"string","enum":["mileage","per-diem"]},"country":{"type":"string"},"name":{"type":"string"},"purpose":{"type":"string"},"serves":{"type":"array","items":{"type":"string"},"description":"Every use these figures are accepted for. Absent means nobody has checked, which is not the same claim as \"one\"."},"currency":{"type":"string"},"unit":{"type":"string"},"appliesTo":{"type":"string"},"minimumHours":{"type":"number","description":"Per-diem: below it nothing is payable."},"vehicleClasses":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"kind":{"type":"string"},"note":{"type":"string"}}}},"destinations":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string","description":"The authority’s own spelling, unaltered."},"country":{"type":"string"},"region":{"type":"string"},"sameAs":{"type":"string","description":"This destination takes another’s figures because the authority says so in words rather than in its table."}}}},"unlistedDestination":{"type":"object","description":"What the authority says to do about a destination its table omits.","properties":{"defaultDestinationId":{"type":"string"},"quote":{"type":"string","description":"The authority’s own words."},"note":{"type":"string"},"source":{"type":"string","format":"uri"}}},"mealDeductions":{"type":"array","items":{"type":"object","properties":{"meal":{"type":"string"},"shareOfFullDay":{"type":"number","description":"⛔ A share of the FULL day, whatever day is being paid."}}}},"modifiers":{"type":"array","description":"Adjustments applied only when asked for by id.","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"kind":{"type":"string","enum":["multiply","add-per-unit"]},"factor":{"type":"number"},"perUnit":{"type":"number"},"per":{"type":"string","enum":["unit","passenger"]},"period":{"type":"object"}}}},"entries":{"type":"array","items":{"$ref":"#/components/schemas/Entry"}},"constraints":{"type":"array","items":{"$ref":"#/components/schemas/Constraint"}},"sources":{"type":"array","items":{"$ref":"#/components/schemas/Source"}},"nextReviewDue":{"type":"string","format":"date"},"tier":{"type":"string","enum":["history","current"]},"withheldEntries":{"type":"integer","minimum":0}},"required":["id","domain","country","name","entries","sources","nextReviewDue"]},"PerDiemComputation":{"type":"object","required":["regime","country","on","amount","currency","base","steps"],"properties":{"regime":{"type":"string","enum":["at-kilometergeld","au-ato-cents-per-km","be-indemnite-kilometrique-annuelle","be-indemnite-kilometrique-bosa","be-indemnite-velo","ca-cra-automobile-allowance","ch-berufskosten-fahrzeug","de-entfernungspauschale","de-kilometerpauschale","es-kilometraje-exento","fr-bareme-kilometrique","gb-amap","ie-civil-service-motor-travel","it-rimborso-chilometrico","nl-aftrek-privevervoermiddel","nl-onbelaste-reiskostenvergoeding","nz-ird-kilometre-rates","pt-viatura-propria","us-irs-business","us-irs-charitable","us-irs-medical","us-irs-moving","de-auslandstagegelder","de-verpflegungsmehraufwand"]},"country":{"type":"string"},"on":{"type":"string","format":"date"},"amount":{"type":"number","description":"What is payable, after meal deductions."},"currency":{"type":"string"},"base":{"type":"number","description":"The published day rate, before deductions."},"destination":{"type":"string"},"accommodation":{"type":"number","description":"The nightly flat rate, never added to the day."},"floored":{"type":"boolean","description":"True when deductions would have taken the day below zero."},"deductions":{"type":"array","items":{"type":"object","required":["meal","value"],"properties":{"meal":{"type":"string"},"shareOfFullDay":{"type":"number"},"value":{"type":"number"}}}},"allowance":{"type":"object","required":["paidPerDay","withinScale","aboveScale","belowScale","treatment"],"properties":{"paidPerDay":{"type":"number"},"use":{"type":"string","enum":["employer-reimbursement","tax-deduction"]},"withinScale":{"type":"number"},"aboveScale":{"type":"number"},"belowScale":{"type":"number"},"treatment":{"type":"string","enum":["exempt-from-contributions","deductible-limit"]}}},"steps":{"type":"array","items":{"type":"string"}},"sources":{"type":"array","items":{"$ref":"#/components/schemas/Source"}},"nextReviewDue":{"type":"string","format":"date"}}},"ServiceIndex":{"type":"object","description":"What exists, in one call.","properties":{"asOf":{"type":"string","format":"date"},"domains":{"type":"array","items":{"type":"string"}},"countries":{"type":"array","items":{"type":"string"}},"regimes":{"type":"integer"},"overdue":{"type":"array","items":{"type":"string","enum":["at-kilometergeld","au-ato-cents-per-km","be-indemnite-kilometrique-annuelle","be-indemnite-kilometrique-bosa","be-indemnite-velo","ca-cra-automobile-allowance","ch-berufskosten-fahrzeug","de-entfernungspauschale","de-kilometerpauschale","es-kilometraje-exento","fr-bareme-kilometrique","gb-amap","ie-civil-service-motor-travel","it-rimborso-chilometrico","nl-aftrek-privevervoermiddel","nl-onbelaste-reiskostenvergoeding","nz-ird-kilometre-rates","pt-viatura-propria","us-irs-business","us-irs-charitable","us-irs-medical","us-irs-moving","de-auslandstagegelder","de-verpflegungsmehraufwand"]},"description":"Regimes past their review date right now, by id."},"tiers":{"type":"object","properties":{"free":{"type":"string"},"current":{"type":"string"}}},"endpoints":{"type":"array","items":{"type":"string"}},"staticTier":{"type":"object","description":"The open tier as published files: no key, no ceiling, the same bytes. Served from the published site, which may be a different origin from this API.","required":["note","paths","verify"],"properties":{"note":{"type":"string"},"paths":{"type":"array","items":{"type":"string"}},"verify":{"type":"string"}}}}},"Problem":{"type":"object","description":"RFC 9457 problem details. `type` is a stable URL that documents the failure.","properties":{"type":{"type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"}},"required":["type","title","status","detail"]},"Source":{"type":"object","properties":{"publisher":{"type":"string","description":"The authority, as it names itself."},"url":{"type":"string","format":"uri"},"verifiedOn":{"type":"string","format":"date","description":"The day a human last opened that page and confirmed the figures."},"kind":{"type":"string","enum":["authority","secondary"],"description":"Who is saying this. `secondary` is a payroll bureau or professional body repeating the figure — often accurate, and not the same claim. Absent means authority."},"covers":{"type":"array","items":{"type":"string","enum":["entries","constraints"]},"description":"Which part of the regime this source stands behind. Absent means all of it. A source restricted to `constraints` backs a published rule the caller applies itself, not the figures in `entries` — the Belgian bicycle allowance cites the Conseil National du Travail for the CCT 164 obligation while its exemption ceiling comes from elsewhere."},"method":{"type":"string","enum":["read","extracted"],"description":"Absent means read: a human opened the page and typed the figures."},"extraction":{"type":"object","description":"Present when a script parsed the document. The risk then is the parser, not the source — so it says which file, its digest, and how much was checked by eye.","properties":{"document":{"type":"string","format":"uri"},"sha256":{"type":"string"},"lines":{"type":"integer"},"sampled":{"type":"integer","description":"Lines compared against the print by eye."}}}}},"Constraint":{"type":"object","description":"A published figure the caller applies itself. Never used by /v1/compute: deciding it needs something only the caller holds — France caps the home-to-work distance at 40 km one way unless the taxpayer justifies living further out.","properties":{"id":{"type":"string"},"label":{"type":"string"},"value":{"type":"number"},"unit":{"type":"string"},"note":{"type":"string"},"source":{"type":"string","format":"uri"}},"required":["id","label","note"]},"CatalogueRow":{"type":"object","properties":{"id":{"type":"string"},"country":{"type":"string"},"name":{"type":"string"},"purpose":{"type":"string"},"currency":{"type":"string"},"domain":{"type":"string","enum":["mileage","per-diem"]},"unit":{"type":"string","enum":["km","mi"],"description":"Absent for domains that do not measure a distance."},"nextReviewDue":{"type":"string","format":"date"},"serves":{"type":"array","items":{"type":"string"},"description":"Every use these figures are accepted for."},"useRequiredForSplit":{"type":"boolean","description":"True when the regime’s own purpose is a kind of journey rather than a tax mechanism: asking for the paid/exempt split then needs an explicit `use`."},"vehicleClassIds":{"type":"array","items":{"type":"string"},"description":"Ids only. The regime endpoints answer `vehicleClasses` as objects; one word may not carry two shapes, so the index says which it holds."},"reviewInDays":{"type":"integer","description":"Negative means the figure is past its review date."},"historyEntries":{"type":"integer"},"currentEntries":{"type":"integer"},"sources":{"type":"array","items":{"$ref":"#/components/schemas/Source"}}}},"AlertRow":{"type":"object","properties":{"id":{"type":"string"},"domain":{"type":"string","enum":["mileage","per-diem"]},"country":{"type":"string"},"name":{"type":"string"},"nextReviewDue":{"type":"string","format":"date"},"reviewInDays":{"type":"integer","description":"Negative means the review date has already passed."},"silent":{"type":"boolean","description":"Present and true when the regime holds no period in force today, so `/v1/compute` refuses every date. This is not the same condition as a passed review date, and it is the more urgent one: the corpus has stopped answering. Silent rows are listed whatever `within` says."},"silentSince":{"type":["string","null"],"format":"date","description":"The last date any period covered. Null if the regime never covered one."},"daysSilent":{"type":["integer","null"],"description":"How long the corpus has answered nothing for this regime."},"coverageEndsOn":{"type":"string","format":"date","description":"The last date this regime covers, when its periods all have an end. Absent for a regime holding an open period, which does not stop. This is the day it will begin refusing — knowable in advance, unlike a review date, which is a date somebody typed."},"coverageEndsInDays":{"type":"integer","description":"Negative once the regime has already stopped answering."},"expected":{"type":"boolean","description":"On a silent row only: true when the regime declares an arrears window that has not yet closed, so the silence is the authority’s calendar rather than a gap. New Zealand sets an income year’s kilometre rates after the year has ended, so it is silent every April. False means nobody predicted this silence."},"arrears":{"type":"object","description":"The declared window, on a silent row that has one. Carried once it has closed too — the date is what says how late the authority now is.","properties":{"note":{"type":"string","description":"What the authority does, in its own terms."},"expectedBy":{"type":"string","format":"date","description":"The date past which the silence stops being expected."},"source":{"type":"string","format":"uri"}},"required":["note","expectedBy","source"]},"sourceUrls":{"type":"array","items":{"type":"string","format":"uri"}}},"required":["id","domain","country","name","nextReviewDue","reviewInDays","sourceUrls"]},"RateChange":{"type":"object","properties":{"date":{"type":"string","format":"date","description":"The day the new figure took effect."},"country":{"type":"string"},"regimeId":{"type":"string"},"regime":{"type":"string"},"line":{"type":"string","description":"The line within the regime that moved."},"lineLabel":{"type":"string"},"from":{"type":"string","description":"Absent for the first period the corpus holds, and withheld without a key on a move dated inside the last twelve months."},"to":{"type":"string","description":"Withheld without a key on a move dated inside the last twelve months."},"valuesWithheld":{"type":"boolean","description":"Present and true when `from` and `to` were withheld: the move itself is open — a date is not a figure — but the figures either side of it need a key."},"publishedOn":{"type":"string","format":"date"},"ahead":{"type":"boolean","description":"Published for a period that has not started."}},"required":["date","country","regimeId","ahead"]},"Computation":{"type":"object","properties":{"regime":{"type":"string"},"country":{"type":"string"},"on":{"type":"string","format":"date"},"knownOn":{"type":"string","format":"date"},"amount":{"type":"number"},"currency":{"type":"string"},"unit":{"type":"string"},"billableUnits":{"type":"number","description":"Distance actually paid for, after any statutory cap."},"base":{"type":"number","description":"Per-diem: the day’s rate before any deduction."},"floored":{"type":"boolean","description":"Per-diem: deductions would have taken the day below zero."},"deductions":{"type":"array","description":"Per-diem: one line per meal provided, each a share of the FULL day.","items":{"type":"object","properties":{"meal":{"type":"string"},"amount":{"type":"number"}}}},"steps":{"type":"array","items":{"type":"string"},"description":"One line per arithmetic step, in the order applied."},"sources":{"type":"array","items":{"$ref":"#/components/schemas/Source"}},"nextReviewDue":{"type":"string","format":"date"},"allowance":{"type":"object","description":"Present when `paidPerUnit` was given.","properties":{"paidPerUnit":{"type":"number"},"paidPerDay":{"type":"number","description":"Per-diem: what was paid for the day."},"use":{"type":"string","description":"Which question the split answered."},"amountPaid":{"type":"number"},"withinScale":{"type":"number"},"aboveScale":{"type":"number"},"belowScale":{"type":"number","description":"The unused headroom under the statutory figure. On a deduction regime this is the claim itself — UK Mileage Allowance Relief is claimed on what the employer did not pay. Only one of `aboveScale` and `belowScale` is ever non-zero."},"treatment":{"type":"string","enum":["exempt-from-contributions","deductible-limit"],"description":"Follows the `use` asked for, which must be one the regime `serves`. An exemption is not a deduction limit."}}},"cumulative":{"type":"object","description":"Present when `alreadyClaimed` was given.","properties":{"alreadyClaimed":{"type":"number"},"totalAfter":{"type":"number","description":"Running total after this claim, after any cap."},"amountForYearToDate":{"type":"number"},"amountAlreadyEarned":{"type":"number"},"crossesBoundary":{"type":"boolean"}}}}}}}}