# Reed Jobs Scraper - UK Salaries, Remote & Expiry (`neverempty/reed-jobs`) Actor

Every job on Reed.co.uk with the salary as two numbers and its real period - per hour, per day or per annum, each confirmed against what the page shows, never guessed. Plus employer, county, remote option, posted and expiry dates. A search with no results says so and is never charged. Unofficial.

- **URL**: https://apify.com/neverempty/reed-jobs.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** Jobs, Lead generation, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.65 / 1,000 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.
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?

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

## Reed Jobs Scraper - UK Salaries, Remote & Expiry

Pull jobs from **Reed.co.uk** with the salary as **two numbers and its real period** - per hour, per day or per annum - plus the employer, the county, the remote-working option, the date it was posted and the date it **expires**.

*Unofficial. This Actor is not affiliated with, endorsed by, or sponsored by Reed. All product names are trademarks of their respective owners.*

No API key. Reads the same public listing pages a visitor sees.

***

### Why this one is different

#### The salary period is confirmed, not guessed

Reed publishes the salary period as a **number**, not a word: `salaryType: 5`. Getting that wrong turns a £600 **day rate** into a £600 salary.

So each code was checked against what the page itself prints, on live listings:

| code | page shows | example |
|---|---|---|
| `1` | per hour | £20 - £28 per hour |
| `2` | per day | £600 - £625 per day |
| `5` | per annum | £80,000 - £100,000 per annum |

**Codes that were not seen on a live page are left as `salaryPeriod: null`** - the raw `salaryTypeCode` is always kept so you can check it yourself. A guess that reads well is worse than an honest null.

#### A city filter that actually filters

Reed accepts three URL shapes for a city. Measured on 2026-09-03 for `developer` (3,174 jobs):

| shape | result |
|---|---|
| `/jobs/developer-jobs-in-london` | **1,159 jobs, every location in London** ✅ |
| `/jobs/developer-jobs/london` | HTTP 200, **23 of 25 results identical to the unfiltered list** ⚠️ |
| `?location=london` | HTTP 200, **total unchanged at 3,174** ⚠️ |

This Actor only ever builds the first one. The other two look like they work.

#### The same job is never charged twice

Reed repeats jobs across pages (measured: 1 of 25 on page 2 was already on page 1) and also shows some of them in its **promoted slots**. Job ids are tracked across the whole run, so a repeat is dropped rather than delivered again.

Promoted jobs are **not** added on top - they are already in the results. You get an `isPromoted` flag instead, so you can see why a job sits high in the list.

#### An empty search is an answer, not a failure

A keyword Reed has no jobs for returns **one `no-results` row saying so**, and it is not charged. A page that could not be read at all is a different row (`unreadable`) with the HTTP status. You always know which of the two you got.

***

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `keyword` | string | `developer` | e.g. `software engineer`, `accountant`, `nurse` |
| `city` | string | — | e.g. `london`, `manchester`, `milton keynes` |
| `remoteOnly` | boolean | `false` | Measured: narrows `developer` 3,174 → 493 |
| `maxJobs` | integer | 25 | 1-1000. One job = one row = one charged event |
| `maxPages` | integer | 0 | Safety cap. 0 = decide from `maxJobs` (25 per page) |
| `keywords` | array | — | Keep only jobs containing one of these words |
| `keywordMatch` | string | `any` | `any` or `all` |
| `excludeKeywords` | array | — | Drop jobs containing one of these |
| `companyNames` | array | — | Keep only these employers (partial match) |
| `minSalary` | integer | 0 | **A job with no published salary is kept** |
| `requireSalary` | boolean | `false` | Drop jobs where the employer hid the salary |
| `postedWithinDays` | integer | 0 | 0 = no limit. **An unreadable date is kept** |
| `monitoringMode` | boolean | `false` | Return only jobs not returned on a previous run |
| `resetMonitoringState` | boolean | `false` | One-shot: forget what was already returned |

#### Filters are applied before you are charged

