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
6 days ago
Last modified
Categories
Security
Limited permissionsShare
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?
- Nine country sites. 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. 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).
- Full details. Detail mode adds the full job description, apply link, salary, work type, and work arrangement for every listing.
- Bounded or full walks. 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. Resume a large pull across separate runs, or let an interrupted run pick back up automatically without re-saving jobs it already collected.
- Change feed. Incremental mode turns a recurring search into a change feed: only new, updated, and reappeared jobs come back on later scheduled runs.
- MCP export. 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/software-engineer-jobs-in-Sydney-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 |
job_id, job_type, date_posted, description_text, description_html, source, sponsored, country, search_keyword, search_location, search_page | Compatibility aliases: the same values as jobId, workType, postedAtText, descriptionText, descriptionHtml, sourceName, isSponsored, countryCode, searchKeywords, searchLocation, page, so pipelines written for other Jora scrapers keep working |
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:
Supported URL shapes (the default and the form prefill carry one example of each search shape):
| Shape | Example |
|---|---|
| Keyword and location path | https://au.jora.com/software-engineer-jobs-in-Sydney-NSW |
| Same path, page 2 (the page in the link is honoured) | https://au.jora.com/Software-Engineer-jobs-in-Sydney-NSW?p=2 |
| Keyword-only path | https://au.jora.com/Software-Engineer-jobs |
| Location-only path | https://au.jora.com/jobs-in-Sydney-NSW |
| Search path, newest first | https://au.jora.com/Developer-jobs-in-Sydney-NSW?st=date |
| Search path, salary, quick apply, radius filters | https://au.jora.com/Developer-jobs-in-Sydney-NSW?sa=90000&qa=y&r=25 |
| Job detail page | https://au.jora.com/job/Software-Engineer-0155ab88203c8731b751b7006d496e7f |
/j query search (pasted links only) and job detail links | https://au.jora.com/j?q=nurse&l=Melbourne, https://au.jora.com/job/Sample-Job-00000000000000000000000000000001 |
Search mode builds the keyword and location path form (/<keyword>-jobs-in-<Location>) with your filters added as link parameters, because Jora's /j?q=&l= form is usually challenged on residential connections. Only keyword or location text that cannot be written in a path (for example symbols such as +) falls back to the /j form, and the run logs that it usually needs the backup connection.
Behaviour change: running URL mode with urls left out now scrapes these seven examples (up to your item limit) instead of returning nothing. Jora may ignore q and l on the /jobs form and serve its general list; the run logs a warning for each such link and leaves searchKeywords and searchLocation empty for it. Links that are not a recognised Jora search or job address also get a warning.
URL mode input:
{"mode": "url","urls": ["https://au.jora.com/software-engineer-jobs-in-Sydney-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.
Eight fields are left out of change detection on purpose, because they 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, page, sourceUrl and searchTotalCount (belong to the search position, not the job), and scrapedAt (a per-run timestamp). All of them are still returned on every row exactly as before; only change detection ignores them, so a job that only moved to another result page is not reported as changed. ignoreFieldsForChanges lets you add your own fields to that list (a dotted path such as a.b reaches nested fields); they stay in the output, and the job id can never be ignored. Baselines saved by earlier versions are re-compared with the current ignore list, so updating does not produce a one-time wave of UPDATED rows.
A few edge cases are worth knowing about before you rely on incremental mode for billing-sensitive monitoring:
- 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 | seven example URLs | URL mode inputs. Accepts search result URLs and job detail URLs. If you leave urls out in URL mode, the seven examples under Supported URL shapes are scraped instead of nothing. |
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. |
ignoreFieldsForChanges | string[] | [] | Incremental mode only: fields whose changes do not make a job UPDATED (they stay in the output). |
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. |
startUrl | string | empty | Compatibility alias for urls: one Jora link, which switches the run to URL mode. A one-item list is accepted too. Applies whenever urls is left at its prefilled examples or empty; a urls list you changed wins. |
keyword | string | empty | Compatibility alias for the keywords of searches. Applies only when searches is not set. |
location | string | empty | Compatibility alias for the location of searches. Applies only when searches is not set. |
country | string | empty | Compatibility alias for the country of searches; accepts a code such as au or a label such as Australia. |
posted_date | string | empty | Compatibility alias for postedWithin. Applies while Posted within is anytime. Accepts 24h, 7d, 14d, 30d, anytime and plain forms such as today, week, month; any other value is ignored and the run logs a warning. |
results_wanted | integer | empty | Compatibility alias for maxItems. Applies while Max items is still 20; a Max items you changed wins. |
max_pages | integer | empty | Compatibility alias for maxPages. Applies when Max pages is empty. |
proxyConfiguration | object | empty | Compatibility alias for proxy. Applies while Proxy is still the Residential AU default; a Proxy you changed wins. |
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/software-engineer-jobs-in-Sydney-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"}
Output views
The dataset has four views: Overview (the key columns), Full (every field), Detail (description, apply link and source, for detail runs) and Changes (change type, changed fields and first and last seen, for incremental runs). Each is also available as an output tab of the run.
Plan Requirement
Jora protects its search and job detail pages with a managed browser challenge, so the connection matters. Residential AU is selected by default, and plain datacenter connections are not enough on their own: this is not a "works with no action needed" actor. A paid Apify plan is recommended, because Apify free plans do not include residential proxies. For searches outside Australia, choose the closest Residential country supported by your Apify plan, or supply your own proxy URLs.
What you see when the connection is not available (exact log and failure texts):
- Free plan warning in the log, then the run continues on a backup connection: "The selected connection is unavailable to this run. Apify free plans do not include residential proxies. Continuing on a backup connection; some pages may still be unavailable. For residential access, upgrade at https://apify.com/pricing or supply your own proxy URLs."
- When the backup connection serves pages, the log says: "Served over the backup connection (the default connection was unavailable)." The backup connection is used only when the default one is challenged or refused, and its traffic is billed to your own Apify account. Pages served over it are slower (up to about a minute each), and the run allows for that.
- Run fails, proxy refused for the account: "The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required: https://apify.com/pricing. If you entered your own proxy URLs, check that their credentials are valid."
- Search text that cannot be written as a search path: "This search text cannot be written as a Jora search path, so it is requested with the query form, which usually needs the backup connection."
- Run fails, free plan and the backup could not load pages: "No results: residential proxies are unavailable on this run (Apify free plans do not include them), and the backup connection could not load the requested Jora pages. Upgrade at https://apify.com/pricing or supply your own proxy URLs, then try again."
- Run fails, any other connection problem: "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search. Please try again later."
Billing
The run charges three events: the actor start event (once per run, per GB of memory, minimum one), the dataset item event (once per job saved to the dataset), and the detail-enrichment event (once per job saved with full details, only when Fetch full job details is on). Suppressed unchanged jobs and failed pages are not charged. The Pricing tab shows the current rates.
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, and the actor start event once per run (see Billing). 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 fail or return few jobs?
If no results page could be loaded at all, the run fails on purpose with one of the messages listed under Plan Requirement, instead of finishing as an empty success. If some pages loaded and a later page stays empty after several retries on fresh connections, that search stops with a warning and the run finishes with what it collected, so check the log for "empty response" warnings and run it again. With Incremental mode on, a search stopped this way never triggers EXPIRED rows. A run also 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:
| ๐ผ CWJobs UK Scraper Scrape CWJobs.co.uk tech and IT job listings by keyword, location, salary, work type... | ๐ผ JobStreet Scraper Scrape JobStreet listings across Malaysia, Singapore, Indonesia, and the Philippines... |
| ๐ผ WeWorkRemotely Scraper Extract current remote job listings from weworkremotely.com. Use keyword search or WWR... | ๐ผ RemoteOK Jobs Scraper Scrape RemoteOK jobs by keyword or job URL. Extract title, company, location, remote... |
| ๐ผ SimplyHired Scraper Scrape active SimplyHired jobs across the US, UK, Canada, Australia, Ireland and India... | ๐ผ SEEK Jobs Scraper Scrape SEEK.com.au and SEEK.co.nz jobs by keyword, location, or filters. Extract full... |
๐ 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.