Job Posting Classifier — remote, visa, salary · $0.5/1k
Pricing
from $0.43 / 1,000 job posting classifications
Job Posting Classifier — remote, visa, salary · $0.5/1k
Filter LinkedIn jobs and Indeed or Glassdoor postings: remote/hybrid/on-site, visa sponsorship jobs, USD salary. Job posting classifier. Use for job data; for company sites use leoworks/company-website-classifier. datasetId = jobs scraper output, or jobs JSON. ~180 jobs/9 s, pay per job.
Job Posting Classifier — remote, experience, visa & salary
Part of LeoWorks lead and hiring data tools: places → leads (Google Maps Lead Qualifier) · company websites → ICP fit (Company Website Classifier) · job postings → filters (Job Posting Classifier).
For recruiters, job boards, HR-tech, sales teams that sell to hiring companies, labour-market researchers and AI agents that already scrape job postings — and need every posting tagged with how and where the work is done, how much experience it asks for, whether it is one of the visa sponsorship jobs and what it pays, for $0.50 per 1,000 postings, with no prompt writing or LLM key.
LinkedIn removed its experience-level, job-type and remote search filters in August 2026 (as the most-used LinkedIn jobs scraper on Apify Store notes in its README). Scrape broadly, then filter LinkedIn jobs here — the answers come from the description, not from the board's own fields.
- Work mode — remote / hybrid / on-site / not stated
- Minimum experience — none (entry level) / 1–2 / 3–5 / 6+ years / not stated
- Employment type — full-time / part-time / contract / internship / temporary / not stated
- Visa sponsorship — offered / not offered (incl. "must be a US citizen or permanent resident") / not mentioned
- Salary as numbers — min, max, currency, period, and annual USD (hour × 2,080, day × 260, week × 52, month × 12) from the scraper's salary fields or the description
- Skills — the skills, tools or licences from your list that each posting mentions (matched by code, free)
- Your own labels — up to 10 yes/no criteria in plain language (e.g. "requires a security clearance")
- Hiring companies —
REPORT_COMPANIES.csv: one row per company with open jobs, top titles, remote share, visa share, median salary, top skills and latest posting date
| You are | Use it to |
|---|---|
| Job seeker or job board | Keep only remote, entry-level or visa-sponsoring postings; add filters to a board without hand-tagging |
| Recruiter or HR-tech | Benchmark salary ranges and experience asks for a role; check pay transparency by company |
| Sales team selling to employers | REPORT_COMPANIES.csv = companies hiring right now, with what and where they hire |
| Labour-market researcher | Remote and hybrid shares, salary disclosure rates and skill demand by week, company or city |
How it reads the posting: each field is answered from the title, company, location and full description; the job board's own type field is used only when the description says nothing (it is often wrong — LinkedIn lists Plaid's $176k–$227k backend engineer role as "Volunteer"; this Actor answers full-time). Salary numbers, skills, dates, filters and the company report are code, not AI, and cost nothing extra. Our other classifiers: Company Website Classifier, Google Maps Lead Qualifier, Amazon Review Classifier, Social Comment Classifier.
How to use
- Scrape postings with a jobs scraper — for example LinkedIn Jobs Scraper or Indeed Jobs Scraper (more in Works with). Several searches and boards in one dataset are fine: duplicates are skipped.
- Open this Actor and pick that dataset.
- Optional: a list of Skills to look for, up to 10 Custom labels, and free filters — Only postings from the last (e.g.
14 days), Title must contain / must not contain, Max postings per company. - Click Start. 180 postings take about 9 seconds.
- Open the Job postings or Salaries view, or download
REPORT_COMPANIES.csv(Output tab).
Run it automatically after every scrape
In the jobs scraper's Actor (or a saved task of it) → Integrations → Connect Actor or task → pick this Actor, event Run succeeded, and set the input to:
{ "datasetId": "{{resource.defaultDatasetId}}", "skills": ["Python", "SQL"], "postedWithin": "7 days" }
Add an Apify Schedule on the scraper (e.g. every morning) and every new scrape is classified without you opening the console.
Output sample
Real rows from run MH5gM9XIKsNbxv9VR (2026-10-11): 180 postings from eight searches — data scientists, accountants, sales representatives and warehouse associates from an Indeed jobs scraper; software engineers, registered nurses, marketing managers and data analyst internships from a LinkedIn jobs scraper — with 14 skills to look for.
| job | company | work mode | experience | type | visa | annual salary (USD) | skills |
|---|---|---|---|---|---|---|---|
| Strategic Planning Data Scientist | Corning | not stated | 3–5 (1.00) | full-time | offered (1.00) | 77,234 – 106,197 | Python, SQL, Excel, Power BI |
| Senior Data Scientist, RegLab | Stanford University | not stated | 3–5 (1.00) | temporary (1.00) | offered (1.00) | 117,134 – 130,006 | Python, SQL |
| Data Scientist II | ACT | remote (1.00) | 3–5 (1.00) | temporary (1.00) | not offered (1.00) | 95,000 – 105,000 | Python, SQL |
| Software Engineer II, Backend (Identity Decisioning) | Affirm | remote (0.86) | 1–2 (1.00) | full-time | not mentioned | 165,000 – 225,000 | Python, AWS, Kubernetes |
| Software Engineer, Backend | Plaid | not stated | 1–2 (1.00) | full-time (0.96) — board says "Volunteer" | not mentioned | 176,400 – 226,800 | — |
| Entry Level Sales Rep | EMBLM | remote (0.88) | none (0.99) | full-time | not mentioned | 50,000 – 70,000 | Salesforce |
| Experienced Warehouse Order Selector | Performance Foodservice | not stated | not stated | full-time | not mentioned | 44,928 ($21.60 per hr) | Forklift |
| Registered Nurse | Betty Jean Kerr People's Health Centers | not stated | 1–2 (1.00) | full-time (0.67) | not mentioned | — | BLS |
Why those answers: Stanford's posting says "ONE-YEAR FIXED TERM position" and "Stanford may sponsor J-1 visas"; ACT's says "Fixed Term Position that will last 6 months" and "without the need for visa sponsorship"; Corning's says "This position supports immigration sponsorship". Each row also keeps the posting's id, url, postedAt, the board's seniority, applicant count, company size and industry when the scraper has them, and the probability and confidence of every answer.
Input example
The form default — two pasted postings, no dataset needed (about $0.001, 2 seconds):
{"jobs": [{"title": "Senior Data Analyst","company": "Example Co","location": "Chicago, IL","description": "Hybrid role: 3 days a week in our Chicago office. 5+ years of SQL and BI experience required. Full-time. Salary $110,000–$130,000 per year. We are unable to sponsor work visas."},{"title": "Warehouse Associate","company": "Example Logistics","location": "Dallas, TX","description": "Entry level — no experience needed, we train you. Part-time evening shifts, in person at our Dallas warehouse. $18/hour."}]}
To classify a jobs scraper's output, pass its dataset instead. Filters run before classification, so skipped postings are not charged:
{"datasetId": "YOUR_JOBS_DATASET_ID","skills": ["Python", "SQL", "AWS", "Go", "Kubernetes"],"titleIncludes": ["data scien", "engineer"],"titleExcludes": ["intern"],"postedWithin": "14 days","maxPerCompany": 2}
With exactly this input on 210 postings (the 180 above plus 30 repeated), run OJGMvSRiUS8ADar5v classified 38, skipped 28 duplicates and 144 postings outside the filters — 38 charged.
Pricing
Pay only for classified postings — no subscription.
| Event | Price | When |
|---|---|---|
job-classified | $0.0005 | One job posting classified (work mode, required experience, employment type, visa sponsorship, salary disclosed and any custom labels). |
That is $0.50 per 1,000 postings. First run with the form defaults: about $0.001 (2 postings, 2 seconds).
Cost examples
| Postings | Cost |
|---|---|
| 100 | $0.05 |
| 1,000 | $0.50 |
| 10,000 | $5.00 |
| 100,000 | $50.00 |
With the free $5 monthly Apify credit you can classify about 10,000 postings.
Postings without a title, with a description shorter than Minimum description length (120 characters by default — some scrapers return titles only), duplicates and postings removed by your filters are skipped and not charged. Postings that fail after retries are reported with an error field and not charged. If you set a maximum cost per run, the Actor stops cleanly when it is reached.
Works with
Field names are matched ignoring case, _ and -, including nested fields (company.name, description.text, salary.min). Checked against real output (2026-10-10/11) and, for the others, a test row built from each scraper's published output schema.
| Scraper on Apify Store | Checked | Notes |
|---|---|---|
| LinkedIn Jobs Scraper (curious_coder) | real output | salary text → numbers; seniority, applicants, industry kept |
| Indeed Jobs Scraper (valig) | real output | baseSalary, employer.name, location object, company size, industry |
| LinkedIn Jobs Scraper (valig), bebity, cheap_scraper | test row | contractType; salaryInfo list |
| Indeed Scraper (borderline), misceres, kaix, curious_coder Indeed | test row | jobType lists, salary objects, formattedAddressShort, urls.indeed, companyDetails.name |
| Advanced LinkedIn Job Search API (fantastic-jobs) | test row | organization, description_text, locations_derived, ai_salary_*, numeric id |
| Glassdoor scrapers (valig, bebity, cheap_scraper) | test row | pay, job_salary, baseSalary_min/max/period, location.name, location_city + location_state |
Google Jobs and JobSpy-style scrapers (job_title, company_name, job_url, detected_extensions, salary_min / salary_max / salary_interval) | test row | "3 days ago" dates read |
| hirebase | test row | salaryRange, applicationLink |
A dataset whose postings have titles but no description (some scrapers return list pages only) is not classified from titles alone: those rows are skipped and not charged, and if nothing could be classified the run stops with the field names it found. Also: the Apify API and JavaScript/Python clients · Apify Schedules and Integrations (above) · Claude, Cursor and Claude Code through the Apify MCP server (below).
Call it from code
curl -X POST "https://api.apify.com/v2/acts/leoworks~job-posting-classifier/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \-H "Content-Type: application/json" \-d '{"datasetId": "YOUR_JOBS_DATASET_ID", "skills": ["Python", "SQL"]}'
JavaScript (npm install apify-client):
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });const run = await client.actor('leoworks/job-posting-classifier').call({ datasetId: 'YOUR_JOBS_DATASET_ID', postedWithin: '7 days' });const { items } = await client.dataset(run.defaultDatasetId).listItems();const remoteSponsoring = items.filter((j) => j.workMode === 'remote' && j.visa === 'offered');
Python (pip install apify-client):
from apify_client import ApifyClientclient = ApifyClient("YOUR_APIFY_TOKEN")run = client.actor("leoworks/job-posting-classifier").call(run_input={"datasetId": "YOUR_JOBS_DATASET_ID"})well_paid = [j for j in client.dataset(run["defaultDatasetId"]).list_items().items if (j.get("salaryAnnualMinUsd") or 0) >= 100000]companies_csv = client.key_value_store(run["defaultKeyValueStoreId"]).get_record("REPORT_COMPANIES.csv")
Use with Claude, Cursor or Claude Code (MCP)
Ask your AI agent. Add the Apify MCP server with this Actor as a tool, then say, for example:
- "Which of these jobs are remote and sponsor visas? Job 1: “Staff Engineer — fully remote (US time zones). We sponsor H-1B for the right candidate. $160k–$190k per year.” Job 2: “Office Manager — on-site in Denver. We are unable to sponsor work visas.”"
- "From dataset YOUR_DATASET_ID (my LinkedIn jobs scraper run), list the entry-level remote jobs that mention Python or SQL, with the annual salary in USD."
- "Tag this posting and give me the pay as an annual USD figure: “Warehouse Associate, part-time evening shifts, in person at our Dallas warehouse. $18/hour, no experience needed.”"
The agent calls the tool leoworks--job-posting-classifier and reads the labels with get-dataset-items. Replace YOUR_DATASET_ID with the ID of your own dataset.
Claude Desktop or Cursor (mcp.json):
{"mcpServers": {"apify": {"url": "https://mcp.apify.com?tools=leoworks/job-posting-classifier","headers": { "Authorization": "Bearer YOUR_APIFY_TOKEN" }}}}
Claude Code: claude mcp add --transport http apify "https://mcp.apify.com?tools=leoworks/job-posting-classifier" --header "Authorization: Bearer YOUR_APIFY_TOKEN". Leave out the header to sign in with OAuth in the browser instead. We verified this setup with the Apify MCP server (v0.17.4) on 2026-10-10: the agent classified a pasted posting in 7 seconds end to end (run 0Xasgsc22J1HhTJi3).
Output (one row per posting)
{"id": "2ee401cc411b433b","url": "https://www.indeed.com/viewjob?jk=2ee401cc411b433b","title": "Strategic Planning Data Scientist","company": "Corning","location": "Corning, NY, US","postedAt": "2026-10-09T05:00:00.000Z","companySize": "10,000+","companyIndustry": "Industrial Manufacturing","workMode": "not_stated","experience": "3-5","employmentType": "full_time","visa": "offered","salaryDisclosed": true,"salaryText": "$77,234 - $106,197 / year","salaryMin": 77234,"salaryMax": 106197,"salaryCurrency": "USD","salaryPeriod": "year","salaryAnnualMinUsd": 77234,"salaryAnnualMaxUsd": 106197,"skills": "Python, SQL, Excel, Power BI","labels": {"workMode": { "label": "not_stated", "probability": 0.85, "confidence": 0.8 },"experience": { "label": "3-5", "probability": 1, "confidence": 1 },"employmentType": { "label": "full_time", "probability": 1, "confidence": 1 },"visa": { "label": "offered", "probability": 1, "confidence": 1 }},"model": "jev-1.13.0"}
The flat columns (workMode, experience, employmentType, visa, customLabelsMatched) repeat the answers in labels for spreadsheets and filters; labels keeps every probability. experience is the minimum asked for, including preferred experience ("1 year preferred" counts as 1–2). Salary fields are code, not AI: a number in the scraper's salary field, or a dollar amount stated as pay in the description — company figures ("raised $1.4B") and benefits ("sign-on bonus up to $15,000", "$10 copays") do not count. salaryAnnualMinUsd / MaxUsd are filled only for US-dollar salaries with a known period. postedAt, seniority, applicants, companySize and companyIndustry are copied from the scraper when it has them.
Hiring companies (REPORT_COMPANIES.csv)
One row per company, most open jobs first (Output tab → Hiring companies). From the same run:
| company | openJobs | topTitles | onsiteShare | salaryDisclosedShare | medianAnnualSalaryUsd | topSkills | latestPostedAt |
|---|---|---|---|---|---|---|---|
| Performance Foodservice | 3 | Experienced Warehouse Order Selector; Order Picker; Sales Representative | 0 | 0.667 | 44,824 | Forklift | 2026-10-09 |
| CommonSpirit Health | 2 | Clinic RN Queen Creek; RN Medical | 0 | 1 | 118,753 | — | 2026-10-10 |
| Cleveland Clinic | 2 | RN - Orthopaedics; RN - Orthopedics | 0.5 | 1 | 73,840 | BLS | 2026-09-04 |
Columns: company, openJobs, topTitles, remoteShare, hybridShare, onsiteShare, visaOfferedShare, salaryDisclosedShare, medianAnnualSalaryUsd, topSkills, topLocations, latestPostedAt, companySize, companyIndustry.
Summary (REPORT)
The REPORT record has the same figures for all postings together and for every company, plus top titles, top locations and postings per week (last 26 weeks). From the same run:
{"overall": {"jobs": 180,"salaryDisclosedShare": 0.667,"medianAnnualSalaryUsd": 82500,"workMode": { "not_stated": 0.472, "onsite": 0.294, "hybrid": 0.156, "remote": 0.078 },"experience": { "1-2": 0.311, "3-5": 0.267, "not_stated": 0.25, "none": 0.144, "6+": 0.028 },"visa": { "not_mentioned": 0.872, "not_offered": 0.111, "offered": 0.017 },"skills": [{ "skill": "Python", "jobs": 38, "share": 0.211 }, { "skill": "Excel", "jobs": 37, "share": 0.206 }, { "skill": "SQL", "jobs": 34, "share": 0.189 }]},"companies": [{ "company": "Performance Foodservice", "jobs": 3, "salaryDisclosedShare": 0.667, "medianAnnualSalaryUsd": 44824 }]}
Accuracy
Measured on 174 hand-labelled English US job postings (2026-10-10) across eight searches — data analyst internships, marketing managers, registered nurses and software engineers from LinkedIn; accountants, data scientists, sales representatives and warehouse associates from Indeed. The questions were adjusted on one part, then checked once on a separate part that was not used for adjusting.
| Set | Postings | Work mode | Experience | Employment type | Visa | All four right |
|---|---|---|---|---|---|---|
| Check set — not used for adjusting | 100 | 90% | 97% | 96% | 100% | 85% |
| Adjusting set | 74 | 92% | 95% | 99% | 99% | 84% |
A field counts as right when it matches the hand label, or an alternative noted for postings that fit two answers (e.g. a nurse posting at a named hospital that never says "on-site" accepts both not stated and on-site). In the check set, 7 of 8 postings that refuse sponsorship were found. Few postings in the sample offer sponsorship (2 of 174), so treat that answer's accuracy as unmeasured. The most common miss is on-site for a posting that never says where the work is done (9 of 29 on-site answers in the check set were wrong) — when that matters, check on-site answers by hand; remote and hybrid answers were right in every case but one. Salary detection was checked by hand on all 180 scraped postings: no benefit or company figure was counted as salary. Automated labels can be wrong; check samples before making big decisions.
Limits
| Item | Limit |
|---|---|
| Posting length | First 12,000 characters of the description are used; descriptions under 120 characters are skipped (adjustable) |
| Custom labels | Up to 10, each up to 200 characters |
| Skills | Up to 100. Whole words, not case-sensitive, except short skills typed with a capital (Go, R, C), which must appear as typed and not inside a hyphenated word ("Go-to-Market") |
| Dataset size | Any — datasets are read in pages of 1,000 |
| Language | English (measured). Other languages are accepted and usually work, but accuracy has not been measured — treat them as beta |
| Salary | Structured salary fields in any currency; salary text in $, £, €, ₹, ¥, C$, A$. Annual USD only for US-dollar salaries |
| Duplicates | Same job ID or URL, or same title + company + city (across boards). Turn off Skip the same job posted twice to keep them |
| Speed | 180 postings in about 9 seconds |
| Data | Only the title, company, location, description and the board's type field are sent for classification; recruiter contact details are not output |
FAQ
Which AI makes the judgments? Jev, TypeSafe's decision model (version jev-1.13.0, pinned). Jev answers each field with a calibrated probability instead of generated text, so a 0.95 means the same thing across postings. Answers are stable but not fixed: in three runs on the same 180 postings, 2 to 5 of the 720 answers differed between runs — almost all between not stated and a definite answer, on postings that are vague about it. If one posting must never flip, filter on probability (e.g. keep labels.workMode.probability ≥ 0.9).
Does it scrape LinkedIn or Indeed? No. It classifies postings you already have — run a jobs scraper first (see Works with) or paste postings.
LinkedIn removed its remote, experience and job-type filters — does this replace them? Yes, after the scrape: search broadly (title and location), then keep workMode = remote, experience = none or employmentType = internship here. The answers come from the description, so they also catch postings whose board fields are wrong or empty.
How is this different from job APIs with AI fields (e.g. fantastic-jobs)? Those sell their own job database with AI fields included, at about $5–$12 per 1,000 postings. This Actor adds the fields to postings from any scraper or source you already use, for $0.50 per 1,000 (plus the scraper — valig's Indeed scraper costs about $0.10 per 1,000, so the pair is about $0.60). Choose a database API when you need their coverage; choose this when you have your own source or need visa, skills and custom labels on it.
Why not ask ChatGPT? You can for a handful. For thousands of postings you would write and test prompts, handle rate limits and parse free text, and the answers would not come with a probability you can filter on. Here each answer is one of a fixed set of labels with a probability, salary numbers are parsed by code, and the cost is $0.50 per 1,000.
Why "not stated" so often for work mode? Many postings never say whether the job is remote or on site. The Actor answers not stated rather than guessing from the job type, so a filter on remote returns postings that actually say remote.
Which postings are skipped and not charged? Postings without a title, with a description shorter than 120 characters, duplicates, postings older than Only postings from the last, postings outside your title filters, and postings over Max postings per company. The status message counts each reason.
Disclaimer
Independent tool — not affiliated with, endorsed by or sponsored by LinkedIn, Indeed, or the authors of the scrapers listed above. Names are used only to describe compatible data sources. The postings shown are public job postings used as examples.
Reviews and support
If this Actor saved you time, a short review on Apify Store helps others find it. Questions or a dataset whose fields are not detected? Open an issue in the Issues tab — we answer within a day.
Changelog
See the Changelog tab.