# Canada Job Bank Postings & Wages - Open Data (`jpopendata/canada-job-bank`) Actor

Job postings advertised on Canada's national Job Bank, from the monthly open data published by ESDC (Open Government Licence - Canada): title, NOC 2021, province, city, wage (hourly/annual), hours, employment type, posted date. Back to 2023. Unofficial; not affiliated with ESDC.

- **URL**: https://apify.com/jpopendata/canada-job-bank.md
- **Developed by:** [JP Open Data](https://apify.com/jpopendata) (community)
- **Stats:** 2 total users, 1 monthly users, 50.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 results

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

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

## Canada Job Bank Postings & Wages — Open Data

Get the job postings advertised on **Job Bank**, Canada's national job board run by Employment and Social Development Canada (ESDC), from the **monthly open-data files** that ESDC publishes on the Government of Canada's Open Government Portal under the **Open Government Licence – Canada** (commercial use allowed, with attribution). Every row comes in one clean English schema: job title, **NOC 2021 code and TEER**, province, city, economic region, **advertised wage with hourly and annual equivalents**, weekly hours, work schedule, employment type and term, language requirement, education and experience, and the first posting date.

- **Open data, not website scraping.** The Job Bank website's terms of use prohibit robots and scrapers, so this Actor never contacts jobbank.gc.ca. It reads only the monthly files ESDC publishes for reuse on open.canada.ca.
- **Comparable wages.** Hourly, daily, weekly, bi-weekly, monthly and yearly salaries are converted to hourly and annual equivalents (using the posting's own weekly hours when it gives them), and implausible values (for example a yearly amount typed as hourly) are flagged.
- **History included.** Every month since **January 2023** — 50,000 to 100,000 postings per month, 45 months as of October 2026. Filter by keyword, province, city, NOC code, wage, posting date, language and posting source.
- **Privacy-safe by design.** The files contain no employer names, contact people, e-mails, phone numbers or job descriptions. The Actor also leaves out the work-location postal code and any "as posted" title that embeds a street address or postal code.

### Important: what this data is (and is not)

- **Monthly, not live.** Each file holds the postings **first posted in that month** and is published a few days after the month ends. On any given day the newest posting is roughly 1 to 6 weeks old. Use it for analysis, not for applying to jobs.
- **No employer name, no link to the posting, no closing date, no job description.** The open data does not contain them, so no row has them. If you need to apply, search jobbank.gc.ca yourself.
- **One row per posting and work location.** `jobId` is the dataset's own row identifier ("WIC Job Location Snapshot ID"), not the job number shown on jobbank.gc.ca. A few older files have no identifier; their rows get `"2024-06-row-123"`-style ids.
- **Good for:** wage benchmarks by occupation and region, labour-demand trends by NOC code or TEER level, immigration and Express Entry research, recruiting market sizing, and economics or HR dashboards.

### Quick start — verified input

```json
{
  "months": ["latest"],
  "maxItems": 100
}
```

This returns the first 100 postings of the newest monthly file. An empty input `{}` does exactly the same.

Software developers and programmers (NOC 2123x) in Ontario whose advertised pay reaches $40 an hour, first posted since July 2026:

```json
{
  "nocCodes": ["2123"],
  "provinces": ["ON"],
  "minHourlyWage": 40,
  "postedFrom": "2026-07-01",
  "maxItems": 1000
}
```

Nursing jobs in Montréal and Québec City posted directly by employers, from two chosen months:

```json
{
  "months": ["2026-09", "2026-08"],
  "keywords": ["nurse", "infirmier", "infirmière"],
  "cities": ["Montreal", "Quebec"],
  "postingSource": "direct",
  "maxItems": 500
}
```

### What you get

One row per posting and work location, newest month first, in the file's own order within a month:

| Field | Description |
|---|---|
| `jobId`, `jobIdSource` | The dataset's row id (`snapshot-id`), or a generated `YYYY-MM-row-N` id (`row-number`) for files without one |
| `dataMonth`, `firstPostingDate` | The monthly file (`2026-09`) and the date the posting was first published (`2026-09-22`) |
| `title`, `titleAsPosted` | Job title as standardised by Job Bank, and the title as typed by the employer or partner board (when published and free of addresses) |
| `nocCode`, `nocTitle`, `nocTeer` | NOC 2021 five-digit code, its occupation title, and the TEER level (0-5, the second digit) |
| `noc2016Code`, `noc2016Title` | The older NOC 2016 code and title |
| `postingSource` | `direct` (posted on Job Bank by the employer) or `partner` (shared by a partner job board through a feed; fewer fields) |
| `province`, `provinceCode`, `city`, `economicRegion` | Work location down to the city and economic region (no street, no postal code). `province` is null for a few national postings |
| `salaryText`, `salaryMin`, `salaryMax`, `salaryPeriod` | Advertised salary as published (`$19.00 hourly`), its bounds and period (`hour`, `day`, `week`, `biweekly`, `month`, `year`) |
| `hourlyWageMin/Max`, `annualSalaryMin/Max` | Hourly and annual equivalents (see below) |
| `weeklyHoursUsed`, `weeklyHoursFromPosting`, `salaryPlausible` | Weekly hours used for the conversion, whether they came from the posting (else 40), and whether the hourly equivalent is between $5 and $500 |
| `hoursMin`, `hoursMax`, `hoursPeriod` | Hours of work as published (per week, bi-weekly, month or year) |
| `employmentType`, `employmentTerm`, `termStartDate`, `termEndDate` | Full time / part time, permanent / term / seasonal / casual, and term dates |
| `workSchedule` | Any of `day`, `evening`, `night`, `weekend`, `shift`, `morning`, `early morning`, `on call`, `overtime`, `flexible hours`, `to be determined` |
| `workHoursFrom`, `workHoursTo` | Daily start and end time when published (`08:00`) |
| `languageRequirement` | Official language required: `English`, `French`, `English or French`, `Bilingual` (null when not published) |
| `vacancies`, `education`, `experience`, `industry`, `employerSector`, `placementAgency` | Number of positions, education and experience required, NAICS sector, `non-government` / `federal` / `provincial/territorial` / `municipal`, and whether a placement agency posted it (direct postings only) |
| `matchedKeywords` | Which of your keywords matched |
| `dataFile` | The open.canada.ca download link of the monthly file the row came from |
| `source`, `sourceUrl`, `license`, `retrievedAt` | Attribution on every row (the licence's attribution statement included) |

#### Example output

```json
{
  "jobId": "15894389",
  "jobIdSource": "snapshot-id",
  "dataMonth": "2026-09",
  "title": "room attendant",
  "titleAsPosted": null,
  "nocCode": "65310",
  "nocTitle": "Light duty cleaners",
  "nocTeer": 5,
  "noc2016Code": "6731",
  "noc2016Title": "Light duty cleaners",
  "postingSource": "direct",
  "firstPostingDate": "2026-09-22",
  "vacancies": 2,
  "languageRequirement": "English",
  "education": "No degree, certificate or diploma",
  "experience": "No experience",
  "employerSector": "non-government",
  "placementAgency": false,
  "industry": "Accommodation and food services",
  "province": "British Columbia",
  "provinceCode": "BC",
  "city": "Westbank",
  "economicRegion": "Thompson–Okanagan Region",
  "employmentType": "Full time",
  "employmentTerm": "Permanent employment",
  "termStartDate": null,
  "termEndDate": null,
  "workSchedule": ["day", "evening", "weekend", "shift", "morning", "early morning"],
  "salaryText": "$19.00 hourly",
  "salaryMin": 19,
  "salaryMax": 19,
  "salaryPeriod": "hour",
  "hourlyWageMin": 19,
  "hourlyWageMax": 19,
  "annualSalaryMin": 34580,
  "annualSalaryMax": 34580,
  "weeklyHoursUsed": 35,
  "weeklyHoursFromPosting": true,
  "salaryPlausible": true,
  "hoursMin": 30,
  "hoursMax": 40,
  "hoursPeriod": "week",
  "workHoursFrom": null,
  "workHoursTo": null,
  "dataFile": "https://open.canada.ca/data/dataset/ea639e28-c0fc-48bf-b5dd-b8899bd43072/resource/03523cbf-a005-40ee-81ad-aca5bb74cf03/download/job-bank-open-data-all-job-postings-en-sept2026.csv",
  "matchedKeywords": [],
  "source": "Job Postings Advertised on Canada's National Job Bank Website — Employment and Social Development Canada (ESDC), monthly open data on the Open Government Portal (open.canada.ca)",
  "sourceUrl": "https://open.canada.ca/data/en/dataset/ea639e28-c0fc-48bf-b5dd-b8899bd43072",
  "license": "Contains information licensed under the Open Government Licence – Canada. Licence: https://open.canada.ca/en/open-government-licence-canada. Source: Employment and Social Development Canada, Job Bank open data. Unofficial tool, not affiliated with or endorsed by ESDC, Job Bank or the Government of Canada.",
  "retrievedAt": "2026-10-13T01:00:00.000Z"
}
```

### Input

All fields are optional.

| Field | Type | Description |
|---|---|---|
| `months` | list | `"latest"` (default) or months as `YYYY-MM` (`"2026-09"`; `"September 2026"` also works). Up to 24 per run. Leave as `"latest"` with `postedFrom` / `postedTo` to read the months of that window automatically |
| `keywords` | list | Matched at the start of a word in the job title, the title as posted and the NOC titles; accents and case are ignored. ANY keyword may match. `"developer"` also finds `"developers"` |
| `provinces` | list | Codes or names, English or French: `AB`, `BC`, `MB`, `NB`, `NL`, `NS`, `NT`, `NU`, `ON`, `PE`, `QC`, `SK`, `YT` |
| `cities` | list | Exact city names; accents, case and `St.` / `Saint` spellings are ignored. Suburbs are separate cities |
| `nocCodes` | list | NOC 2021 codes or their first digits: `"21232"`, `"2123"`, `"3"` |
| `minHourlyWage` | number | Keeps postings whose advertised range reaches this hourly wage (CAD) |
| `minAnnualSalary` | integer | Keeps postings whose advertised range reaches this annual salary (CAD) |
| `postedFrom`, `postedTo` | text | First posting date window, `YYYY-MM-DD` (or `YYYY-MM` for a whole month) |
| `languages` | list | `English`, `French`, `English or French`, `Bilingual` (`en`, `fr`, `both` work too) |
| `postingSource` | text | `all` (default), `direct` or `partner` |
| `maxItems` | integer | Rows for the whole run (default 100, up to 500,000). A full month is 50,000 to 100,000 rows |

#### Salary conversions

`W` = weekly hours from the posting (the midpoint of its minimum and maximum, converted from bi-weekly / monthly / yearly hours when needed), or 40 when the posting gives none.

| Published per | Hourly equivalent | Annual equivalent |
|---|---|---|
| hour | as published | × W × 52 |
| day | ÷ 8 | ÷ 8 × W × 52 |
| week | ÷ W | × 52 |
| bi-weekly | ÷ (2 × W) | × 26 |
| month | × 12 ÷ 52 ÷ W | × 12 |
| year | ÷ 52 ÷ W | as published |

The wage filters compare your threshold with the **top** of the advertised range and only keep rows with `salaryPlausible: true`.

### Common input mistakes

| Wrong | Right | Why |
|---|---|---|
| `"provinces": ["Toronto"]` | `"cities": ["Toronto"]` or `"provinces": ["ON"]` | Cities and provinces are separate filters |
| `"cities": ["Ontario"]` | `"provinces": ["ON"]` | A province in `cities` is rejected with this hint |
| `"nocCodes": ["software developer"]` | `"keywords": ["software developer"]` or `"nocCodes": ["21232"]` | NOC filters take codes; words go in `keywords` |
| `"months": ["2026-10"]` early in October | `"months": ["latest"]` | A month is published a few days after it ends; the run lists the available months |
| `"postedFrom": "01/09/2026"` | `"postedFrom": "2026-09-01"` | Day/month orders are ambiguous and rejected |
| `"minHourlyWage": "25 per hour"` | `"minHourlyWage": 25` | A plain amount in Canadian dollars |

### Empty results?

- **Posting dates outside the chosen months.** Each file holds the postings first posted in its month. Leave `months` as `"latest"` and set `postedFrom` / `postedTo`, and the right months are read for you.
- **City spelling.** Cities must match the posting's work location (accents optional). Suburbs such as Mississauga or Laval are separate cities — add them, or filter by province.
- **A NOC code nobody used that month**, or a 5-digit code with a typo. Try its first 3 or 4 digits.
- **A wage above every advertised range**, or postings without a salary (they are dropped when a wage filter is set).
- **Language or education filters on partner postings.** Postings shared by partner job boards often publish no language, education or hours.
- A run that finds nothing still **succeeds** with 0 rows; the log explains the typical causes.

### How it works

1. One request to the Open Government Portal's catalogue (CKAN `package_show`) lists the monthly files.
2. For each chosen month, the open.canada.ca download link answers with a short-lived storage link, and the file (40-115 MB, UTF-16 or UTF-8, tab- or comma-separated) is streamed, parsed by column name, normalised and filtered on the fly.
3. As soon as `maxItems` rows are found, the download is closed. The Quick start needs 3 requests and finishes in about 30 seconds (20 of them are the polite pause between the two open.canada.ca requests). Reading a whole month to the end takes about 1.5 to 2 minutes for a typical 45 MB file at the default 256 MB (the largest files, about 115 MB, take around 5 minutes).

**Long ranges:** Apify gives a run CPU in proportion to its memory, and parsing is CPU-bound, so for 6 months or more run with 1 GB of memory (up to about 4 times faster) and, beyond about 12 months, a longer timeout than the default hour. If the timeout is near, the run stops 90 seconds early and keeps every row found so far (`complete: false`).

Requests are strictly serial, at least 20 seconds apart on open.canada.ca (its robots.txt `Crawl-delay`) and 1.2 seconds apart otherwise, with retries and backoff for temporary errors. If a download fails after rows were delivered, the run still succeeds with those rows and `complete: false` in the `RUN_SUMMARY` record of the key-value store.

### What is deliberately not included

- **Work-location postal codes** (finer than the city level), and "as posted" titles that contain a street address, postal code, e-mail, phone number or web address.
- **The benefit flags and the telework flag** of the files: in the September 2026 file every one of them is "No" or empty, so publishing them would mislead.
- **Employer names, posting links, closing dates and job descriptions**: they are not in the open data.

### Legal and attribution

- Contains information licensed under the Open Government Licence – Canada (https://open.canada.ca/en/open-government-licence-canada). Source: Employment and Social Development Canada, "Job Postings Advertised on Canada's National Job Bank Website" (https://open.canada.ca/data/en/dataset/ea639e28-c0fc-48bf-b5dd-b8899bd43072).
- Every row carries this attribution in its `license` field. If you republish the data, keep the attribution statement and, where possible, the link to the licence.
- **Unofficial tool. Not affiliated with, endorsed by or sponsored by ESDC, Job Bank or the Government of Canada.** The licence does not allow suggesting official status or endorsement, and it does not cover the Government's names, crests, logos or other official symbols.
- Only public open data is read; no login, no website scraping. You are responsible for using the data lawfully and in line with the licence.
- **GDPR / privacy:** the output contains no personal data — no names, no contact details, no employer identities, no addresses.

### FAQ

**Why are there no employer names or links to the postings?** ESDC's open-data files do not include them. The Job Bank website does, but its terms of use prohibit automated access, so this Actor does not read it.

**How fresh is the data?** The newest month appears a few days after it ends (September 2026 was published on 2 October 2026). The catalogue is checked on every run, so a new month is picked up automatically by `"latest"`.

**Is there a French version?** ESDC also publishes French files with the same postings and translated labels. This Actor reads the English files, which already include French job titles as posted and French place names.

**How many rows per month?** Between about 50,000 and 100,000 (52,368 in September 2026, 102,939 in June 2024). In September 2026, 43% were posted directly by employers and 57% were shared by partner job boards.

# Actor input Schema

## `months` (type: `array`):

Which monthly files to read, one per line: "latest" (the newest published month) or a month as YYYY-MM, e.g. "2026-09", "2026-08" ("September 2026" also works). Up to 24 per run. Leave as "latest" together with Posted from / Posted to to pick the months of that date window automatically. Files exist from 2023-01.

## `keywords` (type: `array`):

Optional. Words or phrases matched (case- and accent-insensitive) at the start of a word in the job title, the title as posted, and the NOC occupation titles, e.g. "software developer", "nurse", "electrician", "cuisinier". A posting is kept if ANY keyword matches. "developer" also finds "developers". Leave empty for all jobs.

## `provinces` (type: `array`):

Optional. Two-letter codes or names (English or French): AB, BC, MB, NB, NL, NS, NT, NU, ON, PE, QC, SK, YT, e.g. "ON", "Quebec", "Colombie-Britannique". Leave empty for all of Canada.

## `cities` (type: `array`):

Optional. City names as written in the posting's work location, e.g. "Toronto", "Montreal" (accents optional), "St. John's", "Surrey". Exact city match: suburbs are separate cities, so add them (e.g. "Mississauga", "Brampton") or use provinces instead. Leave empty for every city.

## `nocCodes` (type: `array`):

Optional. National Occupational Classification 2021 codes or their first digits: "21232" (software developers and programmers), "2123" (every code starting 2123), "3" (health occupations). Job titles go in Keywords.

## `minHourlyWage` (type: `number`):

Optional. Keep postings whose advertised range reaches at least this hourly wage, e.g. 25 or 25.5. Annual, monthly, weekly and daily salaries are converted with the posting's weekly hours (40 h when none are published). Postings without a salary, or with an implausible one, are dropped when this is set.

## `minAnnualSalary` (type: `integer`):

Optional. Keep postings whose advertised range reaches at least this yearly amount, e.g. 60000. Hourly wages are converted with the posting's weekly hours x 52 (40 h when none are published).

## `postedFrom` (type: `string`):

Optional. First posting date from, as YYYY-MM-DD (e.g. "2026-09-15") or YYYY-MM for a whole month. With Months left as "latest", the months of the window are read automatically.

## `postedTo` (type: `string`):

Optional. First posting date to (inclusive), as YYYY-MM-DD or YYYY-MM.

## `languages` (type: `array`):

Optional. Official language required for the job, any of: English, French, English or French, Bilingual ("en", "fr" work too). Many postings shared by partner job boards publish none and are dropped when this is set.

## `postingSource` (type: `string`):

"all" (default), "direct" (posted on Job Bank by the employer: richest fields, incl. education, experience, industry and weekly hours) or "partner" (shared by a partner job board through a feed: fewer fields).

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

Rows kept for the whole run (1..500,000; values outside are clamped). A full month has 50,000 to 100,000 postings. The run stops downloading as soon as this many rows are found.

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

Not needed: the open-data files are served to Apify datacenter IPs directly (tested 2026-10-13). If you set a proxy, the run keeps a single sticky session (one IP), never rotation.

## Actor input object example

```json
{
  "months": [
    "latest"
  ],
  "keywords": [],
  "provinces": [],
  "cities": [],
  "nocCodes": [],
  "postedFrom": "",
  "postedTo": "",
  "languages": [],
  "postingSource": "all",
  "maxItems": 100,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `records` (type: `string`):

Job Bank postings with source attribution (source, sourceUrl, license, retrievedAt) on every item.

# 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 = {
    "months": [
        "latest"
    ],
    "keywords": [],
    "provinces": [],
    "cities": [],
    "nocCodes": [],
    "postedFrom": "",
    "postedTo": "",
    "languages": [],
    "postingSource": "all",
    "maxItems": 100,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("jpopendata/canada-job-bank").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 = {
    "months": ["latest"],
    "keywords": [],
    "provinces": [],
    "cities": [],
    "nocCodes": [],
    "postedFrom": "",
    "postedTo": "",
    "languages": [],
    "postingSource": "all",
    "maxItems": 100,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("jpopendata/canada-job-bank").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 '{
  "months": [
    "latest"
  ],
  "keywords": [],
  "provinces": [],
  "cities": [],
  "nocCodes": [],
  "postedFrom": "",
  "postedTo": "",
  "languages": [],
  "postingSource": "all",
  "maxItems": 100,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call jpopendata/canada-job-bank --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,jpopendata/canada-job-bank"
        }
    }
}
```

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/78VD5MlgDg0vp0jGC/builds/1p2LeylyD0ZFw00vS/openapi.json
