# Prevailing Wage Levels: H-1B & PERM Wage by Job Title and City (`northbell/prevailing-wage-levels`) Actor

Prevailing wage levels from the US Department of Labor (OFLC, from BLS OEWS): all four levels for any job title or SOC code and US city, yearly and hourly, and which level your salary reaches. For H-1B (H1B) salary and LCA checks, PERM and pay checks. No login, no key. Unmatched lookups cost $0.

- **URL**: https://apify.com/northbell/prevailing-wage-levels.md
- **Developed by:** [Northbell](https://apify.com/northbell) (community)
- **Categories:** Jobs, Business
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.40 / 1,000 lookups

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

## Prevailing Wage Levels: H-1B & PERM Wage by Job Title and City

**Type a job title and a US city, get the four prevailing wage levels the US Department of Labor sets for it, and see which level a salary reaches.** The levels are the ones an employer must meet in H-1B (LCA) and PERM filings, from the DOL's OFLC wage library (built from the BLS OEWS survey). Yearly and hourly, with the occupation and area the title and city were matched to, so you can check us. No login, no API key.

**Try it now — the input is already filled in.** Press **Start**: three lookups come back in a few seconds, for about $0.02.

| input | occupation | area | level I | level II | level III | level IV | salary reaches |
| --- | --- | --- | --- | --- | --- | --- | --- |
| Software Engineer | Austin, TX | 150000 | Software Developers | Austin-Round Rock-San Marcos, TX | 95,888 | 119,642 | 143,395 | 167,149 | level III (+4.6% over III) |
| Registered Nurse | Houston, TX | 48/h | Registered Nurses | Houston-Pasadena-The Woodlands, TX | 75,150 | 87,651 | 100,173 | 112,674 | level II (−0.3% under III) |
| 15-2051 | New York, NY | Data Scientists | New York-Newark-Jersey City, NY-NJ | 87,256 | 115,835 | 144,414 | 172,994 | – |

Wage year July 2026 – June 2027, yearly USD.

### What you can ask

One lookup per line: `job title | city, ST | salary`.

- **Job title or SOC code.** A title is matched to an occupation through O*NET's list of about 44,000 job titles people actually use, after dropping words such as Senior, II or Remote (`Senior Software Engineer II` → Software Developers). A title O*NET gives equally to two or more occupations is not guessed: the row lists them (`candidateOccupations`, at $0) so you can put the right SOC code (`15-1252`) instead. A posting title that finds nothing as written (`Java Backend Developer with Blockchain Exp @ Dallas, TX`, `Test Lead :: Onsite :: W2 Position`) is tried again one piece at a time, without words such as W2, C2C or Onsite; a title that matched as written is never changed.
- **City, state.** A metro area's city (`Austin, TX`, `Greater Boston`) or any town, matched to its area through its county (`Conroe, TX` → Houston; `Mountain View, CA` → San Jose), townships included (`Edison, NJ` → New York; `West Chester, OH` → Cincinnati). An OFLC area code (`12420`) works too. A state or "United States" alone is not an area.
- **Salary (optional).** `150000`, `150k`, or `72/h` for hourly (a number under 500 is read as hourly). You get the highest level it meets (`salaryReachesLevel`, 0 = below level I) and how far it is from level III, the median.

**How often a title is matched (measured 2026-11-04 on Apify).** 80 job titles as postings write them (Senior Software Development Engineer, Tax Accountant, Mechanical Engineer II, Physical Therapist (PRN)…), each with a US city: 62 matched and charged, 5 matched through a one-source title at $0, 13 not matched at $0 (most with candidate SOC codes). Among the 62 charged, two we would argue with: Marketing Coordinator read as Public Relations Specialists, and Principal Product Manager as Marketing Managers (also how many employers file it). When you know the SOC code, put it instead of the title. On 200 titles from visa-sponsoring job postings (2026-11-04), trying the pieces raised the matched lines from 66 to 83 (charged from 48 to 57); we read every changed line, and a state in the title (`SDET - TX, MA, NC`) is not read as a job.

### What you get

| field | meaning |
|---|---|
| `occupation`, `occupationCode`, `matchedTitle` | the occupation (SOC 2018) and the O*NET title used for the match |
| `area`, `areaCode` | the metro or non-metro area |
| `level1` … `level4` | yearly USD: level I about the 17th percentile, II the 34th, III the median, IV the 67th |
| `level1Hourly` … `level4Hourly` | the same per hour (null for occupations paid yearly only, such as teachers) |
| `salary`, `salaryReachesLevel`, `salaryVsLevel3Pct` | your salary as a yearly figure, the level it reaches and its distance from level III |
| `titleFromOneSource` | true when the title is one O*NET has from a single source: the levels are given at $0, check the occupation |
| `fromWiderArea` | true when OFLC used a wider area's data because this one had too few survey responses |
| `matched`, `reason` | false and why, when a line could not be matched |
| `candidateOccupations` | for a title that is several occupations, those occupations and their SOC codes |
| `wageYear`, `source` | the wage year and where every number comes from |

### What you never pay for

- A line that could not be matched (title, place or salary not understood): it comes back with the reason, at $0.
- A line matched through a job title O\*NET has from only one source (`titleFromOneSource: true`). You still get its levels, but check the occupation: on our test of 45 posting titles, 4 of 11 such matches were the wrong one ("Account Executive" came back as Advertising and Promotions Managers).
- A run where nothing was charged: the run fee is charged only with the first charged lookup.

### Cost

$0.01 per run (with the first charged lookup) plus **$3.00 per 1,000 matched lookups** (a line matched through a one-source title is free). Nothing is fetched from other sites while it runs: the table is part of the Actor, so a run of 1,000 lookups takes seconds.

### Postings, not just levels

To put real job postings beside these levels, [LinkedIn Salary Scraper](https://apify.com/northbell/linkedin-jobs-salary-scraper) returns US jobs with their posted pay and the same four levels on every row (`officialWage`).

### Where the numbers come from

- **Wage levels:** US Department of Labor, Office of Foreign Labor Certification (OFLC) wage library, flag.dol.gov, built from the Bureau of Labor Statistics' Occupational Employment and Wage Statistics (OEWS) survey. Public domain. The table is refreshed when OFLC starts a new wage year on 1 July.
- **Job titles:** O*NET 30.0 Database by the U.S. Department of Labor, Employment and Training Administration (USDOL/ETA), used under the CC BY 4.0 license. O*NET® is a trademark of USDOL/ETA.
- **Places:** US Census Bureau, 2020 place-by-county list, joined to the counties OFLC lists for each area.

This is a lookup of published wage levels, not legal advice; the DOL's own prevailing wage determination is what counts in a filing.

# Actor input Schema

## `lookups` (type: `array`):

One per line: a job title (or a SOC code such as 15-1252), a US city as "city, ST" (a suburb is matched to its metro area through its county; an OFLC area code such as 12420 works too) and, optionally, a salary: 150000, 150k, or 72/h for hourly. Each line returns the four prevailing wage levels for that occupation and area and, when you give a salary, which level it reaches.

## Actor input object example

```json
{
  "lookups": [
    "Software Engineer | Austin, TX | 150000",
    "Registered Nurse | Houston, TX | 48/h",
    "15-2051 | New York, NY"
  ]
}
```

# Actor output Schema

## `levels` (type: `string`):

One row per lookup: job title, occupation, area, levels I to IV and the level the salary reaches.

## `all` (type: `string`):

Every row with every field, including lines that were not matched and why.

# 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 = {
    "lookups": [
        "Software Engineer | Austin, TX | 150000",
        "Registered Nurse | Houston, TX | 48/h",
        "15-2051 | New York, NY"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("northbell/prevailing-wage-levels").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 = { "lookups": [
        "Software Engineer | Austin, TX | 150000",
        "Registered Nurse | Houston, TX | 48/h",
        "15-2051 | New York, NY",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("northbell/prevailing-wage-levels").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 '{
  "lookups": [
    "Software Engineer | Austin, TX | 150000",
    "Registered Nurse | Houston, TX | 48/h",
    "15-2051 | New York, NY"
  ]
}' |
apify call northbell/prevailing-wage-levels --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,northbell/prevailing-wage-levels"
        }
    }
}
```

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/QqndZKFgCXyHx7CpW/builds/Ygg3TxCeSfguB7nPx/openapi.json
