HMRC VAT check API: UK VAT validation explained
How HMRC's VAT check API works. OAuth 2.0 client credentials, sandbox vs production, rate limits, and why vatverify handles all of it for you.
TL;DR
- HMRC is the UK's VAT registry, completely separate from VIES since Brexit.
- It is a proper REST API (JSON in, JSON out), which is the good news.
- The bad news: it requires OAuth 2.0 client credentials, token refresh every hour, and a ~10-day production application process.
- vatverify handles the OAuth dance, token caching, and retries so you never touch any of it.
Why UK VAT is different from EU
Before Brexit, UK VAT numbers (GB prefix) were accessible through VIES just like any other EU member state. After 1 January 2021, Great Britain left the EU VAT area and HMRC became the sole authoritative registry for GB-prefixed numbers. Northern Ireland is a special case: XI-prefixed numbers remain in scope of EU VAT rules under the Windsor Framework and are still validated through VIES. So in practice: if the prefix is GB, query HMRC; if the prefix is XI, query VIES.
HMRC REST vs VIES SOAP
The HMRC API is genuinely pleasant to work with at the protocol level: REST, JSON, predictable status codes. The friction is entirely in the auth layer: you need OAuth 2.0 client credentials (not just an API key) and you need to renew the access token every 60 minutes. Compare that to VIES, which has no auth at all but forces you through SOAP/XML. Both registries have real operational complexity; they just put it in different places.
UK VAT number format
UK VAT numbers follow one of two patterns:
- Standard:
GB+ 9 digits, e.g.,GB123456789 - Branch code:
GB+ 9 digits + 3-digit branch suffix, e.g.,GB123456789012
Some older references also show a 4-digit government department prefix (GBGD or GBHA) but these are rare and handled automatically by vatverify.
The 9 core digits use the HMRC 97-55 checksum algorithm to detect transposition errors.
HMRC 97-55 checksum
The algorithm is named after the two valid modular remainders (42 and 55 are also valid, but 97 is the modulus, hence "97-55"). To verify a 9-digit VAT number:
function hmrc9755Checksum(digits: string): boolean {
const nums = digits.split('').map(Number);
const sum = nums.slice(0, 7).reduce((a, d, i) => a + d * (8 - i), 0);
const expected = (sum + Number(digits.slice(7, 9))) % 97;
return expected === 0 || expected === 42 || expected === 55; // ← the 97-55 name
}The weights for positions 1–7 are 8, 7, 6, 5, 4, 3, 2. You multiply each digit by its weight, sum the results, add the two-digit check suffix (digits 8–9), and check that the total is divisible by 97 with a remainder of 0, 42, or 55. The 42 and 55 variants were introduced to accommodate a specific range of registration numbers issued in a particular period. Any offline validation library worth using (including @vatverify/vat-rates) implements all three variants.
OAuth 2.0 client credentials flow
The flow in plain prose:
- POST to
https://api.service.hmrc.gov.uk/oauth/tokenwithgrant_type=client_credentials, yourclient_id, andclient_secret. - HMRC returns a JSON body with
access_token,token_type: "bearer", andexpires_in: 14400(4 hours for production, shorter in sandbox). - Include
Authorization: Bearer <access_token>on every subsequent request. - Re-request a token before it expires. Best practice is to re-request at ~55 minutes (5-minute safety buffer) regardless of the
expires_invalue.
The validation endpoint itself is straightforward once you have a token:
GET https://api.service.hmrc.gov.uk/organisations/vat/check-vat-number/lookup/{targetVatNumber}Sandbox vs production
| Sandbox | Production | |
|---|---|---|
| Base URL | https://test-api.service.hmrc.gov.uk | https://api.service.hmrc.gov.uk |
| Credentials | Any test credentials | Approved application required |
| Response data | Canned fixture data only | Live HMRC registry |
| Token TTL | Shorter (varies) | 4 hours |
| Availability | Always open | Requires production approval |
The sandbox is free and open. It returns a fixed set of test VAT numbers with predictable responses, useful for integration testing, but it will never return a "real" company name because no live data is exposed.
Production access application
To get production credentials, you register an application at developer.service.hmrc.gov.uk, select the VAT (MTD) API, and request the read:vat scope (the scope for check-vat-number lookups). HMRC reviews applications manually; expect approximately 10 working days. During review they check your stated use case, your privacy policy, and that your callback URLs are reachable. Once approved, you receive production client_id and client_secret through the developer portal. Store them in environment variables, never in source code.
Token caching
Re-requesting a token on every API call is wasteful and slows your response times. Cache the token for 55 minutes (using a 5-minute safety buffer against the 60-minute TTL). Using a key-value store:
SET hmrc:token <access_token> EX 33003300 seconds = 55 minutes. Before each HMRC request, check whether hmrc:token exists. If it does, use it. If it doesn't, run the OAuth token exchange, store the result, then proceed.
vatverify handles all of this internally. The token lifecycle is invisible to you.
MTD (Making Tax Digital) context
HMRC is migrating all tax operations to API-first under the Making Tax Digital programme. VAT validation is the simplest endpoint in the MTD ecosystem, but understanding it gives you a foundation for MTD filing integrations. The check-vat-number scope is a read-only permission. It does not give you access to return submission or payment data. That is intentional: validation is separate from filing.
Direct HMRC vs vatverify
| Feature | Direct HMRC | vatverify |
|---|---|---|
| Protocol | REST/JSON | REST/JSON |
| Auth | OAuth 2.0 (manual) | Handled internally |
| Token lifecycle | You manage rotation | Automatic |
| Caching | None (you build it) | 30-day automatic |
| Retries | None (you build it) | Automatic with backoff |
| Production approval | ~10-day process on your account | vatverify's approved credentials |
| Rate limit | ~100 req/sec | Unbounded (cached) |
| Northern Ireland (XI) | Separate VIES call | Automatic routing |
FAQ
How do I get production credentials?
Apply at developer.service.hmrc.gov.uk. Select the VAT (MTD) API, request the read:vat scope, and submit your application. HMRC reviews manually. Budget approximately 10 working days. If you use vatverify, you skip this step entirely because the API calls go through vatverify's pre-approved credentials.
Can I use the free sandbox forever?
Yes. The HMRC sandbox has no expiry and no cost. The limitation is that it only returns canned fixture data: a fixed list of test VAT numbers with predetermined responses. It is not useful for validating real numbers or for any production scenario.
Does HMRC return company names?
Yes. The response includes a target object with a name field (the registered business name) and an address object with structured fields (line1, line2, line3, line4, postcode, countryCode). This is more structured than VIES, which returns a free-text address string for most countries.
What's the rate limit?
HMRC's published guidance suggests approximately 100 requests per second sustained from a single OAuth application. In practice the ceiling is higher, but 100 req/sec is a safe target if you are hitting HMRC directly. With vatverify, cached results do not count against any rate limit. Only cache misses result in upstream calls.
What about Northern Ireland VAT?
Northern Ireland VAT numbers use the XI prefix, not GB. These are validated through VIES, not HMRC, because Northern Ireland remains within the EU VAT area for goods under the Windsor Framework. vatverify routes automatically based on prefix: GB → HMRC, XI → VIES. You pass the number and let the API handle it.