# Workopolis Job Scraper (`automation-lab/workopolis-job-scraper`) Actor

Search public Workopolis Canada jobs by keyword, location, recency, or URL and extract employer, salary, requirements, dates, and full descriptions.

- **URL**: https://apify.com/automation-lab/workopolis-job-scraper.md
- **Developed by:** [Stas Persiianenko](https://apify.com/automation-lab) (community)
- **Categories:** Jobs
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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

## Workopolis Job Scraper

Collect public Canadian vacancies from Workopolis as structured, integration-ready data.
Search by job keyword, location, posting recency, or a Workopolis search URL, then export normalized job records with employer, compensation, requirements, and full descriptions.

This Actor is designed for recruiters, staffing firms, job aggregators, compensation analysts, and labor-market researchers who need a repeatable Workopolis feed without manually copying search results.

### What does Workopolis Job Scraper do?

Workopolis Job Scraper turns one or more public Workopolis searches into a default Apify dataset.
It supports:

- keyword and employer searches;
- Canadian city, province, or nationwide location filters;
- 24-hour, 7-day, and 30-day posting filters;
- direct Workopolis `/search` and `/jobsearch/find-jobs` URLs;
- cursor pagination with a hard `maxItems` limit;
- stable deduplication by Workopolis job key;
- optional full-description enrichment;
- optional Apify proxy configuration for challenged requests.

The Actor uses Workopolis' public Next.js data surfaces over HTTP. It does not require a Workopolis login, user cookies, or a browser.

### Who is it for?

- **Recruiters:** monitor new roles by title, employer, or Canadian market.
- **Staffing firms:** build daily candidate-sourcing and client-demand feeds.
- **Job aggregators:** add normalized Workopolis records to a broader jobs index.
- **Compensation analysts:** collect vacancies where salary text is available.
- **Labor-market researchers:** compare hiring activity by city, role, or date.
- **Automation teams:** send new vacancies to Sheets, Airtable, webhooks, or a database.

### Why use this Actor?

- Structured records are easier to analyze than rendered search cards.
- Full descriptions are available as both sanitized HTML and plain text.
- Missing salary or benefit information remains `null` instead of being invented.
- The same limits and filters apply to keyword searches and explicit search URLs.
- Challenge pages are treated as errors, not as successful empty searches.
- HTTP-only extraction keeps memory and runtime lower than browser automation.

### What Workopolis data can I extract?

| Field | Meaning |
| --- | --- |
| `url` | Canonical public Workopolis job URL |
| `jobKey` | Stable source key for deduplication |
| `title` | Job title |
| `company` | Employer name, when supplied |
| `location` | Displayed Canadian location |
| `salary` | Compensation text, when supplied |
| `employmentType` | Full-time, part-time, contract, or other type |
| `workSetting` | Remote, hybrid, or in-person indicator when available |
| `datePosted` | Posting date normalized to `YYYY-MM-DD` when parseable |
| `snippet` | Short source summary |
| `requirements` | Requirements or skills supplied by Workopolis |
| `benefits` | Benefits supplied by Workopolis |
| `descriptionHtml` | Sanitized semantic job-description HTML |
| `descriptionText` | Plain-text description or snippet fallback |
| `sourceQuery` | Keyword used for discovery |
| `sourceUrl` | Normalized search URL |
| `scrapedAt` | ISO timestamp when the item was produced |

Source fields are not equally complete for every vacancy. Optional fields are nullable by design.

### How to scrape Workopolis jobs

1. Click **Try for free** in Apify Console.
2. Enter a job keyword such as `software developer`.
3. Optionally add a location such as `Toronto, ON`.
4. Select a posting recency filter.
5. Set the maximum jobs and pages.
6. Keep **Fetch full descriptions** enabled when descriptions are needed.
7. Start the Actor and open the **Workopolis jobs** dataset view.
8. Export the records as JSON, CSV, Excel, XML, or connect an integration.

A useful first input is:

```json
{
  "keyword": "software developer",
  "location": "Toronto, ON",
  "postedWithin": "7d",
  "maxItems": 20,
  "maxPages": 3,
  "includeDetails": true
}
```

### Input parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `keyword` | string | `software developer` | Job title, skill, or employer keyword |
| `location` | string | empty | Canadian city, province, or location |
| `postedWithin` | enum | `anytime` | `anytime`, `24h`, `7d`, or `30d` |
| `startUrls` | array | empty | Workopolis search URLs; URL filters override matching fields |
| `maxItems` | integer | `50` | Global maximum unique jobs, from 1 to 10,000 |
| `maxPages` | integer | `10` | Maximum cursor pages per search, from 1 to 100 |
| `includeDetails` | boolean | `true` | Fetch full job descriptions and detail backfills |
| `proxyConfiguration` | object | disabled | Optional Apify proxy configuration |

Provide either `keyword` or at least one supported Workopolis search URL.
Unsupported hosts and paths fail validation before scraping.

### Use a Workopolis search URL

Copy a public Workopolis search URL into `startUrls`:

```json
{
  "startUrls": [
    {
      "url": "https://www.workopolis.com/search?q=nurse&l=Toronto%2C+ON"
    }
  ],
  "maxItems": 15,
  "includeDetails": true
}
```

Legacy Workopolis `/jobsearch/find-jobs?ak=...&l=...` search URLs are also accepted.
Job-detail URLs are output records, not search inputs.

### Output example

The default dataset contains one object per unique vacancy. This abbreviated shape is based on current Workopolis output:

```json
{
  "url": "https://www.workopolis.com/jobsearch/viewjob/LXUTAcCfY9wWQtDXbGOM9tJTWXdXE6pen3EQaIgbAqeg9RGtNXyvfc4vBhfJsMf2",
  "jobKey": "LXUTAcCfY9wWQtDXbGOM9tJTWXdXE6pen3EQaIgbAqeg9RGtNXyvfc4vBhfJsMf2",
  "title": "Registered Nurse - Casual",
  "company": "Sienna Senior Living",
  "location": "Toronto, ON",
  "salary": "$36.06–$54.59 an hour",
  "employmentType": "Part-time",
  "workSetting": "In person",
  "datePosted": "2026-07-18",
  "snippet": "Provide resident-centered nursing care.",
  "requirements": "Nursing, Communication skills",
  "benefits": null,
  "descriptionHtml": "<p>Provide resident-centered nursing care...</p>",
  "descriptionText": "Provide resident-centered nursing care...",
  "sourceQuery": "nurse",
  "sourceUrl": "https://www.workopolis.com/search?q=nurse&l=Toronto%2C+ON",
  "scrapedAt": "2026-07-28T04:30:00.000Z"
}
```

### How much does it cost to scrape Workopolis jobs?

The Actor uses pay-per-event pricing:

- **$0.005 per run** for the Actor start event.
- **$0.000041071 per saved job** on the Free tier.
- Lower per-job rates apply on higher Apify subscription tiers, down to $0.00001 per job on Diamond.
- Full descriptions are included in the saved-job event and have no separate event charge.

Examples on the Free tier:

| Saved jobs | Estimated Actor price |
| ---: | ---: |
| 10 | $0.00541 |
| 100 | $0.00911 |
| 1,000 | $0.0461 |

Examples are start fee plus successful saved-job events, rounded to cents where shown.
Infrastructure usage and optional proxy traffic are measured separately by Apify and may vary by run.

### Build a recurring hiring monitor

Schedule a daily run with `postedWithin` set to `24h`.
Use `jobKey` as the stable key in your database or automation to avoid notifying twice.
Common workflows include:

1. Workopolis → Apify dataset → Google Sheets.
2. Workopolis → webhook → ATS or recruiting CRM.
3. Workopolis → database → employer hiring dashboard.
4. Workopolis → description text → skills or salary classifier.
5. Workopolis → daily diff → Slack or email alert.

Apify schedules and integrations can run these workflows without keeping a server online.

### Tips for reliable results

- Start with 20–50 jobs while validating a new search.
- Disable `includeDetails` when listing metadata is enough and speed matters most.
- Keep page limits conservative for frequent schedules.
- Use exact Canadian locations such as `Vancouver, BC` for focused feeds.
- Deduplicate downstream on `jobKey`, not title.
- Enable an Apify proxy only after a run reports a Workopolis challenge.
- Do not treat a nullable salary as zero compensation; it means the source omitted a structured value.

### Limits and failure behavior

Workopolis can change its Next.js data shape or present Cloudflare challenges.
The Actor retries transient challenge, timeout, 429, and 5xx responses a bounded number of times.
It fails non-zero when it cannot recognize or access the source, so monitoring systems can distinguish a source failure from a genuine no-result search.

A valid search with no current matches succeeds with an empty dataset.
Detail failures preserve useful listing fields and log the affected job key.
The Actor does not scrape private applications, candidate data, or account-only pages.

### Run Workopolis Job Scraper with the Apify API

Replace `YOUR_APIFY_TOKEN` with your token.

#### cURL

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~workopolis-job-scraper/runs?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"keyword":"data analyst","location":"Canada","postedWithin":"24h","maxItems":50}'
```

#### JavaScript

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/workopolis-job-scraper').call({
  keyword: 'data analyst',
  location: 'Canada',
  postedWithin: '24h',
  maxItems: 50,
  includeDetails: true,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

#### Python

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("automation-lab/workopolis-job-scraper").call(run_input={
    "keyword": "data analyst",
    "location": "Canada",
    "postedWithin": "24h",
    "maxItems": 50,
    "includeDetails": True,
})
items = client.dataset(run["defaultDatasetId"]).list_items().items
print(items)
```

