Article type: Tutorial
Prerequisites:
curl,jqandawkfor the free route. An Apify API token for the scheduled Actor route. cron (or an Apify schedule) to run it monthly or weekly.Tested on: 31 October 2026
Canada's national Job Bank publishes every posting it carries as open data: one file a month, from January 2023, on open.canada.ca. If you track hiring for one occupation in one province, for a workforce report, a settlement service or a pay comparison, that file has what you need: occupation code, place, vacancies, pay range, hours and the date the job was first posted. It does not include employer names, contact details or apply links.
This post pulls one slice, registered nurses (NOC 2021 code 31301) in Ontario, first with nothing but curl and awk, then as a scheduled job that adds each new month to a CSV.
Disclosure: the hosted Actor in the second half is ours (we built it) and it's paid per row. The free route comes first and is enough for a one-off.
The free route: one file, one awk line
The dataset record lists every monthly file. This gets the newest English one:
URL=$(curl -s "https://open.canada.ca/data/api/action/package_show?id=ea639e28-c0fc-48bf-b5dd-b8899bd43072" \
| jq -r 'first(.result.resources[] | select(.format=="CSV" and (.language|index("en"))) | .url)')
curl -sL -o latest.csv "$URL"
On 31 October 2026 that was the September 2026 file: 46 MB, UTF-16 and tab-separated, with 65 columns and 52,368 postings. Convert it, then filter on NOC code (column 6) and province (column 17):
iconv -f UTF-16 -t UTF-8 latest.csv | tr -d '\r' > latest.tsv
awk -F'\t' 'NR>1 && $6=="31301" && $17=="Ontario" {print $2"\t"$18"\t"$10"\t"$39"\t"$40}' latest.tsv | head -3
registered nurse - telehealth NA 10 Hour 38
registered nurse (R.N.) Lindsay 10 Week 1562.72
registered nurse (R.N.) Sturgeon Falls 1 Hour 41.15
That month had 83 Ontario postings under 31301. Column 8, External Indicator, splits them by source: 21 were posted on Job Bank by employers (0) and the rest were shared from other job boards (1).
Some things you'll hit if you keep doing this by hand:
- The column numbers above are for this file. Columns have been added over time, and some 2024 months are UTF-8 and comma-separated instead, so match on header names, not positions.
- Missing values are the text
NA. - Rows shared from other job boards carry less detail. In September 2026 nearly all of them had a pay figure, but few had hours and none had an education or experience level.
- open.canada.ca asks automated clients to wait 20 seconds between requests.
For one month, that's all you need. To get every month automatically, read on.
The same slice as clean rows
The Actor reads the same official files, filters while it streams and returns one flat JSON row per posting. Here's a real run from 31 October, capped at three rows and limited to postings made on Job Bank itself:
curl -s -X POST \
"https://api.apify.com/v2/acts/yourevenings~canada-job-bank-data/run-sync-get-dataset-items" \
-H "Authorization: Bearer $APIFY_TOKEN" -H 'Content-Type: application/json' \
-d '{"months":["latest"],"nocCodes":["31301"],"provinces":["ON"],"postedVia":"jobBank","maxResults":3}' \
> sample.json
It checked 2,284 rows of the September 2026 file and stopped after the third match. One row, shortened:
{
"rowType": "posting",
"postingId": "2026-09:15871793",
"month": "2026-09",
"jobTitle": "registered nurse (R.N.)",
"noc2021Code": "31301",
"noc2021Name": "Registered nurses and registered psychiatric nurses",
"postedVia": "Job Bank (employer account)",
"firstPostingDate": "2026-09-14",
"vacancies": 1,
"province": "Ontario",
"city": "Sturgeon Falls",
"postalArea": "P2B",
"economicRegion": "Northeast Region",
"industry": "Health care and social assistance",
"employmentType": "Part time",
"employmentTerm": "Permanent employment",
"salaryText": "$41.15 to $58.98 hourly (to be negotiated)",
"salaryPer": "Hour",
"salaryMin": 41.15,
"salaryMax": 58.98,
"hoursPer": "Week",
"hoursMin": 33.75,
"hoursMax": 33.75,
"educationLevel": "College/CEGEP",
"experienceLevel": "3 years to less than 5 years",
"sourceFile": "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",
"licence": "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 Bank open data."
}
The run also returned one free status row, {"rowType":"status","status":"max_results", ...}, saying it stopped at the limit. Status rows sit in the same dataset, so filter on rowType when you build the CSV.
Every row keeps a link to the file it came from (sourceFile) and the licence statement (licence), so the attribution goes wherever the data goes. Postal codes are cut to their first three characters.
Every new month into one CSV
Turn on monitor mode and the Actor remembers which months it has already returned. Run it weekly. Most weeks it finds nothing new and returns a free already_reported row. When ESDC publishes the next month's file, usually a few weeks after the month ends, it returns that month's rows once.
#!/bin/sh
# on-nurses.sh: run weekly (cron, or an Apify schedule with the same input)
curl -s -X POST \
"https://api.apify.com/v2/acts/yourevenings~canada-job-bank-data/run-sync-get-dataset-items" \
-H "Authorization: Bearer $APIFY_TOKEN" -H 'Content-Type: application/json' \
-d '{"months":["latest"],"nocCodes":["31301"],"provinces":["ON"],"maxResults":1000,
"monitorMode":true,"stateStoreName":"on-nurses-31301"}' \
> new.json
[ -f on-nurses.csv ] || echo 'month,firstPostingDate,jobTitle,city,economicRegion,vacancies,employmentType,salaryMin,salaryMax,salaryPer,postedVia' > on-nurses.csv
jq -r '.[] | select(.rowType=="posting")
| [.month,.firstPostingDate,.jobTitle,.city,.economicRegion,.vacancies,.employmentType,.salaryMin,.salaryMax,.salaryPer,.postedVia] | @csv' \
new.json >> on-nurses.csv
Some notes on the settings:
-
First run. It returns the newest month straight away. Set
"emitBaselineOnFirstRun": falseif you only want months published from now on. -
maxResults. A month thatmaxResultscuts short still counts as read, so set it well above your slice. 1,000 is plenty for 83 postings a month. -
stateStoreName. Use a different one for each saved search. -
A different slice. Swap
nocCodes(a code or its first digits, such as"72"for trades) orprovinces, or addcities,keywords,employmentTypes, orsalaryPerwithminSalary. - Timeouts. Run-sync waits up to 300 seconds. If a call times out, start a normal run and read its dataset afterwards.
The CSV opens as-is in a spreadsheet. If you'd rather pull it straight into Google Sheets or Excel, the run's dataset can also be downloaded as CSV or Excel from the Apify API.
Honest limits
- Monthly, not live. The newest file covers last month, so for today's postings use jobbank.gc.ca.
- No employer names, contacts or apply links. The open data leaves them out. This is labour-market data, not a way to apply.
- English files only. French versions exist with French column names, but the Actor doesn't read them yet.
-
Externally shared postings have fewer fields. Set
"postedVia":"jobBank"if you need hours, education and experience on every row. - Each extra month adds about 20 seconds, because the Actor honours open.canada.ca's request spacing. Up to 12 months per run.
- Cost: $0.002 per posting returned and $0.00005 per run start (one start event per GB of memory; the default 1 GB run pays one). Monitor mode adds $0.01 on a run that returns a new month. (These are base prices; higher Apify plans pay less.) Rows your filters drop, status rows and weeks with nothing new carry no posting or monitor charge. For the slice above, a month like September 2026 would be 83 × $0.002 + $0.01 + $0.00005 = $0.17605. If you only want the 21 postings made on Job Bank itself, it's $0.05205.
Contains information licensed under the Open Government Licence – Canada. Source: Employment and Social Development Canada, Job Bank open data. This Actor and this post are not operated, sponsored, approved or endorsed by Employment and Social Development Canada, Job Bank or the Government of Canada.
The Actor, with the inputs above ready to run: Canada Job Bank Data: Job Postings, NOC & Salaries on Apify
Your Evenings
Top comments (0)