NHS ODS Scraper: GP Practices, Pharmacies & Dentists
DeprecatedPricing
from $0.05 / 1,000 provider records
NHS ODS Scraper: GP Practices, Pharmacies & Dentists
Deprecated$0.1/1K providers | Search the NHS ODS registry for GP practices, pharmacies, dentists, trusts, hospices and care homes by name, postcode or type. Each row has the ODS code, address, roles and status. No login or API key. Not affiliated with the NHS.
Pricing
from $0.05 / 1,000 provider records
Rating
0.0
(0)
Developer
Nomad.Devs
Maintained by CommunityActor stats
0
Bookmarked
3
Total users
1
Monthly active users
6 days ago
Last modified
Categories
Share
NHS ODS Providers Scraper (No API Key) + Addresses
$0.1/1K providers | Search the NHS ODS registry for GP practices, pharmacies, dentists, trusts, hospices and care homes by name, postcode or type. Each row has the ODS code, address, roles and status. No login or API key. Not affiliated with the NHS.
NHS ODS Providers Bulk Scraper
Our scraper collects organisations from the official NHS Organisation Data Service (ODS) registry and lets you filter by name, postcode, provider type, role code, status and last-change date. Every row carries the ODS code, registered address, roles, status and a link back to the registry entry. It is ideal for healthcare lead lists, CRM enrichment keyed on ODS codes and incremental registry syncs.
- Looking at other official-registry data? UK Case Law Scraper covers UK judgments, SPAC Redemptions Scraper covers SEC EDGAR SPAC filings and PTAB Trials Scraper covers USPTO patent trials.
- Browse all our scrapers: https://apify.com/nomad-agent
Key Benefits
- No login or API key: the NHS ORD API is open, so there is nothing to set up.
- Low pricing: $0.10 per 1,000 providers, with lower rates on Scale and Business plans ($0.075 and $0.05 per 1,000), and full address detail is included at no extra charge.
- Whole-city postcode searches: a bare postcode area such as
LSexpands to every Leeds district in one run. - Incremental syncs: filter by
lastChangeDateto fetch only organisations changed since your last run (the registry accepts dates up to about 185 days back). - Fresh data: every run reads the live registry API, with no cache in between.
How It Works
- Choose the mode:
search(filters) ordetail(fetch full records for a list of ODS codes). - Provide your search:
- (optional) Postcode (e.g.,
BS1,SW1A 1AA,LS). Prefix match on the registered address. - (optional) Provider type (e.g., GP practice, pharmacy, dental practice, NHS trust, hospice, care home).
- (optional) Name contains (e.g.,
surgery), Role ID, Status or Changed since.
- (optional) Postcode (e.g.,
- Run the Actor and export the dataset.
Run the same search again and you get only organisations you have not received yet; set dedupe.enabled to false to see everything again.
More settings:
includeDetails- Add address, roles, dates and relationships to every hit. Default istrue, with no extra cost per record.includeGeo- Add latitude and longitude from the free postcodes.io service. Default isfalse.maxItems- Maximum number of organisations to return, each one billed. Default is100.0means no cap; the full register is about 280,000 organisations.
Without a provider type, a postcode search returns every kind of ODS organisation at that postcode, including legacy records such as former commissioning-group sites. Pick a provider type to get only the kind you want. Phone numbers come from the registry and are not filled for every organisation: in our test runs all 10 GP practices had one and none of the 10 untyped BS1 records did. The registry holds no website for most of them.
Data You'll Receive
- Organisation: ODS code, name, status, primary role, record class, registry link and last change date.
- Address: lines, town, county, country, postcode and UPRN where the registry has them.
- Roles and relationships: all ODS roles with status, and links such as "is operated by" to parent organisations.
- Dates: operational and legal start and end dates.
- On some records: phone number and website; latitude and longitude with
includeGeo.
Sample output data
Here is an example GP practice from this Actor (shortened):
{"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","addressLine1": "PEMBROKE ROAD","addressLine2": "SHIREHAMPTON","town": "BRISTOL","county": "","country": "ENGLAND","uprn": "28741","phone": "0117 9162226","website": "","roles": [{"id": "RO177","name": "PRESCRIBING COST CENTRE","primary": true,"status": "Active"},{"id": "RO76","name": "GP PRACTICE","primary": false,"status": "Active"}],"relationships": [{"relTypeId": "RE4","relTypeName": "IS COMMISSIONED BY","targetOdsCode": "4VP62","targetName": null},{"relTypeId": "RE4","relTypeName": "IS COMMISSIONED BY","targetOdsCode": "5QJ","targetName": null}],"operationalStart": "1974-04-01"}
NHS Providers API
Results are stored in a dataset that you can export as CSV, JSON or Excel, or read on demand through the Apify API. You can also wire the Actor into Make, Zapier and n8n, or call it from AI agents through the Apify MCP server.
Support and Feedback
We improve these scrapers from your feedback. If something breaks or you need a filter:
- Open an issue on the Actor's Issues tab
- If this Actor saved you time, a short review on the Store helps other users find it.
Run evidence
Start with one input and output
An Apify account is required. Use your Apify API token when calling the API; Console runs do not need that token pasted into the input. No user API-key field is required by this input schema.
Free-tier event rates checked 2026-10-04: result $0.0001; actor-start $0.00005.
Cost: Total = Actor event charges + platform usage if separately billed + any BYO-provider charges. See “Current costs” for your event rates, plan tiers and a calculation. A sample does not promise future billable volume.
Input and output below were read from the same successful run ggow2pkW2cMI3xhig (2026-10-03, immutable build 159AgHfueaphhlQck). This is an observed example, not a guarantee that repeating it returns this row.
Input JSON
{"mode": "search","providerType": "any","status": "active","includeDetails": true,"includeGeo": false,"includeRelationshipNames": false,"maxItems": 100,"concurrency": 8}
Matching output excerpt
Long strings and arrays are shortened for display; this is not a complete dataset fixture.
{"odsCode": "0DF0K","name": "360 BRISTOL","status": "Active","primaryRoleId": "RO214","primaryRoleDescription": "COMMISSIONING SUPPORT UNIT SITE","postCode": "BS1 3NX","orgRecordClass": "RC2","lastChangeDate": "2024-07-16","orgLink": "https://directory.spineservices.nhs.uk/ORD/2-0-0/organisations/0DF0K","source": "nhs-ods","addressLine1": "MARLBOROUGH STREET","addressLine2": "","town": "BRISTOL","county": "AVON","country": "ENGLAND","uprn": "","phone": "","website": "","roles": [{"id": "RO214","name": "COMMISSIONING SUPPORT UNIT SITE","primary": true,"status": "Active"}],"relationships": [{"relTypeId": "RE6","relTypeName": "IS OPERATED BY","targetOdsCode": "0DF","targetName": null}],"operationalStart": "2017-10-01"}
Claude / Codex skill to describe and setup this actor: SKILL.md
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 |
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), orlastChangeDatefor an incremental sync. - 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). |
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.
Pay per event. Check the Actor’s Pricing tab for the current per-run and per-organisation charges for your Apify plan before starting a run. Estimate a registry export using the current event rates and your intended record limit; the number of active providers changes over time.
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 the explicit
lastChangeDatefilter - 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.
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:
- UK Case Law Scraper — Find Case Law Judgments API
- SPAC Redemptions Scraper — SEC EDGAR Lifecycle Tracker
- PTAB Trials Scraper — USPTO IPR & PGR Decisions API
Fast setup and source code
Public repository · Agent setup skill.
Use the Actor’s current input form for filters, or start with its documented API example. Select the latest build. Read the dataset together with the run summary: a successful status alone does not show that every source was reached.
Restrictive filters or repeat-delivery suppression can produce zero results.
Product guides and related tools
Current costs
Current Actor event rates, checked 2026-10-16. USD; use the Pricing tab for your plan and later changes.
| Event | Free | Bronze | Silver | Gold | Platinum | Diamond |
|---|---|---|---|---|---|---|
Provider record (result) | $0.0001 | $0.0001 | $0.000075 | $0.00005 | $0.00005 | $0.00005 |
Actor start (actor-start) | $0.00005 | $0.00005 | $0.00005 | $0.00005 | $0.00005 | $0.00005 |
For example, 100 × result, 1 × actor-start totals $0.01005 in Actor event charges on Free. This is an arithmetic example, before any waiver; actual charged events depend on the run and selected options. It is not a promise of output or a charge for simply setting an item cap.
Your total is the charged Actor events, plus platform usage only if this Actor’s Pricing tab bills it separately, plus any external provider you use with your own key. Do not add platform usage twice when it is included. Apify credits and plan discounts affect your invoice. Set a run cost limit in Console before starting.
Independent tool. We built and run this Actor ourselves. The NHS does not operate, sponsor or endorse it, and its name is used only to say which source the data comes from.