HMRC VAT API explained: UK validation after Brexit
How the HMRC Check a UK VAT Number endpoint works, the OAuth 2.0 flow behind it, sandbox vs production access, and the quirks of the 97-55 checksum.
Key facts
- HMRC is the sole authoritative registry for GB-prefixed VAT numbers since 1 January 2021. Northern Ireland (prefix
XI) stayed in the EU VAT area and routes through VIES. - The endpoint is a proper REST API:
GET https://api.service.hmrc.gov.uk/organisations/vat/check-vat-number/lookup/{vrn}, JSON in, JSON out. - Authentication is OAuth 2.0 client credentials, not an API key. Tokens are bearer-type and expire (four hours in production, shorter in sandbox).
- Production access requires a manual application review on HMRC's Developer Hub; budget roughly ten working days.
- The UK VRN format is
GB+ 9 digits, optionally suffixed with a 3-digit branch code, and validates with the HMRC 97-55 checksum algorithm.
What the registry is
HMRC (His Majesty's Revenue and Customs) is the UK tax authority. Before Brexit, UK VAT numbers were accessible through VIES like any other member-state number. On 1 January 2021 that routing stopped for GB numbers. HMRC took over as the only public source of truth for UK VAT registration. Northern Ireland is the exception: under the Windsor Framework (which replaced the Northern Ireland Protocol in 2023), XI numbers remain in scope for EU VAT on goods and are still listed in VIES.
The endpoint we use is part of HMRC's Check a UK VAT Number API, which in turn sits inside the broader Making Tax Digital (MTD) programme. MTD is HMRC's long-running project to move every tax operation onto an API-first stack. VAT validation is the simplest endpoint in that family and the one with the lowest scope requirements (read:vat-registered-companies); it does not read filings, submissions, or payments.
Governance is straightforward: HMRC owns the data, runs the endpoint, and publishes versioned documentation on the Developer Hub. The legal basis for the registry itself is the Value Added Tax Act 1994 and subsequent amendments; the API layer is governed by HMRC's terms of use for third-party developers.
What the API returns
The lookup operation takes the 9-digit VRN in the path and returns a JSON body with a target object and a processingDate:
{
"target": {
"name": "CREDITIX LIMITED",
"vatNumber": "553557825",
"address": {
"line1": "131 Edgware Road",
"line2": "London",
"postcode": "W2 2AP",
"countryCode": "GB"
}
},
"processingDate": "2026-05-18T10:12:34.567Z"
}There is a lookup/{vrn}/{requesterVrn} variant. Passing your own VRN as requesterVrn triggers an authenticated consultation and returns a consultationNumber on the response. Functionally, it is the UK equivalent of the VIES requestIdentifier: audit-grade proof that you verified the buyer's number at the time of supply.
Error responses follow a predictable structure:
{
"code": "NOT_FOUND",
"message": "The VRN specified was not found."
}with HTTP 404 for unknown VRNs, HTTP 400 for malformed input, HTTP 401 for missing or expired tokens, and HTTP 503 when the backend itself is unavailable.
The HMRC 97-55 checksum ensures you can reject obvious typos before spending a token. The name refers to the modulus (97) and the +55 offset introduced when HMRC ran out of numbers under the original rule. Weights 8, 7, 6, 5, 4, 3, 2 are applied to digits 1 through 7 and summed. The two-digit suffix must then equal either 97 − ((sum + 55) mod 97) (the modern rule) or (−sum) mod 97 (the legacy rule still valid for older numbers). @vatverify/vat-rates implements both variants offline.
Rate limits and quirks
HMRC's public guidance suggests a sustained ceiling of roughly 100 requests per second per OAuth application. In practice, the ceiling is higher for short bursts but 100 req/s is the safe planning number. Exceeding it triggers HTTP 429 with a Retry-After header.
Observed behaviour worth knowing:
- Token expiry ≠ retry signal. A 401 response can mean expired token or revoked credentials. Always try exactly one refresh before surfacing the error to the caller; do not retry in a loop.
- Sandbox is fixture-only. The sandbox URL (
test-api.service.hmrc.gov.uk) serves a fixed set of test VRNs. It is useful for CI and integration tests, useless for verifying a real number. - Branch codes add 3 trailing digits.
GB123456789012is a valid VRN where012is a branch identifier. The registry accepts both the 9-digit and the 12-digit form, but you should always strip the branch suffix before running the 97-55 checksum; the checksum only applies to the nine-digit core. - Government-department prefixes. Older numbers can appear as
GBGD(government department) orGBHA(health authority) with three trailing digits. They are rare in commercial flows. - Production credential reviews. Each application gets a manual review that looks at your stated use case, your privacy policy, and that your callback URLs are reachable. Incomplete applications bounce back silently; check the developer portal dashboard for review status.
How vatverify handles it
The validation pipeline for GB numbers mirrors the VIES path but adds the OAuth layer:
- Normalise: strip whitespace, dots, and hyphens; uppercase the prefix.
- Format check: the normalised input must begin with
GBand contain a valid numeric body; inputs that fail the shape check returninvalid_formatbefore we touch HMRC. - Cache lookup: freshness is 30 days on valids and 24 hours on invalids, with a seven-day fallback window past freshness used to serve degraded responses when HMRC is unreachable.
- Token lookup: Upstash Redis holds the HMRC OAuth token. If present, use it. If absent, run the client-credentials grant and cache the token for the lifetime HMRC advertises on the response minus a five-minute safety margin, then proceed.
- Upstream call: 10-second timeout, bearer auth, structured JSON decode.
The HMRC 97-55 checksum and other VRN format helpers ship in @vatverify/vat-rates for clients that want to reject typos before calling the API.
Token caching matters because the OAuth exchange adds 150-300 ms to a cold validation. A shared cache collapses that cost across every caller on every key. Cached lookups do not hit HMRC at all; a cache hit returns in under 20 ms.
When HMRC returns 503 or times out, we apply the same graceful-degradation posture as with VIES: serve any cached result with meta.source_status: "degraded" and fall through to registry_unavailable only when there is nothing to serve. Passing requester_vat_number on a VIES-routed request persists the VIES requestIdentifier as verify_id; HMRC's equivalent endpoint is not currently plumbed through, so GB lookups return verify_id: null even when a requester is supplied.
Gotchas worth knowing
XIis not HMRC. A Northern Irish number routes through VIES, not HMRC. Do not waste a token looking up anXInumber on the HMRC endpoint; the lookup will 404 or 400 depending on HMRC's current routing rules. vatverify dispatches by prefix.- Scope name changed. Older HMRC docs reference
read:vat; the current scope for the check-vat-number endpoint isread:vat-registered-companies. If you see 401 withINVALID_SCOPE, update the scope parameter on your OAuth request. - Sandbox tokens expire faster. If you build tests against sandbox and assume 4-hour tokens, they will fail intermittently. Cache with the shorter TTL, or refresh on every request in test environments.
processingDateis registry time. Useverified_atin the vatverify response (the UTC timestamp at which we hit the registry) for audit logging; HMRC'sprocessingDateis a best-effort indicator and has drifted from UTC in past incidents.
See the API reference
- API reference:
GET /v1/validate - Glossary: HMRC VRN, checksum, consultation number, VAT number
- Error surface:
registry_unavailable,invalid-format,country-unsupported - Related guides: HMRC VAT check API, VIES explained, Handle VIES downtime