Jora Jobs Scraper - Jobs, Salaries, Companies & Locations
Pricing
from $1.00 / 1,000 dataset items
Jora Jobs Scraper - Jobs, Salaries, Companies & Locations
Scrape Jora.com job listings across supported countries by keyword, filters, or URL. Extract full job descriptions, salaries, work types, companies, locations, and structured data ready for connector export.
Pricing
from $1.00 / 1,000 dataset items
Rating
0.0
(0)
Developer
Abot API
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
0
Monthly active users
2 days ago
Last modified
Categories
Share
Jora Jobs Scraper: Search, Job Details, and Recurring Change Monitoring
Jora Jobs Scraper turns Jora into structured job data. Search by keyword and location, or start from a list of Jora search or job URLs, across Australia, New Zealand, Singapore, Hong Kong, Malaysia, Indonesia, the United States, the United Kingdom, and Canada. Detail mode adds the full job description, apply link, salary, work type, and work arrangement for every listing. Run it once for a snapshot, or turn on incremental mode to get only new, updated, and reappeared jobs on a recurring schedule, then export to JSON, CSV, or Excel, or pull results straight into your app through the API.
Why This Scraper?
- Search by keywords and location across nine Jora country sites, or start from a saved list of Jora search or job URLs.
- Server side filters for listed date, job type, quick apply, sort order, distance, and minimum salary, applied directly on Jora's own search pages (search mode; in URL mode the filters already in your pasted link apply).
- Detail mode adds the full job description, apply link, salary, work type, and work arrangement for every listing.
- Max items is the main output cap; Max pages is unlimited by default, so a search walks every result page until Jora's own end of results.
- Resume a large pull across separate runs, or let an interrupted run pick back up automatically without re-saving jobs it already collected.
- Incremental mode turns a recurring search into a change feed: only new, updated, and reappeared jobs come back on later scheduled runs.
- Optional MCP connector export sends a condensed job summary into the apps you already use, while the full record always stays in the Apify dataset.
Use Cases
- Job board and aggregator builders: mirror Jora search results for a set of keywords and locations into your own listings feed.
- Recruiter and staffing sourcing: pull matching roles across countries into one dataset to build outreach and candidate-facing lists.
- Compensation and labor market research: collect salary ranges, work types, and posting volume across roles and regions.
- Hiring signal tracking: schedule a search and watch NEW and REAPPEARED postings to spot a company's hiring activity over time.
- Job alert tools and bots: feed newly posted, quick apply, or high salary jobs into your own notification, Slack, or email tool.
Data You Get
Sample shape, values are illustrative placeholders, not from a live listing.
| Field | Example |
|---|---|
id / jobId | 00000000000000000000000000000001 (same value, both fields) |
title | Sample Software Engineer |
company | Sample Company |
location | Sydney NSW |
countryCode | au |
url | https://au.jora.com/job/Sample-Job-00000000000000000000000000000001 |
sourceUrl | https://au.jora.com/j?q=software+engineer&l=Sydney%2C+NSW |
salary | $95,000 - $115,000 a year |
salaryMin / salaryMax | 95000 / 115000 |
workType | Full time |
workArrangement | Hybrid |
postedAtText | Posted 3d ago |
quickApply | true |
isSponsored | false |
rank / page | 1 / 1 |
abstract / snippetBullets | "Short summary from the search card." / ["Build sample services", "Work with a sample team"] |
descriptionText / descriptionHtml | Full description in plain text and HTML (detail mode only) |
applyUrl | https://au.jora.com/job/rd/00000000000000000000000000000001 (detail mode only) |
sourceName | Sample Source (detail mode only) |
searchKeywords / searchLocation / searchTotalCount | "software engineer" / "Sydney, NSW" / 250 |
detailFetched | true |
scrapedAt | 2026-01-01T00:00:00.000Z |
changeType / changedFields / firstSeenAt / lastSeenAt | "NEW" / ["salary"] / 2026-01-01T00:00:00.000Z / 2026-01-02T00:00:00.000Z (incremental mode only) |
How to Use
- Pick a mode:
search(keywords, location, and filters) orurl(paste Jora search or job links). - Fill in one or more searches, or paste your URLs, and switch on Fetch full job details if you need descriptions and apply links.
- Set Max items (and Max pages if you want a bounded crawl) to control run size and cost, then click Start.
- Download the dataset as JSON, CSV, or Excel, or read it through the API.
Search by keyword and location:
{"mode": "search","searches": [{"keywords": "software engineer","location": "Sydney, NSW","country": "au"}],"maxItems": 20}
Search with filters:
{"mode": "search","searches": [{"keywords": "developer","location": "Melbourne VIC","country": "au"}],"postedWithin": "7d","workType": "contract","quickApply": true,"sort": "date","salaryMin": 90000,"maxItems": 25,"fetchDetails": true}
Run several searches in one run:
{"mode": "search","searches": [{ "keywords": "data analyst", "location": "Auckland", "country": "nz" },{ "keywords": "data analyst", "location": "Singapore", "country": "sg" }],"maxItems": 40}
Start from URLs:
{"mode": "url","urls": ["https://au.jora.com/j?q=software+engineer&l=Sydney%2C+NSW","https://au.jora.com/job/Sample-Job-00000000000000000000000000000001"],"maxItems": 20}
Run it from your code
Python:
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("abotapi/jora-jobs-scraper").call(run_input={"mode": "search","searches": [{"keywords": "software engineer", "location": "Sydney, NSW", "country": "au"}],"maxItems": 20,})for job in client.dataset(run["defaultDatasetId"]).iterate_items():print(job["title"], job["company"], job["salary"])
JavaScript:
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const run = await client.actor('abotapi/jora-jobs-scraper').call({mode: 'search',searches: [{ keywords: 'software engineer', location: 'Sydney, NSW', country: 'au' }],maxItems: 20,});const { items } = await client.dataset(run.defaultDatasetId).listItems();
Or connect it to Make, Zapier, n8n, Google Sheets, or webhooks from the Integrations tab.
Caps across one or more searches
Max items caps the whole run's saved rows. Max pages caps how many result pages each search walks; leave it empty to walk every page for that search until Jora's own end of results, with Max items still the main stop. When a run has more than one search, Max items is split evenly across them up front. A search that matches fewer jobs than its even share does not hand the leftover budget to another search in the same run, so a run with several searches can return fewer rows than Max items even though another search in it still has more matching jobs. For an exhaustive multi search run, set Max items generously (or 0 for unlimited) or run each search on its own.
Within a single result page, every job the page returns is marked as already handled for this run before the jobs beyond a search's share are cut from what gets saved. With one search this changes nothing (those jobs are past Max items anyway). With several searches, a job cut from an earlier search's share is skipped by every later search in the same run too, even when a later search also matches it and still has budget, so that job is missing from this run's output. It is not reported as expired (it was seen on the site), and it is picked up again on a later run that reaches it inside the cap.
Resume, recurring updates, and incremental mode
Resuming and incremental monitoring are two different things. Pick the one that matches what you are doing:
| Need | Use |
|---|---|
| One long pull stopped and should continue | resumeFromRunId / the automatic checkpoint |
| Run the same search every day and receive only what changed | incrementalMode |
| Keep separate daily campaigns for similar searches | distinct stateKey values |
| Run a normal full snapshot | leave both off |
Resume from a previous run (resumeFromRunId) continues one interrupted run: paste the ID of a previous run (or its dataset), and this run skips every jobId already saved there, appending only new jobs. A checkpoint is also saved automatically as the run progresses. If a platform migration interrupts the run, or you click Resurrect on a failed run, it picks back up in the same run without re-saving (or re-charging for) jobs it already collected. No input is needed for this; it is automatic. Two limits apply to both kinds of resume, see the edge cases below: the result page that was being processed at the moment of the interruption is not fully recovered, and a resumed walk can stop early if Jora's listing shifted in between.
Incremental mode (incrementalMode) is for a schedule. The actor remembers the previous run of the same search by itself, so there is no run ID to paste. The first run returns every matching job as NEW. Later runs return only NEW, UPDATED, and REAPPEARED jobs; everything unchanged since last time is suppressed and not billed unless emitUnchanged is on. UPDATED rows carry changedFields naming exactly which fields moved, and every row carries firstSeenAt and lastSeenAt. State is isolated automatically: each distinct search scope (mode, searches/urls, and the filters) gets its own state, and so does each fetchDetails setting. Set stateKey to give a campaign a stable name, or to deliberately point two differently configured runs at the same state.
Six fields are left out of change detection on purpose, because they were measured to move on their own between runs while the job itself is untouched: url and applyUrl (a per-request tracking token), postedAtText (a relative age that drifts by itself), rank and searchTotalCount (belong to the search, not the job), and scrapedAt (a per-run timestamp). All six are still returned on every row exactly as before; only change detection ignores them. page and sourceUrl (which carries the result page number) are not ignored, see the first edge case below.
A few edge cases are worth knowing about before you rely on incremental mode for billing-sensitive monitoring:
- A job that only moved to another result page is reported as
UPDATED. When new postings push a job from page 1 to page 2 (or any page boundary), it comes back asUPDATEDwithchangedFieldsof["page", "sourceUrl"]and is billed, even though the job itself did not change. On a busy search this can be several rows per page each run. Changingsorton an existing campaign does the same to every job (changedFields: ["sourceUrl"]). Filter onchangedFieldsif you only care about content changes. - Emit expired jobs is not limited by Max items. If the tracked search has genuinely dropped a large number of postings since the last run, every one of them comes back (and is billed) as
EXPIREDin that one run, with no cap. - Bootstrapping a monitoring campaign from Resume from a previous run (turning on both
resumeFromRunIdandincrementalModetogether to seed a brand-new state from an existing dataset) makes the very next scheduled run report every one of those resumed jobs asUPDATED, withchangedFieldslisting every field, even when nothing about the job changed. This is a one-time artifact of the bootstrap; runs after that classify normally. - A run interrupted mid-walk by a platform migration, an abort, or a manual Resurrect does not re-save the jobs it had already saved when that same run resumes, but three side effects remain:
- Jobs on the result page that was being processed at the moment of the interruption can be lost from that run: the ones not yet saved are skipped after the resume, and the ones saved after the last checkpoint are not counted, so the run can end a page's worth of rows over Max items.
- If Jora's listing shifted before the resume (for example, one new posting at the top), the resumed walk can mistake an already collected page for the end of results and stop early. The remaining pages are not collected, and with Incremental mode and Emit expired jobs on, the tracked jobs on them are reported (and billed) as
EXPIREDeven though they are still live. - With Incremental mode on, the state saved for the next scheduled run does not include the jobs saved before the interruption, so they come back once more as
NEW,UPDATED, orREAPPEARED(and are billed again) on the following scheduled run.
- Resume from a previous run has the same early stop. If new postings appeared since the run you resume from, the first page can hold a fresh job followed by a page of jobs you already have, and the walk ends there, returning only a few new jobs. Resume soon after the original run, or run a normal snapshot instead when the search moves quickly.
- Two searches in the same run that return overlapping postings can end one search's walk earlier than its real last page: a page made entirely of jobs already claimed by an earlier search looks like the end of results. The later pages of that search are not collected, and with Emit expired jobs on, tracked jobs that only appear on those pages are reported (and billed) as
EXPIREDalthough they are still live. If you rely on Emit expired jobs, run overlapping searches as separate runs, each with its own state, rather than combining them in one multi-search input. - A manual State key is not tied to the search. When you set
stateKey, the actor does not check whether the search, filters, orfetchDetailschanged. Changing the search under the same key reports every job of the old search asEXPIRED(with Emit expired jobs on) and every job of the new one asNEW; switchingfetchDetailson reports every job asUPDATED. Use a new State key when you change what the campaign tracks.
Send results into your apps (MCP connectors)
Optionally pipe the scraped jobs into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step after the scrape: the Apify dataset is never changed.
What gets written to the connector: a condensed, human-readable summary for each selected job: title, company, location, salary, work type, posted text, and links. The complete record always stays in the Apify dataset.
- Authorize a connector once under Apify > Settings > Integrations (Notion, Linear, Airtable, or Apify).
- Select it in the "Pipe results into your apps" input field. (If the picker is empty, you haven't authorized a connector yet.)
- For Notion, also set
notionParentPageUrlto the page where items should be created.
The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.
Input Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
mode | string | search | Choose search for keyword/filter searches or url for existing Jora URLs. |
searches | array | software engineer in Sydney | Search mode inputs. Each item can include keywords, location, and country. |
postedWithin | string | anytime | Date filter: anytime, 24h, 7d, 14d, or 30d. |
workType | string | any | Job type filter such as full_time, part_time, contract, permanent, or internship. |
quickApply | boolean | false | Limits results to Jora quick apply jobs. |
sort | string | relevance | Use relevance or date. |
distanceKm | integer | empty | Optional location radius. |
salaryMin | integer | empty | Optional minimum salary filter. |
urls | array | sample | URL mode inputs. Accepts search result URLs and job detail URLs. |
fetchDetails | boolean | true | Opens job pages for full details. |
maxItems | integer | 20 | Maximum matching rows to save. 0 means unlimited. Results can be fewer when the source has fewer matching jobs. |
maxPages | integer | empty | Maximum search pages per source. Leave empty to walk every result page for that search until Jora has no next page; maxItems remains the main cap. |
resumeFromRunId | string | empty | A previous run ID or dataset ID to continue a full catalogue walk across separate runs. Jobs already collected there (matched by jobId) are skipped, so this run only saves the new ones. |
incrementalMode | boolean | false | Daily/recurring monitoring of this same search. The first run returns everything as NEW; later runs return only NEW, UPDATED, and REAPPEARED rows by default. See "Resume, recurring updates, and incremental mode" above. |
stateKey | string | empty | Optional name for a monitoring campaign, so its incremental state stays stable or is deliberately shared. Derived automatically from your search scope and fetchDetails setting when left empty. |
emitUnchanged | boolean | false | Incremental mode only. Also return jobs unchanged since the last run, marked UNCHANGED. Adds, and bills for, extra rows you already have. |
emitExpired | boolean | false | Incremental mode only. Also return jobs from a previous run that are no longer found, marked EXPIRED, once a run has covered the whole tracked search (not capped by maxItems/maxPages, not a resume). Adds, and bills for, extra rows, with no cap of its own. |
proxy | object | Residential AU | Connection settings. Residential AU is selected by default. |
mcpConnectors | array | empty | Optional MCP connector ids for app export. |
notionParentPageUrl | string | empty | Required only for Notion page export. |
maxNotifyListings | integer | 50 | Maximum jobs sent to each connector. Does not affect the dataset. |
Output Example
Sample shape, values are illustrative placeholders, not from a live listing.
{"id": "00000000000000000000000000000001","jobId": "00000000000000000000000000000001","title": "Sample Software Engineer","company": "Sample Company","location": "Sydney NSW","countryCode": "au","url": "https://au.jora.com/job/Sample-Job-00000000000000000000000000000001","sourceUrl": "https://au.jora.com/j?q=software+engineer&l=Sydney%2C+NSW","salary": "$95,000 - $115,000 a year","salaryMin": 95000,"salaryMax": 115000,"workType": "Full time","workArrangement": "Hybrid","postedAtText": "Posted 3d ago","quickApply": true,"isSponsored": false,"rank": 1,"page": 1,"abstract": "Short summary from the search card.","snippetBullets": ["Build sample services", "Work with a sample team"],"descriptionHtml": "<div>Full job description appears here.</div>","descriptionText": "Full job description appears here when fetchDetails=true.","applyUrl": "https://au.jora.com/job/rd/00000000000000000000000000000001","sourceName": "Sample Source","searchKeywords": "software engineer","searchLocation": "Sydney, NSW","searchTotalCount": 250,"detailFetched": true,"scrapedAt": "2026-01-01T00:00:00.000Z"}
Plan Requirement
Residential AU is selected by default because Jora can return empty responses from cloud datacenter connections. For searches outside Australia, choose the closest Residential country supported by your Apify plan. Detail mode uses the detail-enrichment charge event in addition to the built-in dataset item event.
FAQ
How much does it cost?
You pay per job saved to the dataset, plus the detail-enrichment event for each job scraped with full details on. The Pricing tab shows the current rates. Use Max items to cap the cost of a run; note that emitExpired rows are not covered by that cap (see "Resume, recurring updates, and incremental mode" above).
Is it legal to scrape Jora?
This actor collects only publicly available job listing data. You are responsible for how you use it: follow Jora's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution of the results.
Can I get only new or changed jobs on a schedule?
Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Each run then returns only new, updated, and reappeared jobs, and unchanged ones are not billed by default.
What is the difference between Resume and Incremental mode?
Resume (resumeFromRunId) continues one specific interrupted run so you do not re-save the same jobs twice; you paste an ID. Incremental mode is for running the identical search again and again (for example, daily) and getting only what changed; the actor tracks its own state and there is no ID to paste. The two are normally not combined, except to seed a brand-new monitoring campaign from an existing full pull (see the tip on that in "Resume, recurring updates, and incremental mode").
Why did a run return fewer jobs than Max items when I used multiple searches?
Max items is split evenly across the searches in that run up front. A search that matches fewer jobs than its share does not pass its unused budget to another search in the same run. Raise Max items (or set it to 0), or run each search separately, if you need every search to return its full share.
Why did my run succeed with few or zero jobs instead of failing?
This actor does not fail when Jora stops answering. If a result page still comes back empty after several retries on fresh connections, it stops that search, writes a warning in the log, moves on to the next search, and finishes successfully with whatever it collected, so an empty dataset can mean "blocked", not "no matching jobs". Check the log for "empty response" warnings and run it again. With Incremental mode on, a search stopped this way never triggers EXPIRED rows. Apart from unexpected errors, a run only fails on purpose when resumeFromRunId does not point to a run or dataset your account can read, or when resumeFromRunId is combined with Incremental mode for a search that already has saved state.
Can I use it with AI agents or MCP?
Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear, or Airtable.
๐ Want more jobs data?
Pair this actor with these related scrapers from the same team:
| ๐ผ We Work Remotely Scraper Extract current remote job listings from weworkremotely.com. Use keyword search or WWR... | ๐ผ JobStreet Scraper From $1/1K. Scrape JobStreet listings across Malaysia, Singapore, Indonesia, and the... |
| ๐ผ RemoteOK Jobs Scraper Scrape RemoteOK jobs by keyword or job URL. Extract title, company, location, remote... | ๐ผ Cwjobs UK From $1/1k. Scrape tech and IT jobs from CWJobs.co.uk into clean, structured data. Search... |
| ๐ผ SEEK Jobs Scraper From $0.7/1K. Scrape SEEK.com.au and SEEK.co.nz jobs by keyword, location, or filters... | ๐ผ Wellfound Jobs Scraper Scrape startup jobs from Wellfound.com by keyword, location, role, or remote status... |
๐ Browse all abotapi scrapers
๐ฌ Support
- ๐ Found a bug or a missing field? Open a ticket on the Issues tab. We usually reply within hours.
- ๐ ๏ธ Need another site, extra fields or a private build? Email contact@abotapi.com.
- โญ Enjoying it? A quick review on the actor page helps other users find it.