# Reed.co.uk Scraper - Jobs, Companies, Courses & Reviews (`abotapi/reed-co-uk-scraper`) Actor

Scrape Reed.co.uk jobs, companies, courses and reviews by keyword, location, filters or URL. Extract 80+ fields including salary, company, GPS, sector, contract type, skills, descriptions, postcodes, company profiles, courses and reviews.

- **URL**: https://apify.com/abotapi/reed-co-uk-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** Jobs, Automation, Developer tools
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 results

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.co.uk Scraper: Job Listings, Company Profiles & Training Courses

Reed.co.uk Scraper turns Reed.co.uk, a major UK job board, into a clean, structured data feed. Search jobs by keyword, location, salary, distance, job type and more, or paste any Reed search or job URL directly. Every job comes back as one flat record with the same 96 fields: title, company, salary range, GPS coordinates resolved from the job location, job type, badges and posting dates. Full job details (on by default) also fill in the complete description, region/postcode, full salary breakdown, sector, contract type, skills, company profile and screening questions. The same actor scrapes Reed company profiles and Reed Courses with their reviews, and exports to JSON, CSV or Excel, or straight into your app through the API.

### Why This Scraper?

- **96 fields on every job record.** Every record has the same fixed set of fields; full job details fill in roughly 45 of them (description, location breakdown, sector, contract type, skills, company profile, screening questions) that otherwise stay empty.
- **GPS coordinates on every job**, resolved from the job's location text against a built-in UK gazetteer, since Reed itself does not publish coordinates.
- **Three things to scrape**: job listings (by filters or pasted URLs), company profiles, and training courses with their reviews.
- **Full filter grammar**: keyword, location, distance, salary range, posted-within window, sort order, job type (permanent, temporary, contract, full-time, part-time), posted-by (agency or employer), plus graduate, easy-apply, visa-sponsorship and early-bird flags.
- **Walks the whole catalogue by default.** Max pages defaults to unlimited: the run walks every result page per search or URL, stopping on its own once Reed's reported page count is reached or a page comes back with nothing new. Max results is the sole cap on volume.
- **Resumable runs.** Point a new run at a previous run or dataset ID to collect only the jobs it doesn't already have. The run also checkpoints its own progress, so an Apify platform migration or a Resurrect of an interrupted run can continue instead of starting over (see the known limitations below for what a mid-run restart does not preserve).
- **Optional export to your apps** via MCP connectors (Notion, Linear, Airtable, Apify).

### Use Cases

- **Recruitment and staffing agencies:** track competitor job ads, salary bands and hiring volume by sector or region.
- **Job board and aggregator operators:** mirror Reed listings into your own search index, complete with GPS coordinates for map-based search.
- **Market and salary researchers:** build salary distributions and posting-volume trends across UK regions, sectors and job types.
- **Sales and lead-generation teams:** use company profiles (open jobs, employee-count range, sector, website) to find growing companies as outbound leads.
- **Training and upskilling platforms:** track Reed Courses pricing, ratings and reviews across providers.

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live listing.

| Field | Example |
| --- | --- |
| `jobId` | `00000001` |
| `title` | `"Data Engineer"` |
| `url` | `"https://www.reed.co.uk/jobs/data-engineer/00000001"` |
| `companyName` | `"Sample Company"` |
| `logoImage` | `"https://resources.reed.co.uk/profileimages/logos/Logo_0000.png"` |
| `location` | `"London"` |
| `latitude` / `longitude` | `51.5074` / `-0.1278` |
| `displaySalary` | `"£45,000 - £55,000 per annum"` |
| `salaryFrom` / `salaryTo` | `45000` / `55000` |
| `salaryType` | `5` |
| `remoteWorkingOption` | `"On-Site"` |
| `postedOn` / `expiryOn` | `"2026-01-01T00:00:00.000Z"` / `"2026-02-01T00:00:00.000Z"` |
| `statuses` | `["Featured", "Easy Apply"]` |
| `contractType` | `"Permanent"` (when full details are on) |
| `sectorName` / `parentSectorName` | `"Other IT & Telecoms"` / `"IT & Telecoms"` (when full details are on) |
| `region` / `county` / `postCode` | `"South East England"` / `"London"` / `"EC1A"` (when full details are on) |
| `description` | `"Full job description text appears here when full job details are on."` |
| `skills` | `["Python", "SQL"]` (when full details are on) |
| `companyProfileId` | `0000` (when full details are on) |
| `detailFetched` | `true` |

Company records (`recordType: "company"`) carry the sector, employee-count range, website, benefits, articles, external links, total open jobs and a list of recent jobs (each with salary and GPS). Course records (`recordType: "course"`) carry the title, provider, price, rating, study method and format; when reviews are enabled each review is returned as its own record (`recordType: "review"`) with overall and per-category ratings, text, reviewer and any provider response.

