# NHS Jobs Scraper (`boncross/nhs-jobs`) Actor

Scrape NHS Jobs vacancies by keyword and location. Just add a job title and a town or postcode and get employer, pay band, salary as numbers, contract type, working pattern, posting and closing dates, the full advert, person specification, contact and apply link. $0.80 per 1,000 jobs.

- **URL**: https://apify.com/boncross/nhs-jobs.md
- **Developed by:** [BonCross API](https://apify.com/boncross) (community)
- **Categories:** Jobs
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.56 / 1,000 job-details

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## NHS Jobs Scraper

### What is NHS Jobs Scraper?

NHS Jobs Scraper is for anyone who needs NHS vacancies as data: by trust, by band, by staff group. It searches NHS Jobs, the health service's vacancy board for England and Wales, and gives you each advert as a record with the employer, the Agenda for Change band, the salary as numbers, the contract, the working pattern, the closing date and the complete advert text.

You can ask it the way the NHS describes its own jobs. Band 5 and 6 nursing posts in Leeds. Part-time admin roles near a postcode. Every estates and ancillary vacancy in the country. Consultant posts above a salary.

Why it's a good pick:

- **You get the whole advert.** The job overview, main duties and detailed responsibilities, plus the person specification split into essential and desirable criteria by category. On a 1,000-advert run, 993 of 997 rows had all of it.
- **NHS Jobs does the filtering.** Pay band, staff group, contract type, working pattern and salary range go to the board, and you don't pay for an advert it filtered out.
- **It's fast.** The 1,000 newest adverts on the board, with full adverts, took 2 minutes 6 seconds on 7 October 2026.
- **A named person to contact.** The advert's contact (name, job title, email and phone) was on 993 of 997 rows.
- **$0.80 per 1,000 jobs with full adverts** on the Free plan, $0.56 on Business.

**BonCross Verified** means we tested this actor against the live site before release and kept the record:

- 42 of 42 live checks on its inputs and filters passed on 7 October 2026
- 21 searches run the same day across Manchester, Cardiff and Norwich: 87 jobs, 2 empty on the board too
- 12 of 12 sampled adverts matched the board's own pages, text and link
- Charges checked against the 87 rows

### How to scrape NHS Jobs

No code and no NHS Jobs account. It's a form.

1. [Sign up for a free Apify account](https://console.apify.com/sign-up).
2. Open [NHS Jobs Scraper](https://apify.com/boncross/nhs-jobs).
3. Add one or more keywords, such as "staff nurse" or "medical secretary".
4. Add a town, city or postcode and a distance. Leave the location empty and you search the whole board.
5. Choose filters if you want them: pay band, staff group, contract type, working pattern or salary.
6. Press Start, then download the vacancies as Excel, CSV, JSON, XML or HTML.

### ⬇️ Input

Each keyword is searched in each location. With no location, the whole of NHS Jobs is searched. With no keyword, you get every advert in the location. Full adverts are fetched unless you turn that off, and adverts that have already closed are dropped.

You set depth with two limits. "Max jobs per search" (default 100) applies to each keyword and location pair, so ten searches at 50 each can return 500. "Maximum results for the whole run" caps the jobs you're charged for in total, and 0 means no cap.

#### Band 5 and 6 nursing posts in two cities, closing soonest first

```json
{
  "keywords": ["staff nurse"],
  "locations": ["Manchester", "Leeds"],
  "distanceMiles": 15,
  "payBands": ["BAND_5", "BAND_6"],
  "contractTypes": ["Permanent"],
  "sortBy": "closing"
}
```

#### Part-time and fixed-term administrative posts near a postcode

```json
{
  "keywords": ["administrator", "receptionist", "medical secretary"],
  "locations": ["B15 2TH"],
  "distanceMiles": 10,
  "staffGroups": ["ADMINISTRATIVE_AND_CLERICAL"],
  "workingPatterns": ["part-time"],
  "contractTypes": ["Fixed-Term"],
  "sortBy": "distance"
}
```

#### Every estates and ancillary vacancy in England and Wales

```json
{
  "keywords": [""],
  "staffGroups": ["ESTATES_AND_ANCILLARY"],
  "maxJobsPerSearch": 2000
}
```

#### Consultant posts above a salary

```json
{
  "keywords": ["consultant psychiatrist", "consultant radiologist"],
  "payBands": ["CONSULTANT"],
  "salaryFrom": 100000,
  "sortBy": "salaryDesc"
}
```

#### A daily monitor with only-new results

```json
{
  "keywords": ["physiotherapist", "occupational therapist"],
  "locations": ["Bristol"],
  "distanceMiles": 25,
  "titleExclude": ["bank", "student"],
  "postedWithinDays": 2,
  "dedupStoreName": "nhs-therapy-bristol"
}
```

| Field | Notes |
|---|---|
| `keywords` | Job titles or words to search for. One search per keyword per location; an empty entry returns every advert. |
| `locations`, `distanceMiles` | Towns, cities or postcodes and a radius in miles (default 10). Leave `locations` empty for the whole board. A place may end with its nation after a comma ("Newport, Wales", "Newport, England") to say which of several same-named places you mean. |
| `maxJobsPerSearch`, `maxItems` | The per-search cap (default 100) and the run-wide cap (0 means none). |
| `sortBy` | `newest` (default), `closing`, `relevance`, `salaryDesc`, `salaryAsc` or `distance` (needs a location). With a cap, the order decides which adverts you get. |
| `payBands` | `BAND_2` to `BAND_9` (`BAND_8A` to `BAND_8D`), `CONSULTANT`, `SPECIALTY_DOCTOR`, `FOUNDATION_DOCTOR`, `SPECIALTY_REGISTRAR`, `DOCTOR_OTHER`, `SPECIALIST`, `VERY_SENIOR_MANAGER`. Several are combined as either-or. |
| `staffGroups` | `NURSING_AND_MIDWIFERY_REGD`, `MEDICAL_AND_DENTAL`, `ALLIED_HEALTH_PROF`, `ADMINISTRATIVE_AND_CLERICAL`, `CLINICAL_SERVICES`, `PROF_SCIENTIFIC_AND_TECHNICAL`, `HEALTHCARE_SCIENTISTS`, `ESTATES_AND_ANCILLARY`, `STUDENTS`. |
| `contractTypes` | `Permanent`, `Fixed-Term`, `Bank`, `Locum`, `Secondment`, `Apprenticeship`, `Training`, `Voluntary`, `Honorary`, `NHS Reservist`. |
| `workingPatterns` | `full-time`, `part-time`, `job-share`, `flexible-working`, `remote-working`, `compressed-hours`, `term-time-hours`, `annualised-hours`. |
| `payRanges`, `salaryFrom`, `salaryTo` | NHS Jobs' pay-range boxes in £ thousands (`"30-40"`, `"40-50"`, … `"100"` for £100,000 and over), or an exact range in whole pounds a year; either end may be left at 0. Hourly-paid adverts are matched on their yearly equivalent. |
| `postedWithinDays`, `closingAfter` | Keep adverts posted in the last N days, or closing on or after a date (`YYYY-MM-DD`). |
| `employers`, `titleInclude`, `titleExclude` | Text filters on the employer name and the job title, case-insensitive. They run on the search results, so an advert they remove costs only the scan fee. |
| `fetchDetails` | On (default) returns the full advert for each job. Off returns the search-result fields only, at the lower `job-listed` price. |
| `maxNewJobs`, `dedupStoreName` | A cap on jobs returned, and a named store that remembers what earlier runs returned so a scheduled run gives only new adverts. |
| `maxConcurrency`, `delayMs` | How many requests run at once (default 16), and an optional pause before each one (0 by default, at most 2,000 ms). |

#### For developers

- `employers`, `titleInclude` and `titleExclude` run on the search results. An advert they remove costs `job-scanned` only. So do duplicates and adverts an only-new store has already seen.
- `dedupStoreName` names the store that remembers what earlier runs returned. `maxNewJobs` caps how many a run returns.
- With a cap set, `sortBy` decides which adverts you get, not only their order.
- Rows arrive in a fixed order: keyword, then location, then the board's own ranking. An advert found by two of your searches is returned once, under the first.
- `maxConcurrency` (default 16, from 1 to 32) is the number of requests in flight. `delayMs` (default 0, at most 2,000 ms) pauses before each.
- When a place name was ambiguous, the run log and the `nhsPlaces` entry of the run's STATS record say which nation was searched.
- If Apify moves a run to another server part-way through, it continues from where it stopped, without returning or charging any job twice.

### What NHS vacancy data do you get?

| | |
|---|---|
| 💼 Job title and vacancy reference | 🏥 Employer: trust, GP practice or provider |
| 📍 Location, address, town and postcode | 💷 Salary minimum, maximum and period |
| Salary text as published | Pay band and pay scheme |
| Contract type and contract duration | Working pattern |
| 📅 Posting date | ⏳ Closing date |
| 📄 Full advert as text and HTML | Job overview and main duties |
| Person specification, essential and desirable | DBS check requirement |
| UK professional registration requirement | Employer accreditations |
| About the employer and its website | Names of the attached documents |
| ☎️ Contact name, job title, email and phone | 🔗 Apply link and advert URL |

### ⬆️ Output

One record per vacancy.

```json
{
  "source": "nhs",
  "sourceName": "NHS Jobs",
  "id": "C9372-26-0508",
  "reference": "372-FSS3134-A",
  "url": "https://www.jobs.nhs.uk/candidate/jobadvert/C9372-26-0508",
  "title": "Advanced Pharmacist - Women and Children",
  "employer": "Calderdale and Huddersfield NHS Foundation Trust",
  "location": "Huddersfield HD3 3EA",
  "address": "Huddersfield Royal Infirmary, Acre Street, Huddersfield, HD3 3EA, United Kingdom",
  "postcode": "HD3 3EA",
  "town": "Huddersfield",
  "salaryText": "£57,528 to £64,750 a year per annum, pro rata",
  "salaryMin": 57528,
  "salaryMax": 64750,
  "salaryPeriod": "year",
  "salaryCurrency": "GBP",
  "band": "Band 8a",
  "payScheme": "Agenda for change",
  "contractType": "Fixed term",
  "contractDuration": "12 months",
  "workingPattern": "Full-time, Flexible working",
  "postedAt": "2026-10-07",
  "closingAt": "2026-10-18",
  "summary": "We are looking for a dynamic, innovative, self-motivated individual to join our clinical pharmacy team as an Advanced Pharmacist - Women and Children. This is fixed term 12 month post to cover maternity leave…",
  "duties": "You will be registered as a pharmacist with the General Pharmaceutical Council and have successfully completed a Postgraduate Diploma in Clinical Pharmacy Practice or be able to demonstrate equivalent and transferrable experience…",
  "description": "Job overview\nWe are looking for a dynamic, innovative, self-motivated individual to join our clinical pharmacy team…\n\nMain duties of the job\nYou will be registered as a pharmacist with the General Pharmaceutical Council…\n\nDetailed job description and main responsibilities\n…",
  "descriptionHtml": "<h3>Job overview</h3>\n<p>We are looking for a dynamic, innovative, self-motivated individual to join our clinical pharmacy team…</p>…",
  "personSpecification": [
    { "category": "QUALIFICATIONS / TRAINING", "essential": ["MPharm or equivalent Degree in Pharmacy approved by General Pharmaceutical Council.", "Registered Pharmacist with the General Pharmaceutical Council", "…"], "desirable": ["Working on RPS or HEE advanced framework portfolio", "Management / Leadership training"] },
    { "category": "KNOWLEDGE, EXPERIENCE & EXPERTISE", "essential": ["Able to demonstrate significant relevant experience of clinical pharmacy practice", "…"], "desirable": ["Significant relevant and recent experience of Paediatrics"] }
  ],
  "personSpecificationText": "QUALIFICATIONS / TRAINING\nMPharm or equivalent Degree in Pharmacy approved by General Pharmaceutical Council.\nRegistered Pharmacist with the General Pharmaceutical Council…",
  "dbsCheck": "This post is subject to the Rehabilitation of Offenders Act (Exceptions Order) 1975 and as such it will be necessary for a submission for Disclosure to be made to the Disclosure and Barring Service (formerly known as CRB) to check for any previous criminal convictions.",
  "ukRegistration": "Applicants must have current UK professional registration. For further information please see\nNHS Careers website (opens in a new window).",
  "aboutEmployer": "Calderdale & Huddersfield NHS Foundation Trust (CHFT) is a dynamic and successful organisation with a strong organisational and personal development focus…",
  "employerAccreditations": ["Age Positive", "Apprenticeships", "Armed Forces Covenant", "CQC Good", "Disability Confident Employer", "Veteran Aware", "…"],
  "contact": { "name": "Lisa Hodgson", "jobTitle": "Head of Clinical Pharmacy Services", "email": "lisa.hodgson@cht.nhs.uk", "phone": "01484355769" },
  "employerWebsite": "https://www.cht.nhs.uk",
  "supportingDocuments": ["JD and PS.pdf (PDF, 504 KB)"],
  "applyUrl": "https://www.jobs.nhs.uk/candidate/jobadvert/C9372-26-0508/ats-direct-apply",
  "searchKeywords": "pharmacy technician",
  "searchLocation": "Manchester",
  "scrapedAt": "2026-10-07T17:11:16.316Z"
}
```

`description` is the whole advert: the job overview, the main duties and the detailed responsibilities, each under its own heading. The first two are also returned separately as `summary` and `duties`, and `descriptionHtml` is the same content as HTML.

| Column | Meaning |
|---|---|
| `id`, `reference` | The NHS Jobs advert id (it is in the URL) and the employer's own vacancy reference, where the advert gives one |
| `band`, `payScheme` | The pay band ("Band 5", "Consultant") and the scheme it belongs to ("Agenda for change", "Hospital medical and dental staff", "Other") |
| `salaryMin`, `salaryMax`, `salaryPeriod` | The salary as numbers, with `year`, `hour`, `day` or `session`. A single-figure salary has the same minimum and maximum. NHS Jobs ranks and filters hourly and sessional pay on a yearly equivalent of its own |
| `contractType`, `contractDuration` | "Permanent", "Fixed term", "Bank" and so on, and how long a fixed-term post lasts |
| `workingPattern` | Every pattern the advert offers, comma-separated, e.g. "Full-time, Part-time, Flexible working" |
| `personSpecification` | A list of `{category, essential[], desirable[]}`; `personSpecificationText` is the same content as plain text |
| `dbsCheck`, `ukRegistration` | The criminal-record check sentence and the professional registration requirement, as the advert states them |
| `employerAccreditations` | The schemes the employer is accredited under, e.g. "Disability Confident Employer", "Veteran Aware" |
| `supportingDocuments` | The names of the documents attached to the advert, such as the job description (downloaded from the advert itself) |
| `searchKeywords`, `searchLocation` | The keyword and location of the search that found the advert |
| `source`, `sourceName` | Always `nhs` and "NHS Jobs" |
| `detailError` | Present only when an advert could not be read; the row then carries the search-result fields alone |

### How many NHS jobs can you get, and how fast?

Whole runs on Apify with full adverts on, 7 October 2026:

| Run | Jobs returned | Time |
|---|---|---|
| The 100 newest adverts on the whole board | 100, 98 with the full advert | 12 seconds |
| 7 keywords in 3 cities within 20 miles, up to 8 per search (21 searches) | 87, all with the full advert | 9 seconds |
| The 1,000 newest adverts on the whole board | 997, 993 with the full advert | 2 minutes 6 seconds |

Larger runs settle at about 470 jobs a minute with full adverts.

Not every advert states every field. On the 1,000-advert run (997 rows):

- Salary numbers on 871. The others publish text such as "depending on experience".
- Pay band on 710. Most GP practices, hospices and private providers aren't on Agenda for Change or the medical pay scales, and state none.
- Person specification, contact and the full advert on 993, and the employer website on 992.
- Apply link and employer reference on 800, attached document names on 747.
- Registration requirement on 555, contract duration on 205.

### How much does it cost to scrape NHS Jobs?

| Event | Free plan | Starter | Scale | Business |
|---|---|---|---|---|
| `actor-start`, per run | $0.002 | $0.002 | $0.002 | $0.002 |
| `job-scanned`, per result card seen, before filters | $0.00005 | $0.00005 | $0.00005 | $0.00005 |
| `job-detail`, per job returned with its full advert | $0.0008 | $0.00072 | $0.00064 | $0.00056 |
| `job-listed`, per job returned from search-result data only | $0.0002 | $0.00018 | $0.00016 | $0.00014 |

Enterprise plans pay the Business price.

**$0.80 per 1,000 jobs with full adverts** on the Free plan and $0.56 on Business. Proxy usage is included, and so is platform usage.

Three real shapes of run:

- 21 searches that scan 128 cards and return 87 full adverts: $0.002 + 128 × $0.00005 + 87 × $0.0008 = **$0.078** ($0.057 on Business).
- The 1,000 newest adverts, with 1,000 cards scanned, 993 full adverts and 4 listing-only: $0.002 + 1,000 × $0.00005 + 993 × $0.0008 + 4 × $0.0002 = **$0.85** ($0.61 on Business).
- A daily monitor that scans 300 cards and finds 20 new adverts: $0.002 + 300 × $0.00005 + 20 × $0.0008 = **$0.033** ($0.028 on Business).

Duplicates, adverts you've already had, and adverts removed by the employer, title or date filters cost the scan fee only.

With the $5 of monthly credits on Apify's Free plan you can pull about 5,700 NHS jobs with full adverts, in runs of 100.

### FAQ

#### Does it cover every NHS vacancy?

Every advert on NHS Jobs, which is where NHS trusts, integrated care boards, GP practices and many independent providers in England and Wales advertise. NHS Scotland and Health and Social Care Northern Ireland use their own boards, so a Scottish or Northern Irish location returns few adverts or none.

#### How do I get only one trust's vacancies?

Put the trust's name, or part of it, in "Employers". It's matched against the employer name without regard to case. A name copied from this actor's output filters as you'd expect: the name on the search result and on the advert were identical on 60 of 60 adverts we compared.

#### My "nurse" search returned jobs that aren't nursing jobs. Why?

NHS Jobs matches a keyword against the whole advert, so a porter role that mentions nurses qualifies. Add "Title must contain" to keep matching titles only, or sort by relevance to put them first.

#### Can I filter by band?

"Pay bands" takes Band 2 to Band 9, including 8a to 8d, and the medical grades. Several bands are combined as either-or, and they combine with staff group, contract type, working pattern and salary.

#### Is the salary a number?

Where the advert states one: 871 of 997 on the measured run. `salaryPeriod` says whether the figure is per year, hour, day or session, and `salaryText` always has the original wording.

#### Which Newport did it search?

A place name the board can't pin to one place is looked up in each UK nation and searched in the nation with the most adverts for it. To choose, write the nation after a comma: "Newport, England". For a smaller place with a common name ("Richmond"), give a postcode or a fuller name ("Richmond upon Thames").

#### How do I get only new vacancies each day?

Give "Only-new store name" a name and schedule the run. Each run returns the adverts it hasn't returned before, and the ones it skips cost the scan fee.

#### What are the limits?

- About one advert in eight states its salary as text only. Sorted by lowest salary, NHS Jobs lists those first.
- A location NHS Jobs doesn't know at all returns no rows for that search, with a line in the log.
- Sorted by closing date, NHS Jobs repeats some adverts from page to page when many close on one day. Repeats are returned once and cost the scan fee, so such a search can return fewer jobs than its cap.
- Attached documents are named, not downloaded. You can get them from the advert page.
- An advert withdrawn between the search and the detail fetch arrives with the search-result fields and a `detailError`, charged as `job-listed`.
- NHS Jobs has planned maintenance windows, seen between 07:00 and 09:30 UK time. A search run during one writes an uncharged row with the search and an `error` message.
- A text column can contain stray markup where the employer's own advert does (about 2 in 1,000 adverts).

#### Do I need proxies, an NHS Jobs account or an API key?

No. The board is read as any logged-out visitor sees it. Proxy usage is included and you set nothing up.

#### Is scraping NHS Jobs legal?

NHS Jobs serves these vacancies to any visitor, and they're published to be found. Scraping public data is generally legal. The contact names and emails inside adverts are personal data, so treat them as the law where you operate requires.

### Monitors, boards and your own code

Healthcare recruiters and job boards mostly run this as a daily monitor: an Apify Schedule, an only-new store, and a webhook or Slack message with the day's new posts. Workforce analysts tend to pull a band or staff group nationally each week and send it to Google Sheets or through Airbyte.

From code, `POST https://api.apify.com/v2/acts/boncross~nhs-jobs/run-sync-get-dataset-items` with the input JSON as the body gives you the vacancies in the response on short runs. For the bigger pulls use the standard run endpoints or the `apify-client` package (NPM for Node.js, PyPI for Python).

Through the Apify MCP server at `mcp.apify.com`, an AI agent can call the actor as a tool and, say, list this week's Band 5 nursing vacancies in Bristol with their closing dates.

### More from BonCross

- [UK Government & NHS Jobs Scraper](https://apify.com/boncross/uk-public-sector-jobs): NHS Jobs, Civil Service Jobs (every department) and Ministry of Justice vacancies in…
- [Reed.co.uk Jobs Scraper](https://apify.com/boncross/reed-jobs-scraper): Reed.co.uk jobs by keyword and location with Reed's own filters: salary, contract,…
- [Workday Jobs Scraper](https://apify.com/boncross/workday-jobs): Every open job from a Workday careers board.
- [Ashby Jobs Scraper](https://apify.com/boncross/ashby-jobs): Every open job on an Ashby careers board.
- [ATS Jobs Scraper](https://apify.com/boncross/ats-jobs): Jobs from any company careers page.
- [Accor Hotel Details Scraper](https://apify.com/boncross/accor-hotel-details): Full Accor hotel records.

### Your feedback

If a band, a salary or a trust name comes back wrong, send us the advert link on the Issues tab in Apify Console. We read every issue and check it against the live advert.

# Changelog

This Actor's version history is a separate document: https://apify.com/boncross/nhs-jobs/changelog.md

# Actor input Schema

## `keywords` (type: `array`):

Job titles, skills or reference words to search NHS Jobs for, for example "staff nurse", "healthcare assistant" or "medical secretary". One search per keyword per location. Leave empty to take every advert in the locations.

## `locations` (type: `array`):

Town, city or postcode; each is searched within the distance below. Leave empty to search the whole of NHS Jobs. A name shared by places in different nations ("Newport") is searched where NHS Jobs has the most adverts for it; add the nation after a comma to choose ("Newport, England", "Newport, Wales").

## `distanceMiles` (type: `integer`):

Radius around each location.

## `maxJobsPerSearch` (type: `integer`):

Cap per keyword and location.

## `maxItems` (type: `integer`):

A run-wide ceiling across every search. The per-search limit above applies to each keyword and location separately, so ten searches at 50 each returns 500; set this to cap the total. Zero means no run-wide limit. Rows that report a search that could not be run are not counted here and are never charged for.

## `sortBy` (type: `string`):

The order NHS Jobs lists each search in: newest first, closing soonest, most relevant, salary, or nearest first (nearest to each location; needs a location). With a cap, this decides which adverts you get.

## `payBands` (type: `array`):

Agenda for Change bands and medical grades. Several are combined as either-or.

## `staffGroups` (type: `array`):

NHS staff groups, for example nursing and midwifery, administrative and clerical, or estates and ancillary. Several are combined as either-or.

## `contractTypes` (type: `array`):

Permanent, fixed-term, bank and the other contract types NHS Jobs lists.

## `workingPatterns` (type: `array`):

Full-time, part-time, job share, flexible, home or remote working and the other patterns NHS Jobs lists.

## `payRanges` (type: `array`):

NHS Jobs' pay-range boxes, in £ thousands a year ("40-50" is £40,000 to £50,000; "100" is £100,000 and over). Hourly-paid adverts are matched on their yearly equivalent.

## `salaryFrom` (type: `integer`):

Lower end of a salary range, in whole pounds a year. 0 means no lower end.

## `salaryTo` (type: `integer`):

Upper end of a salary range, in whole pounds a year. 0 means no upper end.

## `postedWithinDays` (type: `integer`):

Drop adverts posted more than this many days ago. 0 means any age.

## `closingAfter` (type: `string`):

Drop adverts that close before this date. Adverts that have already closed are always dropped.

## `employers` (type: `array`):

Keep only adverts whose employer name contains one of these, case-insensitive, for example "Guy's and St Thomas'" or "Ambulance Service". Applied before a job is charged as detailed.

## `titleInclude` (type: `array`):

Keep only titles containing one of these, case-insensitive.

## `titleExclude` (type: `array`):

Drop titles containing any of these, case-insensitive.

## `fetchDetails` (type: `boolean`):

On, each row carries the full advert: description, duties, person specification, band, address, contact and apply link, charged as job-detail. Off, you get the search-result fields only (title, employer, location, salary, dates, contract type and working pattern), charged at the cheaper job-listed rate.

## `maxNewJobs` (type: `integer`):

Stop after this many jobs have been returned. 0 means no cap.

## `dedupStoreName` (type: `string`):

Name a key-value store and the actor remembers every job it has returned, so scheduled runs give you only new adverts.

## `maxConcurrency` (type: `integer`):

How many requests run at once across the whole run. The default is the fastest level measured clean; lower it only if a run reports refusals or errors.

## `delayMs` (type: `integer`):

Optional pause before each call, in milliseconds, up to 2,000. 0 (the default) runs as fast as the parallel limit allows.

## Actor input object example

```json
{
  "keywords": [
    "staff nurse"
  ],
  "locations": [
    "London"
  ],
  "distanceMiles": 10,
  "maxJobsPerSearch": 100,
  "maxItems": 0,
  "sortBy": "newest",
  "payBands": [],
  "staffGroups": [],
  "contractTypes": [],
  "workingPatterns": [],
  "payRanges": [],
  "salaryFrom": 0,
  "salaryTo": 0,
  "postedWithinDays": 0,
  "employers": [],
  "titleInclude": [],
  "titleExclude": [],
  "fetchDetails": true,
  "maxNewJobs": 0,
  "maxConcurrency": 16,
  "delayMs": 0
}
```

# Actor output Schema

## `dataset` (type: `string`):

NHS jobs: every row the run returned.

## `stats` (type: `string`):

Counts for the run: what was searched, scanned, filtered and returned.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "keywords": [
        "staff nurse"
    ],
    "locations": [
        "London"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("boncross/nhs-jobs").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "keywords": ["staff nurse"],
    "locations": ["London"],
}

# Run the Actor and wait for it to finish
run = client.actor("boncross/nhs-jobs").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "keywords": [
    "staff nurse"
  ],
  "locations": [
    "London"
  ]
}' |
apify call boncross/nhs-jobs --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,boncross/nhs-jobs"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/IxIdSU3vRtYIAkCHj/builds/cRgsSQg9Vh7ZDsOdI/openapi.json
