DEV Community

ImmigrationGPT
ImmigrationGPT

Posted on

UK Sponsor Licence Register: A Technical Reference for HR Systems and Compliance Tools

If you've ever tried to verify whether a UK employer is actually authorised to sponsor overseas workers, you've probably hit the same wall: a government-published CSV file that's updated daily, has inconsistent capitalisation, and contains ~100,000 rows with no API.

This is the definitive technical reference for working with the UK Register of Licensed Sponsors — parsing it, querying it efficiently, and integrating it into HR compliance pipelines.


What the Register Is (and Isn't)

The UK Home Office publishes a list of every employer currently licensed to sponsor overseas workers across various routes (Skilled Worker, Intra-Company Transfer, Global Business Mobility, etc.). Updated every working day. No API. No pagination. Just a downloadable .xlsx file with roughly 100,000 rows.

The columns you actually care about:

Column Values Notes
Organisation Name string Inconsistent casing, Ltd/Limited variants
Town/City string Sometimes blank
Type & Rating "A-Rated", "B-Rated" A = active; B = under action plan
Route Skilled Worker, etc. A licence covers specific routes

What's NOT in the register: company registration number, Companies House ID, contact information, or number of sponsored workers. That's the first pain point for compliance teams — you can't reliably deduplicate on name alone.


Parsing Strategy

Download and normalise

The file URL changes with each update (embedded in a GOV.UK attachment link), so you need to scrape the publication page to get the latest URL each run.

import requests
from bs4 import BeautifulSoup
import pandas as pd
from io import BytesIO

def get_latest_register_url():
    page = requests.get(
        "https://www.gov.uk/government/publications/register-of-licensed-sponsors-workers",
        headers={"User-Agent": "Mozilla/5.0"}
    )
    soup = BeautifulSoup(page.content, "html.parser")
    for link in soup.select("a.gem-c-attachment__link"):
        href = link.get("href", "")
        if href.endswith(".xlsx"):
            return href
    raise ValueError("No .xlsx found on register page")

def load_register(url: str) -> pd.DataFrame:
    r = requests.get(url, headers={"User-Agent": "Mozilla/5.0"})
    df = pd.read_excel(BytesIO(r.content))
    df.columns = [c.strip().lower().replace(" ", "_").replace("/", "_") for c in df.columns]
    df["name_norm"] = (
        df["organisation_name"]
        .str.upper()
        .str.replace(r"\b(LIMITED|LTD|PLC|LLP|LLC)\b\.?", "", regex=True)
        .str.replace(r"[^A-Z0-9 ]", "", regex=True)
        .str.strip()
    )
    return df
Enter fullscreen mode Exit fullscreen mode

Fuzzy matching for employer lookups

Exact name matching fails on roughly 30% of real queries because of abbreviations, trading names, and punctuation. Use rapidfuzz for threshold-based matching:

from rapidfuzz import process, fuzz

def find_employer(query: str, df: pd.DataFrame, threshold: int = 85):
    query_norm = query.upper().strip()
    results = process.extract(
        query_norm,
        df["name_norm"].tolist(),
        scorer=fuzz.token_sort_ratio,
        limit=5,
        score_cutoff=threshold
    )
    if not results:
        return []
    indices = [r[2] for r in results]
    return df.iloc[indices][["organisation_name", "town_city", "type___rating", "route"]].to_dict("records")
Enter fullscreen mode Exit fullscreen mode

A threshold of 85 catches "Acme Ltd" vs "ACME LIMITED" while avoiding false positives. Drop to 75 for broader recall on candidate-facing tools.


Rating Logic: A-Rated vs B-Rated

A-Rated: Active licence. The employer can issue Certificates of Sponsorship (CoS) and hire overseas workers on the relevant routes.

B-Rated: The employer is under a Home Office action plan. They cannot issue new CoS until restored to A-rated. Workers already sponsored on a B-rated licence are not automatically affected, but renewals and extensions become more complex.

For a compliance tool:

  • Filter type___rating == "A-Rated" for new hire eligibility checks
  • Flag B-Rated entries with a warning rather than a hard block
  • No entry = not licensed (or licence recently surrendered/revoked)

Revoked and surrendered licences are removed from the register without trace. If you need historical data, snapshot the file daily — GOV.UK doesn't maintain an archive.


Integration Patterns

Daily sync to your own database: Fetch and upsert every working day at 9AM GMT. Key on normalised name + route + town to detect additions and removals.

Real-time lookup via cached in-memory index: A 100k-row DataFrame is roughly 20MB — fine for a single-instance service. Load on startup, refresh every 24 hours. Sub-millisecond lookups.

Candidate pre-check webhook:

POST /api/check-sponsor
{ "company_name": "...", "route": "Skilled Worker" }

Response:
{ "found": true, "rating": "A-Rated", "town": "London", "matches": [...] }
Enter fullscreen mode Exit fullscreen mode

This is the pattern used by tools like ImmigrationGPT — letting candidates verify a prospective employer's sponsor status before investing time in an application.


Edge Cases to Handle

Multiple routes per company: A large employer might hold licences for Skilled Worker, Intra-Company Transfer, and Global Business Mobility simultaneously. Return all rows for a matched employer.

Charities and public sector: NHS trusts, councils, and universities often appear under their official legal name. "Guy's and St Thomas' NHS Foundation Trust" won't match "Guys Hospital" without tuning.

Subsidiaries: Parent companies and subsidiaries hold separate licences. "Amazon UK Services Ltd" and "Amazon Data Services UK Limited" are different licence holders — a candidate cannot be sponsored by the wrong entity's licence.

Newly licensed employers: There's typically a 24-48 hour lag between a licence decision and appearing on the register.


The Bigger Picture

The register is critical UK immigration infrastructure running on a daily-updated spreadsheet. For serious compliance work: daily snapshots (removals leave no trace), fuzzy matching (names are not standardised), route-specific filtering, and a human review layer for edge cases.

If you're building in this space or want to check a specific employer, ImmigrationGPT indexes the full register and supports plain-language sponsor lookups.


This post is for informational purposes only. Immigration law changes frequently — verify current rules at gov.uk or consult a regulated immigration adviser.

Top comments (0)