s1jobs Scraper – Scotland & UK Jobs, Salaries & Details avatar

s1jobs Scraper – Scotland & UK Jobs, Salaries & Details

Pricing

from $1.40 / 1,000 job results

Go to Apify Store
s1jobs Scraper – Scotland & UK Jobs, Salaries & Details

s1jobs Scraper – Scotland & UK Jobs, Salaries & Details

Scrape jobs from s1jobs.com across Scotland and the UK. Search by keyword, location, or URLs. Returns title, parsed salary band, company, logo, GPS location, skills, contract type, and 90+ fields per job, with optional full description, postcode, and apply links.

Pricing

from $1.40 / 1,000 job results

Rating

0.0

(0)

Developer

Abot API

Abot API

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

5 days ago

Last modified

Share

s1jobs.com Jobs Scraper: Job Listings, Salary Bands & Company Data

s1jobs.com Jobs Scraper turns s1jobs.com, Scotland's largest job site, into a clean, structured jobs feed. Get the job title, parsed salary band, company and logo, location with GPS coordinates, contract type, working pattern, skills, job types and posting dates, over 100 fields on every job. Switch on full detail enrichment and each record also carries the complete job description, postcode, structured location, matched and unmatched skill counts, accreditations and the apply method, URL and email. Search by keyword and one or more locations, or paste search URLs you already refined in your browser, then export to JSON, CSV or Excel, or pull results straight into your app through the API.

Why This Scraper?

  • 100+ fields per job, key values under several names. Most key values are exposed under camelCase, snake_case and nested shapes (for example salary.min, salaryLow, salaryRangeLow, location.postalCode, employer.name), so the output is a drop-in replacement no matter which schema your pipeline already expects.
  • Two ways to search. Build a keyword and location search from the form, or paste s1jobs.com search URLs you already refined in your browser. Multiple URLs are paginated for you.
  • Location narrowing follows the site's own logic. A search for a town returns the commutable area around it too, and a name the site's own search box would correct on its own (for example "Oban" to "Oban, Argyll and Bute") is resolved the same way.
  • Six client-side filters for Search mode. Contract type, full or part time, direct employer or recruitment agency, minimum salary (by year, day or hour), public sector only, and jobs with video only.
  • Full detail enrichment on request. Fetch each job's complete record to add the full description, postcode, structured location, salary band, matched and unmatched skills, accreditations, and the apply method, URL and email.
  • Resume and Incremental & Dedup mode. Continue one interrupted run from a pasted ID, or schedule the same search and get back only what changed.
  • A refused run fails loudly. If every request to s1jobs.com is rejected, the run stops with a clear message instead of quietly reporting an empty search.

Use Cases

  • Recruiters and staffing agencies: monitor competitor postings, salary bands and time-to-fill signals across Scotland.
  • Job boards and aggregators: mirror s1jobs.com listings into your own search index, refreshed on a schedule.
  • Salary and labour-market researchers: build historical datasets of salary bands, contract types and skills demand by region.
  • HR tech and ATS integrations: feed matched jobs straight into your applicant pipeline or a Notion, Airtable or Linear workspace via MCP connectors.
  • Career sites and job alert tools: track new and updated postings for a saved search and notify users only when something actually changes.

Data You Get

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

FieldExample
jobId"482910"
title"Registered Nurse"
company"Sample Care Group"
companyLogohttps://www.s1jobs.com/img/company/0000.png
city"Glasgow"
county"Lanarkshire"
country"Scotland"
coordinates{ "lat": 55.86, "lng": -4.25 }
salary{ "rawText": "£30,000 - £35,000", "min": 30000, "max": 35000, "currency": "GBP", "period": "annual" }
contractType"Permanent"
fullOrPartTime"Full"
employmentType["PERMANENT", "FULL_TIME"]
skills["Healthcare / Pharmaceuticals"]
jobTypes["Nursing"]
datePosted"Fri, 05 Jun 2026 08:00:00 GMT"
expiryDate"Sun, 05 Jul 2026 08:00:00 GMT"
isSponsoredfalse
isPublicSectorfalse
jobUrlhttps://www.s1jobs.com/job/registered-nurse-482910
postcode"G1 1AA" (full details on)
description_text"As a Registered Nurse you will play a central role in delivering care..." (full details on)
applicationUrlhttps://example.com/apply/482910 (full details on)

