SimplyHired Scraper - Jobs, Salaries & Company Details avatar

SimplyHired Scraper - Jobs, Salaries & Company Details

Pricing

from $1.00 / 1,000 results

Go to Apify Store
SimplyHired Scraper - Jobs, Salaries & Company Details

SimplyHired Scraper - Jobs, Salaries & Company Details

Scrape active SimplyHired jobs across the US, UK, Canada, Australia, Ireland and India from $1/1K listings. Search by filters or URL. Extract 40+ fields including job titles, companies, salary ranges, full HTML descriptions, qualifications, coordinates and apply URLs.

Pricing

from $1.00 / 1,000 results

Rating

0.0

(0)

Developer

Abot API

Abot API

Maintained by Community

Actor stats

0

Bookmarked

6

Total users

1

Monthly active users

13 days ago

Last modified

Security

Limited permissions

Share

SimplyHired Jobs Scraper turns SimplyHired into a clean job-postings API. Get job title, company, employer rating, parsed salary range (min, max, currency, period), city, state, latitude/longitude, work setting, qualifications, benefits, the full HTML job description and the SimplyHired apply link for active listings across the US, UK, Canada, Australia, Ireland and India sites. Build a search from keywords, location, job type and country, or paste any simplyhired.com /search or /job URL, then export to JSON, CSV or Excel, or pull the results straight into your app through the API.

Why This Scraper?

  • 40+ fields per job ad. Includes a parsed salary range (min, max, currency, period), city, state, latitude/longitude, work setting, qualifications, benefits and the full HTML description, not just the 10 to 15 fields typical scrapers return.
  • Two ways to search. Build with keywords, location, job type and country site, or paste any simplyhired.com /search URL and the scraper walks pagination forward from there, or a /job URL for that single listing.
  • Six regional sites. One input field covers United States, United Kingdom, Canada, Australia, Ireland and India.
  • Detail enrichment. Concurrent detail-page enrichment with per-record delivery, so a mid-page failure still keeps the listings already fetched instead of losing the whole page.
  • No duplicates. Client-side de-duplication across pages and searches by the canonical listing id, with the listing budget split evenly across multiple keywords in one run.
  • Connection fallback. Automatic retry across several connection routes when one is challenged or refused, and a clear failure message (never a silently empty result) when no page could be read.
  • Built for schedules. Incremental mode returns only new and changed listings on recurring runs, and Resume continues one interrupted run without re-collecting what it already saved.

Use Cases

  • Job boards and aggregators: pull fresh SimplyHired listings by keyword and location to mirror or enrich your own job feed.
  • Recruiter and sourcing tools: track new postings for target companies or roles as soon as they appear, and pull only what changed on a schedule.
  • Compensation research: build salary-range datasets by title, location and job type from the parsed salaryMin / salaryMax fields.
  • Labor market analytics: measure posting volume, remote-vs-onsite mix and qualifications trends across regions and countries.
  • Application tooling: collect the SimplyHired apply link and full job description text to feed an autofill, alerting or matching tool.

Data You Get

Sample shape: values are illustrative placeholders, not from a live ad.

FieldExample
id"abcd1234example"
url"https://www.simplyhired.com/job/abcd1234example"
title / displayTitle"Software Engineer" / "Software Engineer, Backend"
company"Sample Company Inc"
companyRating4.2
companyPageUrl"https://www.simplyhired.com/company/000-sample-company-inc"
location / formattedLocation"New York, NY"
city / state"New York" / "NY"
latitude / longitude40.7128 / -74.006
country"us"
jobTypes / workSettings["Full-time"] / ["Hybrid"]
salary"$140,000 - $180,000 a year"
salaryMin / salaryMax140000 / 180000
salaryCurrency / salaryPeriod"USD" / "year"
descriptionHtml / descriptionTextfull HTML job description / the same description as plain text
qualifications["Bachelor's degree", "5+ years experience"]
benefits["Health insurance", "401(k)"]
skillsAll["Python", "AWS", "API design"]
datePosted"2026-09-20T08:00:00.000Z"
applyUrlthe SimplyHired apply link, a redirect that forwards to the employer's application page (the token rotates on every fetch)
sponsoredfalse
source"SimplyHired"
changeType (incremental mode only)"NEW" (also "UPDATED", "UNCHANGED", "REAPPEARED", "EXPIRED")

