API · 10 endpoints · verified 2026-09-23
Read-only, and it answers without a key.
Everything the corpus knew twelve months ago is open: no account, no key, no rate agreement. A key adds the current year, the future and the alert. Every response is JSON; every refusal is an RFC 9457 problem document with a URL you can follow.
base https://api.lexrates.com
One call, and the answer carries its own sources.
curl "https://api.lexrates.com/v1/compute?regime=gb-amap&vehicleClass=car-van&units=1000&on=2024-06-01"{
"regime": "gb-amap",
"country": "GB",
"on": "2024-06-01",
"amount": 450,
"currency": "GBP",
"unit": "mi",
"billableUnits": 1000,
"steps": [
"1000 × 0.45 = 450"
],
"sources": [
{
"publisher": "gov.uk",
"url": "https://www.gov.uk/government/publications/rates-and-allowances-travel-mileage-and-fuel-allowances/travel-mileage-and-fuel-rates-and-allowances",
"verifiedOn": "2026-09-05"
},
{
"publisher": "gov.uk — Claim tax relief for your job expenses: vehicles you use for work",
"url": "https://www.gov.uk/tax-relief-for-employees/vehicles-you-use-for-work",
"verifiedOn": "2026-09-02"
}
],
"nextReviewDue": "2027-04-05"
}curl "https://api.lexrates.com/v1/compute?regime=gb-amap&vehicleClass=car-van&units=1000&on=2026-06-01"
# → 402, naming the most recent open period
curl -H "Authorization: Bearer YOUR_KEY" \
"https://api.lexrates.com/v1/compute?regime=gb-amap&vehicleClass=car-van&units=1000&on=2026-06-01"
# → 200A key in the query string works too and is the weaker form: ?key= travels into CDN logs, browser history and cache keys, where a header does not. Prefer the header.
YOUR_KEY comes from your account: create one, subscribe, and the key is on that page from then on — it is attached to the account rather than handed over once, so losing it is not an incident. What the subscription costs, and what the open tier gives you without one, is on the pricing page.
Ask what applied, or ask what you could have known.
- on
- The date the journey happened, or the day being paid. Absent, today. This is the axis everybody expects.
- knownOn
- The date you are pretending to stand on. Absent, everything known is used. Set it to reproduce a run you made in the past: a text published in March and retroactive to January did not exist in February, and a reconciliation that ignores that reads as a defect in your product rather than in the gazette.
curl "https://api.lexrates.com/v1/compute?regime=nz-ird-kilometre-rates&vehicleClass=petrol\
&units=500&on=2025-09-01&knownOn=2026-01-01"
# → 404 no-figure-in-force: that instrument was made in June 2026.Asking knownOn explicitly is a paid question, whatever the answer would have been: asking what was published after the open-tier line is a question about today.
| Path | What it answers | Query |
|---|---|---|
| /v1 | Service index | — |
| /v1/catalogue· csv | Every regime of every domain, with days remaining before its figures expire |
|
| /v1/regimes/{id} | One regime and its periods |
|
| /v1/countries/{code} | Every regime published in one country |
|
| /v1/compute | Apply a regime to a distance on a date |
|
| /v1/per-diem/compute | A day of per-diem meal allowance |
|
| /v1/changes· csv | Every date a figure moved |
|
| /v1/alerts | What is about to expire |
|
| /v1/openapi.json | This document | — |
| /health | Liveness | — |
* required · csv also answers ?format=csv
Only two paths serve CSV, and that is a decision rather than an omission: 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. A CSV of the figures would be one number per row, which is the shape this product exists to say is wrong. The catalogue and the change log are genuinely tabular, so they serve it.
https://api.lexrates.com/v1/openapi.json — generate a client from it.
| Status | Type | When |
|---|---|---|
| 404 | not-found | The id or country code in the path is not one this corpus publishes. |
| 400 | bad-request | A parameter is missing, blank, out of range, or not a value this endpoint accepts. A blank parameter is malformed rather than zero: ?units= means a field somebody meant to fill. |
| 402 | outside-the-open-window | The date asked about falls inside the last twelve months, or knownOn asks what was known after the cut — which is a question about today. |
| 404 | no-figure-in-force | The regime exists and holds no period covering that date. |
| 404 | no-national-figure | The country publishes no single rate — Italy sets it per vehicle model in the ACI tables. |
| 400 | wrong-domain | A per-diem regime was sent to /v1/compute, or a mileage regime to /v1/per-diem/compute. |
| 401 | key-required | An account path was called with no credential. Unlike /v1, these have no open tier: they answer about one subscription and cannot do that without knowing which. |
| 401 | invalid-key | A credential was presented and cannot be read, or is not a key this service knows. Sending an unreadable Authorization header counts: it is never treated as an anonymous request. |
| 405 | method-not-allowed | The path is right and the method is not. This API is read-only. |
| 503 | budget-exceeded | A per-caller or service-wide ceiling was reached. The ceilings exist because a public endpoint spends unattended; they are stated on the pricing page. |
Each type is a URL that resolves to a page explaining the failure and what to do about it. That is what RFC 9457 asks the field to be, and it is checked in both directions: no refusal can name a page that does not exist, and no page can document a refusal the API never returns.
The ceiling is in the code, and these are the numbers it enforces.
- Anonymous
- 1,000
- requests a day, per caller. The open tier is meant to be sampled, not mirrored.
- With a key
- 100,000
- requests a day.
- Whole service
- 2,000,000
- requests a month — a stop against a runaway client, not a billing meter.
Every answer carries x-ratelimit-limit and x-ratelimit-remaining. A closed period is settled arithmetic over settled figures, so it is cacheable for an hour and carries an ETag; an authenticated answer is never cached by anything.