# Job Titles to Seniority & Department - Classify Leads in Bulk (`that_red_bird/job-title-classifier`) Actor

Turn messy job titles into seniority (C-level, VP, Director, Manager, IC), department and a decision-maker flag. Works on any LinkedIn or leads dataset, or as a per-call tool for AI agents.

- **URL**: https://apify.com/that_red_bird/job-title-classifier.md
- **Developed by:** [mohamed alaya](https://apify.com/that_red_bird) (community)
- **Categories:** Lead generation, Automation, MCP servers
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.30 / 1,000 title classifieds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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 Titles to Seniority & Department - Classify Leads in Bulk

Turns messy job titles like "Sr. Mktg Mgr" or "Co-Founder & CEO" into a seniority level (Founder/Owner, C-level, Partner, VP, Director, Manager, Senior IC, IC, Intern/Entry), a department and a decision-maker flag.
Made for sales and lead-gen teams who have thousands of titles from LinkedIn or a leads finder and need to filter to the people who can buy.

Rules only, no AI model, no network: the same title always gets the same answer, and it costs a fraction of a cent.

### Quick start (3 copy-paste examples)

**1. See it work (empty input):** run with `{}`. It classifies 8 sample titles in English, French, Spanish and German (free).

**2. A list of titles:**

```json
{ "titles": ["Sr. Mktg Mgr", "Co-Founder & CEO", "Directeur Commercial", "Leiter Entwicklung", "Gerente de Ventas"] }
```

**3. A whole lead dataset (pick the dataset, name the column):**

```json
{ "datasetId": "YOUR_DATASET_ID", "titleField": "jobTitle", "keepOriginalFields": true }
```

### Use it with (pipelines)

- `harvestapi` LinkedIn profile / employee scrapers -> this Actor. Set `titleField` to `position` or `headline`; if a row lacks the field, `jobTitle`, `position`, `currentPosition[0].position` and `headline` are tried.
- Any leads-finder or Google Maps + contact enrichment output -> this Actor -> filter on `isDecisionMaker`.
- Per-call use from an AI agent: run the Actor with a short `titles` list and read the dataset.

### Input guide

| Field | What it does |
|---|---|
| Job titles | One title per line. Ignored when a dataset is picked. |
| Lead dataset | Dataset with a title column (picker). |
| Title column name | Field holding the title, default `title`. |
| Keep the original columns | Copies the original row next to the new columns. |
| Maximum titles | Safety cap, default 10,000. |

### Output

| Field | Meaning |
|---|---|
| `inputTitle`, `normalizedTitle` | What you gave and the cleaned text the rules saw. |
| `seniority`, `seniorityRank` | Founder/Owner (1), C-level (2), Partner (3), VP (4), Director (5), Manager (6), Senior IC (7), IC (8), Intern/Entry (9), Unknown (10). |
| `department`, `departments` | Executive, Sales, Marketing, Engineering, Product, Design, IT, Data, Finance, HR, Operations, Legal, Customer Success, Procurement, Healthcare, Education, Research, Consulting, Admin or Other. |
| `isDecisionMaker` | True for Founder/Owner, C-level, Partner, VP and Director. |
| `buyingRole` | `decision-maker`, `influencer` (managers), `practitioner` or `unknown`. |
| `confidence` | high / medium / low. |
| `language` | Detected title language (en, fr, es, de). |
| `isFormer` | True for "Former CEO" style titles (then not a decision-maker). |
| `matchedRules` | Which rules fired, for debugging. |

Sample row:

```json
{ "type": "title", "inputTitle": "Co-Founder & CEO", "normalizedTitle": "co founder & ceo", "seniority": "Founder/Owner",
  "seniorityRank": 1, "department": "Executive", "departments": ["Executive"], "isDecisionMaker": true,
  "buyingRole": "decision-maker", "confidence": "high", "language": "en", "isFormer": false, "matchedRules": ["founder", "c-level"] }
```

### How it decides

1. The title is cleaned (accents, punctuation, "at Company" removed) and abbreviations are expanded (Sr, Mgr, Mktg, Dir, Eng, Ops and others).
2. It is split on `&`, `,`, `|`, "and/et/y/und"; each part is classified by an ordered rule dictionary and the **highest** seniority wins ("Co-Founder & CEO" is Founder/Owner).
3. Special cases come first: interns and trainees, assistants ("Assistant to the CEO" is an IC), "Assistant Vice President", "Product Owner", "Chief of Staff", "Vice President" (never read as President).
4. Department comes from a keyword dictionary in priority order, per part; the first department found in reading order is the main one.

Languages: English, French, Spanish and German (including German compounds such as Vertriebsleiter, Teamleiter, Marketingmanager, Finanzvorstand).

### Measured accuracy (hand-labeled test set written by the author)

Run `node test/accuracy.js` and `node test/accuracy.js --set=heldout_titles.tsv` to reproduce.

- **Set 1, 195 titles** (written while building the rules, so optimistic): seniority 100%, department 100%, decision-maker 100% after fixing three bugs it exposed (first run: department 98.5%).
- **Set 2, 60 titles written after the rules were frozen** (the fairer number): first run seniority 100%, **department 93.3%**, decision-maker 100%; four department misses were fixed and the set is now 100%, which is tuned, not independent.
- Both sets were labeled by the same person who wrote the rules, with the labeling conventions in this README (for example any "... Manager" is Manager, "Head of ..." is Director, "General Manager" is Director). On real LinkedIn data with unusual titles, expect lower accuracy than these numbers; it has not been measured on a third-party labeled set.

### Price

Pay per event: **$0.0003 per title classified**. Empty or placeholder titles ("N/A", "-") and the free demo are never charged.
Example: 10,000 titles = 10,000 x $0.0003 = **$3.00**.

### FAQ

**Why rules and not AI?** Same answer every time, nothing sent anywhere, and fast and cheap enough for 100,000 titles.

**Is a Manager a decision-maker?** The flag is true from Director up. Managers are marked `influencer` in `buyingRole`, since budget authority varies by company.

### Honest limits

- Only EN, FR, ES and DE vocabulary. Other languages fall back to "IC" or "Unknown" with low confidence.
- Titles are ambiguous: "Principal", "Associate", "Lead" and "Partner" mean different things in different industries; the rules pick the most common reading.
- "Manager" always maps to Manager, even where it is an individual-contributor title (some "Product Manager" roles).
- A part after "|" such as a tagline or company name can add a wrong department; the seniority is unaffected.
- Company size is not considered: a CEO of a 2-person firm and of a bank get the same level.

# Actor input Schema

## `titles` (type: `array`):

One job title per line, for example 'Sr. Mktg Mgr' or 'Co-Founder & CEO'. Leave the sample as it is to see how it works. Ignored when you pick a dataset below.

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

Pick a dataset that has a job title column, for example the output of a LinkedIn or leads-finder scraper. When set, the list above is ignored and every row of the dataset is classified.

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

Name of the field that holds the job title in your dataset, for example 'title', 'jobTitle' or 'position'. If a row does not have it, common alternatives (jobTitle, position, headline, currentPosition\[0].position) are tried. Only used with a dataset.

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

Copy every original column of the dataset row into the output next to the new seniority, department and decision-maker columns. Turn off for a slim output.

## `maxTitles` (type: `integer`):

Safety cap on how many titles or dataset rows are classified in one run.

## Actor input object example

```json
{
  "titles": [
    "Co-Founder & CEO",
    "Sr. Mktg Mgr",
    "VP, Growth & Ops",
    "Directrice des Ressources Humaines",
    "Gerente de Ventas",
    "Vertriebsleiter",
    "Senior Software Engineer",
    "Assistant to the CEO"
  ],
  "titleField": "title",
  "keepOriginalFields": true,
  "maxTitles": 10000
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `downloadCsv` (type: `string`):

No description

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

No description

## `count` (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 = {
    "titles": [
        "Co-Founder & CEO",
        "Sr. Mktg Mgr",
        "VP, Growth & Ops",
        "Directrice des Ressources Humaines",
        "Gerente de Ventas",
        "Vertriebsleiter",
        "Senior Software Engineer",
        "Assistant to the CEO"
    ],
    "titleField": "title",
    "keepOriginalFields": true,
    "maxTitles": 10000
};

// Run the Actor and wait for it to finish
const run = await client.actor("that_red_bird/job-title-classifier").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 = {
    "titles": [
        "Co-Founder & CEO",
        "Sr. Mktg Mgr",
        "VP, Growth & Ops",
        "Directrice des Ressources Humaines",
        "Gerente de Ventas",
        "Vertriebsleiter",
        "Senior Software Engineer",
        "Assistant to the CEO",
    ],
    "titleField": "title",
    "keepOriginalFields": True,
    "maxTitles": 10000,
}

# Run the Actor and wait for it to finish
run = client.actor("that_red_bird/job-title-classifier").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 '{
  "titles": [
    "Co-Founder & CEO",
    "Sr. Mktg Mgr",
    "VP, Growth & Ops",
    "Directrice des Ressources Humaines",
    "Gerente de Ventas",
    "Vertriebsleiter",
    "Senior Software Engineer",
    "Assistant to the CEO"
  ],
  "titleField": "title",
  "keepOriginalFields": true,
  "maxTitles": 10000
}' |
apify call that_red_bird/job-title-classifier --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,that_red_bird/job-title-classifier"
        }
    }
}
```

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/hTvbICftgi8tPam18/builds/112vgIshO8A0hOI1N/openapi.json