Each record also carries normalizedTitle (a lowercased title for matching), employerLogoUrl and employerHomepage, remoteAttributes (SimplyHired's own remote-work tags), the raw compensation object behind the parsed salary fields, a short snippet preview, a requirements list distinct from qualifications, datePostedEpochMs and dateOnIndeed (millisecond timestamps), mobileApplyUrl alongside applyUrl, indeedApply / auction / expired flags, jobCardTrackingKey (SimplyHired's own per-render analytics key), the jobTypeRequested / searchQuery / searchLocation / pageNumber that produced the row, scrapedAt, and migration-friendly names: sourceSearchUrl (the search page the row came from), summary (the snippet, or the start of the description), jobKey (same as id), skills (same as skillsAll) and jobType (jobTypes joined as text). These five are left out of change detection, so they never trigger an UPDATED row. In incremental mode, changedFields, firstSeenAt and lastSeenAt are added alongside changeType.

How to Use

  1. Pick a mode: search (build with keywords, location, job type and country) or url (paste one or more simplyhired.com links).
  2. For search mode, fill in keywords, location, country site and job type. For URL mode, paste any /search or /job URL; the search-builder fields above are ignored.
  3. Set Max listings (and optionally Max pages) to control run size and cost, then click Start.
  4. Download the dataset as JSON, CSV or Excel, or read it through the API.

Search by keyword and location:

{
"mode": "search",
"queries": ["software engineer"],
"location": "Remote",
"country": "us",
"jobType": "fulltime",
"maxPages": 5,
"maxListings": 100,
"fetchDetails": true
}

Multiple keywords, sponsored cards skipped, SERP-only fields for a fast and cheap feed:

{
"mode": "search",
"queries": ["nurse", "physical therapist", "medical assistant"],
"location": "Boston, MA",
"country": "us",
"jobType": "any",
"skipSponsored": true,
"maxListings": 150,
"fetchDetails": false
}

URL shapes (URL mode)

The q (keyword), l (location), jt (job type) and pn (start page) values in a pasted link are applied as given. A link starting at pn=2 or higher is a partial scan and never produces EXPIRED rows in incremental mode. Links on other hosts, or on an unsupported SimplyHired domain, are skipped with a warning in the log.

ShapeExample
Keyword + locationhttps://www.simplyhired.com/search?q=software+engineer&l=Remote
Paginated (starts at page pn)https://www.simplyhired.com/search?q=nurse&l=Boston%2C+MA&pn=2
Job-type filter (jt)https://www.simplyhired.com/search?q=engineer&l=Remote&jt=CF3CP
Keyword onlyhttps://www.simplyhired.com/search?q=nurse
Location onlyhttps://www.simplyhired.com/search?l=Remote
Search with a preselected job=https://www.simplyhired.com/search?q=engineer&l=Remote&job=zzz
Single job pagehttps://www.simplyhired.com/job/mo_LRS0yJ90r4G7Bi9HsIQfPAspEk3xIq65MtLP0wZaC8PKRelCDBQ
Regional sites (.co.uk, .ca, .com.au, .ie, .co.in)https://www.simplyhired.co.uk/search?q=nurse&l=London

URL mode on the UK site, mixing a search link and a single job link:

{
"mode": "url",
"urls": [
"https://www.simplyhired.co.uk/search?q=product+manager&l=London",
"https://www.simplyhired.com/job/abcd1234example"
],
"maxPages": 5,
"maxListings": 200,
"fetchDetails": true
}

Recurring monitoring (scheduled daily run, only what changed):

{
"mode": "search",
"queries": ["software engineer"],
"location": "Remote",
"country": "us",
"maxListings": 0,
"fetchDetails": true,
"incrementalMode": true,
"emitExpired": true
}

Schedule this same input to run daily. The first run returns everything as NEW. Later runs return only NEW, UPDATED and EXPIRED listings (plus REAPPEARED once a listing that was previously marked EXPIRED shows up again), not the full result set every time.

Run it from your code

Python:

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/simplyhired-com-scraper").call(run_input={
"mode": "search",
"queries": ["software engineer"],
"location": "Remote",
"country": "us",
})
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/simplyhired-com-scraper').call({
mode: 'search',
queries: ['software engineer'],
location: 'Remote',
country: 'us',
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.

Max pages, Max listings and the per-keyword budget

Max pages caps how many SERP pages are walked per search entry (or per pasted URL); each page returns up to 20 listings. Leave it empty to walk every page until the site's own end-of-results signal. Max listings caps the total across every search in the run; set it to 0 for no cap. When a run has multiple keywords, the Max listings budget is split evenly across them so one keyword can't crowd out the others.

