# Job Postings AI Classifier – Seniority, Remote & Visa (`glidepath/job-postings-ai-tagger`) Actor

Job Postings AI Classifier – Seniority, Remote & Visa enrichment for any job dataset (LinkedIn, Indeed, ATS scrapers): job function, work mode, employment type, degree, experience. Input: dataset IDs or postings. $1.50/1k postings.

- **URL**: https://apify.com/glidepath/job-postings-ai-tagger.md
- **Developed by:** [Glidepath](https://apify.com/glidepath) (community)
- **Categories:** Jobs, AI, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 tagged job postings

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Job Postings AI Classifier – Seniority, Remote & Visa

**AI job classification for LinkedIn, Indeed and any job scraper dataset:** turn raw job postings into clean, filterable tags (seniority, job function, remote / hybrid / on-site, where remote work is allowed from, employment type, visa sponsorship, relocation support, salary stated, people management, degree requirement, posting language) and the minimum years of experience asked for.

It works on **any job dataset** — the output of our [Greenhouse, Lever & Ashby Jobs Scraper](https://apify.com/glidepath/ats-jobs-scraper) or [Remote Jobs Scraper](https://apify.com/glidepath/remote-jobs-scraper), any other job scraper on Apify, or postings you paste in. Common field names (`title`, `jobTitle`, `description`, `descriptionHtml`, `description_text`, `companyName`, …) and nested ones (`title.text`, `description.text`) are detected automatically, so datasets from popular LinkedIn and Indeed job scrapers work without settings.

Every tag comes with a **confidence score**, so you can automate the clear cases and review the few uncertain ones.

### Who uses it

- **Job boards and aggregators** — consistent seniority, remote and function filters across sources that all label things differently.
- **Recruiters and sourcers** — find the senior remote roles that sponsor visas without reading 2,000 postings.
- **Labour-market research** — count roles by function, level and work mode over time.
- **Job-alert and newsletter builders** — route postings to the right audience automatically.
- **AI agents** — a cheap, typed "what kind of job is this?" tool (Apify MCP).

### How to use it

1. Run a job scraper (or pick any dataset with job postings), or paste postings into **Or paste job postings**.
2. Select the dataset(s) under **Job datasets to tag** and start the run.
3. Download the tagged postings as JSON, CSV or Excel, or join them back to your data by `sourceIndex` / `id` / `url`. Turn on **Keep the source row's job fields** (`keepOriginalFields`) to get the source row's job fields (posted date, salary, employment type, links, …) in `original` on every row, with no join needed. People's names and contact details are never copied. You can also chain it after a scraper with an Apify integration: use `{"datasetId": "{{resource.defaultDatasetId}}"}` as the input.

### Input example

```json
{
  "datasetIds": ["YOUR_DATASET_ID"],
  "maxItems": 5000
}
```

Or with postings inline (objects or plain text):

```json
{
  "items": [
    {
      "title": "Senior Backend Engineer (Python)",
      "companyName": "Northwind Analytics",
      "location": "Remote (EU time zones)",
      "description": "You have 5+ years of experience with Python and PostgreSQL. Fully remote within European time zones. We can't sponsor visas. Salary EUR 75,000-90,000."
    }
  ]
}
```

If your data uses unusual field names, set **Title field** / **Description field** (e.g. `headline`, `body`).

### Output example

```json
{
  "title": "Senior Backend Engineer (Python)",
  "companyName": "Northwind Analytics",
  "location": "Remote (EU time zones)",
  "url": "https://example.com/jobs/northwind-backend",
  "id": null,
  "sourceDatasetId": null,
  "sourceIndex": 0,
  "seniority": "senior",
  "seniorityConfidence": 0.86,
  "jobFunction": "software_engineering",
  "jobFunctionConfidence": 0.93,
  "workMode": "remote",
  "workModeConfidence": 0.9,
  "remoteScope": "restricted",
  "remoteScopeConfidence": 0.71,
  "employmentType": "full_time",
  "employmentTypeConfidence": 0.62,
  "visaSponsorship": "not_offered",
  "visaSponsorshipConfidence": 0.88,
  "degreeRequirement": "not_mentioned",
  "degreeRequirementConfidence": 0.41,
  "postingLanguage": "en",
  "postingLanguageConfidence": 0.99,
  "managesPeople": false,
  "managesPeopleProbability": 0.08,
  "relocationSupport": false,
  "relocationSupportProbability": 0.03,
  "salaryStated": true,
  "salaryStatedProbability": 0.97,
  "minYearsExperience": 5,
  "lowConfidenceTags": ["degreeRequirement"],
  "descriptionShortened": false,
  "aiModel": "typesafe-ai/jev",
  "taggedAt": "2026-09-26T12:00:00Z"
}
```

| Field | Values |
|---|---|
| `title`, `companyName`, `location`, `url`, `id` | Copied from your data (only these fields; the description is never copied to the output) |
| `sourceDatasetId`, `sourceIndex` | Where the posting came from — `sourceIndex` is its position in that dataset (or in `items`), starting at 0 |
| `seniority` | `intern`, `entry`, `mid`, `senior`, `lead` (lead/staff/principal), `manager`, `director_executive`, `not_clear` |
| `jobFunction` | `software_engineering`, `data_ai`, `it_infrastructure_security`, `product_management`, `design`, `sales_business_development`, `marketing_communications`, `customer_support_success`, `operations_project_management`, `finance_accounting`, `hr_recruiting`, `legal_compliance`, `hardware_engineering`, `science_research`, `healthcare`, `education_training`, `supply_chain_logistics`, `skilled_trades_production`, `hospitality_retail`, `administrative`, `other` |
| `workMode` | `remote`, `hybrid`, `onsite`, `not_stated` |
| `remoteScope` | `anywhere`, `restricted` (specific countries, regions or time zones), `not_stated`; `null` unless the role is remote or hybrid |
| `employmentType` | `full_time`, `part_time`, `contract` (incl. freelance, temporary), `internship`, `not_stated` |
| `visaSponsorship` | `offered`, `not_offered` (incl. "must already have the right to work"), `not_mentioned` |
| `degreeRequirement` | `not_mentioned`, `preferred_or_equivalent`, `bachelor_required`, `advanced_required` |
| `postingLanguage` | `en`, `de`, `fr`, `es`, `nl`, `it`, `pt`, `pl`, `other` |
| `managesPeople`, `relocationSupport`, `salaryStated` | `true`/`false`, with the model's probability in `…Probability` |
| `…Confidence` | 0–1: how sure the model is about the choice above |
| `minYearsExperience` | The smallest number of years of experience the posting asks for, read by rule (not by AI); `null` when no number is stated |
| `lowConfidenceTags` | Tags worth a human look: choice confidence below 0.5, or a yes/no probability between 0.35 and 0.65 |
| `descriptionShortened` | `true` when a very long description was cut to its first ~4,000 and last ~1,800 characters before tagging; lines in between that mention pay, visa, relocation, work mode, degree or contract type are kept |

A run summary (postings read, tagged, skipped, failed, datasets that couldn't be read) is stored in the run's key-value store as `SUMMARY`; postings that failed are listed in `ERRORS`.

### Use it with

- **Schedules:** save your input as a Task and run it hourly, daily or weekly.
- **Integrations:** Slack, e-mail, Google Sheets and Google Drive, webhooks, Make, Zapier and n8n (the Integrations tab of the Actor or Task).
- **API and code:** the Apify API, the JavaScript and Python clients, or CSV/JSON/Excel downloads.
- **AI agents:** the Apify MCP server exposes this Actor as a tool; the input is plain text and short lists.

### Pricing

Pay per result: **$1.50 per 1,000 tagged job postings** on the Apify Free plan; on paid plans **$1.50** (Starter), **$1.25** (Scale) and **$1.00** (Business and higher) (the price for your plan is shown on the Pricing tab). The examples below use the Free-plan price. You only pay for postings that were tagged and saved — records without any text and postings that failed are never charged.

- Tagging one scraper run of 2,000 postings → **$3.00**.
- A daily feed of 500 new postings → about **$22.50/month**.

Set a **maximum cost per run** in the run options: the Actor checks it before each posting and stops cleanly when it is reached. On Apify's free plan a run tags up to 100 postings.

If you pick datasets in the form, the three sample postings prefilled under **Or paste job postings** are ignored (and not charged) unless you edit them.

### How accurate is it?

We checked it on 24 public job postings from 9 employers (software, data, sales, support, compliance, fitness and gaming roles; US, Canada, Europe, India, Australia; one in French). We labelled each posting from its text first, then ran the tagger and compared. Where a posting itself is ambiguous (e.g. "work from a US hub or remotely"), either answer counted.

| Tag | Matches |
|---|---|
| `workMode` | 24 / 24 |
| `employmentType` | 24 / 24 |
| `visaSponsorship` | 24 / 24 |
| `salaryStated` | 24 / 24 |
| `relocationSupport` | 24 / 24 |
| `managesPeople` | 22 / 22 |
| `seniority` | 23 / 24 |
| `jobFunction` | 23 / 24 |
| `degreeRequirement` | 23 / 24 |
| **All tags** | **211 / 214 (98.6%)** |

The three misses: a "5+ years, AI product" engineer tagged `lead` instead of `senior`, a game-testing R\&D engineer tagged `data_ai`, and a degree requirement read into a posting that names none. A sample this small is a sanity check, not a guarantee: keep an eye on `lowConfidenceTags`.

### Limits and notes

- Tags come from **TypeSafe AI's Jev**, a decision model that picks from the fixed options above and reports its confidence; it never writes free text, so every value is one of the listed options. It can still pick the wrong option — use the confidence fields and `lowConfidenceTags` for anything important.
- English is the model's strongest language. German, French, Spanish and other languages work, but check a sample and lean on the confidence scores for non-English postings.
- `minYearsExperience` is read by rules (English and German phrasing such as "5+ years of experience", "mindestens 3 Jahre Berufserfahrung"). It takes the lower bound of a range and the smallest of several requirements.
- Postings are tagged several at a time. For very large datasets (tens of thousands of postings), raise the run timeout.
- The Actor doesn't visit any website: it only reads the datasets and postings you give it.
- If a long run is moved to another Apify server, it continues where it stopped: postings already tagged are not tagged or charged again.

### FAQ

**Is it legal to use?** The Actor doesn't scrape anything; it classifies the job postings you provide, so you need the right to process that data (public job ads usually are). It outputs no personal data: e-mail addresses and phone numbers are removed from the text before it is analysed, and only the title, company, location, link and id are copied from your data.

**Where is my data sent?** Each posting's title, company, location, a few short structured fields (e.g. employment type, workplace type, tags) and its description are sent to TypeSafe AI's Jev model through an AI gateway (OpenRouter or Vercel AI Gateway) to compute the tags. TypeSafe states that it doesn't train its models on customer requests. Nothing else leaves Apify.

**How accurate is it?** Every tag carries the model's own confidence. When a posting is vague (no level in the title, no location policy), the model says so (`not_clear`, `not_stated`) or reports low confidence instead of guessing silently. Try it on 100 postings of your own data before tagging everything.

**Can I tag postings from other job scrapers?** Yes — any dataset of job postings works, as long as each record has a title and/or a description.

**Can I get other tags?** Open an issue with the tag you need; the question set is versioned in the changelog.

**Can I use it from an AI agent?** Yes, via the Apify MCP server or API. Pass `items` or `datasetIds`; the result is the dataset items above.

### Support

Open an issue on the Actor's **Issues** tab with the input you used and the run link — we reply within a few days.

Made by **Glidepath**. See the changelog for updates.

# Changelog

This Actor's version history is a separate document: https://apify.com/glidepath/job-postings-ai-tagger/changelog.md

# Actor input Schema

## `datasetIds` (type: `array`):

Datasets that hold job postings, for example the output of a job scraper run (ours or any other). Each record needs a job title and/or a description; common field names are detected automatically.

## `datasetId` (type: `string`):

One dataset ID, for chaining after a scraper run with an Apify integration: set it to {{resource.defaultDatasetId}}. Same as adding the ID to 'Job datasets to tag'.

## `items` (type: `array`):

Job postings as JSON objects (with fields such as title, companyName, location, description) or as plain text. Up to 5,000 per run; use a dataset for more.

## `maxItems` (type: `integer`):

Stop after this many postings. Leave empty to tag everything (your maximum cost per run still applies).

## `titleField` (type: `string`):

Name of the field with the job title, if your data uses an unusual name. Detected automatically: title, jobTitle, job_title, positionName, position, positionTitle.

## `descriptionField` (type: `string`):

Name of the field with the job description (plain text or HTML), if your data uses an unusual name. Detected automatically: descriptionText, description, jobDescription, descriptionHtml, content, body, text, summary.

## `keepOriginalFields` (type: `boolean`):

Adds `original` to every row: the source row's job fields (title, company, location, links, ids, posted date, salary, employment type, seniority, tags and similar), so you don't have to join on sourceIndex. People's names, contact details and the description itself are never copied.

## Actor input object example

```json
{
  "items": [
    {
      "title": "Senior Backend Engineer (Python)",
      "companyName": "Northwind Analytics",
      "location": "Remote (EU time zones)",
      "description": "Northwind builds data APIs for logistics teams. You will design and run our Python services. You have 5+ years of experience with Python and PostgreSQL. Fully remote within European time zones. We can't sponsor visas. Salary EUR 75,000-90,000."
    },
    {
      "title": "Werkstudent Marketing (m/w/d)",
      "companyName": "Alpenblick Reisen GmbH",
      "location": "München",
      "description": "Du unterstützt unser Marketing-Team 16-20 Stunden pro Woche vor Ort in München: Social Media, Newsletter und Kampagnen. Du studierst Marketing oder BWL."
    },
    {
      "title": "Registered Nurse - Intensive Care",
      "companyName": "Lakeside General Hospital",
      "location": "Leeds, UK",
      "description": "Join our 20-bed ICU. At least 2 years of ICU experience and NMC registration required. Visa sponsorship and a relocation package are available for international nurses. Full-time, rotating shifts."
    }
  ],
  "keepOriginalFields": false
}
```

# Actor output Schema

## `taggedPostings` (type: `string`):

No description

## `summary` (type: `string`):

No description

## `errors` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "items": [
        {
            "title": "Senior Backend Engineer (Python)",
            "companyName": "Northwind Analytics",
            "location": "Remote (EU time zones)",
            "description": "Northwind builds data APIs for logistics teams. You will design and run our Python services. You have 5+ years of experience with Python and PostgreSQL. Fully remote within European time zones. We can't sponsor visas. Salary EUR 75,000-90,000."
        },
        {
            "title": "Werkstudent Marketing (m/w/d)",
            "companyName": "Alpenblick Reisen GmbH",
            "location": "München",
            "description": "Du unterstützt unser Marketing-Team 16-20 Stunden pro Woche vor Ort in München: Social Media, Newsletter und Kampagnen. Du studierst Marketing oder BWL."
        },
        {
            "title": "Registered Nurse - Intensive Care",
            "companyName": "Lakeside General Hospital",
            "location": "Leeds, UK",
            "description": "Join our 20-bed ICU. At least 2 years of ICU experience and NMC registration required. Visa sponsorship and a relocation package are available for international nurses. Full-time, rotating shifts."
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("glidepath/job-postings-ai-tagger").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "items": [
        {
            "title": "Senior Backend Engineer (Python)",
            "companyName": "Northwind Analytics",
            "location": "Remote (EU time zones)",
            "description": "Northwind builds data APIs for logistics teams. You will design and run our Python services. You have 5+ years of experience with Python and PostgreSQL. Fully remote within European time zones. We can't sponsor visas. Salary EUR 75,000-90,000.",
        },
        {
            "title": "Werkstudent Marketing (m/w/d)",
            "companyName": "Alpenblick Reisen GmbH",
            "location": "München",
            "description": "Du unterstützt unser Marketing-Team 16-20 Stunden pro Woche vor Ort in München: Social Media, Newsletter und Kampagnen. Du studierst Marketing oder BWL.",
        },
        {
            "title": "Registered Nurse - Intensive Care",
            "companyName": "Lakeside General Hospital",
            "location": "Leeds, UK",
            "description": "Join our 20-bed ICU. At least 2 years of ICU experience and NMC registration required. Visa sponsorship and a relocation package are available for international nurses. Full-time, rotating shifts.",
        },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("glidepath/job-postings-ai-tagger").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "items": [
    {
      "title": "Senior Backend Engineer (Python)",
      "companyName": "Northwind Analytics",
      "location": "Remote (EU time zones)",
      "description": "Northwind builds data APIs for logistics teams. You will design and run our Python services. You have 5+ years of experience with Python and PostgreSQL. Fully remote within European time zones. We can'\''t sponsor visas. Salary EUR 75,000-90,000."
    },
    {
      "title": "Werkstudent Marketing (m/w/d)",
      "companyName": "Alpenblick Reisen GmbH",
      "location": "München",
      "description": "Du unterstützt unser Marketing-Team 16-20 Stunden pro Woche vor Ort in München: Social Media, Newsletter und Kampagnen. Du studierst Marketing oder BWL."
    },
    {
      "title": "Registered Nurse - Intensive Care",
      "companyName": "Lakeside General Hospital",
      "location": "Leeds, UK",
      "description": "Join our 20-bed ICU. At least 2 years of ICU experience and NMC registration required. Visa sponsorship and a relocation package are available for international nurses. Full-time, rotating shifts."
    }
  ]
}' |
apify call glidepath/job-postings-ai-tagger --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,glidepath/job-postings-ai-tagger"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/6JqwS3nNbWE0DBlb9/builds/6xFogZTib3IqO9CCy/openapi.json