The filters narrow the jobs already read - **nothing extra is fetched from Reed** - and a job that does not match is never delivered and never charged. If a search returns jobs but none match your filters you get **one unpaid row saying exactly that** (`no-filter-match`), so you can tell "the filter was too tight" apart from "Reed has nothing".

Two of them refuse to guess: a job with **no published salary is kept** by `minSalary` (an unknown salary is not a low one), and a job with an **unreadable posted date is kept** by `postedWithinDays`. Turn on `requireSalary` to drop the unknowns.

#### Monitoring mode turns this into a daily feed

Schedule it with `monitoringMode: true` and each run returns **only the jobs it has not returned before**. The first run returns everything and remembers it. Jobs read but filtered out are remembered too, so changing a filter later does not resurrect them as false "new" postings.

The minimal call is an empty input: 25 developer jobs.

***

### Output columns

| Column | Example |
|---|---|
| `jobId` | `57205785` |
| `url` | the job page on Reed |
| `title` | `Senior Developer - London` |
| `employer` | `Norton Rose Fulbright LLP` |
| `location` / `county` | `Newcastle Upon Tyne` / `Tyne and Wear` |
| `salaryFrom` / `salaryTo` | `35000` / `45000` |
| `salaryPeriod` | `year`, `day`, `hour` - or `null` when the code is not confirmed |
| `salaryTypeCode` | Reed's raw code, always kept |
| `salaryCurrency` | `GBP` |
| `remoteWorkingOption` | `Hybrid` |
| `isFullTime` / `isPartTime` | `true` / `false` |
| `datePosted` / `dateUpdated` / `expiryDate` | `2026-08-06T14:19:58` / … / `2026-10-05T23:55:00` |
| `category` / `subCategory` | `Developer` / … |
| `isPromoted` | `true` when Reed also shows it in a promoted slot |
| `isEasyApply` / `ukOnly` | `true` / `false` |
| `descriptionSnippet` | the summary Reed shows in the listing |
| `status` / `note` | only on rows that carry no job, with the reason |

***

### Measured, not estimated

Checked against the live site on 2026-09-03:

- `developer` returns **3,174 jobs**, 25 per page.
- **Salary codes** 1 / 2 / 5 confirmed against the printed "per hour" / "per day" / "per annum" on real listings.
- **City filter**: `-in-london` → 1,159 jobs; the two other shapes Reed accepts do not filter at all.
- **Remote filter**: 3,174 → 493.
- **Page 2** shares 1 job with page 1 - that is why ids are tracked across the run.
- A keyword with no jobs returns **0 results** cleanly (not an error).
- `https://www.reed.co.uk/robots.txt` **explicitly allows AnthropicBot, GPTBot and PerplexityBot**; the listing pages are not in its Disallow list.

The Actor ships with **13 checks** run against saved copies of two real result pages, and a **mutation suite of 15 deliberate breakages - all 15 are caught**.

***

### Typical uses

- Benchmark UK salaries by role, city and contract type - the numbers and their period are already parsed.
- Track contract day rates separately from permanent salaries (the period tells you which is which).
- Watch a keyword and city for new postings, using `datePosted` and `expiryDate` to keep the list current.
- Feed a job board or a newsletter with fresh UK roles and their employer already attached.

***

### Notes and limits

- Reed lists UK jobs. Salaries are in GBP.
- `descriptionSnippet` is the summary Reed shows in the listing, not the full advert body.
- Some listings publish no salary at all; those rows carry `null`, never `0`.

### Other tools by NeverEmpty

