# Totaljobs Jobs Scraper (`wildorigins/uk-jobs-totaljobs`) Actor

🏷️ From $0.60 / 1K | Search and export UK job vacancies from Totaljobs with salary, employer and location. Duplicate agency repostings are removed and pay is annualised for comparison.

- **URL**: https://apify.com/wildorigins/uk-jobs-totaljobs.md
- **Developed by:** [Wild Origins](https://apify.com/wildorigins) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.60 / 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

## Totaljobs Jobs Scraper

Export UK job vacancies from Totaljobs, with salary, employer, location and an annualised pay figure on every result.

### 🔍 What does Totaljobs Jobs Scraper do?

It runs a job search on Totaljobs and returns every vacancy it finds as a clean row of data, ready for a spreadsheet, a database or your own product.

Totaljobs serves four pages to a signed out visitor and then stops, so one search returns about 100 jobs however high you set `maxResults`. See [how many jobs one search returns](#how-many-jobs-one-search-returns) below for how to cover a market properly.

### 🧭 Is there an official Totaljobs API?

No. Totaljobs publishes no public developer API for reading its listings. Its integrations are employer-side arrangements for posting jobs, not a route for getting search results out as data. The only official way to read Totaljobs vacancies is the website itself, one page at a time.

This Actor turns that manual reading into structured rows: one search in, deduplicated listings out as JSON, CSV or Excel, with the published salary parsed into a period and an annualised figure so results actually compare. Proxy handling is built in, so there is nothing to configure before the first run.

### 📊 What data can I extract from Totaljobs?

One item per job:

| | Field | What it holds |
|---|---|---|
| 🆔 | `jobId` | Totaljobs' stable id for the listing |
| 🏷️ | `title` | The job title as advertised |
| 🏢 | `employer` | The hiring organisation or agency shown on the listing |
| 📍 | `location` | Town, area and postcode district where given |
| 💰 | `salaryFrom` | Lower bound of the advertised salary |
| 💰 | `salaryTo` | Upper bound of the advertised salary |
| 💰 | `salaryPeriod` | Hourly, Daily, Weekly, Monthly or Annual |
| 💰 | `salaryAnnualisedFrom` | Estimated annual equivalent of the lower bound |
| 💰 | `salaryAnnualisedTo` | Estimated annual equivalent of the upper bound |
| 💰 | `salaryRaw` | The salary text exactly as published |
| ✅ | `hasSalary` | False when pay is hidden or stated as competitive |
| 📌 | `label` | Any badge Totaljobs shows on the card, such as Featured |
| 📅 | `postedText` | Posting age as Totaljobs displays it, such as "4 days ago" |
| 📅 | `postedApprox` | The posting date resolved from that text, UTC |
| 🔗 | `url` | Direct link to the listing |
| 📥 | `searchKeywords` | The keywords this search ran with |
| 📥 | `searchLocation` | The location this search ran with |
| 📅 | `retrievedAt` | When the row was collected |

Annualised figures assume a 37.5 hour week and 52 weeks. They are for comparison, not contractual accuracy. `postedApprox` is derived from relative text like "3 days ago", so it is accurate to the day, not the minute.

### 💡 Why scrape Totaljobs?

**Salary benchmarking.** An HR manager pricing a warehouse operative opening in Greater Manchester pulls every advertised rate in the area, annualised so hourly and salaried roles sit in one sortable column.

**Hiring signals.** A recruiter watching which agencies and employers are hiring for the same role in the same town runs the search weekly and diffs the employer column.

**Job aggregation.** A niche job board for care work feeds deduplicated Totaljobs listings into its own search on a morning schedule.

**Pay movement over time.** An analyst tracking whether driver pay in the West Midlands is still climbing runs the same query monthly and charts the annualised column.

**Fresh listings only.** A job seeker who only wants this week's postings sets `postedWithinDays` to 7 and works through the export before the roles fill.

**One vacancy, one row.** Someone tired of the same agency role appearing under three job ids leaves `dedupeSimilar` on and sees each vacancy once.

### 🚀 How do I use Totaljobs Jobs Scraper?

1. Click **Try for free**.
2. Enter what you are searching for and where, for example `warehouse operative` in `Manchester`.
3. Narrow it with `radiusMiles`, `salaryFrom` or `postedWithinDays`.
4. Cap the run with `maxResults`, then click **Start**.
5. Download the results as JSON, CSV or Excel, or pull them from the API.

### ⬇️ Input

```json
{
  "keywords": "warehouse operative",
  "location": "Manchester",
  "radiusMiles": 10,
  "postedWithinDays": 7,
  "maxResults": 100
}
```

Set at least one of `keywords` or `location`.

| Field | Type | Default | What it does |
|---|---|---|---|
| `keywords` | string | `nurse` | Job title or keywords |
| `location` | string | `manchester` | Town, city or county |
| `radiusMiles` | integer | `10` | How far from that location to search |
| `salaryFrom` | integer | | Annual salary floor in GBP |
| `postedWithinDays` | integer | | Only jobs posted in the last N days |
| `dedupeSimilar` | boolean | `true` | Collapse duplicate repostings of the same role |
| `maxResults` | integer | `100` | Hard cap on billable results |
| `proxyConfiguration` | object | Residential GB | Residential UK proxy, required in practice |

### ⬆️ Output

#### Table view

Results arrive as a table you can sort and filter in the Console. There is a second view, Salary detail, that lines up the period and the annualised figures for comparing listings against each other.

#### JSON

A typical row:

```json
{
  "title": "Registered Nurse",
  "employer": "Example Care Ltd",
  "location": "Manchester",
  "salaryRaw": "£30,000 to £35,000 per annum",
  "postedText": "3 days ago",
  "url": "https://www.totaljobs.com/job/123456",
  "salaryAnnualisedFrom": 30000,
  "salaryAnnualisedTo": 35000
}
```

Download it from the run as JSON, CSV or Excel, or read it straight from the API.

### What is different about this one

**Salaries you can actually compare.** Job boards mix hourly, daily and annual pay in the same list. In a typical nursing search on Totaljobs, almost every result is quoted hourly. Put those next to a salaried role and any analysis is nonsense. This Actor returns the published figure, the period it refers to, and an annualised equivalent, so one column sorts correctly across the whole result set.

**It survives their deploys.** Totaljobs styles with emotion, so every CSS class is a build hash like `res-mnyp7m` that changes when they ship. Anything selecting on those breaks without warning. This anchors on their stable `data-at` automation attributes instead.

**You are not billed for the same job twice.** Agencies repost identical roles under several job ids. Rows with the same title, employer, location and salary are collapsed by default.

### How many jobs one search returns

Totaljobs serves four pages to a signed out visitor and then stops, so a single
search returns at most about 100 jobs no matter how high `maxResults` goes. The
pagination on the site still advertises the full count, which is why a search
that claims 18 pages will hand over four.

To cover a market properly, run several narrower searches rather than one broad
one. Splitting by location is usually the cleanest, for example Manchester,
Salford and Stockport separately instead of Manchester at a 20 mile radius.
Splitting by keyword works too where the roles are distinct.

### Proxy is required

Totaljobs blocks datacentre traffic. A residential UK proxy is set by default and is strongly recommended. Without one the run finishes with an empty dataset and a status message saying the site refused the connection, rather than quietly returning nothing.

### ⏱️ How long does a run take?

Measured on real runs, so you know what normal looks like and can tell it apart from a run that has stalled.

| Vacancies returned | Typical run time |
|---|---|
| 10 | about 8 seconds |
| 95 to 100 | 26 to 74 seconds |

Totaljobs is read through a UK residential address, which is why a run is measured in tens of seconds rather than single figures: every search page is a full HTML fetch over a residential hop rather than an API call. The spread inside that second row is the exit address rather than the search. The same hundred vacancy pull has come back in 26 seconds on a good exit and 74 seconds on a slow one, and a blocked exit is replaced and retried rather than pushed through.

A run returning fewer vacancies than `maxResults` is normal here. One Totaljobs search holds around a hundred distinct vacancies before it starts repeating, so a request for 300 returns roughly 95 and says so, and `dedupeSimilar` removes the near duplicate reposts that would otherwise pad that figure. Run several narrower searches rather than one wide one if you want more. The first few seconds of any run are the container starting rather than the work.

A run is never silently stuck. Each search page is logged as it is read, and vacancies are written to the dataset as they are parsed rather than held back to the end, so a run that hits its time limit still leaves everything it had already collected. A search that matches nothing ends successfully with an empty dataset and the reason in its status message.

**Set the run timeout to suit the size of the ask.** This Actor's default is 3600 seconds, which is comfortably more than the largest run in the table above (100 vacancies takes about 75 seconds). You are charged per delivered result rather than per minute, so a generous timeout costs you nothing and a tight one risks losing the run's work. Lower it only if you want a hard ceiling on how long a scheduled run may sit.

### 💰 How much does it cost?

You pay per job returned. Duplicate repostings are collapsed before charging, so an agency role listed under three job ids is charged once. Set `maxResults` to cap what a run can cost.

Runs that return nothing cost nothing. Paid Apify plans get a lower rate per job.

### 🔌 Integrations

Send results straight to Google Sheets, Slack, Airtable, Zapier, Make or your own webhook using [Apify integrations](https://docs.apify.com/platform/integrations). You can also trigger a run whenever something happens in another tool.

AI agents can run this Actor too, through the [Apify MCP server](https://mcp.apify.com). An agent connected to mcp.apify.com can discover it, read its input schema and start a run under the identifier `spookyweb/uk-jobs-totaljobs`, then read the finished dataset. That means an assistant asked "what are care assistant roles in Leeds paying" can call this Actor and answer from live listings.

### 🔗 Using Totaljobs Jobs Scraper with the Apify API

```bash
curl -X POST "https://api.apify.com/v2/acts/spookyweb~uk-jobs-totaljobs/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"keywords": "warehouse operative", "location": "Manchester", "radiusMiles": 10, "postedWithinDays": 7, "maxResults": 100}'
```

Or with the Apify client:

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

const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('spookyweb/uk-jobs-totaljobs').call({
  keywords: 'warehouse operative',
  location: 'Manchester',
  radiusMiles: 10,
  postedWithinDays: 7,
  maxResults: 100,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Full detail is in the [Apify API reference](https://docs.apify.com/api/v2), and every run is also callable from the [Python](https://docs.apify.com/api/client/python) and [JavaScript](https://docs.apify.com/api/client/js) clients.

### ❓ FAQ

#### How many jobs can I get from one search?

About 100. Totaljobs serves four pages to a signed out visitor and then stops, whatever its pagination claims, so a search advertising 18 pages hands over four. Raising `maxResults` does not change that. Run several narrower searches instead, splitting by town or by job title.

#### Why are duplicates removed before charging?

Agencies repost the same role under several job ids, and you should not pay three times for one vacancy. Rows with the same title, employer, location and salary are collapsed by `dedupeSimilar`, which is on by default, and the charge happens after that.

#### What does the annualised salary mean?

It is the published figure converted to a yearly equivalent so hourly, daily and annual roles sort in one column. The conversion assumes a 37.5 hour week and 52 weeks. It is for comparison rather than contractual accuracy, and `salaryRaw` always carries the original text.

#### Do I need a proxy?

A residential UK proxy is set by default and you should leave it on. Totaljobs blocks datacentre traffic, so without one the run finishes with an empty dataset and a status message saying the site refused the connection, rather than pretending the search matched nothing.

#### Do I need a Totaljobs account?

No. This reads the public search results, the pages any visitor sees without signing in, so there are no credentials to supply and none are ever stored.

### ⚖️ Is it legal to scrape Totaljobs?

This reads Totaljobs' public search results, the pages any visitor sees without an account. Job adverts are business information published by employers, not personal data about individuals.

Scraping publicly available data is legal in the UK, the EU and the US. Nothing here logs in or touches anything behind an account. If you go on to combine this data with personal data, that is on you to handle under GDPR. Apify's [ethical scraping guide](https://blog.apify.com/is-web-scraping-legal/) covers the wider picture.

### 👍 Your feedback

Found a bug, or want a field that is not here yet? Open an issue on the Actor's Issues tab. Requests that make the data more useful get built, and problems get fixed quickly.

### 🔎 You might also like

| Actor | What it does |
|---|---|
| [Indeed Jobs Scraper](https://apify.com/spookyweb/indeed-jobs) | Indeed listings with parsed salary figures, past the 15 job sign in wall |
| [LinkedIn Jobs Scraper](https://apify.com/spookyweb/linkedin-jobs) | LinkedIn jobs with company enrichment and CV keyword matching, no login needed |
| [Reed Jobs Scraper](https://apify.com/spookyweb/uk-jobs-reed) | UK jobs from Reed with applicant counts and annualised salaries |

# Actor input Schema

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

Job title or keywords, for example nurse or project manager.

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

Town, city or county, for example manchester.

## `radiusMiles` (type: `integer`):

Search radius in miles around the location. Totaljobs measures this from the town or postcode given, so a wider radius pulls in neighbouring areas.

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

Lowest annual salary to accept, in GBP. Jobs that advertise no salary are dropped when this is set, which removes a large share of the listings.

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

Only return jobs first posted within this many days. Leave blank to include every listing Totaljobs still has live.

## `maxResults` (type: `integer`):

Hard cap on results. Each job is one billable result. Totaljobs stops serving a signed out visitor after four pages, so one search returns at most about 100 jobs however high you set this. To cover more of the market, run several narrower searches, for example by splitting the location or the keywords.

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

Totaljobs blocks datacentre IPs, so a residential UK proxy is strongly recommended. Without one the run will be blocked.

## `dedupeSimilar` (type: `boolean`):

Agencies often post the same role under several job ids. This collapses rows with an identical title, employer, location and salary so you are not billed for the same job repeatedly.

## Actor input object example

```json
{
  "keywords": "nurse",
  "location": "manchester",
  "radiusMiles": 10,
  "maxResults": 100,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "GB"
  },
  "dedupeSimilar": true
}
```

# Actor output Schema

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

One row per item: job listings with annualised salaries.

## `runSummary` (type: `string`):

Counts and stop reason for the run.

# 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 = {
    "keywords": "nurse",
    "location": "manchester",
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "GB"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("wildorigins/uk-jobs-totaljobs").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 = {
    "keywords": "nurse",
    "location": "manchester",
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "GB",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("wildorigins/uk-jobs-totaljobs").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 '{
  "keywords": "nurse",
  "location": "manchester",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "GB"
  }
}' |
apify call wildorigins/uk-jobs-totaljobs --silent --output-dataset

```

## MCP server setup

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

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/vl3XDGjOGHGVRyW9V/builds/4TBv6is8m5u5SB2QG/openapi.json
