# Totaljobs Scraper - UK Jobs, Salaries & Employers (`abotapi/totaljobs-com-scraper`) Actor

Scrape UK job listings from Totaljobs.com by keyword, location, filters, or URL. Extract 40+ fields including title, parsed salary range and period, employer, logo, GPS location, skills, employment type, full description, posting date, and apply link.

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

## Pricing

from $1.20 / 1,000 listing 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/platform/actors/running/actors-in-store#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

## Totaljobs.com Jobs Scraper

Scrape UK job listings from Totaljobs.com at scale. Search by keyword, location, and filters, or paste search URLs to paginate. Every job comes back with 35+ structured fields straight from the listing, and an optional detail pass adds the full description, GPS coordinates, structured address, employment type, valid-through date, and company profile.

### Why this scraper

- 35+ fields per job from the listing page alone, no detail fetch required: title, parsed salary, employer, logo, location, posting source, skills, snippet, and more.
- Two ways to run: build a search from keyword + location + filters, or paste any Totaljobs.com search URL and walk it forward.
- Real server-side filters: job type, advertiser type (direct employer vs agency), minimum salary by period (year / day / hour), posted-within window, and sort order.
- Parsed salary: the free-text salary string is split into min, max, currency, and period (year / day / hour), including hourly and daily rates common in UK job ads.
- Optional full details: GPS lat/lng, structured address, full description, employment type, valid-through date, apply type, and company profile.
- Every row is tagged with `brand` and `sourceSite` so multi-source consumers can filter or group by source portal.
- Cost control built in: a residential request budget cap and a single clear result limit so a run never overspends.
- Send results straight into Notion, Linear, Airtable, or Apify via optional MCP connectors.
- **Covers the whole catalogue by default.** `maxPages` defaults to `0` (unlimited)  -  every result page per search/URL is processed, and the run stops on its own once the site's reported page count is reached, a page comes back empty, or a page repeats jobs already seen. There is no artificial page-number ceiling; the only cap you need is `maxListings`.
- **`maxListings` is the sole cap on volume** (default `20`, so a first run is small and fast; set `0` for unlimited).
- **Resumable runs.** Set `resumeFromRunId` to a previous run or dataset id to collect only the jobs that run doesn't already have (a delta)  -  combine both datasets for the full set. Long unlimited runs also checkpoint their progress to the key-value store, so an Apify platform migration or a Resurrect of a failed run picks up where it left off instead of starting over.
- **Incremental & dedup mode for scheduled runs.** Turn on `incrementalMode` and run the same search daily  -  the actor remembers the previous run itself and returns only `NEW`, `UPDATED`, and `REAPPEARED` jobs by default; unchanged jobs are suppressed (and not billed). See [Resume & recurring updates](#resume--recurring-updates).

### Data you get

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

| Field | Example |
| --- | --- |
| jobId | 100000001 |
| title | Staff Nurse |
| jobUrl | https://www.totaljobs.com/job/staff-nurse/sample-trust-job100000001 |
| applyUrl | https://www.totaljobs.com/job/staff-nurse/sample-trust-job100000001 |
| brand | totaljobs |
| backend | naps |
| sourceSite | Totaljobs.com |
| datePosted | 2026-01-01T00:00:00.000Z |
| employer.id | 1000000 |
| employer.name | Sample NHS Trust |
| employer.logoUrl | https://www.totaljobs.com/CompanyLogos/00000000000000000000000000000000.png |
| location.text | St James, South West London (SW1), SW1A |
| location.postalCode | SW1A |
| location.latitude (detail) | 51.5000 |
| location.longitude (detail) | -0.1300 |
| salary.rawText | £30,000 - £40,000 per year |
| salary.min | 30000 |
| salary.max | 40000 |
| salary.currency | GBP |
| salary.period | year |
| skills | \[] (always empty on Totaljobs.com) |
| textSnippet | We are looking for a dedicated staff nurse to join our ward team. |
| isSponsored | false |
| description (detail) | Full job description text appears here when fetchDetails is enabled. |
| employmentType (detail) | FULL\_TIME |
| industry (detail) | Healthcare |
| validThrough (detail) | 2026-02-01T00:00:00.000Z |
| applyType (detail) | internal |
| location.locality (detail) | St James |
| location.region (detail) | London |
| location.country (detail) | GB |

> **Field notes:** Fields marked `(detail)` are populated only when `fetchDetails` is enabled. GPS coordinates (`location.latitude`/`longitude`) are detail-only: 0 of 25 listing rows carry them without the detail pass. The `skills` array is always empty on Totaljobs.com (75 of 75 listing cards sampled), though the field is still returned for schema compatibility with the sister Jobsite.co.uk actor.

### How to use

Basic search in one city:

```json
{
  "mode": "search",
  "locations": ["London"],
  "keywords": "nurse",
  "maxListings": 20,
  "proxy": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "GB" }
}
```

Search with filters (permanent nurse roles, at least £30,000/year, posted in the last 7 days, newest first):

```json
{
  "mode": "search",
  "locations": ["Manchester", "Leeds"],
  "keywords": "nurse",
  "jobType": "permanent",
  "minSalary": 30000,
  "salaryType": "annual",
  "postedWithin": 7,
  "sortBy": "date",
  "maxListings": 100,
  "proxy": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "GB" }
}
```

Full details (adds description, GPS, employment type, company profile):

```json
{
  "mode": "search",
  "locations": ["London"],
  "keywords": "nurse",
  "fetchDetails": true,
  "maxListings": 50,
  "proxy": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "GB" }
}
```

Paginate pasted URLs (filter fields are ignored in URL mode):

```json
{
  "mode": "url",
  "urls": [
    "https://www.totaljobs.com/jobs/nurse/in-london",
    "https://www.totaljobs.com/jobs/in-edinburgh"
  ],
  "maxPages": 5,
  "maxListings": 200,
  "proxy": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "GB" }
}
```

Walk the whole catalogue for a location (no page cap, still bounded by `maxListings`):

```json
{
  "mode": "search",
  "locations": ["London"],
  "maxPages": 0,
  "maxListings": 0,
  "proxy": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "GB" }
}
```

Resume a previous run, collecting only jobs it doesn't already have:

```json
{
  "mode": "search",
  "locations": ["London"],
  "maxListings": 0,
  "resumeFromRunId": "<a previous run id or dataset id from this account>"
}
```

Daily monitoring  -  same input every scheduled run, only changes come back:

```json
{
  "mode": "search",
  "locations": ["London"],
  "keywords": "nurse",
  "incrementalMode": true,
  "maxListings": 0
}
```

### Input parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| mode | string | search | `search` builds URLs from filters; `url` paginates URLs you paste. |
| locations | array | \["London"] | Towns, cities, counties, or regions (search mode). One search per location. |
| keywords | string | (empty) | Job title, skill, or employer to search for. Empty lists all jobs in the location. |
| jobType | string | any | any / permanent / contract / temporary / part-time / work-from-home. |
| companyType | string | any | any / employer (direct) / agency. |
| minSalary | integer | (empty) | Lowest salary to include, paired with the salary period. |
| salaryType | string | annual | annual / daily / hourly. |
| postedWithin | string | 0 | 0 (any) / 1 / 3 / 7 / 14 days. |
| sortBy | string | relevance | relevance / date / salary-desc / salary-asc / distance. |
| urls | array | (example) | Totaljobs.com search URLs to paginate (url mode). |
| fetchDetails | boolean | false | Fetch detail pages to add description, GPS, employment type, valid-through, apply type, company profile. |
| maxListings | integer | 20 | The sole cap on total jobs collected across all searches. 0 = unlimited. |
| maxPages | integer | (empty) | Optional bound on result pages (25 jobs each) processed per search/URL. Leave empty or 0 = cover the whole catalogue: stops on its own at the site's reported page count, an empty page, or a page that repeats jobs already served  -  never an artificial page cap. Does not cap job count; use maxListings for that. |
| resumeFromRunId | string | | Optional id of a previous run (or dataset) of this actor. Jobs already in that dataset are skipped, so this run returns only new jobs (a delta). For recurring daily monitoring of the same search, use `incrementalMode` instead. |
| incrementalMode | boolean | false | Turn on for daily/recurring monitoring. Returns only `NEW`/`UPDATED`/`REAPPEARED` jobs after the first run; unchanged jobs are suppressed and not billed unless `emitUnchanged` is on. |
| stateKey | string | | Optional. Name a monitoring campaign explicitly, or share state across differently-configured runs. Leave empty to derive a key automatically from the search/filter setup. |
| emitUnchanged | boolean | false | Also return jobs unchanged since the last run, marked `UNCHANGED` (extra billed rows). |
| emitExpired | boolean | false | Also return jobs no longer found, marked `EXPIRED`. Only produced once a run fully scans the tracked search (not capped, not resumed). |
| proxy | object | Residential GB | Connection settings. |
| mcpConnectors | array | (empty) | Optionally pipe results into the apps you already use via Model Context Protocol (MCP) connectors (Notion, Linear, Airtable, Apify). Authorize a connector once under Apify, Settings, Integrations, then select it here. Each connector receives a condensed, human-readable summary per item; the full record stays in the dataset. |
| notionParentPageUrl | 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 | integer | 50 | Cap on items written to each connector per run. Does not affect the dataset. |

### Detail enrichment (optional surcharge)

Turning on `fetchDetails` fetches each job's detail page via a warm Residential-GB session and adds the full HTML description, GPS coordinates (latitude/longitude, 0 of 25 listing rows carry them without this), structured address (locality, region, country, street address), employment type, valid-through date, apply type, company profile (benefits, industries, size, founded), and recruiter contact details (phones/emails harvested from the description). This adds ~12 fields per job.

Each detail-enriched job that is actually pushed to the dataset incurs a `detail-enrichment` surcharge on top of the base per-result price. The surcharge is counted at the commit point (after the record is pushed), never per detail fetch, so a job whose detail page was fetched but then suppressed by incremental mode is not billed. Needs an Apify Residential GB proxy; datacenter and non-GB residential exits are refused on job pages.

### Resume & recurring updates

There are two different things here  -  pick the one that matches what you're doing:

| Need | Use |
| --- | --- |
| A run stopped and should continue | `resumeFromRunId` / automatic checkpoint recovery |
| Run the same search every day and receive only changes | `incrementalMode` |
| Keep separate daily campaigns for similar searches | distinct `stateKey` values |
| Run a normal full snapshot | leave both off |

**Resume** (`resumeFromRunId`) continues one specific interrupted or previous large run: paste a run id or dataset id and this run skips jobs already collected there, returning only the remaining new jobs. An automatic same-run checkpoint also protects against platform migrations/Resurrects without any input needed.

**Incremental mode** (`incrementalMode`) is for a schedule (for example, daily): the actor remembers the previous run of the *same* search by itself, so you never paste a run id. The first run returns everything as `NEW`. Later runs return only `NEW`, `UPDATED`, and `REAPPEARED` jobs by default  -  duplicates and unchanged jobs are suppressed (and not charged). Turn on `emitUnchanged` or `emitExpired` only when you also want those rows returned (and billed for). State is isolated automatically per mode/locations/keywords/jobType/companyType/minSalary/salaryType/postedWithin/sortBy/urls/fetchDetails setup  -  `maxListings`/`maxPages`/`maxResidentialRequests` are caps/cost knobs, not filters, and deliberately do **not** affect the state key, so raising your cap on a later run does not re-bill the whole catalogue. Set `stateKey` to name or deliberately share a monitoring campaign. `EXPIRED` is only synthesized once a run fully scans every search/URL target (not capped by Max jobs/Max pages, not a Resume run, not a checkpoint-interrupted run).

When `incrementalMode` is on, every returned record also carries:

| Field | Description |
|---|---|
| `changeType` | `NEW` | `UPDATED` | `UNCHANGED` | `REAPPEARED` | `EXPIRED` |
| `changedFields` | Top-level fields that changed since last seen; non-empty only for `UPDATED` |
| `firstSeenAt` | When this job was first observed by this monitoring campaign |
| `lastSeenAt` | When this job was last observed |

**Scanned vs. emitted caps.** `maxListings` counts jobs **scanned** (looked at and classified), not jobs pushed. Outside incremental mode the two numbers are always equal. Under incremental mode most jobs on a quiet recurring run are UNCHANGED and suppressed  -  if the cap instead counted pushed rows, the run would keep paging deeper trying to "backfill" the quota with fresh jobs every single day, and cost would never actually drop. Counting scanned jobs means the run stops once it has looked at `maxListings` worth of the catalogue, exactly like a normal run, whether those jobs turned out to be pushed or suppressed.

**What drives change detection:** every real field on the job (title, salary, location, employer, skills, description snippet, dates, etc.). The following are excluded from the comparison because they are not job content:

| Excluded field | Why |
|---|---|
| `scrapedAt` | Stamped fresh on every scrape  -  would fake an `UPDATED` every run otherwise. |
| `detailFetched` | Describes this run's enrichment depth, not the job  -  toggling `fetchDetails` must not mark every job `UPDATED`. |
| `crossPostedCount` | A live count of *other* employers' duplicate postings of this job (`fingerprintCount`)  -  it changes when a third party's listing changes, not this job. |
| `sourceSearchUrl` | Which search query/location surfaced this job  -  a property of the search, not the job. Does not embed a page number. |
| `travelTime` | A distance-from-search-origin value, not a property of the job. |
| `isSponsored`, `isHighlighted`, `isTopJob`, `isTrafficFromPartner` | Ad-serving / search-ranking placement flags set by the platform's ad auction, not by the employer editing the job. |
| `description`, `descriptionText`, `validThrough`, `employmentType`, `industry`, `directApply`, `applyType`, `jobLocationType`, `applicantLocationRequirements`, `contractType`, `workType`, `company`, `contactPhones`, `contactEmails`, `externalId`, and `location.latitude`/`longitude`/`lat`/`lng`/`locality`/`region`/`country`/`streetAddress`/`postalCode` | Populated or refined only by the optional detail pass. A job collected without detail (or whose detail fetch happened to fail this run only) would otherwise look "changed" purely because of enrichment availability, not a real site change. |

These exclusions are reasoned from the data shape (structural review of `src/extractor.py`), not measured against a live re-scrape over time  -  a genuine promotion-flag flap could not be observed within this task's request budget. A real change to a real field (title, salary, location, employer, skills, snippet, dates) still triggers `UPDATED` as usual; the excluded fields are always still **returned** in the output, just not compared.

Scheduled-run example  -  same search, run daily:

Day 1 (first run ever for this search):

```json
{ "mode": "search", "locations": ["London"], "incrementalMode": true }
```

→ every job comes back with `"changeType": "NEW"`.

Day 2 (the schedule fires again, identical input):

```json
{ "mode": "search", "locations": ["London"], "incrementalMode": true }
```

→ jobs whose title/salary/location/etc. changed come back as `"changeType": "UPDATED"` with `changedFields` listing what changed, brand-new jobs come back as `"changeType": "NEW"`  -  and jobs that are still there, unchanged, are **not** returned at all (suppressed, not charged) unless `emitUnchanged` is on.

### Output example

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

```json
{
  "rowType": "job",
  "brand": "totaljobs",
  "backend": "naps",
  "scrapedAt": "2026-01-01T00:00:00.000Z",
  "jobId": 100000001,
  "jobUrl": "https://www.totaljobs.com/job/staff-nurse/sample-trust-job100000001",
  "applyUrl": "https://www.totaljobs.com/job/staff-nurse/sample-trust-job100000001",
  "sourceSite": "Totaljobs.com",
  "title": "Staff Nurse",
  "datePosted": "2026-01-01T00:00:00.000Z",
  "employer": {
    "id": 1000000,
    "name": "Sample NHS Trust",
    "url": "https://www.totaljobs.com/jobs/sample-nhs-trust?cmpId=1000000",
    "logoUrl": "https://www.totaljobs.com/CompanyLogos/00000000000000000000000000000000.png",
    "isAnonymous": false
  },
  "location": {
    "text": "St James, South West London (SW1), SW1A",
    "postalCode": "SW1A",
    "locality": "St James",
    "region": "London",
    "country": "GB",
    "latitude": 51.5000,
    "longitude": -0.1300,
    "lat": 51.5000,
    "lng": -0.1300
  },
  "salary": { "rawText": "£30,000 - £40,000 per year", "min": 30000, "max": 40000, "currency": "GBP", "period": "year" },
  "skills": ["Triage", "Medication administration"],
  "textSnippet": "We are looking for a dedicated staff nurse to join our ward team.",
  "isSponsored": false,
  "description": "Full job description text appears here when fetchDetails is enabled.",
  "employmentType": ["FULL_TIME"],
  "industry": "Healthcare",
  "validThrough": "2026-02-01T00:00:00.000Z",
  "directApply": true,
  "applyType": "internal",
  "detailFetched": true
}
```

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

Optionally pipe results into the apps you already use. Authorize a connector once under Apify, Settings, Integrations, then select it in the input. Set `notionParentPageUrl` for Notion. Each connector receives a condensed, human-readable summary per item (title plus key fields), not the full JSON; the complete record always stays in the Apify dataset. Supported: Notion, Linear, Airtable, Apify.

### Plan requirement

Totaljobs.com accepts Apify Residential with country GB most reliably, which is the prefilled default. Listing pages are frequently refused on Datacenter, and full job details (fetchDetails) always need Residential GB. Datacenter and non-GB residential are refused on job pages. Apify Residential is available on the Starter plan and above. On the free plan a run may return few or no results; set `proxy.apifyProxyGroups` to `["RESIDENTIAL"]` with `apifyProxyCountry` `"GB"` after upgrading.

# Actor input Schema

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

How to find jobs: build a search from filters, or paginate URLs you paste.

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

Towns, cities, counties, or regions (Search mode). Examples: 'London', 'Manchester', 'Edinburgh', 'Surrey'. One search runs per location. Leave empty for all of the UK.

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

Job title, skill, or company to search for. Example: 'nurse', 'java developer', 'project manager'. Leave empty to list all jobs in the location.

## `jobType` (type: `string`):

Contract / working arrangement filter.

## `companyType` (type: `string`):

Restrict to direct employers or recruitment agencies.

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

Lowest salary to include. Combined with the salary period below. Leave empty for no minimum.

## `salaryType` (type: `string`):

Unit for the minimum salary filter.

## `postedWithin` (type: `string`):

Only jobs posted within this many days. Leave as Any time for no date filter.

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

Result ordering.

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

Totaljobs.com search-result URLs to scrape (URL mode). Multi-URL supported. Filter fields above are ignored. A page number in the URL is honoured as the starting point for results.

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

Fetch detail pages to add the full description, GPS coordinates, structured address, employment type, valid-through date, and company profile. Adds ~12 fields. Needs an Apify Residential GB proxy and costs more per job. Off = fast, cheap, 35 listing fields.

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

Total jobs to collect across all searches. This is the main limit. Set 0 for unlimited (bounded by Max pages).

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

Optional safety limit on pages walked per location/URL (25 jobs per page). Leave empty or set to 0 to walk the whole catalogue: the run stops at Max jobs, the site's own last page, or a repeat-page guard, not an artificial page cap.

## `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 jobs then counts only the new jobs. For recurring daily monitoring of the same search, use Incremental mode below instead.

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

Turn this on for daily or recurring monitoring. The first run returns all matching jobs as NEW. Later runs normally return only NEW, UPDATED, and REAPPEARED jobs. Turn on "Emit unchanged" or "Emit expired" only when you also want those jobs returned (and billed). State is kept separately for each distinct search/filter setup (or by State key below).

## `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 your search/filter settings — different searches then never mix state with each other.

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

Off by default. Turn on to also return jobs 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 jobs that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked search — not when Max jobs or Max pages capped it, or when Resume was used. This returns — and bills — extra synthetic rows.

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

Connection settings. totaljobs.com accepts Apify Residential with country GB most reliably (the default); listing pages occasionally work on Datacenter but it is frequently refused, and full job details (fetchDetails) always need Residential GB. Datacenter and non-GB residential are refused on job pages.

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

Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify → Settings → Integrations, then select it here. The connector receives a condensed, human-readable summary per item (title + key fields), not the full JSON — the complete record stays in the dataset. 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": "search",
  "locations": [
    "London"
  ],
  "jobType": "any",
  "companyType": "any",
  "salaryType": "annual",
  "postedWithin": "0",
  "sortBy": "relevance",
  "urls": [
    "https://www.totaljobs.com/jobs/in-london"
  ],
  "fetchDetails": false,
  "maxListings": 20,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "GB"
  },
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

# 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": "search",
    "locations": [
        "London"
    ],
    "urls": [
        "https://www.totaljobs.com/jobs/in-london"
    ],
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "GB"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/totaljobs-com-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": "search",
    "locations": ["London"],
    "urls": ["https://www.totaljobs.com/jobs/in-london"],
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "GB",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/totaljobs-com-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": "search",
  "locations": [
    "London"
  ],
  "urls": [
    "https://www.totaljobs.com/jobs/in-london"
  ],
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "GB"
  }
}' |
apify call abotapi/totaljobs-com-scraper --silent --output-dataset

```

## MCP server setup

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