### Use Workopolis Job Scraper through MCP

Add the Actor to Claude Code:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/workopolis-job-scraper"
```

#### Claude Desktop, Cursor, and VS Code setup

Claude Desktop, Cursor, and VS Code clients can use this HTTP MCP configuration:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/workopolis-job-scraper"
    }
  }
}
```

Example prompts:

- “Find up to 30 Workopolis nurse jobs in Toronto posted in the last 7 days.”
- “Collect new Canadian data analyst vacancies with salary and full descriptions.”
- “Export this Workopolis search URL as normalized job records.”

### Responsible and legal use

This Actor accesses publicly available Workopolis vacancy data.
You are responsible for complying with applicable laws, Workopolis terms, robots directives, privacy requirements, and the rules that apply to your use case.
Do not use the Actor to collect personal data unnecessarily, bypass account controls, spam employers, or make automated employment decisions without appropriate review.
Store and retain only the data you need.

### FAQ

#### Does it require a Workopolis account?

No. The supported scope uses public job search and detail data.

#### Can it scrape multiple searches?

Yes. Add multiple supported search URLs. `maxItems` remains a global limit and duplicate job keys are saved once.

#### Why is salary sometimes null?

Many employers do not publish structured compensation. The Actor preserves `null` instead of estimating a salary.

