Gas Safe Register Businesses - UK Gas Firm Leads avatar

Gas Safe Register Businesses - UK Gas Firm Leads

Pricing

$4.00 / 1,000 per registered business returneds

Go to Apify Store
Gas Safe Register Businesses - UK Gas Firm Leads

Gas Safe Register Businesses - UK Gas Firm Leads

From $4 per 1,000 businesses, no start fee. Search the official Gas Safe Register by UK postcode: business name, registration number, full address, phone and email. Measured on 95 live businesses: 96.8% email, 95.8% phone, 100% address. Optional appliance qualifications. Deduped by reg number.

Pricing

$4.00 / 1,000 per registered business returneds

Rating

0.0

(0)

Developer

Scrapers Delight

Scrapers Delight

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Share

Gas Safe Register Businesses — UK Gas Firm Leads

Search the official Gas Safe Register — the statutory register every legal gas business in Great Britain must be on — by UK postcode, and get back the business, its registration number, its full postal address, its phone, its email, and (optionally) every appliance it is qualified to work on. Deduplicated by registration number, so a firm that turns up in five of your postcodes is delivered once and charged once.

$4 per 1,000 businesses. No run-start fee. A run that finds nothing costs nothing.


What you actually get — measured, not estimated

Counted on 95 distinct businesses (100 business cards across 10 results pages, deduplicated by registration number), captured live on 2026-09-16 from five postcodes spanning central London, Manchester, Land's End, Snowdonia and the Channel Islands. The fixtures are in fixtures/ and offline_validate.mjs re-counts every number below from those bytes on each run.

FieldFillNotes
registrationNumber100.0%95/95. The dedupe key and the row's identity.
businessName100.0%95/95
premise100.0%95/95 — first address line (often the trading name again)
town100.0%95/95
postcode100.0%95/95, and all 95 are syntactically valid UK postcodes
fullAddress100.0%95/95 — the lines joined, placeholders dropped
distanceMiles98.9%94/95, miles from the postcode you searched
street97.9%93/95
email96.8%92/95
telephone95.8%91/95
outOfHoursAvailable58.9% true56/95 advertise out-of-hours cover; the rest is a real false, not a gap
county32.6%31/95 — the register prints it only for some addresses
locality7.4%7/95 — thin, and said so rather than quietly shipped
  • Phone AND email: 92.6% (88/95). Phone OR email: 100% (95/95) — every business in the sample published at least one way to reach it.
  • Where a business publishes no email the register prints a literal -. Every single unfilled contact cell in the sample was exactly that string. This actor turns it into null; the string "-" never reaches your dataset, because a "-" in an email column is what breaks a mail merge and inflates a fill percentage at the same time.

Appliance qualifications (opt-in)

Switch on includeQualifications and each row also carries the register's "Qualified to work on" table: every appliance category and specific qualification the business holds, each flagged for Natural Gas, LPG and Building Regulations, split into Domestic and Commercial.

Measured on 11 distinct businesses on 2026-09-16 (7 from the captured fixtures, 4 more on a live Glasgow run): between 26 and 82 qualification rows each, and between 17 and 62 specific appliance qualifications actually held. All 11 held at least one.

{
"serviceType": "Domestic",
"service": "CARAVANS - Closed Flue Gas Fires DOM LPG",
"isCategory": false,
"naturalGas": null,
"lpg": true,
"buildingRegs": null
}

A cell has three states, and this actor keeps all three: a tick is true, a cross is false, and a blank is null. Collapsing blank into false would invent an assessment the register never made.

It costs time, which is why it is off by default. One extra page load per business, measured live at about 1.9 seconds each at the default 1,200 ms delay — so a full 50-business postcode takes roughly 95 extra seconds. It adds no charge: those pages belong to a business you are already paying for.

