# CWJobs UK IT & Tech Jobs Scraper (`santamaria-automations/cwjobs-uk-scraper`) Actor

Extract IT contract and permanent jobs from CWJobs.co.uk, the UK tech recruitment board. Returns title, employer, salary, rate type (annual/daily/hourly), employment type, workplace type, IR35 status, tech skills, contract length and full description.

- **URL**: https://apify.com/santamaria-automations/cwjobs-uk-scraper.md
- **Developed by:** [NanoScrape](https://apify.com/santamaria-automations) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.24 / 1,000 serp results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## CWJobs UK IT & Tech Jobs Scraper

Scrape IT contract and permanent jobs from [CWJobs.co.uk](https://www.cwjobs.co.uk/), the UK's specialist board for technology recruitment. Supply a keyword, a search URL, or both; get every listing back as structured JSON with employer, salary, IR35 status, day rate, tech skills, contract length, direct apply URL and full description.

### What it does

Pulls live UK IT and tech vacancies from CWJobs search results, deduplicates them by job ID, and pushes them to your Apify dataset. Handles pagination automatically up to your result cap. Optionally opens each job's detail page for structured salary ranges, IR35 status, contract length in months, workplace type (remote / hybrid / onsite), tech skill tags, direct apply link and full ad copy. A further optional pass fetches each unique employer's profile page on TotalJobs (CWJobs's sister board) for company About text and a list of the employer's other currently open roles.

Fields you get per job:

- Job title, employer name, employer logo
- Location, country, salary (min, max, currency, period)
- Rate type (annual, daily, hourly, monthly)
- Employment type (permanent, contract, temporary)
- Workplace type (remote, hybrid, onsite)
- IR35 status (inside, outside, undetermined) for contract roles
- Contract length in months when the ad specifies it
- Tech skills array (Java, AWS, Kubernetes, React, Python, etc.)
- Sector (Cybersecurity, Data Science, DevOps, Frontend, etc.)
- Description (plain text, HTML, Markdown), direct apply URL, posted date
- Employer profile mode: company-jobs page listing, company website hint, address from PDP schema.org jobLocation

### Sample output

```json
{
  "_type": "job",
  "id": "108018529",
  "title": "Junior Java Developer",
  "company_name": "Hackajob Ltd",
  "company_logo_url": "https://tjgliveassets.s3.eu-west-1.amazonaws.com/company-logos/01a731a76abe4ee4b5f58b72600d5a61.png",
  "location": "South West London, South East",
  "country": "GB",
  "salary_range": null,
  "salary_text": "Competitive",
  "rate_type": null,
  "employment_type": null,
  "workplace_type": "hybrid",
  "ir35_status": null,
  "contract_length_months": null,
  "skills": ["Java", "JavaScript", "Node.js", "Spring", "Azure", "Jenkins", "Agile"],
  "sector": "IT / Software",
  "description_full": "hackajob is partnering directly with Version 1 to hire for this role...",
  "description_html": "<p>hackajob is partnering...</p>",
  "description_md": "hackajob is partnering...",
  "job_url": "https://www.cwjobs.co.uk/job/junior-java-developer/hackajob-ltd-job108018529",
  "apply_url": "https://www.totaljobs.com/job/a5984d09-c06d-4d11-b455-c100241b60f4/application/authentication?locale=en_GB",
  "posted_at": "2026-09-19",
  "posted_at_text": "2026-09-19",
  "posted_at_datetime": "2026-09-19",
  "search_query": "java developer",
  "source_platform": "cwjobs.co.uk",
  "scraped_at": "2026-09-23T07:34:24Z",
  "contact_emails": [],
  "contact_phones": [],
  "contact_urls": [],
  "company_about": null,
  "company_phone": null,
  "company_email": null,
  "company_website": null,
  "company_address_street": null,
  "company_address_city": "South West London",
  "company_address_postcode": null,
  "company_address_country": "GB",
  "company_active_jobs_count": 1596,
  "company_active_jobs": [
    {
      "title": "Senior Java Developer",
      "job_url": "https://www.totaljobs.com/job/senior-java-developer/hackajob-job108019000"
    }
  ]
}
```

### Input examples

**Keyword search only**

```json
{
  "searchQueries": ["java developer", "devops engineer"],
  "location": "London",
  "sortBy": "newest",
  "maxResults": 100,
  "maxResultsPerQuery": 50,
  "includeJobDetails": true
}
```

**Search URLs only**

```json
{
  "searchUrls": [
    "https://www.cwjobs.co.uk/jobs/java-developer/in-london",
    "https://www.cwjobs.co.uk/jobs/contract/it"
  ],
  "sortBy": "newest",
  "maxResults": 200,
  "maxResultsPerQuery": 100,
  "includeJobDetails": true
}
```

**Combined: keywords AND URLs, with employer profiles**

```json
{
  "searchQueries": ["software engineer"],
  "searchUrls": ["https://www.cwjobs.co.uk/jobs/java-developer/in-london"],
  "location": "Manchester",
  "sortBy": "newest",
  "maxResults": 50,
  "maxResultsPerQuery": 25,
  "includeJobDetails": true,
  "includeCompanyDetails": true,
  "maxConcurrency": 5
}
```

Each keyword and each URL runs as a separate task. Results across all tasks are deduplicated by job ID.

### Input parameters

| Field | Type | Default | Notes |
|---|---|---|---|
| `searchQueries` | array | | Keyword queries (e.g. `java developer`, `devops engineer`). Each runs as a separate search. |
| `searchUrls` | array | | CWJobs search URLs to crawl directly. Combine freely with searchQueries. |
| `location` | string | | UK city, region or postcode. Applied to keyword queries. |
| `sortBy` | select | `newest` | `newest` or `relevance`. Use `newest` for scheduled scrapes. |
| `includeJobDetails` | boolean | `false` | Open each PDP for structured salary, IR35, contract length, skills, apply URL and full description. |
| `includeCompanyDetails` | boolean | `false` | Fetch each employer's TotalJobs company page for About text and open-role list. Implies `includeJobDetails`. |
| `maxResults` | integer | `5` | Total cap across all queries and URLs. |
| `maxResultsPerQuery` | integer | `5` | Cap per individual keyword or URL task. |
| `maxConcurrency` | integer | `5` | Concurrent detail page requests (1 to 20). |

### Output fields

Every dataset row has a stable schema documented in the Output tab. Key derived fields:

- `employment_type`: permanent, contract, temporary
- `rate_type`: annual, daily, hourly, monthly
- `workplace_type`: remote, hybrid, onsite
- `ir35_status`: inside, outside, undetermined (populated when IR35 is mentioned in the ad)
- `contract_length_months`: numeric, extracted from phrases like "6 month contract" or "12-week engagement"
- `skills`: array of normalised tech tags (Java, Python, AWS, Kubernetes, React, .NET, etc.)
- `sector`: narrowed sub-sector (Cybersecurity, Data Science, DevOps, Frontend, Backend, Full Stack, Mobile, QA / Testing, Cloud, Networking, IT Support, Project Management, Product / BA, IT / Software)
- `salary_range`: object with `min`, `max`, `currency`, `period`
- `apply_url`: direct application URL from the PDP apply button (TotalJobs-hosted or third-party ATS redirect)
- `description_full`, `description_html`, `description_md`: full ad text in three formats (requires `includeJobDetails`)

**Employer profile fields (`includeCompanyDetails: true`):**

- `company_about`: About-us summary from og:description on the employer's TotalJobs page
- `company_website`: Employer website hint from PDP schema.org hiringOrganization.url (external domain only)
- `company_address_city`, `company_address_country`: From PDP schema.org jobLocation.address
- `company_active_jobs_count`, `company_active_jobs`: Count and list (title + job_url) of the employer's other open roles
- `company_phone`, `company_email`, `company_address_street`, `company_address_postcode`: Not exposed on CWJobs / TotalJobs; always null

### Pricing

Pay-per-event. You only pay for what the actor produces.

| Event | Rate | When charged |
|---|---|---|
| `job-start` | $0.001 | Once per actor run |
| `job-serp-result` | $0.00124 | Per job pushed from a SERP page (`includeJobDetails: false`) |
| `job-detail-result` | $0.0025 | Per job enriched with detail page (`includeJobDetails: true`) |
| `company-detail-result` | $0.005 | Per unique employer resolved (`includeCompanyDetails: true`) |

Compute and proxy costs are absorbed by us. You pay the flat per-event rates above.

#### Worked examples

**SERP-only scan, 100 jobs**

| Event | Count | Rate | Subtotal |
|---|---|---|---|
| `job-start` | 1 | $0.001 | $0.001 |
| `job-serp-result` | 100 | $0.00124 | $0.124 |
| **Total** | | | **$0.125** |

**SERP + detail enrichment, 100 jobs**

| Event | Count | Rate | Subtotal |
|---|---|---|---|
| `job-start` | 1 | $0.001 | $0.001 |
| `job-serp-result` | 100 | $0.00124 | $0.124 |
| `job-detail-result` | 100 | $0.0025 | $0.250 |
| **Total** | | | **$0.375** |

**Full pipeline: SERP + detail + employer profiles, 500 jobs, 80 unique employers**

| Event | Count | Rate | Subtotal |
|---|---|---|---|
| `job-start` | 1 | $0.001 | $0.001 |
| `job-serp-result` | 500 | $0.00124 | $0.620 |
| `job-detail-result` | 500 | $0.0025 | $1.250 |
| `company-detail-result` | 80 | $0.005 | $0.400 |
| **Total** | | | **$2.271** |

New Apify accounts include $5 free monthly credit, enough to cover roughly 1,300 detail-enriched jobs before any charge.

### API integration

See the [auto-generated API tab](https://console.apify.com/actors/HMcgtfbMXEyMltlFv/info/api) for ready-to-run language-specific examples (cURL, JavaScript, Python, .NET, Ruby, PHP) that stay in sync with the actor's input schema.

### Use with AI agents (MCP)

This actor is compatible with Apify's MCP server for use in Claude, Cursor, LangChain and other agent frameworks. For per-tool schemas and MCP-client copy-paste snippets, see the [MCP tab](https://apify.com/santamaria-automations/cwjobs-uk-scraper/api/mcp) on the actor page.

```json
{
  "mcpServers": {
    "apify": {
      "command": "npx",
      "args": [
        "mcp-remote",
        "https://mcp.apify.com/?tools=santamaria-automations/cwjobs-uk-scraper",
        "--header",
        "Authorization: Bearer <YOUR_API_TOKEN>"
      ]
    }
  }
}
```

Example prompt: "Find 30 Java developer contract jobs on CWJobs in London that are outside IR35 with a day rate over £500, return company, salary, contract length and posted date."

### Why this scraper

Built HTTP-only in Go for consistent speed and low cost. Uses schema.org JobPosting data on every detail page, so structured salary and dates come straight from the site's own markup instead of guesswork. Derives IR35 status, contract length and tech skills from the full ad text with UK IT contracting vocabulary (inside / outside IR35, deemed inside via umbrella, month or week engagement lengths). Runs at 128 MB RAM with no browser overhead.

### Common use cases

- Recruitment agencies tracking new IT vacancies daily by keyword and location
- Contract recruiters benchmarking day rates by tech stack and IR35 status
- IT staffing firms building account lists of hiring companies by tech skill
- Market intelligence on which UK employers hire for AWS, Kubernetes, .NET or Java at scale
- Researchers studying inside-vs-outside IR35 rate deltas since HMRC reforms

### Notes and limits

- Coverage: UK IT and tech vacancies posted to CWJobs.co.uk. CWJobs is a sister board of TotalJobs; detail pages are hosted on totaljobs.com.
- `searchQueries` and `searchUrls` are independent inputs. Supply both and the actor runs all keyword tasks plus all URL tasks in sequence, deduplicating by job ID across the full run.
- Salary is normalised to `min`, `max`, `currency` and `period` when the ad exposes numbers. When the ad only shows "Competitive" or "Undisclosed", the range comes through as `null`.
- IR35 status is populated only when the ad or salary text explicitly mentions IR35. Permanent listings will show `null`.
- `apply_url` is extracted from the PDP apply-button anchor and requires `includeJobDetails: true`. For TotalJobs-hosted applications the URL follows the form `https://www.totaljobs.com/job/{uuid}/application/authentication?locale=en_GB`. Third-party ATS redirects return the external ATS URL.
- Contract length parses `6 month contract`, `12 months`, `3-month`, and `N weeks` formats and normalises to months.
- `description_full`, `description_html` and `description_md` require `includeJobDetails: true`. SERP rows carry no description.

### Related actors

Feed the `company_website` field from this actor into either of the two below to enrich rows with direct contact data:

- **[Website Email & Phone Scraper](https://apify.com/santamaria-automations/website-email-scraper)** (`VpLcKwzcl70CPHljg`) — regex-based, fast and cheap; extracts emails and phones from a company website.
- **[Website Contact Extractor](https://apify.com/santamaria-automations/website-contact-extractor)** (`JmMPJ7zumUfB7p0Kr`) — LLM-based, higher precision on structured contacts (names, roles, department-scoped emails).

More UK and international job scrapers from our fleet:

- **[Reed.co.uk Scraper](https://apify.com/santamaria-automations/reed-uk-scraper)** — UK jobs across all industries with salaries and full descriptions.
- **[Indeed Scraper](https://apify.com/santamaria-automations/indeed-scraper)** — global job coverage with logos and benefits.
- **[LinkedIn Jobs Scraper](https://apify.com/santamaria-automations/linkedin-scraper)** — LinkedIn's public job search across every geography.
- **[Job Feed](https://apify.com/santamaria-automations/job-aggregator)** — one normalised, deduplicated job feed across 50+ international job boards.
- **[Career Site Jobs Scraper](https://apify.com/santamaria-automations/career-site-jobs-scraper)** — scrape jobs directly from company career pages (Greenhouse, Lever, Workday, etc.).

### Support

- **Questions or issues with this actor?** Email contact@nanoscrape.com or open a ticket in the [Issues tab](https://console.apify.com/actors/HMcgtfbMXEyMltlFv/info/issues) — we typically reply within 6 hours.
- **Feature request for this actor?** Open a ticket in the [Issues tab](https://console.apify.com/actors/HMcgtfbMXEyMltlFv/info/issues) or email contact@nanoscrape.com with the field or filter you need and (if possible) a URL showing the data live on CWJobs.
- **Need a scraper for a job board we do not cover yet?** Email contact@nanoscrape.com with the target site and your rough usage volume — we ship new boards regularly.

# Actor input Schema

## `searchQueries` (type: `array`):

One or more keyword queries (for example 'java developer', 'devops engineer', 'data scientist'). Each runs as a separate search. Results are deduplicated by job ID.

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

Alias of searchUrls: direct SERP URLs to crawl. Combined with searchQueries when both are provided — results are deduplicated by job ID.

## `searchUrls` (type: `array`):

Paste one or more CWJobs search URLs. Go to cwjobs.co.uk, run a search or open a category page (e.g. /jobs/contract/it), and copy the URL. Filters like keyword, location and permanent/contract are preserved. Combined with searchQueries when both are provided — each URL and each keyword runs as a separate task, results are deduplicated.

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

UK city, region or postcode (for example 'London', 'Manchester', 'Bristol', 'Cambridge'). Applied to all keyword searches.

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

How to order results on the CWJobs SERP. 'newest' returns the most recently posted jobs first (recommended for scheduled scrapes). 'relevance' uses CWJobs keyword relevance ranking.

## `includeJobDetails` (type: `boolean`):

When true, opens each job's detail page for structured salary, IR35 status, contract length, tech skills and full description. Costs more but returns richer data.

## `includeCompanyDetails` (type: `boolean`):

Also fetch each unique employer's company profile page for the About-us text, phone, address, website, and a list of the employer's other currently-open roles. Deduplicated per employer within a run. Adds $0.005 per unique employer. Implies includeJobDetails=true (employer link only lives on the PDP).

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

Total cap across all queries and URLs.

## `maxResultsPerQuery` (type: `integer`):

Maximum results per search URL or keyword.

## `maxConcurrency` (type: `integer`):

Maximum concurrent detail page requests. Only used when Fetch job detail pages is on.

## Actor input object example

```json
{
  "searchUrls": [
    "https://www.cwjobs.co.uk/jobs/java-developer/in-london"
  ],
  "location": "London",
  "sortBy": "newest",
  "includeJobDetails": false,
  "includeCompanyDetails": false,
  "maxResults": 5,
  "maxResultsPerQuery": 5,
  "maxConcurrency": 5
}
```

# Actor output Schema

## `jobListings` (type: `string`):

Dataset of UK IT jobs from CWJobs. Each row has: id, title, employer, salary_range, rate_type, employment_type (permanent/contract/temporary), workplace_type (remote/hybrid/onsite), sector, ir35_status (inside/outside/undetermined), skills (array of tech tags like Java, AWS, Docker), contract_length_months, description, job_url, apply_url, posted_at.

# 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 = {
    "searchUrls": [
        "https://www.cwjobs.co.uk/jobs/java-developer/in-london"
    ],
    "sortBy": "newest",
    "includeJobDetails": false,
    "maxResults": 5,
    "maxResultsPerQuery": 5,
    "maxConcurrency": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("santamaria-automations/cwjobs-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 = {
    "searchUrls": ["https://www.cwjobs.co.uk/jobs/java-developer/in-london"],
    "sortBy": "newest",
    "includeJobDetails": False,
    "maxResults": 5,
    "maxResultsPerQuery": 5,
    "maxConcurrency": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("santamaria-automations/cwjobs-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 '{
  "searchUrls": [
    "https://www.cwjobs.co.uk/jobs/java-developer/in-london"
  ],
  "sortBy": "newest",
  "includeJobDetails": false,
  "maxResults": 5,
  "maxResultsPerQuery": 5,
  "maxConcurrency": 5
}' |
apify call santamaria-automations/cwjobs-uk-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,santamaria-automations/cwjobs-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/HMcgtfbMXEyMltlFv/builds/5QpFs2AxEdSEZqF5J/openapi.json
