# NHS Jobs Scraper — Vacancies & Recruiter Contacts (`scrapersdelight/nhs-jobs-scraper`) Actor

Scrape jobs.nhs.uk: every live NHS and private-healthcare vacancy with band, pay scheme, salary, contract type, employer address & website — plus the recruiter contact block (name, job title, email, phone) that 9 in 10 adverts publish. Filter by band, staff group, locum/bank, location & date.

- **URL**: https://apify.com/scrapersdelight/nhs-jobs-scraper.md
- **Developed by:** [Scrapers Delight](https://apify.com/scrapersdelight) (community)
- **Categories:** Jobs, Lead generation, Business
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.80 / 1,000 per job returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## 🏥 NHS Jobs Scraper — vacancies **and** the recruiter you can actually email

Scrape **jobs.nhs.uk**, the UK's official NHS Jobs board, into a clean table: every live vacancy with
its employer, location, salary, Agenda-for-Change band, contract type and closing date — **plus the
contact block every advert publishes**: a named person, their job title, their work email and often a
direct phone number.

That last part is the difference. Most job scrapers give you a posting. This one gives you a
**person at a healthcare employer, with a role and a live budget**.

> **Measured on 2026-09-04:** 11,613 live adverts across 1,162 pages. In one 24-hour window the board
> took **1,182 new adverts from 370 distinct employers** — 705 of them NHS trusts, boards and ICBs,
> 477 private healthcare groups, hospices, care-home operators and GP practices. This Actor pulled
> that entire day's intake in **74 seconds**.

***

### ⚡ What you get per advert

**From the search card (100% fill, measured on 100 adverts):**

- 🆔 `advertReference` — the stable NHS Jobs reference (e.g. `C9426-26-0318`), used for deduplication
- 💼 `jobTitle`, `employerName`, `locationText` (town + postcode)
- 💷 `salaryText` plus parsed `salaryMin` / `salaryMax` / `salaryPeriod` / `salaryMinPerAnnum`
- 📄 `contractType` (Permanent · Fixed-Term · **Locum** · **Bank** · Secondment · Apprenticeship · …)
- 🕒 `workingPattern` (full time, part time, remote, compressed hours, job share, …)
- 📅 `datePosted` and `closingDate` as ISO dates, plus `daysUntilClosing`
- 🔗 `jobUrl`

**From the advert page (one extra request, `scrapeJobDetails`):**

- 👤 **`contactName` · `contactJobTitle` · `contactEmail` · `contactPhone`** — the recruiting manager
- 🏥 `employerLooksNhs` + `employerNhsSignal` — tells an NHS body apart from a private provider or GP
  practice, and tells you *which* signal decided it (`name` or `website`)
- 🎚️ `band` and `payScheme` (Agenda for change · Hospital medical and dental staff · Other)
- 🏢 `employerAddressLine1/2`, `employerTown`, `employerCounty`, `employerPostcode`, `employerCountry`
- 🌐 `employerWebsite` + `employerWebsiteDomain` — the domain you can enrich against
- 📋 `personSpecification` grouped by category, plus flat `essentialCriteria` / `desirableCriteria`
- 📝 `jobOverview`, `jobDescription`, `jobResponsibilities`, `aboutOrganisation`
- ✅ `dbsCheckRequired`, `ukRegistrationRequired`, `sponsorshipStatementPresent`
- 📎 `supportingDocuments`, `accreditations`, `applyUrl`, `tracJobReference`

***

### 🎯 Who buys this

| Buyer | What the row is worth |
|---|---|
| **Locum & bank staffing agencies** | Filter `contractType = Locum, Bank` and email the named ward manager or rota lead directly — 114 locum/bank adverts landed in a single 24-hour window |
| **Permanent healthcare recruiters** | 872 permanent roles a day, each with the hiring manager's own address |
| **Medical device, clinical software & training vendors** | The advert names the department head — Oncology Matron, Head of Audiology, Consultant Clinical Scientist — and the trust's website domain |
| **Workforce & pay analysts** | Band, pay scheme and parsed salary across the whole live market, refreshed daily |
| **Care-home & private-provider suppliers** | 40% of adverts are Barchester, Avery, Cygnet, Elysium, HCRG, Agincare and independent GP practices — not NHS bodies at all |

***

### 📊 Measured field fill

Two real runs through this Actor on 2026-09-04, details on. No estimates.

**Sample A — the 100 newest adverts on the board** (representative of the daily flow):

| Field | Fill | Field | Fill |
|---|---|---|---|
| all 9 search-card fields | **100%** | `tracJobReference` | 78% |
| `contactName` | **100%** | `applyUrl` | 78% |
| `contactEmail` | **99%** | `desirableCriteria` | 46% |
| `contactJobTitle` | 91% | `supportingDocuments` | 38% |
| `contactIsNamedIndividual` → true | **93 of 100** | `contactPhone` | 32% |
| `payScheme` | 100% | `accreditations` | 30% |
| `employerWebsite` / `Town` / `Postcode` | 100% | `band` | 27% |
| `jobOverview` / `jobDescription` / `jobResponsibilities` / `aboutOrganisation` | 100% | `employerCounty` | 21% |
| `personSpecification` / `essentialCriteria` | 100% | `contractDuration` | 13% |
| `salaryMin` / `salaryMax` (parsed) | 87% | `employerLogoUrl` | 0% — see limits |

**Sample B — 90 adverts stratified across three unrelated filters** (estates + admin · consultant +
specialty doctor · bank + locum), to check the numbers hold away from the newest slice:
`band` 46%, `contactPhone` 47%, `contactJobTitle` 91%, named individual **87 of 90**, every
search-card field and every contact email still 100%.

***

### 🧭 Honest limits — read these before you buy

- **This is not "NHS trusts only".** Across a full day's 1,182 adverts, **705 were NHS bodies and 477
  were not** — private hospital groups, care-home operators, hospices, GP practices and Primary Care
  Networks all advertise here. `employerLooksNhs` marks the difference. It fires on either the
  employer **name** (nhs / trust / health board / ICB / CCG / HSCNI, and the Welsh equivalents) or
  the employer's own **website domain** (`*.nhs.uk`, `*.nhs.wales`, `*.nhs.scot`, `*.hscni.net`), and
  `employerNhsSignal` records which one. The domain test matters: trusts advertise under short
  trading names, so **"Leeds Teaching Hospitals"** (leedsth.nhs.uk) and **"SECamb"** (secamb.nhs.uk —
  South East Coast Ambulance Service) carry no "NHS" and no "Trust" in the name at all.
  Measured on 62 distinct employers across the 120 newest adverts: every classification agreed with a
  by-hand reading. Two judgement calls are yours to make, and `employerNhsSignal` is what lets you
  make them: **GP practices and NHS-commissioned CICs** (Locala, Your Healthcare CIC, GP federations)
  count as *not* NHS unless they publish on an NHS domain, and **independent charity hospices**
  (Marie Curie, St Giles Hospice) count as *not* NHS. Filter on `employerNhsSignal === 'name'` if you
  want trusts and boards only.
- **The contact email domain is deliberately not used to decide this.** `nhs.net` mailboxes are
  issued to NHS-commissioned GP surgeries and private providers too — in a 60-row sample, 5 of the 6
  `nhs.net` senders were GP surgeries or a private care group — so treating it as an NHS signal would
  over-claim.
- **`band` is only ~27–46% filled, and that is the site, not the scraper.** Only employers on Agenda
  for Change publish a band; a care-home group posting an hourly rate has none.
- **`employer` is a fuzzy word match, not an exact employer filter.** Searching
  `Guys and St Thomas NHS Foundation Trust` returns 5,118 of 11,612 adverts because it matches *any*
  of those words. Use one distinctive word and filter the output.
- **`employerLogoUrl` is almost always empty.** NHS Jobs inlines employer logos as base64 `data:`
  URIs, which would add tens of KB to every row, so only a real hosted URL is emitted.
  `employerHasLogo` tells you whether a logo exists at all.
- **`applyUrl` is jobs.nhs.uk's own apply redirect**, not the employer's ATS vendor URL.
- **`contactPhone` is present on about a third of adverts**, not all of them.
- **Welsh mode (`language: cy`) changes the labels, not the corpus** — the same 11,613 adverts,
  with employer-written text served in Welsh where the employer wrote it in Welsh.
- **The site 403s datacenter IPs.** Measured: Apify datacenter proxies pass 60% first try and 87%
  even with four retries; Apify **RESIDENTIAL, country GB** passes 100% (25/25 sustained, 50/50 at
  concurrency 20). Keep the default proxy setting.

***

### 💰 Pricing

Pay per event. No monthly rental, no platform-usage surcharge, no charge for a run that returns nothing.

| Event | Price | When it fires |
|---|---|---|
| **Per job returned** | **$0.0008** | Once per advert delivered to your dataset |
| **Per job detail enriched** | **$0.002** | Only when detail fetching is on **and** the advert page actually returned data |

- A listing-only crawl of **1,000 adverts costs $0.80**.
- A fully enriched crawl with recruiter contacts — **1,000 adverts costs $2.80**.
- A **daily delta** (everything posted in the last 24h, ~1,180 adverts, fully enriched) is about **$3.30 a day**.
- A **weekly full sweep** of all 11,613 live adverts, fully enriched, is about **$32.50**.

**Nothing is charged for:** duplicate advert references (collapsed *before* billing), adverts your
posted/closing window dropped, a detail fetch that failed, or a run that found no adverts.

***

### 🚀 Quick starts

**A staffing agency's daily locum feed**

```json
{
  "contractType": ["Locum", "Bank"],
  "sort": "publicationDateDesc",
  "postedWithinDays": 1,
  "scrapeJobDetails": true,
  "outputFormat": "lead",
  "maxItems": 0
}
```

**Everything closing in the next 3 days (fill-rate outreach)**

```json
{
  "sort": "closingDate",
  "closingWithinDays": 3,
  "staffGroup": ["NURSING_AND_MIDWIFERY_REGD", "ALLIED_HEALTH_PROF"],
  "outputFormat": "lead"
}
```

**Senior decision-makers at NHS trusts (a vendor's target list)**

```json
{
  "payBand": ["BAND_8B", "BAND_8C", "BAND_8D", "BAND_9", "VERY_SENIOR_MANAGER"],
  "scrapeJobDetails": true,
  "outputFormat": "lead",
  "maxItems": 0
}
```

**Paste a search you built on the site**

```json
{
  "startUrls": [
    { "url": "https://www.jobs.nhs.uk/candidate/search/results?keyword=radiographer&location=Leeds&distance=30" }
  ],
  "maxItems": 200
}
```

***

### ⚙️ Every input, and what it really does

| Input | Notes |
|---|---|
| `startUrls` | Search-results URLs *or* single `/candidate/jobadvert/REF` URLs. **When non-empty these ARE the search** and the filter fields below are ignored (row-level options still apply). |
| `keyword` | The site's free-text "What?" box. |
| `location` + `distance` | Town, city, county or postcode; radius 5 / 10 / 20 / 30 / 50 / 100 miles. Measured: `Leeds` alone = 201 adverts, `Leeds` + 100 miles = 4,421. Radius without a location is ignored — by the site, so by this Actor too. |
| `employer` | Fuzzy word match. See Honest limits. |
| `jobReference` | Exactly one advert. |
| `payBand` | 18 values, Band 2 → Band 9 plus the medical grades. Verified additive: Band 7 (850) + Band 8a (397) = 1,247. |
| `staffGroup` | The 9 NHS occupational groups. Verified additive: 2,490 + 1,911 = 4,401. |
| `contractType` | Permanent · Fixed-Term · Locum · Bank · Secondment · Apprenticeship · Voluntary · Training · Honorary · NHS Reservist. Verified additive: Locum (187) + Bank (1,125) = 1,312. |
| `workingPattern` | Includes `remote-working` — 128 live remote adverts when measured. |
| `payRange`, `salaryFrom`, `salaryTo` | Banded and exact salary filters, both server-side and independent of each other. |
| `sort` | Best match · newest · closing soonest · salary high/low. |
| `postedWithinDays` | The delta lever. The site has no date filter, so the Actor sorts newest-first and **stops the moment a whole page leaves your window** — measured: a 24h window read 136 of 1,162 pages, not all of them. |
| `closingWithinDays` | Keeps adverts closing within N days and drops already-closed ones. |
| `maxItems`, `maxPagesPerSearch` | Your cost controls. `maxItems: 0` = the whole result set. |
| `scrapeJobDetails` | The second request per advert — the whole contact/band/person-spec block. |
| `includeContactDetails` | Independent switch for the four personal-data fields. |
| `includeFullDescription` | Adds the three long text blocks. Off by default (rows stay ~2 KB instead of ~8 KB). |
| `includeCriteria` | Adds the person specification. |
| `outputFormat` | `full` · `lead` (29 CRM-ready columns) · `minimal` (10 columns). |
| `deduplicateByReference` | On: one row per advert for the whole run. Off: one row per (search, advert) with `sourceSearchUrl` — and each copy is billed. Page overlap inside one search is collapsed either way. |
| `language` | `en` / `cy`. |
| `covidJobsOnly` | The site's legacy flag. Still works, still tiny (9 adverts). |
| `proxyConfiguration` | Defaults to Apify Proxy RESIDENTIAL, country GB. |
| `tryDatacenterFirst` | Tries the cheap datacenter rung first, falls back to residential on a block. Measured across real runs: **roughly half of all requests are served by the cheap rung**, halving residential bandwidth. Skipped automatically when you bring your own proxy. |
| `maxConcurrency`, `maxRequestRetries` | Measured clean at 20 parallel; 10 is the polite default. |

**Deliberately NOT exposed**, because they are measured no-ops on this site and would be fake levers:
`country=` (returns the unfiltered 11,612), `internalOnly` / `externalOnly` (same), and `employerCode`
(returns 0 rows without an internal code list nobody outside NHS Jobs has).

***

### ❓ FAQ

**Does this need an NHS Jobs login or API key?**
No. Every page this Actor reads is a public page any visitor can open. NHS Jobs does run a
partner API at `api.jobs.nhs.uk`, which is key-gated and returns 403 — this Actor never touches it,
because using it would mean forging credentials.

**Is every employer an NHS trust?**
No, and that surprises people. Over a full day's intake, 705 of 1,182 adverts came from NHS bodies
and 477 from private healthcare groups, hospices, care-home operators and GP practices. Use
`employerLooksNhs` to split them, and `employerNhsSignal` to see whether the employer name or the
employer's NHS website domain is what decided it.

**How fresh is the data?**
NHS Jobs takes roughly 1,000–1,200 new adverts a day, so the ~11,600-advert corpus turns over about
every 11 days. Schedule this Actor daily with `postedWithinDays: 1` and you have the whole market.

**Why is `band` blank on so many rows?**
Because the employer isn't on Agenda for Change. Care-home groups and private providers post an
hourly or annual rate with no band. That is the source data, not a parsing gap.

**Can I get an exact single employer?**
Not from the site's `employer` box, which matches any word you type. Search a distinctive word
(`Barchester`, `Barts`) and filter `employerName` in your own pipeline, or paste a start URL.

**How many results can one run return?**
The whole result set. Pagination is lossless — verified to the last page of a 495-page result set,
which returns its final two rows before page 496 returns zero. A 136-page contiguous crawl returned
1,182 rows with **zero duplicates**.

**Will it break my budget?**
No. `maxItems` hard-caps the run, and the Actor also clamps itself to whatever your
`maxTotalChargeUsd` can actually pay for before it starts crawling.

**What happens if the site blocks the run?**
It exits cleanly with a status message explaining that it was a transport block, not an empty
result — and charges nothing. It only fails loudly if NHS Jobs answers 200 with something that
isn't a results page, which would mean the markup changed and the output could be silently wrong.

**Does it work with the Welsh site?**
Yes — set `language: "cy"`. Dates, pagination and every field are parsed in both languages.

**Can I export to CSV / Google Sheets / my CRM?**
Yes. Use `outputFormat: "lead"` for a 29-column CRM-ready sheet, then Apify's standard CSV, JSON,
Excel or API export, or a webhook straight into your stack.

***

### ⚖️ Legal & fair use

- This Actor reads **public pages** on `jobs.nhs.uk` as an ordinary visitor. It uses no login, solves
  no CAPTCHA, forges no credentials, and touches no anti-abuse control. `jobs.nhs.uk/robots.txt`
  does not exist — the path serves the site's generic HTML page and carries zero directives.
- **The contact fields are personal data under UK GDPR.** They are names, work email addresses and
  sometimes direct or mobile numbers of real people. Employers publish them so candidates can ask
  about *that specific vacancy*. Contacting someone about the role they advertised sits close to
  that original purpose; using the same details for unrelated marketing repurposes them and puts a
  lawful-basis, transparency and opt-out obligation **on you, the user of this Actor**, not on Apify
  and not on us. `includeContactDetails: false` removes those four fields entirely if you would
  rather not hold them.
- You are responsible for complying with the NHS Jobs terms of use and with applicable data
  protection law in your jurisdiction. This Actor gives you access to published information; how you
  use it is your decision and your liability.
- Not affiliated with, endorsed by, or connected to NHS England, NHS Jobs, or any NHS organisation.

# Actor input Schema

## `startUrls` (type: `array`):

Paste jobs.nhs.uk URLs — either search-results URLs (build the search in the site's own UI, copy the address bar) or single job-advert URLs (/candidate/jobadvert/REF). Every filter in a pasted URL is preserved; only \&page= is dropped so the Actor can paginate. IMPORTANT: when this list is non-empty it IS the search, and the keyword / location / band / staff-group / contract / pattern / salary fields below are ignored for that run (the row-level options — max jobs, details, contact details, posted/closing windows, output format — still apply).

## `keyword` (type: `string`):

Free-text job title, skill or speciality, exactly as typed into the site's 'What?' box (e.g. 'nurse', 'radiographer', 'consultant psychiatrist'). Leave empty to sweep every live advert (~11,600).

## `location` (type: `string`):

UK town, city, county or postcode (e.g. 'Leeds', 'Manchester', 'SW1A'). Leave empty for the whole UK.

## `distance` (type: `string`):

Radius around the Location. Only these six values exist on jobs.nhs.uk. Ignored (and logged) when no Location is set — the site itself ignores it there.

## `employer` (type: `string`):

MEASURED WARNING — this is a fuzzy OR-token match, NOT an exact employer filter. Searching 'Guys and St Thomas NHS Foundation Trust' returns 5,118 of 11,612 adverts because it matches any advert containing any of those words. Use a distinctive single word ('Barchester', 'Barts') and filter the output yourself if you need one exact employer.

## `jobReference` (type: `string`):

A jobs.nhs.uk advert reference, e.g. 'C9426-26-0318'. Returns exactly that one advert. Combine with 'Fetch job-advert details' for a full single-record pull.

## `payBand` (type: `array`):

Agenda for Change bands and medical grades. Pick as many as you like — they are OR'd together (verified additive: BAND\_7 = 850 adverts + BAND\_8A = 397 -> 1,247 together).

## `staffGroup` (type: `array`):

The NHS occupational staff groups. OR'd together (verified additive: 2,490 + 1,911 -> 4,401).

## `contractType` (type: `array`):

The locum / bank / fixed-term lever a staffing agency actually wants. OR'd together (verified additive: Locum 187 + Bank 1,125 -> 1,312).

## `workingPattern` (type: `array`):

Full-time / part-time / remote and the flexible-working patterns. OR'd together.

## `payRange` (type: `array`):

The site's banded salary filter, in thousands of GBP per year. OR'd together. Independent of the exact Min/Max salary fields below — use either.

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

Exact annual GBP floor, applied server-side (verified: salaryFrom=60000 -> 2,130 adverts).

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

Exact annual GBP ceiling, applied server-side (verified: salaryTo=20000 -> 232 adverts).

## `sort` (type: `string`):

How jobs.nhs.uk orders the result set. 'Date posted (newest)' is what makes a daily delta crawl cheap — pair it with 'Posted within (days)'.

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

Keep only adverts posted in the last N days. jobs.nhs.uk has no server-side date filter, so the Actor sorts newest-first and STOPS as soon as a whole page falls outside the window — that is what makes a daily delta run cheap (~1,000 new adverts land per day). Leave empty for no date limit.

## `closingWithinDays` (type: `integer`):

Keep only adverts whose closing date is within the next N days (already-closed adverts are dropped). Set Sort to 'Closing date' and the Actor also stops paging once it passes the window. Built for locum/agency fill-rate outreach on roles about to expire.

## `covidJobsOnly` (type: `boolean`):

The site's legacy COVID-19 flag. Still functional but tiny (9 live adverts when last measured).

## `language` (type: `string`):

jobs.nhs.uk serves a full Welsh UI. This switches the pages the Actor requests, so free-text the employer wrote in Welsh comes back in Welsh. It does NOT change WHICH adverts match — the corpus is identical (11,613 either way).

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

Your primary cost control: a hard cap on adverts returned (and therefore billed) this run. 0 = no cap (the whole result set, up to 20,000). Defaults to 50 for a fast, cheap first run.

## `maxPagesPerSearch` (type: `integer`):

Secondary cap for wide or unfiltered crawls — 10 adverts per page. 0 = follow the result set to its end (verified lossless: keyword=nurse ends at page 495, which returns its final 2 rows).

## `scrapeJobDetails` (type: `boolean`):

One extra request per advert. Unlocks the recruiter contact block (name, job title, email, phone), Agenda-for-Change band and pay scheme, structured salary, employer address + website, the essential/desirable person specification, DBS and sponsorship flags, supporting documents and the apply link. Billed as the separate 'Job detail enriched' event, and never charged when the detail fetch fails.

## `includeContactDetails` (type: `boolean`):

Independent switch for the named-person fields — contact name, job title, email and direct phone. These are PERSONAL DATA under UK GDPR: employers publish them so candidates can ask about that specific vacancy. Turn this off if your use case does not need them. See the README's Legal section.

## `includeFullDescription` (type: `boolean`):

Adds jobDescription (main duties), jobResponsibilities and aboutOrganisation. These run to several KB per row; off by default keeps datasets small and CSV exports readable. The short jobOverview (job summary) is always included when details are on.

## `includeCriteria` (type: `boolean`):

Adds the structured person spec: personSpecification (grouped by category) plus flat essentialCriteria / desirableCriteria arrays.

## `outputFormat` (type: `string`):

'Full' = every field. 'Lead' = the contact-centric row a staffing or healthcare-vendor buyer imports straight into a CRM (employer, role, band, contact name/title/email/phone, closing date, website). 'Minimal' = the nine search-card fields only.

## `deduplicateByReference` (type: `boolean`):

On (default): one row per advert reference for the whole run, so overlapping searches never bill you twice. Off: one row per (search, advert) pair with sourceSearchUrl set, so each pasted search gets its own copy — an advert matched by two searches is then delivered AND BILLED twice. Page overlap inside a single search is always collapsed either way.

## `proxyConfiguration` (type: `object`):

MEASURED: jobs.nhs.uk sits behind CloudFront, which 403s Apify datacenter IPs (60% first-try pass, 87% with retries). Apify Proxy RESIDENTIAL with country GB passes 100% (25/25 sustained, 50/50 at concurrency 20). Keep this default unless you are supplying your own UK residential proxy.

## `tryDatacenterFirst` (type: `boolean`):

Cost optimisation: attempt each request on a datacenter IP once and fall back to residential only when CloudFront blocks it. Roughly 60% of requests are then served without spending residential bandwidth. Automatically skipped when you supply your own proxy configuration. Turn off for the lowest possible latency.

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

Parallel requests for search pages and advert pages. Measured clean at 20 (50/50 successes at 5/10/15/20 parallel); 10 is the polite default.

## `maxRequestRetries` (type: `integer`):

Residential retries after a block or transport flake. Measured need: ~1% of residential calls flake once and every one recovered on a single retry.

## Actor input object example

```json
{
  "keyword": "nurse",
  "distance": "10",
  "sort": "",
  "covidJobsOnly": false,
  "language": "en",
  "maxItems": 20,
  "maxPagesPerSearch": 0,
  "scrapeJobDetails": true,
  "includeContactDetails": true,
  "includeFullDescription": false,
  "includeCriteria": true,
  "outputFormat": "full",
  "deduplicateByReference": true,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "GB"
  },
  "tryDatacenterFirst": true,
  "maxConcurrency": 10,
  "maxRequestRetries": 4
}
```

# Actor output Schema

## `items` (type: `string`):

The dataset of scraped jobs.nhs.uk adverts — one advert per row, with the recruiter contact block when detail fetching is on.

# 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 = {
    "keyword": "nurse",
    "maxItems": 20,
    "scrapeJobDetails": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/nhs-jobs-scraper").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 = {
    "keyword": "nurse",
    "maxItems": 20,
    "scrapeJobDetails": True,
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/nhs-jobs-scraper").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 '{
  "keyword": "nurse",
  "maxItems": 20,
  "scrapeJobDetails": true
}' |
apify call scrapersdelight/nhs-jobs-scraper --silent --output-dataset

```

## MCP server setup

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

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/LFYkAXnWCO7UMxo7N/builds/sDPeBRqJj0Tuo5Qc9/openapi.json