(An earlier measurement of ~9 s per business was an artefact of a wrong approach — walking back to the results list between each fetch. That approach is also incorrect, not just slow: see limit 5. Each business's qualification link is taken off the results bytes already in hand and followed directly, which was verified live to land on the right business 4 times out of 4.)

A sample row

{
"registrationNumber": "211205",
"businessName": "Plumbforce Direct Ltd",
"telephone": "02038416530",
"email": "scott@plumbforcedirect.co.uk",
"premise": "Plumbforce Direct Ltd",
"street": "4 Old Park Lane",
"locality": null,
"town": "LONDON",
"county": null,
"postcode": "W1K 1QW",
"fullAddress": "Plumbforce Direct Ltd, 4 Old Park Lane, LONDON, W1K 1QW",
"distanceMiles": 0.36,
"outOfHoursAvailable": true,
"serviceType": "Domestic",
"searchPostcode": "SW1A 1AA",
"resultPage": 1,
"qualificationCount": null,
"qualifiedFor": null,
"qualifications": null,
"scrapedAt": "2026-09-16T21:02:38.092Z"
}

Who buys this

  • Installer-network recruiters (the BOXT / Heatable / Hometree shape) building a vetted engineer panel in a given catchment.
  • Manufacturer loyalty schemes (Worcester Bosch, Vaillant Advance and similar) recruiting registered installers — the qualification table tells you who can actually fit the product.
  • Merchants and distributors opening trade accounts in a new branch catchment.
  • Trade insurers and warranty providers underwriting registered gas businesses.

What makes the row defensible is what Google Maps cannot give you: a statutory registration number, and the appliance-by-appliance qualification behind it.


The honest limits

Read these before you plan a national sweep. They are not caveats bolted on at the end; they are the shape of the source.

1. There is a hard ceiling of 50 businesses per search, and no national total

Every postcode search — central London, Shetland, Land's End, everywhere tried — answers Showing 1 to 10 of 50 results across exactly 5 pages of 10. There is no page-size override and the register never publishes how many businesses it holds in total. Coverage is a function of how densely you space your postcodes, not of paging deeper.

Measured in central London on 2026-09-16: those 50 slots were used up within 1.24 miles. In a dense metro, space your postcodes roughly 2 miles apart or the grid under-covers. A rural search reaches far further for the same 50, so one postcode covers much more ground.

This actor cannot tell you "all UK gas businesses". It tells you who the register returns for the postcodes you asked for. RUN_SUMMARY.coverageCaveat says the same thing inside every run.

2. The register answers a rate-limited request with HTTP 200 and a block page

This is the nastiest thing about the target and the reason this actor is built the way it is. When you go too fast, the site does not return 429 or 403 — it returns HTTP 200 carrying an Imperva/Incapsula block page. A scraper that trusts the status code parses zero rows and reports "no gas businesses near this postcode": confident, wrong, and billable.

Every response body is sniffed for the block markers before a zero-row parse is believed, and a block fails the postcode (Actor.fail() if it happens to every postcode) instead of being reported as an empty result. A genuinely empty page is distinguishable and is accepted: it still carries its Showing … results envelope. Both cases are in the offline test suite, against real captured bytes.

Defaults are set to the rate that measured clean: one search every 15 seconds, 1.2 s between pages inside a search. Three back-to-back searches tripped the limiter; it recovered after about 50 seconds idle. Lowering searchIntervalSecs will not make your run faster, it will get it blocked.

3. It needs a real, headful browser — which is what it costs

Measured on 2026-09-16 against this host:

ClientResult
got-scraping / curl, full Chrome header set403, ~860–968-byte Incapsula body on every path
Playwright Chromium, headless: true403, 858-byte Incapsula body
Playwright Chromium, headless: false200 — the reese84 interstitial solves itself in ~2.5 s and the real page loads

So the actor runs a headful Chromium on the Apify Playwright image's Xvfb display, at 4096 MB. That is the cost of this lane, and it is priced in.

One more measured subtlety: clicking the search button with a synthetic mouse event produced a 403 on the results redirect (the site's cookie-consent overlay sits over the button). Setting the field values and calling the form's own requestSubmit() produced a clean 200. This actor therefore never clicks through a consent dialog on your behalf.

4. UNMEASURED: Apify's own proxy pools

The build was verified end to end from one ordinary IP (which happened to be a US one — the site did not geo-block it). It was not measured through Apify's DATACENTER or RESIDENTIAL pools, and no claim is made that it clears Imperva from them. proxyConfiguration therefore defaults to RESIDENTIAL, which is the right bet for an Imperva-fronted site, and if you see RUN_SUMMARY.blockedPostcodes filling up, point it at your own proxy. A blocked postcode is reported as blocked; it is never silently converted into "found nothing", and nothing is charged for it.

5. The result list re-sorts between requests, so never re-read it to find a row

Requesting the same search page twice does not reliably return the same businesses in the same order. Measured: ten index-based visits to "the Nth result" returned only seven distinct businesses. The actor never re-reads a list to find a row again — it takes each business's own link off the page bytes it already parsed, and dedupes everything by registration number. Do not treat the envelope's 50 as a promise of 50 distinct rows.

6. A valid UK postcode can still be unknown to the register

The register runs its own postcode lookup when you submit. A postcode that is perfectly well-formed can come back with "That postcode or town was not found in our records. Please try again with a full UK postcode." — measured on B1 1AA, which is a textbook-shaped Birmingham postcode.

When that happens the page does not navigate at all. This actor reads the register's own message, reports the postcode in RUN_SUMMARY.rejectedPostcodes, charges nothing for it, and moves on to the next one. It does not force the submit through (that was tried: it navigates to a 64,122-byte page with no results envelope on it — a junk page there is no honest way to read). A refused postcode is detected in about 4 seconds, not by burning a navigation timeout.

7. What is deliberately not shipped

  • No engineer-level records. The register has a second surface behind "View Engineers" with named individual engineers. It is out of scope for this actor: it is a different function (people, not businesses), it multiplies the request count against a hard rate limit, and the contact data there is thin — the engineer record inspected during recon printed Telephone: n/a. The rich contact data is at business level, which is the right level for a lead list anyway.
  • No URLs back to the register. Every deep link on this site is addressed by an encrypted ep token that expires three minutes after it is minted. Shipping one would ship a field that is dead by the time you open the dataset, so there is no registerUrl field. Look a business up by its registration number instead.
  • No ratings or review counts. The register does not publish them.

8. Robots and access

robots.txt (fetched 2026-09-16) disallows /aspnet_client/, /bin/, /config/, /data/, /macroScripts/, /umbraco/, /umbraco_client/, /usercontrols/, /xslt/, /engineer/ and *?wcag*. The paths this actor uses — /find-an-engineer-or-check-the-register/, /widgetfc, /findbusinessresults, /businesscompetencies — are not disallowed. Everything read is public data that the register publishes so consumers can check their installer.


Input

FieldDefaultWhat it does
postcodes(required)One search each. Anything that is not a valid UK postcode is rejected before any request, listed in RUN_SUMMARY.invalidPostcodes, and never charged.
serviceTypeDomesticDomestic or Commercial — separate registers on the site.
maxPagesPerPostcode510 businesses per page; 5 is the register's own ceiling.
maxBusinesses1000Hard stop on delivered (= charged) rows. 0 means no cap. Checked against rows produced, so it never overshoots.
includeQualificationsfalseThe appliance-qualification table. ~9 s per business. No extra charge.
requireContactfalseDrop rows with neither phone nor email — before delivery, so they are never charged.
requireEmailfalseDrop rows with no email — same.
searchIntervalSecs15The measured-clean pace.
requestDelayMs1200Between pages inside one search.
proxyConfigurationApify RESIDENTIALSee limit 4.

The default input is deliberately a one-postcode, one-page, 10-business run so a first try costs four cents and finishes in under a minute.

Output

Rows land in the dataset (three views: overview, contacts-only, qualifications). RUN_SUMMARY in the key-value store reconciles the run and keeps every kind of "nothing" apart:

  • invalidPostcodes — not a UK postcode; rejected before any request was made
  • rejectedPostcodes — a valid postcode the register's own lookup does not know
  • blockedPostcodes — the site's edge refused us; not "no businesses there"
  • unreachablePostcodes — a transport or parse failure, with the reason
  • postcodesThatReturnedNothing — a real, empty answer
  • excludedByRequireEmail / excludedByRequireContact — your filters, never charged
  • cardsParsed, duplicatesSkippedByRegistrationNumber, cardsWithoutRegistrationNumber
  • delivered and charged — always equal
  • perPostcode — the same breakdown per search

Billing

One event, business-scraped, at $0.004 per distinct business delivered. No start fee, neither Apify auto-event. Charging rides on Actor.pushData(items, event), which bills per item and pushes only what your charge cap allows — so delivered equals charged, and a cap never overshoots by a batch.

Verified live, end to end

These modules — not a prototype of them — were driven against the live site on 2026-09-16 from an ordinary IP:

RunResult
EH1 1RE, 5 pages50 cards parsed, 50 distinct businesses, 0 duplicates, 0 cards missing a registration number, in 13.6 s (38.3 s including browser launch and the Imperva clear)
NE1 4ST + CF10 1EP, 2 pages each40 distinct businesses, 39/40 phone, 39/40 email, no "-" leaked into any row
B1 1AArefused by the register's own lookup, detected in 3.9 s, reported and not charged
G1 1XW qualifications4 businesses, 26–76 qualification rows each, 4/4 landed on the right business, 1.9 s each

Verify it yourself

$node offline_validate.mjs

No network, no platform, no node_modules. It runs every assertion in this README against the captured bytes in fixtures/ — including the 858-byte block body, the 6,183-byte interstitial, a real results page holding zero businesses, and the triple-URL-encoded links whose encoding must survive the walk untouched.