Every job also carries dozens more fields under the aliases above plus advertType, skillsMatchedCount, accreditations, applicationEmail and the raw company block. These keys are present on every record; the detail-only ones stay empty (null or []) until full detail enrichment fills them in.

How to Use

  1. Pick a mode: search (build a search from keywords and locations) or url (paginate the search URLs you paste).
  2. In Search mode, set your keyword and one or more locations, and switch on any of the six client-side filters you need.
  3. Set Max jobs to control run size and cost (0 for unlimited), and leave Fetch full job details on for the richest output.
  4. Click Start, then download the dataset as JSON, CSV or Excel, or read it through the API.

Search by keyword and location:

{
"mode": "search",
"locations": ["Glasgow", "Edinburgh"],
"keywords": "nurse",
"maxListings": 50,
"fetchDetails": true
}

Filtered search (permanent, full time, public sector, minimum salary):

{
"mode": "search",
"locations": ["Aberdeen"],
"keywords": "project manager",
"contractType": "permanent",
"workingHours": "full",
"publicSectorOnly": true,
"minSalary": 35000,
"salaryType": "annual",
"sortBy": "date",
"maxListings": 100
}

Paste search URLs:

The Search mode filters above are not applied in this mode, so refine the URL in your browser first and paste the refined URL here.

{
"mode": "url",
"urls": [
"https://www.s1jobs.com/jobs/?Keywords=warehouse&JobLocation.City=Glasgow",
"https://www.s1jobs.com/jobs/?JobLocation.City=Edinburgh"
],
"maxListings": 200,
"fetchDetails": false
}

Daily monitoring of one saved search:

{
"mode": "search",
"locations": ["Glasgow"],
"keywords": "nurse",
"incrementalMode": true,
"emitExpired": true
}

Run it from your code

Python:

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/s1jobs-com-scraper").call(run_input={"mode": "search", "locations": ["Glasgow"], "keywords": "nurse"})
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/s1jobs-com-scraper').call({ mode: 'search', locations: ['Glasgow'], keywords: 'nurse' });
const { items } = await client.dataset(run.defaultDatasetId).listItems();

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

Max jobs (maxListings) is the only run-wide cap, default 20, 0 for unlimited. Max pages per search (maxPages) is an optional per-search safety stop on how many pages of one location or URL are walked; leave it empty to walk every result page and let Max jobs decide when the run stops. With several locations, the run's budget is split across them and each is walked in turn.

Resume and recurring updates

  • Resume (resumeFromRunId) continues one interrupted run: paste its run or dataset ID and this run skips every job already collected there, so you get only the delta and do not pay twice.
  • Incremental mode (incrementalMode) is for running the same search again and again on a schedule. Each job is classified NEW, UPDATED (with changedFields), UNCHANGED (suppressed, not returned or billed unless emitUnchanged is on), REAPPEARED or EXPIRED (only once a run has completed a full, uncapped, non-resumed scan of every tracked search, and only with emitExpired on). stateKey names or shares the stored state; leave it empty to let the actor derive one from your search and detail settings. With incremental mode off, output is exactly as before.

