# UK Government & NHS Jobs Scraper (`boncross/uk-public-sector-jobs`) Actor

Scrape NHS Jobs, Civil Service Jobs (every department) and Ministry of Justice vacancies in one run. Add keywords and a location and get parsed salary, grade or band, contract, working pattern, posting and closing dates, full description, person specification and apply link. $0.80 per 1,000 jobs.

- **URL**: https://apify.com/boncross/uk-public-sector-jobs.md
- **Developed by:** [BonCross API](https://apify.com/boncross) (community)
- **Categories:** Jobs, Lead generation
- **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

## UK Government & NHS Jobs Scraper

### What is UK Government & NHS Jobs Scraper?

Three boards carry most UK public sector hiring: NHS Jobs, Civil Service Jobs and the Ministry of Justice careers portal. UK Government & NHS Jobs Scraper searches all three in one run and gives you the vacancies in a single schema, so a Band 5 nurse, an HMRC caseworker and a prison officer sit in the same table with the same column names and the salary parsed into numbers.

Enter keywords and a place. That's a full run across central government (the Home Office, HMRC, HMCTS and the other departments all recruit through Civil Service Jobs), the NHS in England and Wales, and the prison, probation and courts estate.

What makes it worth choosing:

- **One schema, three sources.** You write your pipeline once. Board-specific columns are added beside the shared ones, never in place of them.
- **The whole Civil Service board, then only the changes.** One run returned all 1,305 live adverts in 3 minutes. Scheduled daily, later runs return only adverts that are new or were edited, and an unchanged advert costs nothing.
- **You filter the way each board does.** NHS pay band and staff group, Civil Service grade and department, MoJ prison, court and salary band are applied by the boards themselves.
- **You get the full advert, and it's been checked.** In our release run, 11 of 11 sampled NHS and MoJ adverts matched the boards' own pages.
- **$0.80 per 1,000 jobs with full details** on the Free plan, proxy usage included.

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

- 56 of 56 live checks on its inputs and filters passed on 7 October 2026
- 18 searches run the same day across the NHS, Civil Service and MoJ boards: 58 jobs, no error rows
- 11 of 11 sampled NHS and MoJ adverts matched the boards' own pages
- Charges checked against the 58 rows

It's used to feed public sector job boards from all three sources at once, to watch a department, trust or grade daily, to compare NHS pay bands and Civil Service grades by region, and to track recruitment volumes across government as a labour-market indicator.

### What data can you extract?

| | |
|---|---|
| 🏛 Source board and vacancy reference | 💼 Job title |
| 🏥 Employer or department | 📍 Location, town and postcode |
| 💷 Salary minimum, maximum and period | Salary text as published |
| NHS band and pay scheme | Civil Service or MoJ grade |
| Contract type | Working pattern |
| 📅 Posting date | ⏳ Closing date and closing text |
| 📄 Full description and summary | Duties |
| Person specification, essential and desirable | Civil Service behaviours |
| Benefits | DBS check requirement |
| ☎️ Contact name, email and phone | 🔗 Apply link and advert URL |

### How to scrape UK public sector jobs

1. [Create a free Apify account](https://console.apify.com/sign-up) and open [UK Government & NHS Jobs Scraper](https://apify.com/boncross/uk-public-sector-jobs).
2. Leave all three boards ticked, or untick the ones you don't want.
3. Add keywords and a location, with a distance in miles.
4. Add board-specific filters if you need them. A filter only affects the board it names.
5. Press Start, and download the vacancies as Excel, CSV, JSON, XML or HTML when it's done.

### ⬇️ Input

Keywords and a location are all you need. By default all three boards are searched and the full page of each vacancy is fetched. Adverts that have already closed are dropped unless "Closing on or after" says otherwise.

There are two limits. "Max jobs per search" (default 100) applies to each combination of keyword, location and board, so ten combinations at 50 each can return 500. "Maximum results for the whole run" caps the jobs you're charged for across the run, and 0 means no cap.

#### One search across all three boards

```json
{
  "keywords": ["administrator", "caseworker"],
  "locations": ["London"],
  "distanceMiles": 10,
  "sources": ["nhs", "csj", "moj"],
  "maxJobsPerSearch": 40,
  "fetchDetails": true
}
```

#### NHS band 5 and 6 nursing roles, closing soonest first

```json
{
  "sources": ["nhs"],
  "keywords": ["staff nurse"],
  "locations": ["Manchester", "Leeds"],
  "distanceMiles": 15,
  "nhsPayBands": ["BAND_5", "BAND_6"],
  "nhsContractTypes": ["Permanent"],
  "nhsWorkingPatterns": ["full-time"],
  "sortBy": "closing"
}
```

#### Civil Service roles at named departments above a salary

```json
{
  "sources": ["csj"],
  "keywords": ["officer"],
  "locations": ["Croydon", "Leeds"],
  "departments": ["Home Office", "HM Revenue and Customs"],
  "salaryMin": 30000
}
```

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

```json
{
  "keywords": ["paralegal", "legal assistant", "court officer"],
  "locations": ["London"],
  "titleExclude": ["senior", "manager"],
  "postedWithinDays": 2,
  "dedupStoreName": "uk-public-legal-seen"
}
```

#### The whole Civil Service board, then only what changed

```json
{
  "sources": ["csj"],
  "csjScope": "changedSinceLastRun",
  "dedupStoreName": "csj-whole-board"
}
```

Your first run returns every live Civil Service advert. Schedule it daily with the same store name and each later run returns only the adverts that are new or were edited since the run before.

| Field | Notes |
|---|---|
| `sources` | Any of `nhs`, `csj`, `moj`. All three by default. |
| `keywords`, `locations`, `distanceMiles` | Search terms, places and a radius in miles. On NHS Jobs 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`, `maxNewJobs` | The per-search cap, the run-wide cap, and a cap on jobs returned. |
| `sortBy` | Newest, closing soonest, relevance, salary high to low or low to high, or nearest first (needs a location). NHS Jobs and Civil Service Jobs apply it; MoJ rows keep the board's own order. |
| `postedWithinDays`, `closingAfter`, `titleInclude`, `titleExclude`, `salaryMin` | Filters applied across all boards. |
| `departments`, `employers` | Match the department or employer name on any board where the listing shows one. |
| `csjGrades`, `csjRoleTypes`, `csjWorkingPatterns`, `csjContractTypes` | Civil Service Jobs filters for grade, job category, working pattern and contract type, applied by Civil Service Jobs. |
| `nhsContractTypes`, `nhsWorkingPatterns`, `nhsPayBands`, `nhsPayRanges`, `nhsStaffGroups`, `nhsSalaryFrom`, `nhsSalaryTo` | NHS Jobs filters, applied by NHS Jobs. `nhsSalaryFrom` and `nhsSalaryTo` are its own salary range in whole pounds a year; either end may be left at 0. |
| `mojRegions`, `mojEmployment`, `mojGrades`, `mojWorkingPatterns`, `mojRoleTypes`, `mojOrganisation`, `mojPlaces`, `mojSalaryBands` | Ministry of Justice filters, applied by the MoJ portal. `mojPlaces` takes named prisons, courts and probation offices ("Belmarsh", "HMP Wandsworth", "Wandsworth County Court"), several combined as either-or, and replaces your locations on this board; `mojSalaryBands` takes the portal's bands (`30001-40000`, `50001-60000`…), and an advert spanning two bands matches either. `mojRoleTypes` takes the portal's role types ("Prison Officer", "Probation Officer", "Legal Services"…), several combined as either-or; `mojOrganisation` takes one organisation ("HMPPS - Public Sector Prisons", "HMPPS - Probation Service", "AGY - HMCTS"…). |
| `fetchDetails`, `includeRawData`, `dedupStoreName` | Detail fetching, raw payloads and the only-new store. |
| `csjScope` | Civil Service Jobs only: `search` (default) runs your keywords and locations; `wholeBoard` returns every live advert, newest change first; `changedSinceLastRun` returns only adverts new or edited since the last run with the same `dedupStoreName`. In both whole-board options keywords, locations, distance, sort and `maxJobsPerSearch` do not apply to Civil Service Jobs, and the other filters are applied to each advert after it is read. |
| `maxConcurrency`, `delayMs` | How many requests run at once, and an optional pause before each one (0 by default, at most 2,000 ms). |

#### For developers

- `titleInclude`, `titleExclude`, `departments`, `employers` and `postedWithinDays` are checked on the search results. A job they remove costs `job-scanned` only, as do duplicates and jobs an only-new store has already seen.
- A job whose advert was fetched is charged `job-detail` even if a later check (salary, or the MoJ location text) then drops it.
- `dedupStoreName` names the store that remembers what earlier runs returned. `maxNewJobs` caps how many a run returns.
- With `csjScope` set to `wholeBoard` or `changedSinceLastRun`, there's no keyword or location search on Civil Service Jobs. The title, department, employer, salary, closing-date and Civil Service filters are checked on each advert after it's read, so an advert they remove is still charged `job-detail` and counts toward `maxItems`. An advert that has already closed is left out free. An advert whose page can't be read is an uncharged row with an `error`, and it's tried again on the next run.
- `maxConcurrency` (default 16, from 1 to 32) and `delayMs` (default 0, at most 2,000 ms) control pacing.
- Rows arrive in a fixed order: board, then keyword, then location, then the board's own ranking. Two runs of one input read the same way.
- If Apify moves a run to another server midway, it resumes at the point it stopped, and no vacancy is returned or charged twice.

### ⬆️ Output

You get one record per vacancy, in one schema across the three boards.

```json
{
  "source": "nhs",
  "sourceName": "NHS Jobs",
  "id": "C9197-26-0826",
  "reference": "197-AO8698",
  "url": "https://www.jobs.nhs.uk/candidate/jobadvert/C9197-26-0826",
  "title": "Therapy Assistant Practitioner",
  "employer": "Lewisham and Greenwich NHS Trust",
  "location": "London SE18 4QH",
  "address": "Queen Elizabeth Hospital, Stadium Road, London, SE18 4QH, United Kingdom",
  "postcode": "SE18 4QH",
  "town": "London",
  "salaryText": "£28,392 to £31,157 a year per annum plus HCAS",
  "salaryMin": 28392,
  "salaryMax": 31157,
  "salaryPeriod": "year",
  "salaryCurrency": "GBP",
  "band": "Band 4",
  "payScheme": "Agenda for change",
  "contractType": "Permanent",
  "workingPattern": "Full-time",
  "postedAt": "2026-09-08",
  "closingAt": "2026-09-20",
  "summary": "An exciting opportunity has arisen for an enthusiastic and flexible individual to join a friendly and dynamic service at Lewisham and Greenwich Trust…",
  "duties": "The Physiotherapy Assistant Practitioner is able to carry out a full episode of care from assessment to discharge, for a specified and designated clinical caseload identified by a qualified clinician.",
  "personSpecification": [
    { "category": "Qualifications & Training", "essential": ["Evidence of study to or attainment of NVQ Level III, relevant qualification or equivalent knowledge and experience"], "desirable": [] },
    { "category": "Experience", "essential": ["Experience of working clinically in physiotherapy / occupational therapy service", "…"], "desirable": ["Community and acute experience", "…"] }
  ],
  "personSpecificationText": "Qualifications & Training\nEvidence of study to or attainment of NVQ Level III…",
  "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…",
  "ukRegistration": "Applicants must have current UK professional registration…",
  "contact": { "name": "Danielle Cotton", "jobTitle": "Inpatient Physiotherapy Lead", "email": "Danielle.cotton1@nhs.net", "phone": "07551100500" },
  "applyUrl": "https://www.jobs.nhs.uk/candidate/jobadvert/C9197-26-0826/ats-direct-apply",
  "searchKeywords": "administrator",
  "searchLocation": "London",
  "scrapedAt": "2026-09-08T17:50:48.645Z"
}
```

Civil Service rows add `typeOfRole`, `numberOfJobs`, `closingText`, `behaviours`, `technicalSkills`, `qualifications` and `benefits`. They hold the person specification as text in `personSpecificationText`. The structured essential and desirable lists are NHS only.

Some columns are published by one board and not the others:

| Column | Board | Meaning |
|---|---|---|
| `grade` | Civil Service, MoJ | The grades the post is open at, as a list (e.g. \["Executive Officer"] or \["AO"]) |
| `department` | Civil Service | The employing department |
| `security` | Civil Service | The security clearance the post needs |
| `nationality` | Civil Service | Who may apply, e.g. "Open to UK nationals only." |
| `selectionProcess` | Civil Service, MoJ | How candidates are assessed (Success Profiles, experience questions and so on) |
| `furtherInformation` | Civil Service, MoJ | The advert's closing information: appointment rules, reserve lists, nationality rules |
| `workingForCivilService` | Civil Service | The Civil Service Code section |
| `interviewDates`, `reserveList`, `allowance` | MoJ | Interview dates as the advert gives them, how long a reserve list is held, and any allowance (e.g. a prison allowance) |
| `contractDuration`, `ukRegistration`, `dbsCheck` | NHS | How long a fixed-term post lasts, the professional registration the post needs, and the criminal-record check sentence |
| `employerAccreditations` | NHS | The schemes the employer is accredited under, e.g. "Disability Confident Committed", "Veteran Aware" |
| `supportingDocuments` | NHS | The names of the documents attached to the advert, such as the job description (downloaded from the advert itself) |
| `aboutEmployer`, `employerWebsite` | NHS | The trust's own description of itself, and its website |
| `businessUnit`, `regions`, `vacancyApproach` | MoJ | The prison or unit, the regions, and whether the vacancy is open externally |
| `attachments` | MoJ | Linked documents such as the job description, as `{url, name}` |
| `updatedAt` | Civil Service, whole-board options | When the board last changed the advert |
| `detailError` | all | Present only when an advert could not be read; the row then carries the listing fields alone |
| `descriptionHtml` | all | The full advert as HTML, beside the plain-text `description` |

### How much does it cost to scrape NHS, Civil Service and MoJ 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 page | $0.0008 | $0.00072 | $0.00064 | $0.00056 |
| `job-listed`, per job returned from listing data only | $0.0002 | $0.00018 | $0.00016 | $0.00014 |

Enterprise plans pay the Business price.

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

- A search that scans 97 cards and returns 97 jobs with details: $0.002 + 97 × $0.00005 + 97 × $0.0008 = **$0.084** ($0.061 on Business).
- A daily monitor over three boards that scans 400 cards and finds 25 new adverts: $0.002 + 400 × $0.00005 + 25 × $0.0008 = **$0.042** ($0.036 on Business).
- The whole Civil Service board at 1,361 adverts: $0.002 + 1,361 × $0.00005 + 1,361 × $0.0008 = **$1.16** ($0.83 on Business).
- A daily changed-only run that finds 100 new or edited adverts: $0.002 + 100 × $0.00005 + 100 × $0.0008 = **$0.087** ($0.063 on Business). Adverts that didn't change aren't charged at all.

A run of 100 jobs with details costs about $0.087. Apify's Free plan includes $5 of credits a month, which is about 57 such runs, or roughly 5,700 jobs.

### How many jobs, and how fast?

All three boards with details on, measured on Apify on 2 October 2026 (whole-board runs on 5 October):

| Run | Jobs returned | Time |
|---|---|---|
| "administrator" in London within 10 miles, up to 8 per search | 20, all with full details | 14 to 20 seconds |
| "analyst" and "administrator" in London within 10 miles, up to 30 per search | 92 (NHS 42, Civil Service 46, MoJ 4), all with full details | 35 to 36 seconds |
| "administrator", "analyst" and "officer" in London within 10 miles, up to 100 per search | 322, all with full details | 44 to 51 seconds |
| The whole Civil Service Jobs board (`csjScope: "wholeBoard"`), 5 October 2026 | 1,305, all with full details | 3 minutes |
| The same board the next run, `csjScope: "changedSinceLastRun"`, nothing edited in between | 0 | 8 seconds |

Larger runs settle at about 400 jobs a minute with full details. Each of the city searches above gave you all three boards in under a minute.

How full are the NHS details? On 47 NHS adverts (8 September 2026), salary numbers were on 34, because the others publish "depending on experience" text. Band was on 26, person specification on 46 and contact on 47.

### Limits, board by board

**Coverage**

- The Home Office's own careers site isn't covered, and you don't need it: its vacancies are all on Civil Service Jobs. Use `departments: ["Home Office"]`.
- GOV.UK Find a Job, NHS Scotland, jobs.ac.uk and local government boards aren't included.
- If a board can't be searched, you get an uncharged row with the search and an `error` message, and the run carries on with the others.
- An advert withdrawn between the search and the detail fetch reaches you listing-only with a `detailError`, billed as `job-listed`.

**Civil Service Jobs**

- `postedAt` is null. The board publishes a closing date and no posting date, so you can't narrow its rows with "Posted within (days)".
- It publishes salary text and up to two grade labels, with no numeric band beyond what the advert states.
- You get overseas and offshore adverts (Cyprus, the Falkland Islands, NATO posts and the like) when your search has no location, or when the location is the overseas place itself. "Cyprus", "Falkland Islands" or "Belgium" returns the adverts whose own location names that place (a Cyprus post listed as "RAF Akrotiri or Episkopi" counts as Cyprus). The radius doesn't apply to them, and a UK place search leaves them out.
- A location the board doesn't recognise as a UK place (a misspelling, or a country) returns no UK adverts from it, with a warning in the log. You don't get the whole board by accident.

**Ministry of Justice**

- "MoJ places" takes the portal's own names: prisons by name ("Belmarsh", and "HMP Belmarsh" also works), courts in full ("Wandsworth County Court"). Adverts covering many prisons are listed under each one, so you'll see a national recruitment campaign once for every prison it names.
- If your location is a city that names a region (London, Manchester, Cardiff, Edinburgh and so on), that region is searched. Any other town on the portal's own town list (Durham, for example) uses the portal's town filter. A place it doesn't list searches nationally and is then filtered on the advert's location text.
- `departments` and `employers` filter on the name shown in the search results. MoJ results usually show no business unit there, so most MoJ rows pass those two filters untouched. Check `businessUnit` in your results.
- MoJ rows keep the portal's own order whatever "Sort" says.

**NHS Jobs**

- If you give a place name the board can't pin to one place (such as "Newport", in Wales and in several English counties) it's looked up once in each UK nation and searched in the nation where the board has the most adverts for it. "Newport" means Newport, Wales (56 nurse adverts against 26 for England, measured). The run log and the `nhsPlaces` entry of the run's STATS record say which nation was used and the counts for the others.
- To choose for yourself, add the nation after a comma: `"Newport, England"`. Within a nation the board picks the place, so for a smaller place with a common name ("Richmond") give a postcode or a fuller name ("Richmond upon Thames"). Only NHS Jobs reads the nation. The other two boards receive the location as typed.

### FAQ

#### Which boards does it cover, and where's the Home Office?

NHS Jobs, Civil Service Jobs and the Ministry of Justice careers portal. Civil Service Jobs has every central government department, so the Home Office, HMRC and HMCTS all arrive through that source.

#### How do I watch the whole Civil Service for new and edited adverts?

Set "Civil Service Jobs: whole board" to `changedSinceLastRun`, name an only-new store, and schedule the run daily. The first run takes the whole board. After that you get only what's new or edited, and when nothing changed we measured a run of 8 seconds returning 0 rows.

#### Can I get only new vacancies from all three boards?

Name an "Only-new store name" and schedule the run. Each run returns only adverts it hasn't returned before.

#### Why do some jobs have no posting date?

They're Civil Service Jobs rows. That board doesn't publish one, and those rows have a closing date. NHS Jobs and MoJ rows have both dates.

#### Is the salary always a number?

Only where the advert states one. NHS adverts that say "depending on experience" have salary text and no numbers, which was 13 of 47 on the measured run.

#### How do I filter to one department or one trust?

Put the name you want in "Civil Service departments" or "NHS employers". Each keeps rows whose department or employer name contains one of your terms, on any board that shows one. For Civil Service only, set the boards to `csj` as well.

#### I only want NHS vacancies. Is this the right actor?

It works, but [NHS Jobs Scraper](https://apify.com/boncross/nhs-jobs) is the same data with a form built around bands, staff groups and trusts.

#### Do I need proxies, an account or an API key for any of the boards?

None of those. All three boards are searched as a logged-out visitor, and proxy usage is included in the price.

#### Is scraping these sites legal?

These are public sector job adverts, published to be found, and the actor reads only what the boards show a logged-out visitor. Scraping publicly available data is generally legal. Contact names and emails inside adverts are personal data, though, so handle them in line with the law that applies to you.

### Schedules, API and AI agents

A daily schedule with an only-new store is what turns this into a monitor, and a webhook or the Slack integration can pass on each day's new vacancies. Google Sheets, Airbyte, Zapier and Make connect through Apify's integrations.

From code: `POST https://api.apify.com/v2/acts/boncross~uk-public-sector-jobs/run-sync-get-dataset-items` with the input JSON as the body returns the vacancies in one call for a short run. Use the standard run endpoints, or `apify-client` for Node.js or Python, for the whole-board runs.

Add the Apify MCP server (`mcp.apify.com`) to an MCP-capable client and an agent can call the actor by name, for instance to pull this week's Grade 7 policy roles in Leeds and summarise the person specifications.

### More from BonCross

- [NHS Jobs Scraper](https://apify.com/boncross/nhs-jobs): NHS Jobs vacancies by keyword and location.
- [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.
- [CharityJob Scraper](https://apify.com/boncross/charityjob-jobs): UK charity and non-profit jobs from CharityJob.
- [Glassdoor Job Scraper](https://apify.com/boncross/glassdoor-jobs): Glassdoor job listings for a role and location.

### Your feedback

Three boards means three sets of quirks, and we'd like to hear about the ones we've missed. Open an issue from the Issues tab in Apify Console with the board and the advert. BonCross reads them all and replies there.

# Changelog

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

# Actor input Schema

## `sources` (type: `array`):

Which boards to search. Civil Service Jobs carries every department (Home Office, HMRC, HMCTS, DWP, MoD and the rest), so use the departments field to narrow it.

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

One search per keyword per location. An empty entry searches everything.

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

Town, city or postcode. NHS Jobs and Civil Service Jobs search within the radius you set. On NHS Jobs, a name shared by places in different nations ("Newport") is searched where the board has the most adverts for it; add the nation after a comma to choose ("Newport, England", "Newport, Wales"). Civil Service overseas adverts are included when the location is left empty, or when the location is the overseas place itself ("Cyprus", "Falkland Islands", "Belgium"): then the adverts whose own location names it are returned, whatever the radius. A place Civil Service Jobs does not recognise returns no UK adverts from it. The MoJ board only knows regions (London, North West, Scotland and so on), so a major city is mapped to its region.

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

Radius around each location on NHS Jobs and Civil Service Jobs.

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

Cap per keyword, location and board.

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

A run-wide ceiling across every target. The per-target limit above applies to each one separately, so ten targets at 50 each returns 500; set this to cap the total. Zero means no run-wide limit. Rows that report an input the actor could not resolve are not counted here and are never charged for.

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

Newest first, closing date, relevance, salary or nearest first (nearest to each location; needs a location). NHS Jobs and Civil Service Jobs apply it; MoJ has no sort parameter, so its rows come back in the board's own order. Measured 2026-09-18. Rows are grouped by board either way.

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

Drop jobs posted more than this many days ago, where the board shows a posting date (NHS Jobs does; MoJ after the detail fetch; Civil Service Jobs never does). 0 means any age.

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

Drop adverts that close before this date. By default, adverts that have already closed are dropped.

## `salaryMin` (type: `integer`):

Drop jobs whose top-of-range annual salary is below this. Jobs with no parsed salary are kept.

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

Keep only titles containing one of these, case-insensitive. Applied before any paid detail fetch.

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

Drop titles containing any of these.

## `departments` (type: `array`):

Keep only adverts whose organisation name contains one of these, on every board, for example "Home Office", "HM Revenue and Customs" or "HM Courts and Tribunals Service". Selecting a Civil Service department with NHS Jobs also selected drops the NHS adverts that do not match.

## `csjGrades` (type: `array`):

Civil Service Jobs' own grade filter, applied by the board itself.

## `csjRoleTypes` (type: `array`):

Civil Service Jobs' own job-category filter, for example "Policy", "Digital", "Human Resources" or "Operational Delivery". The site's category names; anything else is ignored with a warning.

## `csjWorkingPatterns` (type: `array`):

Civil Service Jobs' own working-pattern filter.

## `csjContractTypes` (type: `array`):

Civil Service Jobs' own contract-type filter.

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

Keep only adverts whose organisation name contains one of these, on every board, for example "Guy's and St Thomas'".

## `nhsContractTypes` (type: `array`):

NHS Jobs' own contract-type filter.

## `nhsWorkingPatterns` (type: `array`):

NHS Jobs' own working-pattern filter.

## `nhsPayBands` (type: `array`):

NHS Jobs' own pay-band filter, covering Agenda for Change bands and medical grades.

## `nhsPayRanges` (type: `array`):

NHS Jobs' own pay-range filter, 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.

## `nhsSalaryFrom` (type: `integer`):

NHS Jobs' own salary-range filter, lower end, in whole pounds a year. Applied by the board before anything is read. 0 means no lower end.

## `nhsSalaryTo` (type: `integer`):

NHS Jobs' own salary-range filter, upper end, in whole pounds a year. 0 means no upper end.

## `nhsStaffGroups` (type: `array`):

NHS Jobs' own staff-group filter.

## `mojRegions` (type: `array`):

The MoJ board's own region filter. Overrides the region worked out from your locations. Use the names on jobs.justice.gov.uk: London, North West, Wales, National and so on.

## `mojEmployment` (type: `array`):

The MoJ board's own employment filter: Permanent, Fixed Term, Apprenticeship, Loan, Detached Duty, Internship, Graduate Placement, Expression of Interest.

## `mojGrades` (type: `array`):

The MoJ board's own grade filter: AA, AO, EO, HEO, SEO, Grade 7, Grade 6, SCS PB1 to PB4.

## `mojWorkingPatterns` (type: `array`):

The MoJ board's own working-pattern filter: Full Time, Part Time, Part Time/Job Share, Flexible Working, Compressed Hours, Other.

## `mojRoleTypes` (type: `array`):

The MoJ board's own role-type filter, for example "Prison Officer", "Probation Officer", "Operational Support Grade", "Legal Services" or "Administration / Corporate Support". Several are combined as either-or. The site's role-type names; anything else is ignored with a warning.

## `mojOrganisation` (type: `string`):

The MoJ board's own organisation filter, one value, for example "HMPPS - Public Sector Prisons", "HMPPS - Probation Service", "HMPPS - Youth Custody Estate" or "AGY - HMCTS". The site's organisation names; anything else is ignored with a warning.

## `mojPlaces` (type: `array`):

The MoJ board's own "Where" filter: named prisons, courts and probation offices, for example "Belmarsh", "HMP Wandsworth" or "Wandsworth County Court". A prison goes by its name with or without HMP. Several are combined as either-or. When set, these replace your locations on the MoJ board. A name the board does not list is ignored with a warning, and if none is found the MoJ board is not searched.

## `mojSalaryBands` (type: `array`):

The MoJ board's own salary-band filter. Several are combined as either-or. An advert spanning two bands matches either.

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

On, you get the description, person specification, grade, benefits, contact and apply link, charged as job-detail. Off, you get listing fields only, charged at the cheaper job-listed rate.

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

Stop after this many jobs have been pushed. 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.

## `csjScope` (type: `string`):

Search (the default) runs your keywords and locations on Civil Service Jobs like the other boards. Whole board returns every live Civil Service advert, newest change first, including adverts returned before; the per-search cap does not apply, so use the run-wide maximum to cap it. Changed since last run returns only adverts that are new or were edited since the previous run with the same dedupStoreName, which suits a daily schedule (without a store name it reads the whole board). In both whole-board options keywords, locations, distance and sort do not apply to Civil Service Jobs; the title, department, employer, salary, closing-date and Civil Service filters are applied after each advert is read, so an advert they remove is still billed as a detailed job. NHS Jobs and MoJ are searched as usual.

## `includeRawData` (type: `boolean`):

Keep board-specific extras such as vacancy ids and distance text on each record.

## `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
{
  "sources": [
    "nhs",
    "csj",
    "moj"
  ],
  "keywords": [
    "administrator",
    "caseworker"
  ],
  "locations": [
    "London"
  ],
  "distanceMiles": 10,
  "maxJobsPerSearch": 100,
  "maxItems": 0,
  "sortBy": "newest",
  "postedWithinDays": 0,
  "salaryMin": 0,
  "titleInclude": [],
  "titleExclude": [],
  "departments": [],
  "csjGrades": [],
  "csjRoleTypes": [],
  "csjWorkingPatterns": [],
  "csjContractTypes": [],
  "employers": [],
  "nhsContractTypes": [],
  "nhsWorkingPatterns": [],
  "nhsPayBands": [],
  "nhsPayRanges": [],
  "nhsSalaryFrom": 0,
  "nhsSalaryTo": 0,
  "nhsStaffGroups": [],
  "mojRegions": [],
  "mojEmployment": [],
  "mojGrades": [],
  "mojWorkingPatterns": [],
  "mojRoleTypes": [],
  "mojPlaces": [],
  "mojSalaryBands": [],
  "fetchDetails": true,
  "maxNewJobs": 0,
  "csjScope": "search",
  "includeRawData": false,
  "maxConcurrency": 16,
  "delayMs": 0
}
```

# Actor output Schema

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

UK public-sector jobs: every row the run returned.

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

Counts for the run: what was resolved, fetched, 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 = {
    "sources": [
        "nhs",
        "csj",
        "moj"
    ],
    "keywords": [
        "administrator",
        "caseworker"
    ],
    "locations": [
        "London"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("boncross/uk-public-sector-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 = {
    "sources": [
        "nhs",
        "csj",
        "moj",
    ],
    "keywords": [
        "administrator",
        "caseworker",
    ],
    "locations": ["London"],
}

# Run the Actor and wait for it to finish
run = client.actor("boncross/uk-public-sector-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 '{
  "sources": [
    "nhs",
    "csj",
    "moj"
  ],
  "keywords": [
    "administrator",
    "caseworker"
  ],
  "locations": [
    "London"
  ]
}' |
apify call boncross/uk-public-sector-jobs --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,boncross/uk-public-sector-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/2vP6DAf6qGOqPebdK/builds/f5ovvZTDIaWKTQAne/openapi.json