#### Output views

The dataset has one view per record kind in the Console: **Results** (all kinds, see `recordType`), **Jobs**, **Companies**, **Courses** and **Reviews**. The same records are available through the API.

### How to Use

1. Pick a **mode**: Jobs (default), Companies, or Courses + reviews.
2. For Jobs, either paste Job URLs or leave them empty and set keywords, locations and filters. Keep **Fetch full job details** on (the default) for the complete description and company profile, or switch it off for list-page fields only.
3. Set **Max results** to control run size and cost (and, optionally, **Max pages** as a safety limit), then click **Start**.
4. Download the dataset as JSON, CSV or Excel, or read it through the API.

**Jobs by keyword search with filters:**

```json
{
  "mode": "jobs",
  "keywords": "software developer",
  "locations": ["manchester"],
  "salaryFrom": 50000,
  "proximity": 20,
  "postedDate": "last7days",
  "sortBy": "date",
  "permanent": true,
  "fetchDetails": true,
  "maxListings": 100
}
```

**Jobs by pasting URLs (filters are ignored when Job URLs are set):**

```json
{
  "mode": "jobs",
  "urls": [
    "https://www.reed.co.uk/jobs/accountant-jobs-in-bristol",
    "https://www.reed.co.uk/jobs/marketing-jobs-in-london?sortby=displayDate"
  ],
  "maxListings": 200
}
```

**Company profiles by URL or slug:**

```json
{
  "mode": "company",
  "companies": [
    "https://www.reed.co.uk/company-profile/Sample-Company-00000",
    "Sample-Company-00000"
  ],
  "maxListings": 50
}
```

**Training courses and their reviews:**

```json
{
  "mode": "course",
  "courseQueries": ["python", "project management"],
  "fetchReviews": true,
  "reviewsSort": "MostRecent",
  "maxListings": 50
}
```

#### URL mode: supported URL shapes

Paste any of these into **Job URLs** (jobs mode) or **Course URLs** (course mode). Every shape below is prefilled as an example in the input form.

| Mode | Shape | Example |
| --- | --- | --- |
| Jobs | Keyword + location search | `https://www.reed.co.uk/jobs/data-engineer-jobs-in-london` |
| Jobs | Paginated search (`?pageno=N`, the walk starts at that page) | `https://www.reed.co.uk/jobs/data-engineer-jobs-in-london?pageno=2` |
| Jobs | Filtered search (distance, salary, job type) | `https://www.reed.co.uk/jobs/data-engineer-jobs-in-london?proximity=20&salaryfrom=50000&salaryto=70000&fulltime=True&permanent=True` |
| Jobs | Sorted or date-limited search | `https://www.reed.co.uk/jobs/data-engineer-jobs-in-london?sortby=DisplayDate&dateCreatedOffSet=lastthreedays` |
| Jobs | Location-only search | `https://www.reed.co.uk/jobs/jobs-in-london` |
| Jobs | Keyword-only (UK-wide) search | `https://www.reed.co.uk/jobs/data-engineer-jobs?pageno=2` |
| Jobs | All jobs | `https://www.reed.co.uk/jobs` |
| Jobs | Legacy query form | `https://www.reed.co.uk/jobs?keywords=data+engineer&location=london&pageno=1` |
| Jobs | Sector / category page | `https://www.reed.co.uk/jobs/it-jobs` |
| Jobs | Single job | `https://www.reed.co.uk/jobs/data-engineer/56212211` |
| Jobs | Single job with tracking query (query is ignored) | `https://www.reed.co.uk/jobs/data-engineer/56212211?source=searchResults` |
| Jobs | Single job, any slug | `https://www.reed.co.uk/jobs/x/56212211` |
| Jobs | Single job, host without `www` | `https://reed.co.uk/jobs/data-engineer/56212211` |
| Company | Company profile URL or slug | `https://www.reed.co.uk/company-profile/Marshall-Wolfe-63256` |
| Courses | Course search | `https://www.reed.co.uk/courses/python` |
| Courses | Paginated course search (`?page=N`) | `https://www.reed.co.uk/courses/python?page=2` |
| Courses | Course detail | `https://www.reed.co.uk/courses/python-course/271655` |
| Courses | Course detail by numeric ID | `https://www.reed.co.uk/courses/c/271655` (or just `271655`) |

A single job URL returns one full record (the same fields as the detail-enriched search result) and counts one detail-enrichment unit per delivered job record, whether or not **Fetch full job details** is on (the page is always fetched in full); a skipped or expired URL is never charged. Not supported, and skipped with a warning in the run log naming the URL and the reason (never a silent zero): a numeric-only `/jobs/56212211` (use `/jobs/x/56212211`), the `m.reed.co.uk` host, non-Reed URLs, and job pages that are expired or removed.