Known limitations when searches overlap:

  • Several locations (or several pasted URLs) in one run. s1jobs.com's location search covers the commutable area around each place, so nearby locations often share jobs. Each search is walked page by page, and once a search has found new jobs, a later page on which every job was already collected earlier in the run ends that search. If that page is one that simply repeats another location's jobs, that search's deeper pages are skipped. A page with even one new job does not trigger this. Example: a Glasgow search returns jobs 1 to 40, then an Edinburgh search with 80 results serves jobs 1 to 20 on page 1, 61 to 80 on page 2, 21 to 40 on page 3 and 81 to 100 on page 4. Page 3 holds only Glasgow's jobs, so the Edinburgh search stops there and jobs 81 to 100 are never collected.
  • The early stop still counts as a complete scan. With incrementalMode and emitExpired on, jobs from those skipped pages that an earlier run collected are marked EXPIRED even though they are still live. In the example above, jobs 81 to 100 would come back as EXPIRED.
  • Resume after new jobs were posted. The same rule applies to resumeFromRunId. If the first page now holds a few new jobs ahead of ones the earlier run already collected, the next page of already-collected jobs ends the walk, and jobs beyond where the earlier run stopped are not reached.
  • Workaround. For complete results, or if you rely on EXPIRED, run each overlapping location or URL as its own run (for example, one saved task per location, each with its own state). If a resumed run returns far fewer jobs than expected, run the search again without Resume. maxPages does not help here, because the early stop happens before any page limit is reached.

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 of each job, not the full JSON. Each job 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 have not authorized a connector yet.)
  3. For Notion, also set Notion parent page to the page under which a sub-page is created per job.

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 builds a search from the filters below; url paginates the URLs you paste.
locationsarray["Glasgow"]Towns, cities or counties. One search runs per location; s1jobs searches the area around the place you name, so nearby commutable towns come back too. An unrecognised name is resolved to the closest one the site lists. Empty lists jobs from everywhere. Search mode only.
keywordsstring(none)Role, skill or company to search for. Search mode only.
contractTypestringanyany, permanent, contract or temporary. Search mode only.
workingHoursstringanyany, full or part. Search mode only.
advertiserTypestringanyany, employer (direct) or agency. Search mode only.
minSalaryinteger(none)Lowest salary to include, combined with the period below. Jobs that quote no numeric salary are always kept. Search mode only.
salaryTypestringannualUnit for the minimum salary filter: annual, daily or hourly. Search mode only.
publicSectorOnlybooleanfalseKeep only public sector roles. Search mode only.
hasVideoOnlybooleanfalseKeep only jobs that include a recruiter video. Search mode only.
sortBystringrelevancerelevance or date. Search mode only; URL mode uses the ordering already in your pasted URL.
urlsarraysample URLs1jobs.com search URLs to scrape (URL mode). The Search mode fields above are not applied to these URLs; build the filtering you want into the URL itself. A page number already in the URL is honoured as the starting point.
fetchDetailsbooleantrueFetch each job's full record: complete description, postcode, structured location, salary band, matched and unmatched skills, accreditations and apply links.
maxListingsinteger20Total jobs to collect across all searches. The only run-wide limit. 0 for unlimited.
maxPagesinteger(none)Optional per-search safety stop on pages walked. Leave empty to walk every result page and defer to Max jobs for run size.
resumeFromRunIdstring(none)ID of a previous run of this actor (or a dataset ID). Jobs already in that dataset are skipped, so this run returns only new jobs.
incrementalModebooleanfalseTurn on for daily or recurring monitoring of the same search; see Resume and recurring updates above.
stateKeystring(none)Optional name for an incremental monitoring campaign, or to deliberately share state across differently configured runs. Auto-derived from your search and detail settings when left empty.
emitUnchangedbooleanfalseIncremental mode only. Also return (and bill) jobs unchanged since the last run, marked UNCHANGED.
emitExpiredbooleanfalseIncremental mode only. Also return (and bill) jobs no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked search.
proxyobjectApify ProxyConnection settings.
maxResidentialRequestsinteger0Safety cap on how many residential proxy requests a run may use. 0 for unlimited.
maxResidentialMBinteger0Safety cap on residential proxy traffic in megabytes. 0 for unlimited; once spent, the run continues on the default connection.
mcpConnectorsarray(none)Optional: send a summary of each job to apps you authorized under Integrations.
notionParentPageUrlstring(none)Notion connector only: page under which job 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 listing.