Resume and recurring updates

Two different things, both under one input section, so they don't get confused:

  • Resume from a previous run (resumeFromRunId) continues one specific interrupted run. Paste the ID of a previous run (or its dataset), and this run skips every listing id already saved there, appending only new ones. Good for an unbounded pull (maxPages left empty, maxListings: 0) that got cut off.

  • Incremental mode (incrementalMode) is for recurring monitoring of the SAME search (for example, scheduled daily). Turn it on and the actor remembers the previous run's results itself, no run or dataset id to paste. Each listing in the output then gets a changeType:

    • NEW: never tracked before for this search.
    • UPDATED: tracked before, but at least one non-volatile field changed since last time (see changedFields). Fields that change on every scrape are never compared: scrapedAt, applyUrl, mobileApplyUrl, jobCardTrackingKey, pageNumber and auction stay in the output but never trigger a false UPDATED; so do jobKey, summary, skills, jobType and sourceSearchUrl, which are copies or derivations of compared fields (id, snippet / descriptionText, skillsAll, jobTypes and your search inputs), so a real change still shows through the source field. A plain field name is ignored at any depth, including inside nested objects. Add more with ignoreFieldsForChanges (a plain name is ignored at any depth, a dotted path such as compensation.min ignores that exact nested field); widening the list never causes a one-time wave of UPDATED rows. A run that starts from a pasted URL with pn=2 or higher is a partial scan and never produces EXPIRED.
    • UNCHANGED: identical to last time. Only emitted (and billed) when emitUnchanged is on; off by default.
    • EXPIRED: was tracked as present in a previous run, and was not found in this one. Only produced when emitExpired is on and this run reached the natural end of results for every tracked search, with no maxListings cap hit and no resumeFromRunId. A capped, blocked or resumed run cannot tell "gone" apart from "not reached yet", so it leaves the previous state untouched instead of guessing.
    • REAPPEARED: a listing marked EXPIRED in an earlier run that has now been found again. This can only happen for a listing that some earlier run actually marked absent, which itself only happens when that earlier run had emitExpired on and completed a full, uncapped, non-resumed scan of the tracked search. If emitExpired has never been on, or every run so far was capped, blocked, or used resumeFromRunId, no listing is ever marked absent, so REAPPEARED cannot occur, no matter how many times a listing actually disappears and comes back.

    State is tracked separately per query/location/country/jobType/skipSponsored/URL-set/fetchDetails combination, or under one shared campaign if you set stateKey, so two differently-configured runs never mix baselines.

Independently of both, a checkpoint is saved automatically as the run progresses. If the run is interrupted by an Apify platform migration or you click Resurrect on a failed run, it picks back up in the same run without re-saving, or re-charging for, listings it already collected. No input is needed for this; it's automatic.

Output tabs and run summary

The Output tab offers three views of the same listings: overview (the table), detail (full description, qualifications, benefits, skills, requirements and the salary breakdown) and changes (incremental rows: change type, changed fields, first/last seen). Runs with more than one keyword or URL also save a run summary under the key OUTPUT in the key-value store: for each keyword or URL the pages loaded, the listings delivered and a status (completed, partial, unreachable or skipped; a /job URL whose listing an earlier entry already delivered is completed with 0 listings).

Send results into your apps (MCP connectors)