- **[Internshala Scraper - India Jobs & Internships, Salary](https://apify.com/neverempty/internshala-jobs)**
- **[Instahyre Scraper - India Tech Jobs & Company Data](https://apify.com/neverempty/instahyre-jobs)**
- **[Job Listing API - Career Site Jobs from Any ATS](https://apify.com/neverempty/ats-jobs-api)**

### Support

Found a row that looks wrong? Open an issue on the Actor page with the `url` from the row - `salaryTypeCode` is kept precisely so a salary can be checked against the page.

# Actor input Schema

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

What to search for, for example `developer`, `software engineer`, `accountant`, `nurse`. Reed builds its listing from this, so a keyword it does not have returns a single no-results row rather than a silently unfiltered list.

## `city` (type: `string`):

Filter by city, for example `london`, `manchester`, `birmingham`, `milton keynes`. Measured on 2026-09-03: this narrows `developer` from 3,174 jobs to 1,159 and every returned location is in that city. Two other URL shapes Reed accepts (`/jobs/x-jobs/london` and `?location=`) answer HTTP 200 while ignoring the city completely - this Actor never uses them.

## `remoteOnly` (type: `boolean`):

Default: false. Measured: narrows `developer` from 3,174 to 493 jobs.

## `maxJobs` (type: `integer`):

How many jobs to return at most. One job = one row = one charged event. Reed returns 25 jobs per page; the same job appearing again on a later page is dropped, so you are never charged twice for it. Rows that carry no job (no results, or a page that could not be read) are returned too and are never charged.

## `maxPages` (type: `integer`):

Safety cap on how many listing pages to walk. 0 lets the Actor decide from Maximum jobs (25 jobs per page).

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

Keep only jobs whose title, description, employer, category or location contains one of these words. Case is ignored. Leave empty to keep everything. Nothing extra is fetched from Reed - this narrows the jobs already read, and jobs that do not match are never charged.

## `keywordMatch` (type: `string`):

`any` keeps a job that contains at least one keyword. `all` keeps only jobs that contain every keyword.

## `excludeKeywords` (type: `array`):

Drop any job containing one of these words - for example `apprentice`, `commission only`, or an agency you do not want. Applied after the keyword filter.

## `companyNames` (type: `array`):

Keep only jobs from employers whose name contains one of these (partial match, case ignored). Leave empty for all employers.

## `minSalary` (type: `integer`):

Keep only jobs paying at least this much, compared against the salary Reed publishes. A job with no published salary is **kept**, not dropped - an unknown salary is not a low one. Turn on `requireSalary` if you want those dropped too. Note the salary period differs per job (`salaryPeriod` is per hour, per day or per annum), so set this with the period you are searching in mind.

## `requireSalary` (type: `boolean`):

Default: false. Turn it on to drop jobs where the employer hid the salary.

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

Keep only jobs posted in the last N days. 0 means no limit. A job whose posted date cannot be read is **kept**, not dropped.

## `monitoringMode` (type: `boolean`):

Default: false. Turn it on and schedule this Actor daily: each run returns only the jobs it has not returned before, so you pay for new postings instead of the same list again. The first run returns everything and remembers it. Jobs read but filtered out are remembered too, so they never come back as false 'new' ones.

## `resetMonitoringState` (type: `boolean`):

Default: false. Turn it on for a single run to forget which jobs were already returned and start monitoring again from scratch.

## Actor input object example

```json
{
  "keyword": "developer",
  "city": "",
  "remoteOnly": false,
  "maxJobs": 25,
  "maxPages": 0,
  "keywords": [],
  "keywordMatch": "any",
  "excludeKeywords": [],
  "companyNames": [],
  "minSalary": 0,
  "requireSalary": false,
  "postedWithinDays": 0,
  "monitoringMode": false,
  "resetMonitoringState": false
}
```

# Actor output Schema

## `results` (type: `string`):

One row per job: title, employer, location and county, the salary as two numbers with its currency and period, remote option, the date it was posted and the date it expires, and Reed’s own category. Searches with no results and pages that could not be read come back as their own rows and are not charged.

# 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": "developer"
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/reed-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 = { "keyword": "developer" }

# Run the Actor and wait for it to finish
run = client.actor("neverempty/reed-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 '{
  "keyword": "developer"
}' |
apify call neverempty/reed-jobs --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/reed-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/qCmlsGf1wqkCQIjap/builds/N8MugcnmwQ7fC0R5P/openapi.json
