NHS ODS Scraper: GP Practices, Pharmacies & Dentists avatar

NHS ODS Scraper: GP Practices, Pharmacies & Dentists

Deprecated

Pricing

from $0.05 / 1,000 provider records

Go to Apify Store
NHS ODS Scraper: GP Practices, Pharmacies & Dentists

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

Nomad.Devs

Maintained by Community

Actor stats

0

Bookmarked

3

Total users

1

Monthly active users

6 days ago

Last modified

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.

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 LS expands to every Leeds district in one run.
  • Incremental syncs: filter by lastChangeDate to 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

  1. Choose the mode: search (filters) or detail (fetch full records for a list of ODS codes).
  2. 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.
  3. 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 is true, with no extra cost per record.
  • includeGeo - Add latitude and longitude from the free postcodes.io service. Default is false.
  • maxItems - Maximum number of organisations to return, each one billed. Default is 100. 0 means 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:

FieldMeaning
odsCodeOfficial ODS organisation code (e.g. L81008)
nameRegistered organisation name
addressLine1/2, town, county, country, uprnFull registered address
phone, websiteRegistered contacts where the registry holds them
primaryRoleId / primaryRoleDescriptionProvider type (e.g. GP practice, pharmacy)
rolesAll ODS roles with names, primary flag and status
relationshipsOrganisation-hierarchy links — e.g. an NHS trust site "IS OPERATED BY" its parent trust
postCodeRegistered postcode
statusActive / Inactive
lastChangeDateLast registry change (useful for incremental syncs)
operationalStart/End, legalStart/EndOperational and legal dates
orgLinkCanonical 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

EnrichmentAddsTurn on with
Add lat/long geocodinglatitude, 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 namesFills 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

  1. Pick a mode: search (filters below) or detail (pass odsCodes).
  2. 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), or lastChangeDate for an incremental sync.
  3. 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 ApifyClient
client = 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

FieldTypeDefaultDescription
modeselectsearchsearch queries the registry with the filters below; detail fetches full records for the ODS codes in odsCodes.
namestring–Case-insensitive substring match on the organisation name.
postCodestring(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.
providerTypeselectanyCommon 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).
specialtystring–Free-text specialty (e.g. pharmacy, hospice, dental), matched against the ODS role vocabulary. Ignored if providerType (non-any) or roleId is set.
roleIdstring–Any official ODS role code (advanced) — overrides providerType and specialty.
statusselectactiveactive, inactive, or all.
lastChangeDatestring (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).
odsCodesarray–ODS codes to fetch in detail mode (e.g. L81008, RA7, FLM49).
includeDetailsbooleantrueEnrich 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.
includeGeobooleanfalseAdd latitude/longitude by geocoding each postcode via the free postcodes.io service. Best-effort — unresolved postcodes return null.
includeRelationshipNamesbooleanfalseResolve relationships[].targetName (needs detail data) — one extra API call per unique related organisation.
maxItemsinteger100Hard cap on organisations returned. Each result is billed, so start low and raise once your filter is dialed in. 0 = no cap.
concurrencyinteger8(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 lastChangeDate filter
  • 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 relationships field (with includeRelationshipNames for resolved names)
  • Geospatial analysis and mapping via optional latitude/longitude geocoding (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.

More official-registry and government-data scrapers from the same family:

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.

Current costs

Current Actor event rates, checked 2026-10-16. USD; use the Pricing tab for your plan and later changes.

EventFreeBronzeSilverGoldPlatinumDiamond
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.