How to Validate UK VAT Numbers, NINO, Company Numbers and UTR in Any Language (2026)
If you're building software for the UK market, you'll eventually need to validate fiscal and identification documents. This guide covers the algorithms, code examples, and the fastest way to add validation to any project.
What are these documents?
-
VAT number — Tax ID for UK businesses registered for VAT. Format:
GB+ 9 digits (e.g.GB123456789) -
NINO — National Insurance Number, used for tax and social security. Format: 2 letters + 6 digits + letter (e.g.
AB123456C) -
Company Number — Companies House registration number. Format: 8 digits or prefix + 6 digits (e.g.
SC123456for Scottish companies) -
UTR — Unique Taxpayer Reference, used for Self Assessment. Format: 10 digits (e.g.
1234567890)
Option 1: Implement it yourself
VAT number validation (Python)
UK VAT numbers use HMRC's mod-97 checksum on the first 7 digits:
def validate_uk_vat(vat: str) -> bool:
vat = vat.strip().upper().replace(" ", "")
if vat.startswith("GB"):
vat = vat[2:]
if len(vat) not in (9, 12) or not vat.isdigit():
return False
digits = [int(d) for d in vat[:7]]
weights = [8, 7, 6, 5, 4, 3, 2]
total = sum(d * w for d, w in zip(digits, weights))
check = int(vat[7:9])
return check == (97 - total % 97) or check == (97 - (total + 55) % 97)
NINO validation (JavaScript)
function validateNINO(nino) {
nino = nino.replace(/\s/g, '').toUpperCase();
const invalidFirst = /[DFIQUV]/;
const invalidSecond = /[DFIOQUV]/;
const invalidPrefixes = ['BG','GB','NK','KN','NT','TN','ZZ'];
if (!/^[A-Z]{2}\d{6}[ABCD]$/.test(nino)) return false;
if (invalidFirst.test(nino[0])) return false;
if (invalidSecond.test(nino[1])) return false;
if (invalidPrefixes.includes(nino.slice(0,2))) return false;
return true;
}
Company number validation (Python)
PREFIXES = {'SC', 'NI', 'OC', 'SO', 'FC', 'BR', 'CE', 'CS'}
def validate_company_number(number: str) -> bool:
number = number.strip().upper()
if number[:2] in PREFIXES:
return len(number) == 8 and number[2:].isdigit()
return number.isdigit() and len(number) <= 8
Option 2: Use an API (3 lines of code)
For VAT the algorithm is manageable, but NINO has 15+ edge cases and Company Numbers have 15+ prefix types. If you don't want to maintain all of this, the UK Document Validator API handles everything:
Python
import requests
r = requests.get(
'https://uk-document-validator1.p.rapidapi.com/validate/vat',
params={'value': 'GB123456789'},
headers={
'X-RapidAPI-Key': 'YOUR_API_KEY',
'X-RapidAPI-Host': 'uk-document-validator1.p.rapidapi.com'
}
)
print(r.json())
# {'input': 'GB123456789', 'valid': True, 'type': 'VAT', 'country': 'GB', 'formatted': 'GB123 456 789'}
JavaScript
const r = await fetch(
'https://uk-document-validator1.p.rapidapi.com/validate/nino?value=AB123456C',
{
headers: {
'X-RapidAPI-Key': 'YOUR_API_KEY',
'X-RapidAPI-Host': 'uk-document-validator1.p.rapidapi.com'
}
}
);
const data = await r.json();
// { valid: true, type: 'NINO', formatted: 'AB 12 34 56 C' }
PHP
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => 'https://uk-document-validator1.p.rapidapi.com/validate/company?value=SC123456',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'X-RapidAPI-Key: YOUR_API_KEY',
'X-RapidAPI-Host: uk-document-validator1.p.rapidapi.com'
],
]);
$r = json_decode(curl_exec($curl));
// $r->company_type = "Scottish company"
API response examples
VAT:
{ "input": "GB123456789", "valid": true, "type": "VAT", "country": "GB", "formatted": "GB123 456 789" }
NINO:
{ "input": "AB123456C", "valid": true, "type": "NINO", "formatted": "AB 12 34 56 C" }
Company Number:
{ "input": "SC123456", "valid": true, "type": "COMPANY_NUMBER", "company_type": "Scottish company", "formatted": "SC123456" }
Auto-detect (any document type):
{ "input": "1234567890", "valid": true, "type": "UTR", "detected_as": "UTR", "formatted": "12345 67890" }
When to use the API vs DIY
| DIY | API | |
|---|---|---|
| VAT validation | ~15 lines | 3 lines |
| NINO edge cases | 15+ rules to implement | Included |
| Company prefix types | 15+ prefixes | Included |
| UTR checksum | Complex | Included |
| Auto-detect document type | Extra logic | Included |
| Maintenance | Your problem | Automatic |
Free tier: 500 requests/month. Subscribe here.
Full docs and interactive playground: uk-validation-api.vercel.app/docs
Top comments (0)