**Behaviour change.** The Job URLs, Course URLs and Companies fields now ship with the example lists above as their default. A run in URL mode whose field you never touched now scrapes one example per shape instead of nothing. To search by keywords and locations instead, clear the Job URLs list (an API call that sends its own `keywords` or `locations` and leaves `urls` out still runs as a filter search). In course mode the same rule applies: an API call that sends its own `courseQueries` and leaves `courseUrls` out runs the keyword searches only, and one that sends its own `courseUrls` and leaves `courseQueries` out scrapes exactly those courses (the sample `python` keyword is dropped).

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/reed-co-uk-scraper").call(run_input={
    "mode": "jobs",
    "keywords": "data engineer",
    "locations": ["london"],
    "maxListings": 50,
})
for job in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(job["title"], job["companyName"], job["displaySalary"])
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/reed-co-uk-scraper').call({
  mode: 'jobs',
  keywords: 'data engineer',
  locations: ['london'],
  maxListings: 50,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the **Integrations** tab.

#### How Max results and Max pages interact

**Max results** is the single cap that applies in every mode: jobs, companies and courses (course reviews are additional and not counted against it). In Jobs mode with several locations or pasted URLs, the cap is split evenly: each search may contribute at most Max results divided by the number of searches, rounded up. `maxListings: 100` with two locations returns up to 50 per location, and a location with fewer matches does not pass its unused share to the other. **Max pages** is only an optional safety limit; left at `0` (the default) a search walks every result page until it reaches its share of Max results, Reed's own reported last page, or a page that contributes no job the run hasn't already collected.

#### Known limitation: overlapping searches in one run can stop a later search early

Duplicate protection is run-wide, not per search. Once a job has been returned by one search, every later search in the same run treats it as already collected. If a results page of a later search (after its first page) consists entirely of such jobs, the actor reads that as the end of that search's new results and stops it, even when Reed reports more pages.

Example: `locations: ["london", "manchester"]`, where Reed reports 4 pages for each. London walks all 4 pages. Manchester's page 1 has new jobs, but its page 2 happens to hold only remote or nationally advertised roles London already returned. Manchester stops after page 2, and the jobs unique to Manchester on its pages 3 and 4 are never collected. A page with even one new job does not trigger this, and a later search whose very first page is all duplicates keeps walking.

Workaround: when you need complete coverage of each location, keyword or URL, run each one in its own run instead of combining overlapping ones. Unrelated, non-overlapping searches in one run are not affected.

#### Known limitation: a migration or Resurrect in the middle of a run

The actor saves a checkpoint after every page, so an Apify platform migration or the Resurrect button continues the same run instead of starting over. Three things are not carried across that restart correctly:

- **The Max results counter restarts at 0.** The resumed process counts only its own rows, so the run as a whole can return the rows collected before the restart plus up to Max results again, and each row is billed. Example: `maxListings: 5`, 3 jobs collected on page 1, a migration, and the resumed process then collects 5 more: 8 rows in total.
- **Jobs the cap left out are remembered as collected.** When a page has more new jobs than the budget left, the jobs the cap leaves out are still recorded as collected. Example: `maxListings: 2`, page 1 has 5 new jobs J1 to J5. J1 and J2 are returned; if the run restarts right after that page, the resumed process skips J3, J4 and J5 for good and returns J6 and J7 from page 2, so you get 4 rows and J3 to J5 are never returned. Even without a restart, later searches in the same run also skip those left-out jobs, so a multi-location run can end below Max results while matching jobs remain.
- **The page in progress can be lost.** Jobs on the page being processed at the moment of the migration (with Fetch full job details on, while their detail pages are being fetched) are recorded as collected before they are saved, so the resumed process skips them. This happens with or without a cap.

Workaround: if the run log contains a "Checkpoint: resuming run" line, the run was restarted. When an exact count or complete coverage matters, start a follow-up run with `resumeFromRunId` set to that run. It reads what was actually saved to the dataset, so jobs skipped by the restart are collected then. Prefer a generous Max results (or `0`) plus a follow-up resume over relying on a tight cap across a restart.

#### Resuming a run

Set `resumeFromRunId` to a previous run ID or dataset ID (from this account) to collect only the jobs that run doesn't already have. This reads the actual dataset of that previous run, so jobs a restarted run skipped (see above) are collected by the follow-up run. Jobs mode only.

### Send results into your apps (MCP connectors)

Optionally pipe each job into the apps you already use, with no change to the dataset output:

