DEV Community

ValidatorAPI
ValidatorAPI

Posted on

# 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)
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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'}
Enter fullscreen mode Exit fullscreen mode

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' }
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

API response examples

VAT:

{ "input": "GB123456789", "valid": true, "type": "VAT", "country": "GB", "formatted": "GB123 456 789" }
Enter fullscreen mode Exit fullscreen mode

NINO:

{ "input": "AB123456C", "valid": true, "type": "NINO", "formatted": "AB 12 34 56 C" }
Enter fullscreen mode Exit fullscreen mode

Company Number:

{ "input": "SC123456", "valid": true, "type": "COMPANY_NUMBER", "company_type": "Scottish company", "formatted": "SC123456" }
Enter fullscreen mode Exit fullscreen mode

Auto-detect (any document type):

{ "input": "1234567890", "valid": true, "type": "UTR", "detected_as": "UTR", "formatted": "12345 67890" }
Enter fullscreen mode Exit fullscreen mode

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)