# Canada Job Bank Data API: Job Postings, NOC Codes & Salaries (`yourevenings/canada-job-bank-data`) Actor

Canada Job Bank data from the official monthly open data (OGL-Canada): Canada job postings with NOC 2021 codes, salary ranges, province, city and vacancies. Filter by month, job title, NOC code, province, city, employment type and wage. Canadian labour-market data as CSV, JSON or Excel.

- **URL**: https://apify.com/yourevenings/canada-job-bank-data.md
- **Developed by:** [Your Evenings](https://apify.com/yourevenings) (community)
- **Categories:** Jobs, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.60 / 1,000 job posting returneds

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/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 Data API: Job Postings, NOC Codes & Salaries

![Canada Job Bank Data: job postings, NOC codes and salaries](https://apify-image-uploads-prod.s3.us-east-1.amazonaws.com/3aJAAcuntBUjxtHMh-actor-BFDDNAI2Msq9b725N-pnYfNc2Xym-canada-job-bank-data-512.png)

**Canada Job Bank data** as clean, flat rows: the job postings advertised on Canada's national Job Bank, read from the official monthly **open-data files** that Employment and Social Development Canada (ESDC) publishes on open.canada.ca. Filter by month, job title, **NOC 2021** occupation code, province or territory, city, employment type, who posted it and pay. Every row has the occupation codes, workplace location (city, economic region and postal area), vacancies and posting date. Most rows also have a posted salary range and period (52,358 of 52,368 in September 2026). Postings made on Job Bank by employers add hours, schedule, education, experience, industry and work language. Turn on **monitor mode** and schedule it to get each new month once.

Use it for **Canadian labour-market data**: wages by occupation and province, which occupations post the most vacancies, or a monthly feed of **Canada jobs data** for a dashboard. Export as CSV, JSON or Excel.

> Contains information licensed under the [Open Government Licence – Canada](https://open.canada.ca/en/open-government-licence-canada). This Actor is not operated, sponsored, approved or endorsed by Employment and Social Development Canada, Job Bank or the Government of Canada.

### What this data is (and is not)

- **Monthly, not live.** ESDC publishes one file per month, a few weeks after the month ends. The files start in January 2023. For the newest postings, look on jobbank.gc.ca itself.
- **Anonymised.** The open data has **no employer names, no contact details and no links to apply**. Rows describe the job (occupation, place, pay, hours, conditions), not who is hiring. Workplace postal codes are cut to their first three characters (the forward sortation area) in this Actor.
- **Two kinds of posting.** More than half the postings in recent files (57% in September 2026) are shared to Job Bank from external job boards (`postedVia: "external job board"`). In the September 2026 file, 30,098 of the 30,108 external rows had a salary range, but only 3,512 had hours (with no hours period), and none had education, experience, industry, schedule or telework. Postings made on Job Bank by employers had all of these.
- **Pay and hours as posted.** Rows carry the posted pay (`salaryText`, `salaryMin`, `salaryMax`, `salaryPer`) and hours (`hoursMin`, `hoursMax`, `hoursPer`) wherever the posting gives them. The `benefits` field is reserved for the source's 16 benefit and pay-condition flags (dental, pension, RRSP, bonus and so on) and fills in once ESDC publishes them; the files for September 2025, January 2026, August 2026 and September 2026 mark them all `No` (Job Bank postings) or `NA` (external postings).
- **English files.** The Actor reads the English version of each file. French versions exist, with French column names and values; they are not read yet.

### Why use this Canada Job Bank data?

- **Official source, open licence.** Rows come from the published open-data files (dataset "Job Postings Advertised on Canada's National Job Bank Website"), listed through the open.canada.ca data API. No page scraping, no login, no key.
- **Every month in one shape.** The monthly files change format (UTF-16 tab-separated in most months, UTF-8 comma-separated in some 2024 months, columns added over time). The Actor reads them all by column name and gives the same row shape.
- **Filters run before you pay.** Postings removed by your filters are never charged. A run stops reading the file as soon as it has `maxResults` rows.
- **Free status rows** say when nothing matched, when a month was already read in monitor mode, when the limit stopped the read, when the input has a mistake, or when the source failed.

### How do I get Canada Job Bank postings? Quick start

1. Leave **Months** as `latest`, or enter months like `2026-09`, or `latest-3` for the newest three.
2. Add filters: **Job title contains** (for example `nurse`), **NOC 2021 codes**, **Provinces**, **Cities**, or a **Minimum salary** with its **Salary period**.
3. Run. Results appear in the **Postings** view; download as JSON, CSV or Excel, or read them by API.

Or just press **Start**: the form comes prefilled to return up to 100 postings with "nurse" in the title from the newest month.

A narrower example, for postings in Ontario paying at least $25 an hour:

```json
{
  "months": ["latest"],
  "provinces": ["ON"],
  "salaryPer": "Hour",
  "minSalary": 25,
  "maxResults": 100
}
```

### Data source

| What | Where it comes from |
| --- | --- |
| List of monthly files | open.canada.ca data API, `package_show?id=ea639e28-c0fc-48bf-b5dd-b8899bd43072` |
| Postings | The English monthly CSV file for each month, downloaded from the link in that record |

Publisher: Employment and Social Development Canada. Licence: Open Government Licence – Canada, which allows copying, adapting and commercial use with attribution. Every row carries the attribution statement and the link to the official file it came from. open.canada.ca asks automated clients to wait 20 seconds between requests; the Actor does, so each extra month in a run adds about 20 seconds.

### Who it's for

- **Labour-market analysts, economists and workforce boards** looking at vacancies and posted wages by occupation and region.
- **Immigration and settlement advisers and researchers** who track which occupations and provinces post the most jobs.
- **HR and compensation teams** comparing posted pay ranges for an occupation across provinces.
- **Job boards and data teams** that want a clean monthly Canadian postings dataset next to other sources.

### How do I filter job postings by NOC code or province?

Put NOC 2021 codes, or their first digits, in `nocCodes` (`31301` is registered nurses; `72` keeps every code starting with 72). Put provinces or territories in `provinces`, as names or two-letter codes (`ON`, `QC`, `BC` …). Filters can be combined, and postings removed by your filters are never charged.

```json
{ "months": ["latest-3"], "nocCodes": ["31301"], "provinces": ["BC"], "maxResults": 500 }
```

That returns registered nurses (NOC 31301) in British Columbia from the newest three months. More examples follow below.

### Input examples: Canada job postings by NOC code, province and salary

#### Registered nurses (NOC 31301) in British Columbia, last three months

```json
{ "months": ["latest-3"], "nocCodes": ["31301"], "provinces": ["BC"], "maxResults": 500 }
```

#### Truck drivers in Alberta and Saskatchewan, full time

```json
{ "months": ["latest"], "keywords": ["truck driver"], "provinces": ["AB", "SK"], "employmentTypes": ["Full time"], "maxResults": 200 }
```

#### Software engineers and developers in Toronto paying $90,000 a year or more

```json
{ "months": ["latest"], "nocCodes": ["21231", "21232"], "cities": ["Toronto"], "salaryPer": "Year", "minSalary": 90000, "maxResults": 100 }
```

### How do I get salaries by occupation?

Put NOC 2021 codes, or their first digits, in `nocCodes`, add `provinces` if you like, and read `salaryMin`, `salaryMax` and `salaryPer` on each row. To keep only postings above a pay level, set `salaryPer` (Hour, Day, Week, Bi-weekly, Month or Year) and `minSalary`, for example 25 per Hour or 60000 per Year. Most rows have a posted salary range (52,358 of 52,368 in September 2026).

### What Canada Job Bank data do I get?

One row per posting (`rowType: "posting"`): `postingId`, `month`, `jobTitle`, `originalJobTitle`, `noc2021Code`, `noc2021Name`, `noc2016Code`, `noc2016Name`, `postedVia`, `firstPostingDate`, `vacancies`, `province`, `city`, `postalArea`, `economicRegion`, `industry`, `employmentType`, `employmentTerm`, `termStartDate`, `termEndDate`, `schedule`, `telework`, `salaryText`, `salaryPer`, `salaryMin`, `salaryMax`, `benefits`, `hoursPer`, `hoursMin`, `hoursMax`, `educationLevel`, `experienceLevel`, `workLanguage`, `governmentEmployer`, `placementAgency`, `sourceFile`, `licence`, `retrievedAt`. Missing values (`NA` in the files) are `null`.

Free status rows (`rowType: "status"`): `no_matches`, `already_reported`, `max_results`, `time_limit`, `invalid_input`, `source_error`, `budget_reached`. If the input has a mistake (for example an unknown province or a month that isn't published), the run still finishes successfully with one free `invalid_input` row that says what to fix, and no rows are charged. A `SUMMARY` record in the key-value store lists the months read, rows checked and returned, filters and request counts.

### What does the output look like?

Five rows from our test run on 31 October 2026 (postings with "nurse" in the job title from the September 2026 file, stopped at a limit of 5 rows), shortened:

| Month | Job title | NOC 2021 | Province | City | Posted via | Salary | Hours | Vacancies |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 2026-09 | registered nurse - telehealth | 31301 | Ontario | – | Job Bank (employer account) | $38–40.50 per hour | 37.5–40 per week | 10 |
| 2026-09 | nurse practitioner | 31302 | Ontario | Fort Erie | external job board | $42–75 per hour | – | 1 |
| 2026-09 | registered nurse (R.N.) | 31301 | Ontario | Lindsay | Job Bank (employer account) | $1,562.72–2,240.04 per week | 40 per week | 10 |
| 2026-09 | RN (registered nurse) | 31301 | Ontario | Perth | external job board | $55–60 per hour | – | 1 |
| 2026-09 | nurse | 31301 | Ontario | Shelburne | external job board | $65,000–75,000 per year | – | 1 |

The run ended with a free status row: "Stopped at your limit of 5 rows while reading 2026-09; the rest of that month was not read."

### Input: how do I filter Canada job postings?

Every field is optional. With no filters, a run returns up to 100 postings from the newest month.

#### Months (`months`)

`YYYY-MM`, `latest` or `latest-N`. Up to 12 months per run. Each month takes about 20 to 25 seconds to read, because the source asks for a 20-second gap between requests. In testing, 12 months finished in about 4 minutes, inside the default 5-minute timeout. If the run nears its time limit it stops cleanly, keeps the rows already returned and adds a free `time_limit` row naming the months not read. Default `latest`. The files start in January 2023.

#### Job title contains (`keywords`)

Job title contains any of these (accents and case ignored).

#### NOC 2021 codes (`nocCodes`)

NOC 2021 codes or their first digits, e.g. `31301` or `72`.

#### Provinces and territories (`provinces`)

Names or two-letter codes (`ON`, `QC`, `BC` …).

#### Cities (`cities`)

City names as written in the data (accents and case ignored).

#### Employment types (`employmentTypes`)

`Full time`, `Part time`, `Part time leading to full time`.

#### Posted via (`postedVia`)

`all`, `jobBank` (posted on Job Bank by employers) or `external` (shared from other job boards).

#### Salary period (`salaryPer`)

Hour, Day, Week, Bi-weekly, Month or Year. Keep postings that state pay for that period. Needed when you set a minimum salary.

#### Minimum salary (CAD) (`minSalary`)

With `salaryPer`: keep postings whose minimum pay for that period is at least this, e.g. 25 per Hour or 60000 per Year.

#### Telework only (`teleworkOnly`)

Keep postings flagged for telework (recent files flag very few).

#### Max results (`maxResults`)

Stop after this many postings. Default 100. A full month has roughly 50,000 to 110,000 postings.

#### Monitor mode (`monitorMode`)

Remember months already returned and skip them on later runs. See *How do I get each new month of Job Bank data?* below.

#### Return current months on the first monitor run (`emitBaselineOnFirstRun`)

Default on. See *How do I get each new month of Job Bank data?* below.

#### Monitor state store (`stateStoreName`)

Named key-value store that remembers months already returned. Use a different name for each saved search. See *How do I get each new month of Job Bank data?* below.

### How do I get each new month of Job Bank data?

Turn on **Monitor mode** and schedule the Actor (weekly is plenty). Keep **Months** as `latest`. The first run returns the newest month (or, with *Return current months on the first monitor run* off, just remembers it). Later runs skip months already read and return rows only when a new month is published. If `maxResults` stops a month early, that month still counts as read, so set `maxResults` high enough for your filters. Use a different **Monitor state store** name for each saved search. A run with nothing new pays only Apify's start event.

### How much does it cost?

Pay per event. You pay only for what is delivered. Prices on the Actor's Pricing tab are authoritative.

| Event | Price |
| --- | --- |
| Job posting returned | $0.002 per posting |
| Monitor run with a new month | $0.01 per run, only on monitor runs that return at least one posting |
| Actor start (once per run, per GB of memory; the default 1 GB run counts as one) | $0.00005 |

Status rows, the SUMMARY record, postings removed by your filters and months already read in monitor mode are free. If a run reaches your maximum charge it stops cleanly with a free `budget_reached` row.

### Use Canada Job Bank data from code or an AI agent

Call it with the Apify API, the Apify client for JavaScript or Python, or through Apify's MCP server for AI agents. The input is plain JSON and every field is described in the input schema.

The examples below return at most 10 postings (plus any free status rows) and set a maximum cost of $0.05 for the run, so trying them costs a few cents at most. Read your API token from an environment variable (`APIFY_TOKEN`) rather than pasting it into code.

#### JavaScript (Node.js)

```js
// npm install apify-client
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('yourevenings/canada-job-bank-data').call(
  {
    "months": [
      "latest"
    ],
    "keywords": [
      "nurse"
    ],
    "maxResults": 10
  },
  { maxTotalChargeUsd: 0.05 },
);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

#### Python

```python
## pip install "apify-client>=3"
import os
from decimal import Decimal
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("yourevenings/canada-job-bank-data").call(
    run_input={
        "months": [
            "latest"
        ],
        "keywords": [
            "nurse"
        ],
        "maxResults": 10
    },
    max_total_charge_usd=Decimal("0.05"),
)
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)
```

#### HTTP API (cURL)

```bash
curl -X POST "https://api.apify.com/v2/acts/yourevenings~canada-job-bank-data/run-sync-get-dataset-items?maxTotalChargeUsd=0.05" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"months": ["latest"], "keywords": ["nurse"], "maxResults": 10}'
```

#### AI agents (MCP)

Add Apify's MCP server with just this Actor as a tool. On first connection your browser opens so you can sign in to Apify and approve access.

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=yourevenings/canada-job-bank-data"
    }
  }
}
```

### FAQ

#### Why are there no company names or apply links?

The official open data does not include them. It's built for job-market data: occupations, locations, pay and posting dates.

#### How fresh is it?

Monthly. A new file usually appears a few weeks after the month ends.

#### Why do some rows have no hours, education or experience?

Postings shared from external job boards rarely carry them: in September 2026, 3,512 of 30,108 external rows had hours and none had education or experience, although 30,098 had a salary range. Set `postedVia` to `jobBank` for rows with full details.

#### Why is `benefits` always empty?

The source files currently mark every benefit column `No` or `NA`, so there is nothing to report. The field will fill in if ESDC starts publishing these flags.

#### Can I use the data commercially?

The Open Government Licence – Canada allows it, with attribution. Keep the attribution statement from the `licence` field with the data.

#### Do I need an API key or login for Canada Job Bank data?

No. The Actor reads the official monthly open data files listed through the open.canada.ca data API. No page scraping, no login, no key.

#### How do I export Canada job postings to CSV or Excel?

Each run saves its rows to an Apify dataset. Download it as JSON, CSV, Excel, XML or HTML from the run's Output or Storage tab, or fetch it through the Apify API. Integrations can also send each run's results to Google Sheets, Make, Zapier or webhooks.

### Feedback

Found a problem or want a field added? Open an issue on the Actor's Issues tab.

# Changelog

This Actor's version history is a separate document: https://apify.com/yourevenings/canada-job-bank-data/changelog.md

# Actor input Schema

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

Data months to read, as YYYY-MM (e.g. 2026-09), "latest" for the newest published month, or "latest-3" for the newest three. Files are published monthly, starting January 2023. Up to 12 months per run.

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

Keep postings whose job title contains any of these words, e.g. "nurse" or "welder". Accents and case are ignored.

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

National Occupational Classification 2021 codes or their first digits, e.g. "31301" (registered nurses) or "72" (trades). A posting matches if its NOC 2021 code starts with any of these.

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

Names or two-letter codes, e.g. "Ontario", "BC" or "QC".

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

City names as written in the data, e.g. "Toronto" or "Montréal" (accents and case are ignored).

## `employmentTypes` (type: `array`):

Full time, Part time or Part time leading to full time. "Part time" also matches "Part time leading to full time".

## `postedVia` (type: `string`):

External-board postings carry fewer details: in the September 2026 file almost all stated a salary, but most had no hours and none had education or experience.

## `salaryPer` (type: `string`):

Keep only postings that state pay for this period. Needed when you set a minimum salary.

## `minSalary` (type: `number`):

Keep only postings whose minimum pay for the salary period is at least this, e.g. 25 per Hour or 60000 per Year.

## `teleworkOnly` (type: `boolean`):

Keep only postings flagged for telework. Recent files flag very few postings, so this often returns nothing.

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

Stop after this many postings. A full month has around 50,000 to 110,000 postings.

## `monitorMode` (type: `boolean`):

Remember months already returned and skip them on later runs. Schedule weekly to get each new month once, about a month after it ends. A small alert fee is charged only on runs that return rows from a new month.

## `emitBaselineOnFirstRun` (type: `boolean`):

Turn off to remember the chosen months silently on the first monitor run (nothing charged per posting) and get only later months.

## `stateStoreName` (type: `string`):

Named key-value store that remembers months already returned. Use a different name for each saved search.

## Actor input object example

```json
{
  "months": [
    "latest"
  ],
  "keywords": [
    "nurse"
  ],
  "postedVia": "all",
  "teleworkOnly": false,
  "maxResults": 100,
  "monitorMode": false,
  "emitBaselineOnFirstRun": true,
  "stateStoreName": "canada-job-bank-data-state"
}
```

# Actor output Schema

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

Postings view of the dataset.

## `details` (type: `string`):

All fields.

## `status` (type: `string`):

Status rows (free) view of the dataset.

## `summary` (type: `string`):

SUMMARY record: months read, rows checked and returned, filters, request counts and timings.

# 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": [
        "nurse"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("yourevenings/canada-job-bank-data").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": ["nurse"],
}

# Run the Actor and wait for it to finish
run = client.actor("yourevenings/canada-job-bank-data").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": [
    "nurse"
  ]
}' |
apify call yourevenings/canada-job-bank-data --silent --output-dataset

```

## MCP server setup

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

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/BFDDNAI2Msq9b725N/builds/k40dUIbodv1n57YBT/openapi.json
