# How to Validate UK VAT Numbers, NINO, Company Numbers and UTR in Any Language (2026)

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. SC123456 for 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