Optionally pipe the scraped results 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 of each record, not the full JSON. Each item becomes one entry with a title and its key fields flattened to plain text. The complete record always stays in the Apify dataset.

  1. Authorize a connector once under Apify → Settings → Integrations (Notion, Linear, Airtable, or Apify).
  2. Select it in the "Pipe results into your apps" input field. (If the picker is empty, you haven't authorized a connector yet.)
  3. For Notion, also set notionParentPageUrl to 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

ParameterTypeDefaultDescription
modestringsearchsearch (use the builder fields below) or url (paste SimplyHired URLs).
queriesarrayno default (prefill: ["software engineer"])One scrape per keyword. Leave empty to browse without a keyword filter.
locationstringno default (prefill: Remote)City, state, ZIP, or "Remote". Leave empty to search nationwide.
countrystringusWhich SimplyHired regional site to query: us, uk, ca, au, ie, in.
jobTypestringanyany / fulltime / parttime / contract / temporary / internship / commission.
skipSponsoredbooleanfalseDrop sponsored / promoted job cards. Applied client-side, in both modes.
datePostedstringany1, 3, 7 or 14 days. Sent to the site as a native filter in Search mode and also checked against each row's post date (both modes).
minSalaryinteger0Minimum yearly pay; hourly, weekly and monthly pay is annualised and jobs with no listed pay are dropped. Native filter in Search mode, also checked on this side (both modes).
remoteOnlybooleanfalseKeep only jobs flagged remote. Applied client-side, in both modes.
sortBystringrelevancedate orders the listings of each fetched page newest first, client-side only: the site ignores its own sort parameters, so this does not re-order across pages.
keywordstringnoneAlias for queries, so inputs written for other SimplyHired scrapers run unchanged.
startUrlsarraynoneAlias for urls; replaces the 7 sample URLs and switches to URL mode when no keywords are given. A urls list you edited yourself takes priority.
results_wantedintegernoneAlias for maxListings; used when maxListings is unset or still at its default of 20.
max_pagesintegernoneAlias for maxPages.
urlsarray7 sample URLs, one per shape (see the URL shapes table)URL mode only. A /search URL is paged forward independently; a /job URL returns that one listing. Keyword, location, job type and start page come from the pasted URL; datePosted, minSalary, remoteOnly, sortBy and skipSponsored are still applied on this side to every URL's results. If you switch to URL mode and leave this untouched, those 7 sample URLs are scraped; replace them with your own.
maxPagesintegerno default (empty = unlimited)Max SERP pages per search (or per pasted URL). Each page returns up to 20 listings. Leave empty to walk every result page until the site's own end-of-results signal.
maxListingsinteger20Stop after this many listings across all searches. 0 = no cap.
fetchDetailsbooleantrueAlso fetch each job's detail page for the full HTML description, qualifications list, employer logo, lat/lng and apply URL.
resumeFromRunIdstringno default (empty)A previous run ID or dataset ID to continue one specific interrupted run. Listings already collected there (matched by id) are skipped.
incrementalModebooleanfalseTurn on for daily or recurring monitoring of the same search. See "Resume and recurring updates" above.
stateKeystringno default (empty)Incremental mode only. Names the monitoring campaign so its state stays stable, or lets you deliberately share state across differently-configured runs. Leave empty to derive a key automatically from the query/location/URL and detail settings.
emitUnchangedbooleanfalseIncremental mode only. Also return (and bill) listings that haven't changed, marked UNCHANGED.
ignoreFieldsForChangesarray[]Incremental mode only. Extra fields that never count as a change (plain name = any depth, dotted path = exact nested field). They are still returned in the output.
emitExpiredbooleanfalseIncremental mode only. Also return (and bill) listings from a previous run that are no longer found, marked EXPIRED. Only produced after a run that fully scanned the tracked search.
proxyobjectno default (prefill: Apify Residential, country US)SimplyHired is behind a Cloudflare challenge. The prefill is Apify Residential pinned to country US; the actor escalates through further routes automatically when a page is challenged. For other regional sites, change the country to match (GB / CA / AU / IE / IN).
mcpConnectorsarrayno default (empty)Optional: send a summary of each listing to apps you authorized under Integrations.
notionParentPageUrlstringno default (empty)Notion connector only: page under which listing pages are created.
maxNotifyListingsinteger50Cap on items written to each connector per run. Does not affect the dataset.

Output Example

Sample shape: values are illustrative placeholders, not from a live ad. changeType, changedFields, firstSeenAt and lastSeenAt are only added when incrementalMode is on; a normal run's output shape below is unchanged.

{
"id": "abcd1234example",
"url": "https://www.simplyhired.com/job/abcd1234example",
"title": "Software Engineer",
"displayTitle": "Software Engineer, Backend",
"normalizedTitle": "software engineer",
"company": "Sample Company Inc",
"companyRating": 4.2,
"companyPageUrl": "https://www.simplyhired.com/company/000-sample-company-inc",
"employerLogoUrl": "https://logos.example-cdn.com/sample-company-inc.png",
"employerHomepage": "https://www.samplecompany.example",
"location": "New York, NY",
"formattedLocation": "New York, NY",
"city": "New York",
"state": "NY",
"latitude": 40.7128,
"longitude": -74.006,
"country": "us",
"jobTypes": ["Full-time"],
"workSettings": ["Hybrid"],
"remoteAttributes": [],
"salary": "$140,000 - $180,000 a year",
"salaryMin": 140000,
"salaryMax": 180000,
"salaryCurrency": "USD",
"salaryPeriod": "year",
"compensation": { "currency": "USD", "period": "year" },
"snippet": "Short listing preview text appears here...",
"descriptionHtml": "<p>Full long-form HTML description appears here.</p>",
"descriptionText": "Full long-form description as plain text appears here.",
"requirements": ["Python", "AWS"],
"qualifications": ["Bachelor's degree", "5+ years experience"],
"benefits": ["Health insurance", "401(k)"],
"skillsAll": ["Python", "AWS", "API design"],
"datePosted": "2026-09-20T08:00:00.000Z",
"datePostedEpochMs": 1789891200000,
"dateOnIndeed": 1789891200000,
"applyUrl": "https://www.simplyhired.com/out?r=sampletoken000",
"mobileApplyUrl": "https://www.simplyhired.com/m/out?r=sampletoken000",
"indeedApply": false,
"sponsored": false,
"auction": false,
"expired": false,
"jobCardTrackingKey": "0-pdx0-0-0000000000000000-0000000000000000",
"jobTypeRequested": "fulltime",
"searchQuery": "software engineer",
"searchLocation": "New York, NY",
"pageNumber": 1,
"source": "SimplyHired",
"scrapedAt": "2026-09-29T12:00:00.000Z"
}

Plan Requirement

SimplyHired sits behind a Cloudflare managed challenge ("Just a moment..."). In our measurements, plain requests, Apify datacenter proxy and Apify residential proxy (US) were all challenged on the search and home pages, so no single proxy group is guaranteed to get through. The prefilled proxy setting uses the Apify Residential group pinned to the country site you query, and the actor then escalates automatically through further connection routes, including a backup route, whenever a page comes back as a challenge or is refused. Results depend on the site's current defence, so a run can occasionally fail; it then ends as FAILED instead of returning a misleading empty result.

If your plan does not include residential proxy access (Apify free plans do not), the log says "The selected connection is unavailable to this run. Apify free plans do not include residential proxies." and the run continues on a backup connection where some pages may still be unavailable. Upgrade at apify.com/pricing or supply your own proxy URLs in the Proxy field.

FAQ

How much does it cost?

Billing is pay-per-event, and the Pricing tab shows the current rates. The events are: apify-actor-start once per run; apify-default-dataset-item once per listing row pushed to the dataset; and detail-enrichment once per listing whose detail page was fetched successfully and whose row was delivered (it is not charged for rows that fail to load a detail payload, or that are suppressed such as UNCHANGED rows with Emit unchanged off). With Fetch details off, only the start and per-row events apply. Use Max listings to cap the cost of any run.

This actor collects only publicly available job listing data. You are responsible for how you use it: follow SimplyHired's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution.

Can I get only new or changed listings on a schedule?

Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Each run then returns only new and updated listings, and unchanged ones are not billed unless you turn on Emit unchanged. Turn on Emit expired to also get EXPIRED listings after a run completes a full, uncapped scan; REAPPEARED appears only after such a run has marked a listing expired.

Why do my keyword and location filters have no effect?

Search-builder fields (queries, location, jobType) only apply when mode is search. In url mode, the scraper follows whatever filters are already baked into the pasted URL and ignores the builder fields entirely, since two different filter sources for the same run would conflict.

Why did my run return zero listings instead of failing?

If no results page could be loaded on any connection route, the run ends as FAILED with a status message instead of finishing green with an empty dataset, and you are not billed for a page that was never read. The message is one of: "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."; "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"; or, on a free plan, "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 SimplyHired pages." Try again in a few minutes or supply your own proxy URLs. A run that loaded pages but found nothing logs "No listings returned." and finishes normally. The run fails outright when it can detect a configuration problem immediately, such as an unreadable resumeFromRunId, turning on Incremental mode together with resumeFromRunId while a monitoring baseline already exists for that search, or the proxy configuration itself failing to set up.

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:

💼 ZipRecruiter Scraper
Scrape ZipRecruiter jobs with titles, companies, salaries, locations, benefits, apply...
💼 Jobs.cz
Scrape Czech job listings from Jobs.cz. Search builder (keyword, location, field, salary...
💼 Jora Jobs Scraper
Scrape Jora.com job listings across supported countries by keyword, filters, or URL...
💼 SEEK Jobs Scraper
Scrape SEEK.com.au and SEEK.co.nz jobs by keyword, location, or filters. Extract full...
💼 JobStreet Scraper
Scrape JobStreet listings across Malaysia, Singapore, Indonesia, and the Philippines...
💼 NIJobs Scraper
Scrape NIJobs.com listings across Northern Ireland by keyword, location, salary, posted...

👉 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.