#### Why did my run report a challenge?

Workopolis sometimes challenges HTTP traffic. Retry later or enable an appropriate Apify proxy in `proxyConfiguration`. Avoid high-frequency schedules and very large page limits.

#### Can I skip full descriptions?

Yes. Set `includeDetails` to `false` for a faster listing feed. The snippet remains available when supplied by Workopolis.

#### How do I detect newly posted jobs?

Schedule the Actor with `postedWithin: "24h"` and compare the stable `jobKey` with keys already stored downstream.

### Related automation-lab Actors

- [Canada Job Bank Scraper](https://apify.com/automation-lab/canada-job-bank-scraper) — collect vacancies from the Canadian government Job Bank as a distinct complementary source.
- [LinkedIn Jobs Scraper](https://apify.com/automation-lab/linkedin-jobs-scraper) — add broader professional-network job coverage to a multi-source pipeline.

Workopolis remains a separate source with its own employers, listings, and source keys; merging sources downstream can improve coverage.

### Support

If a run fails, include the public run URL, sanitized input, and whether a proxy was enabled when reporting the issue.
Do not include your Apify token, private credentials, or candidate data.

# Actor input Schema

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

Job title, skill, or employer keyword to search. Required when start URLs are not supplied.

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

City, province, or Canadian location, for example Toronto, ON or Vancouver, BC.

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

Return jobs posted within the selected period when Workopolis supports the filter.

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

Optional Workopolis /search or /jobsearch/find-jobs URLs. Their keyword and location filters override the matching fields above.

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

Stop after saving this many unique job records across all searches.

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

Safety limit for cursor pagination on each search input.

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

Enrich each listing with full description HTML and text. Disable for a faster listing-only feed.

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

Optional Apify proxy settings for source access. Start without a proxy and enable one only if your run encounters a Workopolis challenge.

## Actor input object example

```json
{
  "keyword": "software developer",
  "location": "Toronto, ON",
  "postedWithin": "7d",
  "startUrls": [],
  "maxItems": 20,
  "maxPages": 3,
  "includeDetails": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

Dataset containing all unique jobs saved by this 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 = {
    "keyword": "software developer",
    "location": "Toronto, ON",
    "postedWithin": "7d",
    "startUrls": [],
    "maxItems": 20,
    "maxPages": 3,
    "includeDetails": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/workopolis-job-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 = {
    "keyword": "software developer",
    "location": "Toronto, ON",
    "postedWithin": "7d",
    "startUrls": [],
    "maxItems": 20,
    "maxPages": 3,
    "includeDetails": True,
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/workopolis-job-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "keyword": "software developer",
  "location": "Toronto, ON",
  "postedWithin": "7d",
  "startUrls": [],
  "maxItems": 20,
  "maxPages": 3,
  "includeDetails": true
}' |
apify call automation-lab/workopolis-job-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=automation-lab/workopolis-job-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/l7pfdubfs3k9Sp1rk/builds/cVXwhzUQlIf9Gvf0E/openapi.json
