# Canada Job Postings and Salaries by Occupation (`iocodo/canada-job-bank-postings`) Actor

Monthly job postings from Canada's national Job Bank open data, filtered by province, city, occupation (NOC 2021) and keyword. One row per posting with title, occupation, location, vacancies, schedule and salary, plus an hourly wage for comparison.

- **URL**: https://apify.com/iocodo/canada-job-bank-postings.md
- **Developed by:** [IOCODO LLC](https://apify.com/iocodo) (community)
- **Categories:** Jobs
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.50 / 1,000 job postings

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

### What does this Actor do?

This Actor reads the monthly open dataset [Job Postings Advertised on Canada's National Job Bank Website](https://open.canada.ca/data/en/dataset/ea639e28-c0fc-48bf-b5dd-b8899bd43072), published by Employment and Social Development Canada, and returns **one row per posting** in a clean, typed schema. You pick a month and filter by province, city, occupation (`NOC 2021` code) and job title keyword. Pay comes back as published and also converted to dollars per hour, so postings with different pay units can be compared.

**This is a monthly dataset, not live listings.** Each file holds the postings advertised on Job Bank during one calendar month, and a new file appears in the first days of the next month. The Actor reads one monthly file per run. It cannot tell you which jobs are open today.

The dataset has **no employer names and no contact details**, and this Actor adds none. Postal codes are shortened to their first three characters.

It needs no API key, no login and no browser.

### Why use it?

- **Salary benchmarking.** Compare hourly wages for one occupation across provinces, or across months by running the Actor once per month.
- **Labour market research.** Count postings and vacancies by occupation, city or economic region, and see which employment types and schedules are advertised.
- **Recruitment analytics.** See how an occupation is advertised: job titles, education and experience asked for, full time or part time, weekend or evening work.
- **One tidy format.** Dates are `YYYY-MM-DD`, numbers are numbers, flags are short lists, and every missing value is `null`.

On Apify you can schedule runs, call the Actor through the API, and send results to Google Sheets, Zapier, Make or a webhook.

### What data does it return?

| Field | Example |
|---|---|
| `postingId` | 15877330 |
| `datasetMonth` | 2026-09 |
| `jobTitle`, `originalJobTitle` | engineer, mining |
| `noc2021Code`, `noc2021Title` | 21330, Mining engineers |
| `noc2016Code`, `noc2016Title` | 2143, Mining engineers |
| `naicsSector` | Professional, scientific and technical services |
| `channel` | `job_bank` or `partner_job_board` |
| `firstPostedOn` | 2026-09-16 |
| `vacancies` | 1 |
| `language`, `education`, `experience` | English, Bachelor's degree, 5 years or more |
| `governmentType`, `placementAgency` | Not a government entity, `false` |
| `province`, `city`, `postalArea`, `economicRegion` | Ontario, Haileybury, `P0J`, Northeast Region |
| `multipleLocations` | `false` |
| `employmentType`, `employmentTerm` | Full time, Permanent employment |
| `termStartsOn`, `termEndsOn` | dates, `YYYY-MM-DD` |
| `schedule` | `["overtime", "day"]` |
| `salaryText` | $75,000.00 to $150,000.00 annually (to be negotiated) |
| `salaryPer`, `salaryMin`, `salaryMax` | `year`, 75000, 150000 |
| `salaryMinHourly`, `salaryMaxHourly` | 36.06, 72.12 |
| `benefits` | `[]` (see the notes below) |
| `hoursPer`, `hoursMin`, `hoursMax` | `week`, 37.5, 44 |

Every record also says where it came from and how fresh it is:

- `source` and `license`: the dataset and its licence.
- `source_updated_at`: when the publisher last updated that month's file.
- `retrieved_at`: when this run read the data.

Things to know about the data:

- **Two channels.** `job_bank` postings were placed by employers directly on Job Bank. `partner_job_board` postings come from partner job boards and carry fewer fields: `education`, `experience`, `naicsSector`, `schedule` and `benefits` are mostly `null`. In the `2026-09` file, a little over half of the postings are `partner_job_board`.
- **Missing values.** Cells the file marks as missing (`NA`, or `*No data` in the language column) come back as `null`. `schedule` and `benefits` are `null` when the file gives no answer, and `[]` when it answers and no flag is set.
- **Benefit flags are empty.** The file has columns for conditions such as dental, pension and bonus, but the months we checked (`2025-01` and `2026-09`) mark none of them. `benefits` is `[]` for postings placed on Job Bank and `null` for partner postings. Do not use it to count postings that offer benefits.
- **Postal area only.** `postalArea` is the first three characters of the postal code. The full code is never returned.
- **Published values.** Pay is copied as published. A few postings have a wrong unit or a typing mistake, such as an hourly rate of several thousand dollars, so check outliers before you average.

### How to get job postings

1. Open the Actor. To read an older month, enter it as `YYYY-MM` in **Month**. Leave it empty for the latest month.
2. Pick provinces, and optionally cities, occupation codes and job title keywords.
3. Set **Maximum postings**. The Actor stops reading the file as soon as it has that many.
4. Click **Start** and download the results as JSON, CSV, Excel or HTML.

Files go back to January 2023. If the month you enter is not available, the run stops with a message that lists the months it can read.

### How much does it cost?

You pay **$0.50 per 1,000 postings returned** ($0.0005 each). A run that matches nothing costs nothing. A run stops by itself at the maximum charge you set for it. The monthly credit of the free Apify plan is enough to try it.

### Input

See the input tab for every option. In short:

- `month`: `YYYY-MM`, for example `2026-09`. Empty means the latest month.
- `provinces`: one or more provinces or territories. Empty means all of Canada.
- `cities`: city names, matched without regard to case or accents.
- `nocCodes`: NOC 2021 codes or code prefixes, for example `21` or `21231`.
- `keywords`: a posting is kept when its job title or original job title contains any of them.
- `minHourlyWage`: keep postings whose `salaryMaxHourly` is at least this many dollars.
- `maxPostings`: up to 500,000 (default 1,000).

Filters combine: a posting must pass every filter you set.

### Output

```json
{
    "postingId": "15877330",
    "datasetMonth": "2026-09",
    "jobTitle": "engineer, mining",
    "originalJobTitle": null,
    "noc2021Code": "21330",
    "noc2021Title": "Mining engineers",
    "noc2016Code": "2143",
    "noc2016Title": "Mining engineers",
    "naicsSector": "Professional, scientific and technical services",
    "channel": "job_bank",
    "firstPostedOn": "2026-09-16",
    "vacancies": 1,
    "language": "English",
    "education": "Bachelor's degree",
    "experience": "5 years or more",
    "governmentType": "Not a government entity",
    "placementAgency": false,
    "province": "Ontario",
    "city": "Haileybury",
    "postalArea": "P0J",
    "economicRegion": "Northeast Region",
    "multipleLocations": false,
    "employmentType": "Full time",
    "employmentTerm": "Permanent employment",
    "termStartsOn": null,
    "termEndsOn": null,
    "schedule": ["overtime", "day"],
    "salaryText": "$75,000.00 to $150,000.00 annually (to be negotiated)",
    "salaryPer": "year",
    "salaryMin": 75000,
    "salaryMax": 150000,
    "salaryMinHourly": 36.06,
    "salaryMaxHourly": 72.12,
    "benefits": [],
    "hoursPer": "week",
    "hoursMin": 37.5,
    "hoursMax": 44,
    "source": { "name": "Employment and Social Development Canada, Job Bank open data (Job Postings Advertised on Canada's National Job Bank Website)", "url": "https://open.canada.ca/data/en/dataset/ea639e28-c0fc-48bf-b5dd-b8899bd43072" },
    "license": { "name": "Open Government Licence - Canada", "url": "https://open.canada.ca/en/open-government-licence-canada", "attribution": "Contains information licensed under the Open Government Licence - Canada." },
    "source_updated_at": "2026-10-02T18:44:07.369Z",
    "retrieved_at": "2026-10-24T18:44:15.472Z"
}
```

A `SUMMARY` record in the key-value store describes the run:

- `datasetMonth` and `availableMonths`: the month that was read, and every month the Actor can read.
- `rowsRead`, `rowsMatched`, `pushed`: rows scanned, rows that passed your filters, postings returned.
- `malformedRows`: rows skipped because their field count did not fit the header.
- `stoppedAtLimit`: whether the run stopped at its maximum charge.
- `source_updated_at`: when the publisher last updated the file.

### How hourly wages are worked out

`salaryMinHourly` and `salaryMaxHourly` convert the posted pay to dollars per hour, rounded to the cent:

| `salaryPer` | Hourly figure |
|---|---|
| `hour` | as published |
| `week` | divided by the weekly hours when `hoursPer`, `hoursMin` and `hoursMax` give them (the average of the two), otherwise by 40 |
| `bi-weekly` | divided by two weeks of those hours, otherwise by 80 |
| `month` | multiplied by 12, then divided by 52 and by 40 |
| `year` | divided by 2,080 |
| `day`, or no pay unit | `null` |

These are rules of thumb for comparing postings, not a payroll calculation. The posted values stay in `salaryMin`, `salaryMax` and `salaryText`.

### Tips

- **Filter by occupation prefix.** In `nocCodes`, `2` covers every natural and applied sciences occupation, `21` the professional ones, `2123` software and web development, and `21231` one occupation (software engineers and designers).
- **Schedule a monthly run.** Each month's file is published in the first days of the next month. A scheduled run with the same filters builds a month-by-month series.
- **Compare like with like.** Filter on `channel` when you need fields that only `job_bank` postings carry.
- **Limit the run.** Keep `maxPostings` close to what you need. You pay per posting returned, and the run stops reading as soon as the limit is reached.

### Data source and licence

- **Publisher:** Employment and Social Development Canada.
- **Dataset:** [Job Postings Advertised on Canada's National Job Bank Website](https://open.canada.ca/data/en/dataset/ea639e28-c0fc-48bf-b5dd-b8899bd43072).
- **Licence:** [Open Government Licence - Canada](https://open.canada.ca/en/open-government-licence-canada).

Contains information licensed under the Open Government Licence - Canada.

The Actor selects, renames and converts fields (dates, numbers, salary units) and shortens postal codes to the first three characters; it does not change other values.

This Actor is independent and is not affiliated with, endorsed by or approved by Employment and Social Development Canada or the Government of Canada.

### FAQ

#### Are these live job listings?

No. The dataset is published once a month and holds the postings advertised during that month. Use it for analysis and benchmarking, not to find a job that is open today.

#### Can I get employer names or contacts?

No. The dataset has no employer names and no contact details, and this Actor does not add them.

#### Why are many fields `null` for some postings?

Postings from partner job boards (`channel` is `partner_job_board`) arrive with fewer details than postings placed directly on Job Bank.

Fields the source does not give are `null`.

#### Which months can I read?

The English monthly files from January 2023 onward. If you ask for a month that is not available, the run stops and lists the months it can read.

#### Found a problem?

Open an issue in the Issues tab and describe the input that went wrong.

# Actor input Schema

## `month` (type: `string`):

The month of postings to read, as YYYY-MM, for example 2026-09. Leave empty for the latest month available. The dataset is monthly: each file holds the postings advertised during that month.

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

Keep postings located in these provinces or territories. Leave empty for all of Canada.

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

Optional. One city name per line, for example Toronto or Montréal. Case and accents do not matter. Postings with no city (some list only a province) never match.

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

Optional. NOC 2021 codes or code prefixes, one per line. A prefix keeps every occupation that starts with it: 2 is all natural and applied sciences occupations, 21 is the professional ones, 2123 is software and web development, 21231 is one occupation (software engineers and designers).

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

Optional. A posting is kept when its job title or original job title contains any of these words, whatever the case or accents.

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

Optional. Keep postings whose top hourly wage (salaryMaxHourly) is at least this many dollars. Postings with no salary, or with a pay unit that cannot be converted to hourly, are left out when this is set.

## `maxPostings` (type: `integer`):

Upper limit of postings returned. The Actor stops reading the file as soon as it is reached. You pay per posting returned.

## Actor input object example

```json
{
  "provinces": [
    "Ontario"
  ],
  "cities": [],
  "nocCodes": [
    "21"
  ],
  "keywords": [],
  "maxPostings": 100
}
```

# Actor output Schema

## `postings` (type: `string`):

No description

## `summary` (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 = {
    "provinces": [
        "Ontario"
    ],
    "nocCodes": [
        "21"
    ],
    "maxPostings": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("iocodo/canada-job-bank-postings").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 = {
    "provinces": ["Ontario"],
    "nocCodes": ["21"],
    "maxPostings": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("iocodo/canada-job-bank-postings").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 '{
  "provinces": [
    "Ontario"
  ],
  "nocCodes": [
    "21"
  ],
  "maxPostings": 100
}' |
apify call iocodo/canada-job-bank-postings --silent --output-dataset

```

## MCP server setup

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

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/kx4sAKi3Y4HdpiewM/builds/nlyyKW41jRJyrtUl4/openapi.json
