NHS UK Providers Scraper — GP, Pharmacy, Dentist, Care Home
Pricing
from $1.00 / 1,000 provider records
NHS UK Providers Scraper — GP, Pharmacy, Dentist, Care Home
Every UK healthcare provider from the official NHS ODS registry — GP practices, pharmacies, dentists, NHS trusts, hospices, care homes. Search by name, postcode or whole city and get ODS codes, full addresses and phone numbers. Government open data: no login, no proxies, no CAPTCHAs.
Pricing
from $1.00 / 1,000 provider records
Rating
0.0
(0)
Developer
Nomad.Dev
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
14 days ago
Last modified
Categories
Share
Every UK healthcare provider from the official NHS Organisation Data Service (ODS) registry — GP practices, pharmacies, dental practices, NHS trusts, hospices, care homes and more, with full addresses and phone numbers.
This Actor reads NHS England's official open-data API. It does not scrape HTML, so there is no login, no proxy, no CAPTCHA, and nothing to break when a website changes its markup — which is why it has a 100% run success rate.
What NHS provider data does this scraper extract?
Every record carries the full registry entry — address, phone, roles and all:
| Field | Meaning |
|---|---|
odsCode | Official ODS organisation code (e.g. L81008) |
name | Registered organisation name |
addressLine1/2, town, county, country, uprn | Full registered address |
phone, website | Registered contacts where the registry holds them |
primaryRoleId / primaryRoleDescription | Provider type (e.g. GP practice, pharmacy) |
roles | All ODS roles with names, primary flag and status |
relationships | Organisation-hierarchy links — e.g. an NHS trust site "IS OPERATED BY" its parent trust |
postCode | Registered postcode |
status | Active / Inactive |
lastChangeDate | Last registry change (useful for incremental syncs) |
operationalStart/End, legalStart/End | Operational and legal dates |
orgLink | Canonical registry URL for the organisation |
Turning Include full address, phone & roles off (includeDetails: false) gives a faster, lighter run with just the identity fields:
odsCode, name, status, primaryRoleId, primaryRoleDescription, postCode, orgRecordClass, lastChangeDate, orgLink.
Optional enrichments
| Enrichment | Adds | Turn on with |
|---|---|---|
| Add lat/long geocoding | latitude, longitude — the organisation's postcode geocoded via the free, open postcodes.io service (Ordnance Survey / ONS open data). null when a postcode can't be resolved. Works in search or detail mode. | includeGeo: true |
| Resolve relationship target names | Fills relationships[].targetName with each related organisation's registered name (e.g. the parent NHS trust). Needs detail data — detail mode, or includeDetails: true in search mode. | includeRelationshipNames: true |
With Only new since last run enabled, every returned record also carries isNew: true.
How to scrape NHS providers with this Actor
- Pick a mode:
search(filters below) ordetail(passodsCodes). - In search mode set at least one filter:
name,postCode,providerType(GP practice, pharmacy, dental, NHS trust, hospice, care home, …),roleId(any official role code),lastChangeDatefor a one-off incremental sync, oronlyNewSinceLastRunfor automatic incremental syncs across scheduled runs. - Run and export JSON, CSV or Excel — or call it over the API:
Postcode search takes a district, a full postcode, or a whole city. BS1 (district), SW1A 1AA (full postcode), or LS — a bare postcode area, which expands to every Leeds district and returns the ~2,800 providers across the whole city in one run. M gets you all of Manchester, B all of Birmingham.
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("nomad-agent/uk-nhs-ods-providers-scraper").call(run_input={"mode": "search","postCode": "BS1","providerType": "pharmacy","includeDetails": True,})for item in client.dataset(run["defaultDatasetId"]).iterate_items():print(item["odsCode"], "|", item["name"], item["postCode"])
curl -X POST \"https://api.apify.com/v2/acts/nomad-agent~uk-nhs-ods-providers-scraper/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \-H "Content-Type: application/json" \-d '{"mode": "search", "postCode": "BS1", "providerType": "gp_practice"}'
Input
| Field | Type | Default | Description |
|---|---|---|---|
mode | select | search | search queries the registry with the filters below; detail fetches full records for the ODS codes in odsCodes. |
name | string | – | Case-insensitive substring match on the organisation name. |
postCode | string | (prefilled BS1 in Console) | UK postcode district (BS1), full postcode (SW1A 1AA), or whole postcode area (LS = all of Leeds). Prefix match on the registered address. |
providerType | select | any | Common provider types mapped to official ODS role codes (GP practice, pharmacy, dental, NHS trust, NHS trust site, hospice, care home, optical site, independent provider, branch surgery). |
specialty | string | – | Free-text specialty (e.g. pharmacy, hospice, dental), matched against the ODS role vocabulary. Ignored if providerType (non-any) or roleId is set. |
roleId | string | – | Any official ODS role code (advanced) — overrides providerType and specialty. |
status | select | active | active, inactive, or all. |
lastChangeDate | string (YYYY-MM-DD, datepicker) | – | Only organisations changed on or after this date. The registry rejects dates more than ~185 days in the past (HTTP 406). |
onlyNewSinceLastRun | boolean | false | Search only organisations changed since this Actor's own last successful run (server-side, via the registry's LastChangeDate filter). Overrides lastChangeDate once a prior run exists. Search mode only. Tags every record isNew: true. |
odsCodes | array | – | ODS codes to fetch in detail mode (e.g. L81008, RA7, FLM49). |
includeDetails | boolean | true | Enrich every search hit with the full registry record — address, phone, roles, dates. No extra cost per record; adds ~35s per 1,000 organisations. Turn off for a faster, codes-and-names-only run. |
includeGeo | boolean | false | Add latitude/longitude by geocoding each postcode via the free postcodes.io service. Best-effort — unresolved postcodes return null. |
includeRelationshipNames | boolean | false | Resolve relationships[].targetName (needs detail data) — one extra API call per unique related organisation. |
maxItems | integer | 100 | Hard cap on organisations returned. Each result is billed, so start low and raise once your filter is dialed in. 0 = no cap. |
concurrency | integer | 8 | (Advanced) Parallel detail-record fetches, clamped to 1–16. |
Output example
Default run (includeDetails: true) — a real GP practice, with the contact data you actually want:
{"odsCode": "L81008","name": "SHIREHAMPTON GROUP PRACTICE","status": "Active","primaryRoleId": "RO177","primaryRoleDescription": "PRESCRIBING COST CENTRE","addressLine1": "HEALTH CENTRE","town": "BRISTOL","postCode": "BS11 9SB","phone": "0117 9162226","orgRecordClass": "RC1","lastChangeDate": "2024-07-15","roles": [{"id": "RO177", "name": "PRESCRIBING COST CENTRE", "primary": true, "status": "Active"},{"id": "RO76", "name": "GP PRACTICE", "primary": false, "status": "Active"}],"orgLink": "https://directory.spineservices.nhs.uk/ORD/2-0-0/organisations/L81008","source": "nhs-ods"}
Lighter run (includeDetails: false):
{"odsCode": "L81008","name": "SHIREHAMPTON GROUP PRACTICE","status": "Active","primaryRoleId": "RO177","primaryRoleDescription": "PRESCRIBING COST CENTRE","postCode": "BS11 9SB","orgRecordClass": "RC1","lastChangeDate": "2024-07-15","orgLink": "https://directory.spineservices.nhs.uk/ORD/2-0-0/organisations/L81008","source": "nhs-ods"}
Detail mode / includeDetails: true — a real NHS trust site, showing the relationships link back to its parent trust:
{"odsCode": "A0A1E","name": "CAMHS - ADASH","status": "Active","primaryRoleId": "RO198","primaryRoleDescription": "NHS TRUST SITE","postCode": "AL10 8HR","orgRecordClass": "RC2","lastChangeDate": "2024-05-08","orgLink": "https://directory.spineservices.nhs.uk/ORD/2-0-0/organisations/A0A1E","source": "nhs-ods","addressLine1": "DOWNS FARM CENTRE","town": "HATFIELD","country": "ENGLAND","roles": [{"id": "RO198", "name": "NHS TRUST SITE", "primary": true, "status": "Active"},{"id": "RO31", "name": "PPA EPACT SYSTEM", "primary": false, "status": "Active"}],"relationships": [{"relTypeId": "RE6", "relTypeName": "IS OPERATED BY", "targetOdsCode": "RWR", "targetName": null}],"operationalStart": "2024-03-22"}
Integrations
Every run's results are available as a dataset you can export as JSON, CSV, Excel (XLSX), or query straight from the API with run-sync-get-dataset-items. Wire it into Make, Zapier, or n8n via their native Apify integrations, or drive it programmatically through the Apify MCP server so an LLM agent can call this Actor as a tool.
Pricing
Pay per event — no subscription, pay only for what you fetch. Full address and phone data is included at no extra charge.
From 20 July 2026: $0.005 per Actor start + $0.001 per organisation — $1 per 1,000 providers. 1,000 providers ≈ $1.01. Every GP practice in England (~6,400) ≈ $6.41.
Until then: $0.05 per start + $0.004 per organisation.
Use cases
- Healthcare lead generation: every pharmacy, GP practice or dental practice in a region, with addresses and phone numbers
- Master-data / CRM enrichment keyed on official ODS codes
- Incremental registry syncs via
lastChangeDate, or automatically on a schedule viaonlyNewSinceLastRun - Health-tech integrations that need canonical NHS organisation reference data
- Market analysis of UK primary care, community pharmacy and independent providers
- Mapping organisational hierarchy (e.g. which trust operates which site) via the
relationshipsfield (withincludeRelationshipNamesfor resolved names) - Geospatial analysis and mapping via optional
latitude/longitudegeocoding (includeGeo)
FAQ
Is it legal to scrape NHS ODS data? Yes. The ORD API is the official, public, unauthenticated open-data API of NHS England's Organisation Data Service, and the data is published under the Open Government Licence v3 — free to use, including commercially, with attribution ("Contains information from NHS England, licenced under the current version of the Open Government Licence").
Do I need an API key or login? No. The ORD API requires no authentication of any kind.
Which countries does it cover? Primarily England (plus border/cross-registered organisations in Wales, Scotland and Northern Ireland where they hold ODS codes).
Why does a GP practice show PRESCRIBING COST CENTRE as its primary role?
That is how ODS models GP practices — the primary role is RO177 with RO76 (GP PRACTICE) as a non-primary role. Filtering by the "GP practice" provider type handles this for you.
What if an ODS code isn't found? It is logged and skipped — the run continues with the other codes.
How fresh is the data? Every run hits the live registry API. No caching layer in between.
How do I run this on a schedule and only get what's new?
Turn on Only new since last run (onlyNewSinceLastRun). It stores this Actor's last successful run date and, on the next run, asks the registry for organisations changed since then — no need to track ids yourself, and no re-paying for organisations you already have. Every returned record is tagged isNew: true.
The very first run with the flag on has nothing to compare against, so it records today as the starting checkpoint and returns no organisations (charging nothing per result); every run after that is incremental. If you also set a name / postCode / providerType filter, that first run returns the full matching set as your baseline and goes incremental from the next run on.
Why is this more reliable than scraping the NHS website? Because it isn't scraping. It calls NHS England's official ORD open-data API, so there is no HTML to break, no bot detection, and no rate-limit wall — the Actor's public run history is 100% successful. Website scrapers of the same data fail regularly when the page markup or anti-bot rules change.
What's in relationships?
Organisation-hierarchy links from the registry's own data, e.g. an NHS trust site record links back to the trust that operates it (relTypeId: "RE6", "IS OPERATED BY"). Only present in detail mode / with Include full details on. targetName is null by default; turn on Resolve relationship target names (includeRelationshipNames) to fill in each related organisation's name.
Can I get map coordinates?
Yes — turn on Add lat/long geocoding (includeGeo) and each record gains latitude/longitude, geocoded from its postcode via the free, open postcodes.io service. Unresolvable postcodes return null.
Something broken or missing? Open an issue on the Actor's Issues tab — it is monitored and fixes ship fast.
Found this useful? A ⭐ review on the Actor page genuinely helps — and feature requests posted in Issues get built.
Related Actors
More official-registry and government-data scrapers from the same family: