NHS UK $0.8💰 URL | Keyword |  Deep Search Scraper avatar

NHS UK $0.8💰 URL | Keyword | Deep Search Scraper

Pricing

from $0.08 / 1,000 results

Go to Apify Store
NHS UK $0.8💰 URL | Keyword |  Deep Search Scraper

NHS UK $0.8💰 URL | Keyword | Deep Search Scraper

From $0.8/1k. Scrape nhs.uk Jobs listings into a flat dataset. Extract 50+ fields, including pay band, salary, full description, essential and desirable criteria, PDFs, sponsorship, DBS, employer details, contacts, and apply URL. Search by filters or use any URL.

Pricing

from $0.08 / 1,000 results

Rating

0.0

(0)

Developer

Abot API

Abot API

Maintained by Community

Actor stats

0

Bookmarked

8

Total users

2

Monthly active users

8 days ago

Last modified

Share

NHS Jobs UK Scraper: Salary, Person Specification and Apply Details for Every Listing

NHS Jobs UK Scraper turns jobs.nhs.uk into a clean, structured feed of health service vacancies. Get 50+ fields per listing: salary parsed into numbers, pay band, contract and working pattern, the full person specification with essential and desirable criteria split out, supporting document IDs, sponsorship, UK registration and DBS flags, employer address and contact details, and the direct apply link. Build a search by keyword, location and seven facet filters, or paste any jobs.nhs.uk URL, then run it once, on a schedule with only new and changed listings returned, or straight into your own app through the API.

Why This Scraper?

  • 50+ fields per record, including fields many NHS Jobs feeds drop: desirable person-specification criteria kept separate from essential, supporting document IDs, sponsorship eligibility, UK registration requirement, DBS check requirement, salary parsed into salaryMin / salaryMax / salaryPeriod, and the full address split into postcode, town, county and address lines.
  • Two ways to start a run: build a search with keyword, location and up to seven facet filters, or paste any jobs.nhs.uk URL (a search-results page or a single advert) and let the actor take it from there.
  • Resilient to interruption: each finished record is written to the dataset as soon as it is parsed, so an interrupted run still keeps everything it managed to finish.
  • Forward pagination that respects your input: starts from the page number in a pasted URL when given one, walks forward up to Max pages if you set it, and stops naturally once a page repeats already-collected listings or comes back empty, no fixed page ceiling required.
  • Incremental mode for recurring monitoring: schedule the same search and get only new and changed rows by default, with unchanged listings suppressed and expired or reappeared listings tracked correctly across runs.
  • Resume without re-paying: point resumeFromRunId at a prior run or dataset and this run skips every listing it already saved, appending only new ones.
  • Clear failure signals instead of silent gaps: if the connection setup itself fails, or a pasted resume run ID cannot be read, the run stops immediately with an explanation. A run that legitimately finds zero listings still finishes normally, with a status message describing what happened.

Use Cases

  • Healthcare recruiters and agencies: track new roles across NHS Trusts by staff group, pay band or region to spot hiring surges.
  • Job boards and aggregators: mirror jobs.nhs.uk into your own listings pages with salary, contract and address fields already parsed out.
  • Workforce and pay research: build a dataset of salary bands, contract types and working patterns across the NHS to analyse pay trends by region or specialty.
  • International recruitment and sponsorship desks: filter for roles with sponsorship, UK registration and DBS flags to shortlist positions that fit a candidate's circumstances.
  • Compliance and application review: pull the full essential and desirable criteria per category to check applications against requirements before submitting.

Data You Get

Sample shape: values are illustrative placeholders, not from a live listing.

FieldExample
id"C0000-00-0000"
url"https://www.jobs.nhs.uk/candidate/jobadvert/C0000-00-0000"
referenceNumber"000-X000-XXX"
title"Sample Job Title"
employer"Sample NHS Foundation Trust"
location"London SW1A 1AA"
postcode"SW1A 1AA"
salary"£25,000 to £30,000 a year"
salaryMin25000
salaryMax30000
salaryPeriod"per annum"
payScheme / grade"Agenda for change" / "Band 0"
contractType"Permanent"
workingPattern"Full-time"
datePosted / datePostedIso"01 January 2026" / "2026-01-01"
closingDate / closingDateIso"15 January 2026" / "2026-01-15"
applyUrl"https://www.jobs.nhs.uk/candidate/jobadvert/C0000-00-0000/ats-direct-apply"
contactEmail / contactPhone"recruitment@example.org" / "+44 0000 000000"
personSpecification[0]{"category": "Qualifications", "essentials": ["Sample essential criterion"], "desirables": ["Sample desirable criterion"]}
supportingDocuments[0]{"name": "Job Description.pdf", "sizeText": "PDF, 000 KB", "documentId": "0000000"}
sponsorshipAvailable / ukRegistrationRequired / dbsCheckRequiredtrue / true / true
basicInfo{"id": "...", "title": "...", ...} (a compact summary of the same record)
scrapedAt"2026-01-01T00:00:00.000Z"

Each record also carries the employer's website, logo image, and full address broken into addressLine1, addressLine2, addressTown, addressCounty and addressLines; a salaryQualifier string (for example "inclusive of HCAS"), contractDuration and interviewDate; the contact person's name and job title alongside a combined contactDetails object, plus an employerDetails object; the job overview, main duties, about-organisation and full job description each as both HTML and plain text; a privacyPolicyUrl and a saveJobUrl. When Incremental mode is on, every row additionally carries changeType, changedFields, firstSeenAt and lastSeenAt.

How to Use

  1. Pick a mode: search (keyword, location and facet filters) or url (paste any jobs.nhs.uk URL).
  2. Fill in the fields for that mode, and leave Fetch detail pages on if you want the full 50+ field record rather than just the search-result fields.
  3. Set Max listings (and Max pages, if you want an explicit cap) to control run size and cost, then click Start.
  4. Download the dataset as JSON, CSV or Excel, or read it through the API.

Search by keyword and location:

{
"mode": "search",
"keywords": ["nurse"],
"location": "Manchester",
"distance": "10",
"maxPages": 2,
"fetchDetails": true
}

Search by pay band and contract type:

{
"mode": "search",
"keywords": ["doctor"],
"location": "London",
"distance": "20",
"payBands": ["CONSULTANT", "SPECIALTY_DOCTOR"],
"contractTypes": ["Permanent", "Fixed-Term"],
"workingPatterns": ["full-time"],
"maxPages": 3,
"maxListings": 100
}

Browse a whole staff group across the UK:

{
"mode": "search",
"keywords": [],
"location": "",
"distance": "",
"staffGroups": ["NURSING_AND_MIDWIFERY_REGD"],
"sortBy": "publicationDateDesc",
"maxListings": 200
}

Paste a jobs.nhs.uk URL:

{
"mode": "url",
"urls": [
"https://www.jobs.nhs.uk/candidate/search/results?keyword=physiotherapist&location=Birmingham&distance=20",
"https://www.jobs.nhs.uk/candidate/jobadvert/C0000-00-0000"
],
"maxPages": 2,
"fetchDetails": true
}

Run it from your code

Python:

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/jobs-nhs-uk-scraper").call(run_input={"mode": "search", "keywords": ["nurse"], "location": "Manchester", "distance": "10"})
for job in client.dataset(run["defaultDatasetId"]).iterate_items():
print(job["title"], job["employer"], job["salary"])

JavaScript:

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/jobs-nhs-uk-scraper').call({ mode: 'search', keywords: ['nurse'], location: 'Manchester', distance: '10' });
const { items } = await client.dataset(run.defaultDatasetId).listItems();

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.

Resume and recurring updates

  • Resume (resumeFromRunId) continues one interrupted run: paste its run or dataset ID and the actor loads the listings it already saved, skipping them, so this run only appends new listings.
  • Incremental mode (incrementalMode) is for scheduling this actor to monitor the same search or URL list over time. Turn it on and every pushed row is tagged changeType (NEW, UPDATED with changedFields, UNCHANGED, REAPPEARED or EXPIRED), plus firstSeenAt and lastSeenAt. The baseline is remembered by the actor itself, keyed on your search filters and the fetchDetails setting, or on stateKey if you set one. UNCHANGED rows are suppressed by default (not pushed, not billed); turn on emitUnchanged to get them anyway. EXPIRED only fires when emitExpired is on and the run completed a full scan of the tracked search, meaning no resumeFromRunId and no maxListings / maxPages cap reached; a partial run skips EXPIRED detection that time rather than guessing. REAPPEARED can only occur after a listing was first marked gone by an earlier emitExpired run that itself completed a full scan; only once a listing has been marked gone that way does a later sighting of it come back tagged REAPPEARED instead of NEW. Combining resumeFromRunId with the very first incremental run bootstraps the baseline from that resumed dataset, but the run fails immediately if the chosen state key already has saved state from an earlier run. With Incremental mode off, output is exactly as a normal run.

Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step after the scrape, the Apify dataset is never changed.

What gets written to the connector: a condensed, human-readable summary of each record, not the full JSON. Each item becomes one entry with a title and its key fields flattened to plain text. The complete record always stays in the Apify dataset.

  1. Authorize a connector once under Apify -> Settings -> Integrations (Notion, Linear, Airtable, or Apify).
  2. Select it in the "Pipe results into your apps" input field. If the picker is empty, you haven't authorized a connector yet.
  3. For Notion, also set notionParentPageUrl to the page where items should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

Input Parameters

ParameterTypeDefaultDescription
modeenumsearchsearch or url. Fields for the other mode are ignored.
keywordsarray of stringsnone (prefill: ["doctor"])Free-text job-title or skill keywords. One scrape per keyword. Empty means browse without a keyword filter.
locationstringnone (prefill: "London")Town, city, county or postcode. Empty means nationwide.
distanceenum10Search radius in miles (5/10/20/30/50/100), or empty for all of the UK.
payBandsarray of enumnoneMulti-select. Agenda for Change bands and medical/dental grades.
staffGroupsarray of enumnoneMulti-select NHS staff group filter.
contractTypesarray of enumnoneMulti-select contract type filter.
workingPatternsarray of enumnoneMulti-select working pattern filter.
payRangesarray of enumnoneMulti-select salary band filter, in thousands of GBP.
employerNamestringnoneFilter by NHS Trust or employer name, partial match.
covidJobsOnlybooleanfalseLimit to roles tagged as COVID-related.
sortByenum"" (Best Match)How jobs.nhs.uk orders the results.
urlsarray of stringsnone (prefill: one example search URL)URL mode only. Any jobs.nhs.uk search-results or single-advert URL.
maxPagesinteger0Pages walked per keyword or per pasted URL. 0 means unlimited: the walk stops once a page comes back empty, repeats listings already collected, or Max listings is reached.
maxListingsinteger20Stop after this many listings across all searches. 0 means unlimited.
resumeFromRunIdstringnoneContinue one interrupted run: paste its run or dataset ID.
incrementalModebooleanfalseRecurring monitoring of the same search across scheduled runs.
stateKeystringnoneManually name the incremental baseline instead of auto-deriving it from the search filters.
emitUnchangedbooleanfalseIncremental mode only. Also push (and bill) UNCHANGED rows.
emitExpiredbooleanfalseIncremental mode only. Push a listing once as EXPIRED after a complete scan finds it gone.
fetchDetailsbooleantrueFetch each advert's detail page for the full 50+ field record. Off returns search-result-only fields.
proxyobjectResidential, GB (applied automatically when left empty)Connection settings.
mcpConnectorsarraynoneOptional: send a summary of each record to apps you authorized under Integrations.
notionParentPageUrlstringnoneNotion connector only: page under which records are created.
maxNotifyListingsinteger50Cap on items written to each connector per run. Does not affect the dataset.

Output Example

Sample shape: values are illustrative placeholders, not from a live listing.

{
"id": "C0000-00-0000",
"url": "https://www.jobs.nhs.uk/candidate/jobadvert/C0000-00-0000",
"referenceNumber": "000-X000-XXX",
"title": "Sample Job Title",
"employer": "Sample NHS Foundation Trust",
"employerWebsite": "https://example.org/working-with-us",
"employerLogoDataUri": "data:image/png;base64,...",
"location": "London SW1A 1AA",
"postcode": "SW1A 1AA",
"addressLine1": "Sample Hospital",
"addressLine2": null,
"addressTown": "London",
"addressCounty": null,
"addressLines": ["Sample Hospital", "London", "SW1A 1AA"],
"salary": "£25,000 to £30,000 a year",
"salaryQualifier": "inclusive of HCAS",
"salaryMin": 25000,
"salaryMax": 30000,
"salaryPeriod": "per annum",
"payScheme": "Agenda for change",
"grade": "Band 0",
"contractType": "Fixed-Term",
"contractDuration": "12 months",
"workingPattern": "Full-time",
"datePosted": "01 January 2026",
"datePostedIso": "2026-01-01",
"closingDate": "15 January 2026",
"closingDateIso": "2026-01-15",
"interviewDate": null,
"applyUrl": "https://www.jobs.nhs.uk/candidate/jobadvert/C0000-00-0000/ats-direct-apply",
"contactName": "Jane Doe",
"contactJobTitle": "Recruitment Lead",
"contactEmail": "recruitment@example.org",
"contactPhone": "+44 0000 000000",
"contactDetails": {
"jobTitle": "Recruitment Lead",
"name": "Jane Doe",
"email": "recruitment@example.org",
"phone": "+44 0000 000000"
},
"employerDetails": {
"name": "Sample NHS Foundation Trust",
"website": "https://example.org/working-with-us",
"address": ["Sample Hospital", "London", "SW1A 1AA"],
"postcode": "SW1A 1AA"
},
"jobOverviewHtml": "<p>Sample short job overview HTML appears here.</p>",
"jobOverviewText": "Sample short job overview text appears here.",
"mainDutiesHtml": "<p>Sample main duties HTML appears here.</p>",
"mainDutiesText": "Sample main duties text appears here.",
"aboutOrganisationHtml": "<p>Sample about-us HTML appears here.</p>",
"aboutOrganisationText": "Sample about-us text appears here.",
"jobDescriptionHtml": "<p>Sample full job description HTML appears here.</p>",
"jobDescriptionText": "Sample full job description text appears here.",
"personSpecification": [
{
"category": "Qualifications",
"essentials": ["Sample essential criterion"],
"desirables": ["Sample desirable criterion"]
},
{
"category": "Experience",
"essentials": ["Sample essential criterion"],
"desirables": []
}
],
"supportingDocuments": [
{ "name": "Job Description.pdf", "sizeText": "PDF, 000 KB", "documentId": "0000000", "downloadFormUrl": "https://www.jobs.nhs.uk/candidate/search/save-job" }
],
"privacyPolicyUrl": "https://example.org/privacy",
"sponsorshipAvailable": true,
"ukRegistrationRequired": true,
"dbsCheckRequired": true,
"saveJobUrl": "https://www.jobs.nhs.uk/candidate/search/save-job/C0000-00-0000",
"basicInfo": {
"id": "C0000-00-0000",
"title": "Sample Job Title",
"url": "https://www.jobs.nhs.uk/candidate/jobadvert/C0000-00-0000",
"employer": "Sample NHS Foundation Trust",
"location": "London SW1A 1AA",
"salary": "£25,000 to £30,000 a year",
"datePosted": "01 January 2026",
"closingDate": "15 January 2026",
"contractType": "Permanent",
"workingPattern": "Full-time"
},
"scrapedAt": "2026-01-01T00:00:00.000Z"
}