{
"jobId": "482910",
"title": "Registered Nurse",
"company": "Sample Care Group",
"companyName": "Sample Care Group",
"companyLogo": "https://www.s1jobs.com/img/company/0000.png",
"employer": {
"name": "Sample Care Group",
"logoUrl": "https://www.s1jobs.com/img/company/0000.png",
"url": "https://www.s1jobs.com/jobs/?CompanyId=0000"
},
"city": "Glasgow",
"county": "Lanarkshire",
"country": "Scotland",
"postcode": "G1 1AA",
"coordinates": { "lat": 55.86, "lng": -4.25 },
"location": {
"text": "Glasgow, Lanarkshire",
"locality": "Glasgow",
"region": "Lanarkshire",
"country": "Scotland",
"postalCode": "G1 1AA",
"lat": 55.86,
"lng": -4.25
},
"salary": {
"rawText": "£30,000 - £35,000",
"min": 30000,
"max": 35000,
"currency": "GBP",
"period": "annual"
},
"salaryLow": 30000,
"salaryHigh": 35000,
"contractType": "Permanent",
"fullOrPartTime": "Full",
"employmentType": ["PERMANENT", "FULL_TIME"],
"workingHourPattern": "Regular working hours",
"skills": ["Healthcare / Pharmaceuticals"],
"primaryCoreSkill": "Healthcare / Pharmaceuticals",
"skillsUnmatchedCount": 16,
"jobTypes": ["Nursing"],
"datePosted": "Fri, 05 Jun 2026 08:00:00 GMT",
"expiryDate": "Sun, 05 Jul 2026 08:00:00 GMT",
"closingDate": null,
"isSponsored": false,
"isFeatured": false,
"isPublicSector": false,
"hasVideo": false,
"applicationMethod": "url",
"applicationUrl": "https://example.com/apply/482910",
"applicationEmail": "jobs@example.com",
"description_text": "As a Registered Nurse you will play a central role in delivering high quality care...",
"jobUrl": "https://www.s1jobs.com/job/registered-nurse-482910",
"canonicalUrl": "https://www.s1jobs.com/job/registered-nurse-482910",
"status": "live",
"detailFetched": true,
"scrapedAt": "2026-06-15T00:00:00+00:00"
}

Plan Requirement

The default connection setting works out of the box, and the run adjusts automatically if it starts getting refused. For very large or frequent runs, picking a different connection under Connection gives more headroom.

FAQ

How much does it cost?

You pay a small run-start fee plus per job returned, with detail enrichment billed separately only when Fetch full job details is on. The Pricing tab shows the current rates. Use Max jobs to cap the cost of any run.

This actor collects only publicly available job listings. You are responsible for how you use the data: follow s1jobs.com's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Job postings are generally facts, but company logos and branding may be subject to third-party rights.

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 unless you turn that on too. If you also turn on emitExpired, track one location or URL per run: overlapping locations in one run can mark still-live jobs EXPIRED (see the next question).

Why did my second location return fewer jobs than its total, or mark live jobs EXPIRED?

Nearby locations on s1jobs.com overlap, because each location search also covers the commutable area around it. When a later location's search reaches a page made up entirely of jobs an earlier location already returned, after it has already found some new jobs, it treats that page as the end of its results and stops, so jobs on its later pages are skipped. That early stop still counts as a full scan for incremental mode, so with emitExpired on, the skipped jobs can be marked EXPIRED while still live. The same can happen to a Resume run when new jobs were posted since the earlier run. Run each overlapping location or URL as its own run for complete results. The full example is under Resume and recurring updates above.

Why weren't my filters applied to the URLs I pasted?

Contract type, hours, advertiser type, minimum salary, public sector and video filters only build a search in Search mode. A pasted URL in URL mode already carries whatever facets you set when you refined it in your browser, so this actor scrapes it as given rather than second-guessing it. Build the filtering into the URL itself, or switch to Search mode.

Why did my run fail instead of returning an empty dataset?

If s1jobs.com refuses every request, the run stops with a clear message so "no jobs for this search" is never confused with "nothing could be read". Run it again in a few minutes, or try a different connection under Connection.

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:

💼 Totaljobs Scraper
Scrape UK job listings from Totaljobs.com by keyword, location, filters, or URL. Extract...
💼 NIJobs Scraper
Scrape NIJobs.com listings across Northern Ireland by keyword, location, salary, posted...
💼 Reed.co.uk Scraper
Scrape Reed.co.uk jobs, companies, courses, and reviews. Search by keyword, location...
💼 Dice.com Scraper
Scrape tech job listings from Dice.com by keyword, filters, or URL. Extract job titles...
💼 HiJobs.net Scraper
Scrape hijobs.net job listings via the official mobile API: title, employer, salary...
💼 ZipRecruiter Scraper
From $1/1K. Scrape ZipRecruiter jobs with titles, companies, salaries, locations...

👉 Browse all abotapi scrapers

💬 Support & custom scrapers

  • 🐞 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 abotapi@proton.me or message Telegram @abotapi.
  • ⭐ Enjoying it? A quick review on the actor page helps other users find it.