1. Authorize a connector under Apify, Settings, API & Integrations (Notion, Linear, Airtable, or Apify).
2. Select it in the `mcpConnectors` input. For Notion, also set `notionParentPageUrl`.
3. Run the actor. Each job is written as a condensed, human-readable summary (title plus key fields); the complete record always stays in the Apify dataset.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave `mcpConnectors` empty to skip this entirely.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `jobs` | `jobs` = job listings (filters or pasted URLs); `company` = company profiles; `course` = courses + reviews. |
| `urls` | array | one example per supported shape (see the URL mode table) | Reed job-search or job URLs (jobs mode). If set, these are scraped directly and the filters below are ignored. Clear the list to use the filters. |
| `keywords` | string | (empty) | Job search terms, e.g. "data engineer" (jobs mode, when no Job URLs are pasted). |
| `locations` | array | (empty) | UK cities or regions; one search per location (jobs mode). Empty searches UK-wide. |
| `proximity` | integer | `10` | Search radius in miles. Allowed: 0, 1, 3, 5, 10, 15, 20, 30, 50. |
| `salaryFrom` | integer | (none) | Minimum salary per annum. |
| `salaryTo` | integer | (none) | Maximum salary per annum. |
| `postedDate` | string | `anytime` | `anytime`, `today`, `last3days`, `last7days`, or `last14days`. |
| `sortBy` | string | `relevance` | `relevance` (Reed's default) or `date` (newest first). |
| `fullTime` | boolean | `false` | Restrict to full-time roles. |
| `partTime` | boolean | `false` | Restrict to part-time roles. |
| `permanent` | boolean | `false` | Restrict to permanent roles. |
| `temporary` | boolean | `false` | Restrict to temporary roles. |
| `contract` | boolean | `false` | Restrict to contract roles. |
| `agency` | boolean | `false` | Only jobs posted by recruitment agencies. |
| `direct` | boolean | `false` | Only jobs posted directly by the employer. |
| `graduate` | boolean | `false` | Only graduate-level roles. |
| `easyApply` | boolean | `false` | Only jobs that support one-click apply. |
| `visaSponsorship` | boolean | `false` | Only jobs offering visa sponsorship. |
| `earlyBird` | boolean | `false` | Only newly posted "early bird" jobs. |
| `workFromHome` | boolean | `false` | Only jobs on Reed's Remote working-option filter. |
| `fetchDetails` | boolean | `true` | Jobs mode only. Enrich each job from its detail page: full description, region/county/postcode, full salary breakdown, sector, company profile, contract type, breadcrumbs and screening questions. |
| `companies` | array | one example company profile | Reed company-profile URLs or bare slugs (company mode). Slugs are case-sensitive. |
| `courseQueries` | array | `["python"]` | Keywords to search Reed Courses (course mode). One search per keyword. |
| `courseUrls` | array | one example per course URL shape | Reed course search URLs, course detail URLs or bare course IDs (course mode). |
| `fetchReviews` | boolean | `true` | Course mode only. Return each course's reviews as separate records. |
| `maxReviewsPerCourse` | integer | `20` | Cap on reviews fetched per course (default 20, maximum 1000). `0` means the maximum of 1000; higher values are clamped to 1000. |
| `reviewsSort` | string | `MostRecent` | Review order: `MostRecent`, `RatingDesc`, or `RatingAsc`. |
| `maxListings` | integer | `20` | The single result cap in every mode. The run stops once this many are collected. `0` = unlimited. |
| `maxPages` | integer | `0` | Optional safety limit on result pages walked per search (Jobs and Courses). `0` (default) walks the whole catalogue. |
| `resumeFromRunId` | string | (empty) | Jobs mode only. A previous run ID or dataset ID from this account. Jobs already there are skipped, so this run returns only new jobs. |
| `incrementalMode` | boolean | `false` | Remember what earlier runs returned and return only new, updated or reappeared records (jobs, companies, courses, reviews). Unchanged records are not returned and not billed. |
| `stateKey` | string | (empty) | Optional name for the monitoring campaign. Empty derives a key from the mode, URLs and filters. |
| `emitUnchanged` | boolean | `false` | Also return records that did not change, marked UNCHANGED. They are billed like any returned row. |
| `emitExpired` | boolean | `false` | Also return records that disappeared, marked EXPIRED, after a run that scanned every search to its end (not when capped or resumed). Billed like any returned row. |
| `ignoreFieldsForChanges` | array | (empty) | Field names (dotted paths allowed) that must not make a record count as UPDATED. |
| `maxResidentialRequests` | integer | `0` | Optional cap on how many stronger-tier connections the run may use (cost control). `0` = unlimited. |
| `proxy` | object | Apify datacenter | Connection settings. |
| `mcpConnectors` | array | (none) | Optional: send a condensed summary of each record to apps you authorized under Integrations. |
| `notionParentPageUrl` | string | (none) | Notion connector only: page under which records are created. |
| `maxNotifyListings` | integer | `50` | Cap on items written to each connector per run. |

Alternative input names are also accepted when the canonical field is empty: `startUrls`, `maxItems`, `query`, `location`, `jobType`, `datePosted`, `proxyConfiguration`. Job rows also carry `datePosted`, `validThrough`, `employmentType`, `currency`, `salaryMin`, `salaryMax`, `salaryTimeUnit`, `industry`, `jobLocation*` and company link fields (company profile fields appear when Reed shows a company profile for the job).

### Output Example

> Sample shape: values are illustrative placeholders, not from a live listing.

```json
{
  "id": 1,
  "jobId": 1,
  "url": "https://www.reed.co.uk/jobs/data-engineer/1",
  "title": "Data Engineer",
  "companyName": "Sample Company",
  "logoImage": "https://resources.reed.co.uk/profileimages/logos/Logo_0000.png",
  "description": "Full job description text appears here when full job details are on.",
  "skills": ["Python", "SQL"],
  "statuses": ["Featured", "Easy Apply"],
  "location": "London",
  "latitude": 51.5074,
  "longitude": -0.1278,
  "remoteWorkingOption": "On-Site",
  "displaySalary": "£45,000 - £55,000 per annum",
  "salaryFrom": 45000,
  "salaryTo": 55000,
  "salaryCurrencyId": 1,
  "salaryType": 5,
  "salaryTypeName": "per annum",
  "jobType": 1,
  "isFullTime": true,
  "isPartTime": false,
  "postedOn": "2026-01-01T00:00:00.000Z",
  "expiryOn": "2026-02-01T00:00:00.000Z",
  "contractType": "Permanent",
  "sectorName": "Other IT & Telecoms",
  "parentSectorName": "IT & Telecoms",
  "region": "South East England",
  "county": "London",
  "country": "United Kingdom",
  "countryCode": "GB",
  "postCode": "EC1A",
  "companyProfileId": 0,
  "companyProfileUrl": "Sample-Company-00000",
  "isEasyApply": true,
  "isExternal": false,
  "detailFetched": true
}
```

### Plan Requirement

No paid proxy plan is required. In testing, Reed served real content to a plain datacenter connection on every search, job, company and course page, so the default Apify datacenter connection is used first. Residential proxy was not needed in testing and was not tried. If Reed refuses requests under heavy or frequent use, the actor falls back to a backup connection and logs a boxed warning: "The selected connection is unavailable to this run." and "Apify free plans do not include residential proxies." If every request in a run is refused, the run fails with "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search. Please try again later." In that case switch to an Apify Residential proxy with `proxy.apifyProxyGroups: ["RESIDENTIAL"]` and `apifyProxyCountry: "GB"` (a paid Apify plan is needed for Residential).

### FAQ

#### How much does it cost?

You pay per record returned, plus a separate detail charge for each job detail page actually fetched when **Fetch full job details** is on, and for every job record returned from a pasted single-job URL whatever that setting is (that URL is always fetched as a detail page; skipped or expired URLs are not charged). Course reviews returned in Courses mode are charged separately. The **Pricing** tab shows the current rates. Use **Max results** to cap the cost of any run, or turn Fetch full job details off and search by filters or search-page URLs to collect only the list-page fields.

#### Is it legal to scrape Reed.co.uk?

This actor collects only publicly available job, company and course listing data. You are responsible for how you use it: follow Reed's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Job postings are generally facts, but logos, photos and some description text may be subject to third-party rights.

#### Can I schedule this to collect only new jobs?

Yes. Schedule the actor from the **Schedules** tab and turn on `incrementalMode`. The first run returns everything as NEW; later runs return only NEW, UPDATED and REAPPEARED records with `changeType`, `changedFields`, `firstSeenAt` and `lastSeenAt` added. Unchanged records are not returned, so they are not billed, and detail enrichment is billed only for rows that are returned. `resumeFromRunId` remains available to continue one specific run.

#### Why did I get fewer jobs than I expected across multiple locations?

Two reasons. First, Max results is split evenly across locations, so each location returns at most its share. Second, when locations share jobs (remote or nationally advertised roles), a later location's search stops early if one of its pages after the first holds only jobs an earlier location already returned, so its remaining pages are never read. See "Known limitation: overlapping searches in one run" above for an example. For complete coverage, run one location per run.

#### Why did my run fail instead of returning an empty dataset?

If Reed refuses every request in a run, the run stops with a clear message so "no jobs match this search" is never confused with "nothing could be read." Run it again in a few minutes, or switch to a stronger Apify Residential GB proxy under Connection.

#### Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.

### 🔗 Want more jobs data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>💼 <a href="https://apify.com/abotapi/jobsite-co-uk-scraper"><b>Jobsite UK</b></a><br>Collect UK job listings from Jobsite.co.uk at scale. Search by keyword, location...</td><td>💼 <a href="https://apify.com/abotapi/jobs-nhs-uk-scraper"><b>NHS UK</b></a><br>Scrape nhs.uk Jobs listings into a flat dataset. Extract 50+ fields, including pay band...</td></tr>
<tr><td>💼 <a href="https://apify.com/abotapi/francetravail-fr-scraper"><b>France Travail Scraper</b></a><br>Scrape job offers from France Travail with titles, companies, locations, salaries...</td><td>💼 <a href="https://apify.com/abotapi/s1jobs-com-scraper"><b>s1jobs Scraper</b></a><br>Scrape jobs from s1jobs.com across Scotland and the UK. Search by keyword, location, or...</td></tr>
<tr><td>💼 <a href="https://apify.com/abotapi/dice-com-scraper"><b>Dice.com Scraper</b></a><br>Scrape tech job listings from Dice.com by keyword, filters, or URL. Extract job titles...</td><td>💼 <a href="https://apify.com/abotapi/nijobs-scraper"><b>NIJobs Scraper</b></a><br>Scrape NIJobs.com listings across Northern Ireland by keyword, location, salary, posted...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/reed-co-uk-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

## `mode` (type: `string`):

Jobs = job listings (by filters or pasted URLs). Companies = company profiles. Courses = training courses and their reviews.

## `urls` (type: `array`):

Reed job-search URLs (keyword, location, category, filtered, sorted or paginated) or direct job URLs (/jobs/<slug>/<id>). If set, these are scraped directly and the filters below are ignored. The pre-filled list is a sample of every supported URL shape: clear it and use the filters below to search by keywords and locations instead.

## `keywords` (type: `string`):

Job search terms, e.g. 'data engineer'. Used only when no Job URLs are pasted. Leave empty to browse all jobs in the location.

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

UK cities or regions, e.g. \['london', 'manchester']. One search per location. Leave empty for a UK-wide search.

## `proximity` (type: `integer`):

Search radius around the location, in miles. Allowed: 0, 1, 3, 5, 10, 15, 20, 30, 50 (other values are ignored). Default 10.

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

Only jobs with a salary at or above this amount (per annum).

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

Only jobs with a salary at or below this amount (per annum).

## `postedDate` (type: `string`):

Only jobs posted within this window.

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

Result ordering. 'relevance' is Reed's default; 'date' shows newest first.

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

Restrict to full-time roles.

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

Restrict to part-time roles.

## `permanent` (type: `boolean`):

Restrict to permanent roles.

## `temporary` (type: `boolean`):

Restrict to temporary roles.

## `contract` (type: `boolean`):

Restrict to contract roles.

## `agency` (type: `boolean`):

Only jobs posted by recruitment agencies.

## `direct` (type: `boolean`):

Only jobs posted directly by the employer.

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

Only graduate-level roles.

## `easyApply` (type: `boolean`):

Only jobs that support one-click apply.

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

Only jobs offering visa sponsorship.

## `earlyBird` (type: `boolean`):

Only newly posted 'early bird' jobs.

## `workFromHome` (type: `boolean`):

Only jobs Reed marks as remote (the site's Remote working-option filter).

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

Jobs mode only. When enabled, each job found by a search is enriched from its detail page: full HTML description, region/county/postcode, full salary breakdown, sector + parent sector, company profile, contract type, breadcrumbs, and screening questions. Adds one fetch and one detail charge per job. A pasted single-job URL is always fetched as a detail page and charged one detail unit per delivered job whatever this setting is.

## `companies` (type: `array`):

Reed company-profile URLs (e.g. https://www.reed.co.uk/company-profile/Marshall-Wolfe-63256) or bare slugs (e.g. Marshall-Wolfe-63256). Slugs are case-sensitive.

## `courseQueries` (type: `array`):

Keywords to search Reed Courses, e.g. \['python', 'project management']. One search per keyword; Max results caps the number of courses.

## `courseUrls` (type: `array`):

Reed course search URLs (e.g. https://www.reed.co.uk/courses/python, optional ?page=N), course detail URLs (e.g. https://www.reed.co.uk/courses/python-course/271655 or /courses/c/271655) or bare course IDs (e.g. 271655). Combined with any search keywords above.

## `fetchReviews` (type: `boolean`):

Courses mode only. When enabled, each course's reviews are returned as separate review records (rating breakdown, text, reviewer, provider response).

## `maxReviewsPerCourse` (type: `integer`):

Cap on reviews fetched per course. 0 = the maximum of 1000 reviews per course.

## `reviewsSort` (type: `string`):

Order in which course reviews are fetched.

## `maxListings` (type: `integer`):

The single result cap, applied in every mode: max jobs, companies, or courses (course reviews are additional and not counted here). The run stops once this many are collected. Set 0 for unlimited (the run then walks the whole catalogue).

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

Optional safety limit on result pages walked per search (Jobs and Courses). Leave at 0 to walk the whole catalogue: the run stops at Max results, the site's own last page, or a repeat-page guard, not an artificial page cap.

## `maxResidentialRequests` (type: `integer`):

Optional cap on how many residential proxy exits the run may use (cost control). 0 = unlimited. Datacenter is used first regardless.

## `proxy` (type: `object`):

Proxy servers used by the scraper. Default is Apify datacenter (free-tier friendly).

## `resumeFromRunId` (type: `string`):

Optional. ID of a previous run of this actor (or a dataset ID). Jobs already in that dataset are skipped, so this run returns only NEW jobs (a delta). Combine both runs' datasets for the full set. Max results then counts only the new jobs.

## `incrementalMode` (type: `boolean`):

Turn this on for daily or recurring monitoring of jobs, companies, courses and reviews. The first run returns everything matching as NEW. Later runs return only NEW, UPDATED and REAPPEARED records; unchanged records are not returned and not billed. Turn on "Emit unchanged" or "Emit expired" only when you also want those rows (they are billed as normal dataset items). State is kept separately for each mode, URL or filter setup; use State key to name or share a monitoring campaign. Detail-page enrichment is billed only for rows that are returned. To continue one specific interrupted run, use Resume from a previous run above.

## `stateKey` (type: `string`):

Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently-configured runs. Leave empty to let the actor derive a key automatically from the mode, search/URL and filter settings -- different searches then never mix state with each other.

## `emitUnchanged` (type: `boolean`):

Off by default. Turn on to also return records that have not changed since the last run, marked UNCHANGED. This returns -- and bills -- extra rows you already have, so leave it off unless you specifically want the full snapshot every run.

## `emitExpired` (type: `boolean`):

Off by default. Turn on to also return records that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned every tracked search/URL -- not when Max listings capped it or when Resume was used. This returns -- and bills -- extra synthetic rows, so leave it off unless you need expiry tracking.

## `ignoreFieldsForChanges` (type: `array`):

Optional. Field names that must NOT make a record count as UPDATED, for example "displaySalary" (dotted paths reach nested fields). Ignored fields stay in the output and the saved baseline; they are only left out of the comparison and of changedFields. Leave empty to compare every field.

## `mcpConnectors` (type: `array`):

Optionally send results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize one under Apify, Settings, API & Integrations, then select it here. Notion gets a rich page-per-item export; other connectors get a best-effort write. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "mode": "jobs",
  "urls": [
    "https://www.reed.co.uk/jobs/data-engineer-jobs-in-london",
    "https://www.reed.co.uk/jobs/data-engineer-jobs-in-london?pageno=2",
    "https://www.reed.co.uk/jobs/data-engineer-jobs-in-london?proximity=20&salaryfrom=50000&salaryto=70000&fulltime=True&permanent=True",
    "https://www.reed.co.uk/jobs/data-engineer-jobs-in-london?sortby=DisplayDate&dateCreatedOffSet=lastthreedays",
    "https://www.reed.co.uk/jobs/jobs-in-london",
    "https://www.reed.co.uk/jobs/data-engineer-jobs?pageno=2",
    "https://www.reed.co.uk/jobs",
    "https://www.reed.co.uk/jobs?keywords=data+engineer&location=london&pageno=1",
    "https://www.reed.co.uk/jobs/it-jobs",
    "https://www.reed.co.uk/jobs/data-engineer/56212211"
  ],
  "keywords": "data engineer",
  "locations": [
    "london"
  ],
  "proximity": 10,
  "postedDate": "anytime",
  "sortBy": "relevance",
  "fullTime": false,
  "partTime": false,
  "permanent": false,
  "temporary": false,
  "contract": false,
  "agency": false,
  "direct": false,
  "graduate": false,
  "easyApply": false,
  "visaSponsorship": false,
  "earlyBird": false,
  "workFromHome": false,
  "fetchDetails": true,
  "companies": [
    "https://www.reed.co.uk/company-profile/Marshall-Wolfe-63256"
  ],
  "courseQueries": [
    "python"
  ],
  "courseUrls": [
    "https://www.reed.co.uk/courses/python",
    "https://www.reed.co.uk/courses/python?page=2",
    "https://www.reed.co.uk/courses/python-course/271655",
    "https://www.reed.co.uk/courses/c/537019"
  ],
  "fetchReviews": true,
  "maxReviewsPerCourse": 20,
  "reviewsSort": "MostRecent",
  "maxListings": 20,
  "maxPages": 0,
  "maxResidentialRequests": 0,
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "maxNotifyListings": 50
}
```

# Actor output Schema

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

Individual job records with 80+ fields, plus ~45 detail fields when fetchDetails is enabled.

## `companies` (type: `string`):

Companies records (recordType filter view)

## `courses` (type: `string`):

Courses records (recordType filter view)

## `reviews` (type: `string`):

Reviews records (recordType filter view)

## `output` (type: `string`):

Run summary: total jobs, detail pages fetched, pages walked, duration.

# 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 = {
    "mode": "jobs",
    "urls": [
        "https://www.reed.co.uk/jobs/data-engineer-jobs-in-london",
        "https://www.reed.co.uk/jobs/data-engineer-jobs-in-london?pageno=2",
        "https://www.reed.co.uk/jobs/data-engineer-jobs-in-london?proximity=20&salaryfrom=50000&salaryto=70000&fulltime=True&permanent=True",
        "https://www.reed.co.uk/jobs/data-engineer-jobs-in-london?sortby=DisplayDate&dateCreatedOffSet=lastthreedays",
        "https://www.reed.co.uk/jobs/jobs-in-london",
        "https://www.reed.co.uk/jobs/data-engineer-jobs?pageno=2",
        "https://www.reed.co.uk/jobs",
        "https://www.reed.co.uk/jobs?keywords=data+engineer&location=london&pageno=1",
        "https://www.reed.co.uk/jobs/it-jobs",
        "https://www.reed.co.uk/jobs/data-engineer/56212211"
    ],
    "keywords": "data engineer",
    "locations": [
        "london"
    ],
    "companies": [
        "https://www.reed.co.uk/company-profile/Marshall-Wolfe-63256"
    ],
    "courseQueries": [
        "python"
    ],
    "courseUrls": [
        "https://www.reed.co.uk/courses/python",
        "https://www.reed.co.uk/courses/python?page=2",
        "https://www.reed.co.uk/courses/python-course/271655",
        "https://www.reed.co.uk/courses/c/537019"
    ],
    "proxy": {
        "useApifyProxy": true
    },
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/reed-co-uk-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 = {
    "mode": "jobs",
    "urls": [
        "https://www.reed.co.uk/jobs/data-engineer-jobs-in-london",
        "https://www.reed.co.uk/jobs/data-engineer-jobs-in-london?pageno=2",
        "https://www.reed.co.uk/jobs/data-engineer-jobs-in-london?proximity=20&salaryfrom=50000&salaryto=70000&fulltime=True&permanent=True",
        "https://www.reed.co.uk/jobs/data-engineer-jobs-in-london?sortby=DisplayDate&dateCreatedOffSet=lastthreedays",
        "https://www.reed.co.uk/jobs/jobs-in-london",
        "https://www.reed.co.uk/jobs/data-engineer-jobs?pageno=2",
        "https://www.reed.co.uk/jobs",
        "https://www.reed.co.uk/jobs?keywords=data+engineer&location=london&pageno=1",
        "https://www.reed.co.uk/jobs/it-jobs",
        "https://www.reed.co.uk/jobs/data-engineer/56212211",
    ],
    "keywords": "data engineer",
    "locations": ["london"],
    "companies": ["https://www.reed.co.uk/company-profile/Marshall-Wolfe-63256"],
    "courseQueries": ["python"],
    "courseUrls": [
        "https://www.reed.co.uk/courses/python",
        "https://www.reed.co.uk/courses/python?page=2",
        "https://www.reed.co.uk/courses/python-course/271655",
        "https://www.reed.co.uk/courses/c/537019",
    ],
    "proxy": { "useApifyProxy": True },
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/reed-co-uk-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 '{
  "mode": "jobs",
  "urls": [
    "https://www.reed.co.uk/jobs/data-engineer-jobs-in-london",
    "https://www.reed.co.uk/jobs/data-engineer-jobs-in-london?pageno=2",
    "https://www.reed.co.uk/jobs/data-engineer-jobs-in-london?proximity=20&salaryfrom=50000&salaryto=70000&fulltime=True&permanent=True",
    "https://www.reed.co.uk/jobs/data-engineer-jobs-in-london?sortby=DisplayDate&dateCreatedOffSet=lastthreedays",
    "https://www.reed.co.uk/jobs/jobs-in-london",
    "https://www.reed.co.uk/jobs/data-engineer-jobs?pageno=2",
    "https://www.reed.co.uk/jobs",
    "https://www.reed.co.uk/jobs?keywords=data+engineer&location=london&pageno=1",
    "https://www.reed.co.uk/jobs/it-jobs",
    "https://www.reed.co.uk/jobs/data-engineer/56212211"
  ],
  "keywords": "data engineer",
  "locations": [
    "london"
  ],
  "companies": [
    "https://www.reed.co.uk/company-profile/Marshall-Wolfe-63256"
  ],
  "courseQueries": [
    "python"
  ],
  "courseUrls": [
    "https://www.reed.co.uk/courses/python",
    "https://www.reed.co.uk/courses/python?page=2",
    "https://www.reed.co.uk/courses/python-course/271655",
    "https://www.reed.co.uk/courses/c/537019"
  ],
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false
}' |
apify call abotapi/reed-co-uk-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/reed-co-uk-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/gDsh5uERBbjTrMpip/builds/0maiM48I2iVuMMRQ5/openapi.json
