# Reed Jobs Scraper (UK) (`hexagone-data/reed-scraper`) Actor

Scrape reed.co.uk job searches and job ads, with every job a search announces: each search is read in one call (no jobs lost between pages) and searches over Reed's 10,000 limit are split. Salary as shown, company, location, dates, full ad, skills, sector. Pay per job.

- **URL**: https://apify.com/hexagone-data/reed-scraper.md
- **Developed by:** [Hexagone Data](https://apify.com/hexagone-data) (community)
- **Categories:** Jobs, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.28 / 1,000 jobs

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

## Reed Jobs Scraper (UK)

Scrape job offers from [reed.co.uk](https://www.reed.co.uk): paste the search you made on the site, get **every job it
announces**, one record per job, with the salary, location, contract and date **exactly as the site shows them** and the
full job details included.

**Why this scraper: you get the whole search, and you can check it.**

- **No job lost between pages.** Reed shuffles jobs that rank equally every time a page is loaded. Reading a search page
  by page (25 jobs at a time, as a visitor or most scrapers do) serves some jobs twice and never serves others: on a
  "nurse" search of 1,330 jobs, page-by-page reading gave 1,330 rows but only **1,295 different jobs (35 lost)**. This
  actor reads each search in **one call** (up to 10,000 jobs): **1,330 of 1,330**. Measured again on 2026-10-30 on the UK
  "nurse" search (about 1,350 jobs): two other public Reed scrapers missed **1.6% and 1.8%** of the jobs, in blocks of
  consecutive ranks; this actor delivered **1,348 of 1,348**.
- **Beyond Reed's 10,000 limit.** Reed never serves more than 10,000 jobs per search (page 401 repeats page 400). This
  actor splits bigger searches by sector, then by contract type, and puts the parts back together without duplicates:
  all jobs in London, **23,048 jobs for 23,043 announced**, no duplicate (one run on Apify, 2026-10-30: 38 parts, 11 minutes).
- **Every gap is reported with both numbers.** Each start URL ends with a status: `done` only when every job the site
  still announced was served (if Reed's total moved by a few jobs meanwhile, `TOTAL_CHANGED` gives both numbers);
  otherwise `partial_collection` with "X of Y" and the reason. A repeated row never counts as a job.
- **Labels as on the website**: salary text ("£14 - £15 per hour", "Competitive salary"), card location, "Permanent,
  full-time", "On-site", "Yesterday" (London time), badges (Featured, New, Easy Apply…). The promoted banner is not
  delivered as results.
- **Job URLs too**: paste a job URL and get its record; an expired job is delivered and marked (`isLive: false`).

### Try it with 20 jobs

1. Open the actor's input: the start URL is pre-filled with `https://www.reed.co.uk/jobs/nurse-jobs-in-london` and
   "Maximum number of jobs" with 20.
2. Click **Start**. After a minute you get 20 jobs with their details (about **$0.008**).
3. Open **Run summary** (key-value store, `SUMMARY`) to see the status of your URL and the number of jobs the site
   announced.

### What to paste

| You paste | Example | What happens |
|---|---|---|
| A search results URL, filters included | `https://www.reed.co.uk/jobs/nurse-jobs-in-london` | The site itself reads the URL (location, filters); the whole search is collected |
| A search with site filters | `https://www.reed.co.uk/jobs/it-jobs-in-london?sectorIds=52&workingOption=remote&salaryFrom=50000` | Same, with the filters you chose on reed.co.uk |
| A search without keyword | `https://www.reed.co.uk/jobs/jobs-in-london` | 22,000+ jobs: split beyond 10,000 automatically |
| A job URL | `https://www.reed.co.uk/jobs/aspiring-nurse/57387415` | That job, with its details |

`pageno=` in a pasted URL is ignored: the search is always read from its first job. Several URLs can overlap: each job is
delivered once, and each URL still goes to the end of its own search.

Without start URLs you can fill **keyword** and **location** (plus distance, contract types, full/part-time, work
location, salary, date posted, posted by, Easy Apply, graduate, visa sponsorship, hide training courses). The actor builds
the same URL as the site's search form and lets Reed read it, so the location is resolved exactly as on the website.
If Reed understands your location differently (e.g. "Newport" is read as "Newport, Dyfed"), the run summary says so
(`LOCATION_INTERPRETED`); "Remote" becomes the remote-work filter, as on the site; a place Reed does not know gives
`LOCATION_UNKNOWN` and no results.

### Output

One record per job. Real record (job 57387415, read on 2026-10-30; the two description fields are cut here, every other
field is shown in full):

```json
{
  "jobId": "57387415",
  "url": "https://www.reed.co.uk/jobs/aspiring-nurse/57387415",
  "title": "Aspiring Nurse",
  "company": "Academics",
  "companyId": "432060",
  "companyProfileId": "14099",
  "companyUrl": "https://www.reed.co.uk/jobs/academics/p14099",
  "companyLogo": "https://resources.reed.co.uk/profileimages/logos/thumbs/Logo_14099.png",
  "postedBy": "agency",
  "location": "East London, London",
  "locationName": "East London",
  "county": "London",
  "salary": "£21,600 - £25,200 per annum",
  "salaryFrom": 21600,
  "salaryTo": 25200,
  "salaryPeriod": "year",
  "salaryCurrency": "GBP",
  "salaryFlags": [],
  "contractType": "Contract",
  "isFullTime": true,
  "isPartTime": false,
  "jobTypeLabel": "Contract, full-time",
  "workingOption": "On-site",
  "postedAt": "2026-09-24T11:16:23.503",
  "postedAtLabel": "24 September",
  "createdAt": "2026-09-24T11:16:23.517",
  "updatedAt": "2026-10-06T11:17:17.957",
  "expiresAt": "2026-11-05T23:55:00",
  "isFeatured": true,
  "isPromoted": false,
  "isEarlyBird": true,
  "isEasyApply": true,
  "isTrainingJob": false,
  "isSurveyJob": false,
  "eligibleUkOnly": false,
  "badges": ["Featured", "Early bird", "Easy Apply"],
  "snippet": "Aspiring Nurse Location: Newham Are you passionate about supporting children and young people and considering a future career in nursing? We are lo...",
  "taxonomy": ["Nurse"],
  "sourceUrl": "https://www.reed.co.uk/jobs/nurse-jobs-in-london",
  "page": 1,
  "position": 1,
  "searchPart": null,
  "scrapedAt": "2026-10-30T01:11:23Z",
  "detailsSource": "jobPage",
  "descriptionHtml": "<p><strong>Aspiring Nurse</strong><br><strong>Location: Newham</strong></p><p>Are you passionate about supporting childr…",
  "descriptionText": "Aspiring Nurse\nLocation: Newham\nAre you passionate about supporting children and young people and considering a future c…",
  "skills": [],
  "sector": "Learning Support Assistant",
  "sectorId": "311",
  "sectorParent": "Education",
  "sectorParentId": "68",
  "breadcrumbs": ["Education", "Nurse"],
  "town": "London",
  "countyName": "London",
  "region": "South East England",
  "country": "United Kingdom",
  "countryCode": "GB",
  "postcode": null,
  "isRemoteJob": false,
  "jobPageLocation": "East London, London",
  "jobPageSalary": "£21,600 - £25,200 per annum",
  "jobPageCompanyName": "Academics",
  "companyProfileUrl": null,
  "recruiterReference": "12:12",
  "recruiterKind": "agency",
  "isExternalApply": false,
  "isLive": true,
  "salaryPerAnnumFrom": 21600,
  "salaryPerAnnumTo": 25200,
  "isGraduate": false,
  "visaRequirement": null,
  "jobPageContractType": "Contract"
}
```

- Unknown values are `null` (never an empty string, an invented 0 or false). `salary` is the text the card shows (Reed's
  API has another, different text that this actor does not use). `salaryFrom`/`salaryTo` are the amounts the card shows
  (numbers); when the card shows "Competitive salary", "Salary negotiable", "Commission only" or "Training Course", the
  site hides the amount and they are null, like `salaryPeriod`, `salaryCurrency` and `salaryPerAnnumFrom`/`To` (the
  hidden amounts are often placeholder ranges such as 10,000-100,000).
- Dates are as Reed serves them (UK time, no time zone); `postedAtLabel` and `badges` are computed like the site, in
  London time, at `scrapedAt`.
- `page`/`position` are the job's rank in the search call this run made (25 per page; Reed reorders on every call, see
  Known limits). Jobs found only by splitting a search over 10,000 have `searchPart` set and `page`/`position` null.
- `detailsSource` is `jobPage` when the job details were read; if a job's details could not be read (or the job was
  removed in the meantime), the job is still delivered with `listing` and the detail fields null, and the run summary
  counts it.
- For a pasted job URL, card-only fields that the job page does not give the same way (card location, work location,
  snippet, taxonomy) are null.

The **Jobs overview** view shows title, company, location, salary, contract, work location, date and link.

### Run summary

Every run writes `SUMMARY` (readable page) and `OUTPUT` (JSON) in the key-value store: per start URL, its status, the jobs
delivered, the jobs the site announced, the distinct jobs served, the total at the end, repeated rows, split parts, and
the reason for anything missing. Statuses:

| Status | Meaning |
|---|---|
| `done` | Every job the site still announced was served and delivered (or delivered by an earlier URL); if Reed's total moved by a few jobs between the search page and its search call, `TOTAL_CHANGED` gives both numbers |
| `partial_collection` | Some announced jobs were not served: both numbers and the cause are given |
| `max_items` / `max_charge` | Stopped at your maximum number of jobs / maximum charge per run |
| `time_limit` | Stopped cleanly a few minutes before the run timeout (summary written) |
| `no_results` | The site announces 0 jobs for this search |
| `blocked_by_site`, `rate_limited`, `network_error`, `unrecognized_page` | The site could not be read even after new IPs: **not** an empty search |
| `job_not_found` / `job_not_available` | Pasted job URL: Reed says the job does not exist / answered another job |
| `filter_not_applied` | Keyword mode: the site ignored one of your filters; nothing is read, delivered or charged for this search |
| `no_new_jobs` | Every job of this URL was already delivered by an earlier start URL |
| `not_processed` / `interrupted` | Not reached because the run stopped earlier (maximum, time limit) / stopped while being read |
| `internal_error` | Unexpected error on this URL (the other URLs go on) |

Warnings (section "Checks") have stable codes: `PARTIAL_COLLECTION`, `PIECE_CAPPED` (a split part still over 10,000),
`NO_RESULTS`, `TOTAL_CHANGED`, `LOCATION_INTERPRETED`, `LOCATION_UNKNOWN`, `FILTER_NOT_APPLIED`, `JOB_EXPIRED`, `JOB_NOT_FOUND`,
`DETAILS_NOT_READ`, `DETAILS_GONE`, `DETAILS_STOPPED`, `UNEXPECTED_DATA`, `JOBS_SKIPPED`, `TIME_LIMIT`, `PROXY_SETTING`.
A run that delivers no job at all fails, with the cause in its status message. A pasted URL that is the same search as
an earlier one (e.g. with `?pageno=2`) is read once and listed in `mergedUrls`.

### Pricing

**$0.40 per 1,000 jobs, job details included** (pay per event: $0.0004 per delivered job). 20 jobs ≈ $0.008; 1,000 jobs
\= $0.40; a 10,000-job search = $4.00. Nothing else is charged: no start fee, no extra event for details. Apify
subscribers pay less automatically: 10% off on the Bronze plan, 20% on Silver, 30% on Gold and above. Only delivered jobs
are charged; a job delivered by two overlapping URLs is charged once. Your "maximum charge per run" is respected: no job
details are read for a job that could not be paid.

### Known limits

- **Speed** (measured on Apify, 2026-10-30): 272 jobs with details in 41 s; 1,348 jobs without details in 13 s; all
  23,048 London jobs without details in 11 minutes. 10,000 jobs with details: about 25 minutes (estimated from these
  runs). Turn details off for a faster run (same price).
- **Over 10,000 per part**: a part that still holds more than 10,000 jobs after splitting by sector and contract type
  (or a search whose URL already fixes both) gets only its first 10,000 jobs: `PIECE_CAPPED` and `partial_collection`.
- **Order and total change on every call on Reed's side**, sometimes a lot: on an "accountant" search in Leeds
  (2026-10-30), one job ranked 6th in one call and 105th in the next identical call, and the total announced was 246 to
  259 for 25 jobs at a time but 269 to 272 for the whole search in one call (so a job or two can be in one call and not
  in the next; the site's own 10 pages showed only 210 different jobs). `page`/`position` are the job's rank in the
  one call this run made (25 per page): the same job can sit on another page in your browser. When that one call comes
  back full while Reed announces more, it is made once more, larger.
- **Locations** are resolved by Reed, including its own mistakes (e.g. "Newport" → "Newport, Dyfed", the URL path
  `/nurse-jobs-in-stoke-on-trent` → "Stoke on Tern"): the run summary says so, the actor never replaces your location.
- **Number of applicants** is not available without a Reed account.
- **Access**: about a third of Apify datacenter IPs are refused by Reed; refused requests are retried with new IPs, then
  the run switches to Apify residential proxy (Great Britain), at no extra cost to you.
- After a platform migration the search being read is called again: the jobs not yet delivered are delivered from that
  second call (no duplicate, no loss), their `page`/`position` coming from it.

### Support

Questions, a missing field, a URL that does not work: open an issue on the actor's **Issues** tab with the start URL and
the run ID; the run summary usually says what happened.

Reed and reed.co.uk are trademarks of their owner. This actor is not affiliated with, endorsed or sponsored by Reed. It reads only public pages and the data the public website itself loads, without an account.

# Actor input Schema

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

Reed search results URLs exactly as your browser shows them, filters included (e.g. https://www.reed.co.uk/jobs/nurse-jobs-in-london, https://www.reed.co.uk/jobs/it-jobs-in-london?sectorIds=52\&workingOption=remote\&salaryFrom=50000), or job URLs (https://www.reed.co.uk/jobs/<title>/<id>). The site itself reads the URL, location and filters. When set, keyword and its filters are ignored.

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

Job search keywords, e.g. nurse, software developer.

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

Town, county or postcode as you would type it on reed.co.uk, e.g. London, Manchester, SW1A 1AA. Reed resolves it exactly as on the website (an ambiguous name like 'Newport' may be read as another place: the run summary says so). 'Remote' becomes the remote-work filter, as on the site.

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

0, 1, 3, 5, 10, 15, 20, 30 or 50, like the site. Empty = 10 (site default).

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

Any of: perm, temp, contract. Empty = all.

## `fullTime` (type: `boolean`):

Only full-time jobs.

## `partTime` (type: `boolean`):

Only part-time jobs.

## `workingOption` (type: `string`):

On-site, hybrid or remote. Empty = all.

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

Only jobs paying at least this, in GBP (site filter).

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

Only jobs paying at most this, in GBP (site filter).

## `datePosted` (type: `string`):

Empty = any time.

## `postedBy` (type: `string`):

Empty = all.

## `easyApplyOnly` (type: `boolean`):

Only jobs with Easy Apply.

## `graduate` (type: `boolean`):

Only graduate jobs.

## `visaSponsorship` (type: `boolean`):

Only jobs offering visa sponsorship.

## `hideTrainingJobs` (type: `boolean`):

Hide paid training courses listed as jobs.

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

Stops after this many unique jobs across all start URLs. 0 = no limit. You pay per delivered job.

## `includeDetails` (type: `boolean`):

On by default and included in the price: each delivered job's details are read once (Reed's own job API): full ad (HTML and text), skills, sector, breadcrumbs, town, county, region, country, postcode, salary per annum, recruiter reference and type, external apply, live or expired. Turn off for a faster run with search fields only (same price).

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

Apify datacenter proxy by default. About a third of datacenter IPs are refused by Reed (measured 2026-10-30): a refused request is retried with a new IP, up to 5 times, then the run switches to Apify residential proxy (Great Britain). Choosing your own proxy groups or URLs turns this fallback off.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.reed.co.uk/jobs/nurse-jobs-in-london"
    }
  ],
  "maxItems": 20,
  "includeDetails": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `jobs` (type: `string`):

All job records, one per job offer, with every field.

## `jobsOverview` (type: `string`):

Title, company, location, salary, contract, work location, date, link.

## `summary` (type: `string`):

Readable per-URL summary: status, jobs delivered vs announced, reason for anything missing.

## `summaryJson` (type: `string`):

The same summary as JSON (OUTPUT record).

# 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 = {
    "startUrls": [
        {
            "url": "https://www.reed.co.uk/jobs/nurse-jobs-in-london"
        }
    ],
    "maxItems": 20,
    "includeDetails": true,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("hexagone-data/reed-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 = {
    "startUrls": [{ "url": "https://www.reed.co.uk/jobs/nurse-jobs-in-london" }],
    "maxItems": 20,
    "includeDetails": True,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("hexagone-data/reed-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 '{
  "startUrls": [
    {
      "url": "https://www.reed.co.uk/jobs/nurse-jobs-in-london"
    }
  ],
  "maxItems": 20,
  "includeDetails": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call hexagone-data/reed-scraper --silent --output-dataset

```

## MCP server setup

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