Plan Requirement

This actor's default connection setting already routes through the UK residential group required for reliable access (groups: ["RESIDENTIAL"], country: "GB"), so most runs need no changes. At startup the actor also checks that setting and automatically falls back to the standard Apify proxy without a group, and finally to a direct connection, if the first choice cannot reach the site from your account. A run that is refused everywhere still finishes normally and explains what to try. If your plan's connection allowance does not include the residential group, uncheck automatic routing under Connection and paste your own UK connection URLs instead.

FAQ

How much does it cost?

You pay per record returned, plus Apify platform compute and connection usage. The Pricing tab on the actor page shows current rates. Use Max listings to cap the size, and cost, of any run.

This actor collects only publicly available job listings from jobs.nhs.uk. You are responsible for how you use the data: follow the site's terms and the laws that apply to you, particularly around handling recruiter and applicant contact details, and get legal advice if you plan commercial redistribution.

Can I get only new or changed listings on a schedule?

Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Each run then returns only NEW and UPDATED listings by default, and UNCHANGED ones are not billed unless you turn Emit unchanged on.

What's the difference between Resume and Incremental mode?

resumeFromRunId continues ONE specific interrupted run: paste its run or dataset ID and this run skips everything that run already saved. Incremental mode is for scheduling the SAME search repeatedly and getting a change feed across runs. They solve different problems, and can be combined on the very first incremental run to seed its baseline from a resumed dataset.

Why did my run finish with zero listings instead of failing?

If every request this run made was refused, or the search genuinely has no matching listings, the run still finishes normally with zero rows: a status message on the run explains what happened and how to fix a persistent connection problem. The run only fails outright when something is set up wrong before scraping even starts, for example a resumeFromRunId that does not point at a run or dataset this account can read, or a connection configuration error.

Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push a summary of results into Notion, Linear or Airtable.

🔗 Want more Jobs data?

Pair this actor with these related scrapers from the same team:

💼 Jobs.cz
Scrape Czech job listings from Jobs.cz. Search builder (keyword, location, field, salary...
💼 Reed.co.uk Scraper
Scrape Reed.co.uk jobs, companies, courses, and reviews. Search by keyword, location...
🏠 Rightmove
From $1/1K. Fast, reliable Rightmove.co.uk scraper for sale, rent, and sold-price...
💼 s1jobs Scraper
Scrape jobs from s1jobs.com across Scotland and the UK. Search by keyword, location, or...
🧩 Zoopla Scraper
Scrape UK property from Zoopla: for sale, to rent and Land Registry sold prices. Search...
🏠 Jitty UK AI Property Scraper
Scrape residential property listings from jitty.com, extract properties across the UK...

👉 Browse all abotapi scrapers

💬 Support & custom scrapers

  • 🐞 Found a bug or a missing field? Open a ticket on the Issues tab. We usually reply within hours.
  • 🛠️ Need another site, extra fields or a private build? Email abotapi@proton.me or message Telegram @abotapi.
  • ⭐ Enjoying it? A quick review on the actor page helps other users find it.