# H-1B LCA & PERM Wage Data API – Employer Salaries (US DOL) (`groundplanes/dol-h1b-lca-employer-wages`) Actor

Employer wages from US DOL H-1B LCA & PERM disclosure files: employer, job title, worksite, offered wage, decision date. For immigration, staffing & salary research.
Free sample (100 rows, real output): https://drive.google.com/uc?export=download\&id=1KYPawajTeWfvgxUcHDtBwaqXC3MiQrUv

- **URL**: https://apify.com/groundplanes/dol-h1b-lca-employer-wages.md
- **Developed by:** [Groundplane Labs](https://apify.com/groundplanes) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.40 / 1,000 cases

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

## H-1B LCA & PERM Employer Salary Data Scraper (US DOL)

Filter official quarterly U.S. Department of Labor (DOL) Office of Foreign Labor Certification (OFLC) disclosure workbooks (`LCA_Disclosure_Data_FY*.xlsx` and `PERM_Disclosure_Data_FY*.xlsx`) by sponsoring employer, job title keyword, SOC occupation code, worksite state, case status, and minimum annualized offered wage.

### Free sample

Download a free sample (100 rows, real actor output, CSV): https://drive.google.com/uc?export=download\&id=1KYPawajTeWfvgxUcHDtBwaqXC3MiQrUv — check the columns and data quality before you run anything.

### What it does

Every employer sponsoring an H-1B, H-1B1, or E-3 specialty occupation worker must file a Labor Condition Application (Form ETA-9035/9035E), and employers sponsoring employment-based green cards file Application for Permanent Employment Certification (Form ETA-9089 / PERM). The DOL OFLC publishes cumulative fiscal-year disclosure data **quarterly**, but the raw `.xlsx` files exceed 150–240 MB and contain nearly 100 columns whose names shift across fiscal years.

This Actor:

- Automatically discovers and streams the newest quarterly `LCA_Disclosure_Data_FY*.xlsx` or `PERM_Disclosure_Data_FY*.xlsx` disclosure file from `dol.gov/agencies/eta/foreign-labor/performance` to temporary disk.
- Parses the workbook with a streaming reader built for these files (the sheet XML alone can exceed 1 GB) and maps columns through a cross-year alias dictionary (`WAGE_RATE_OF_PAY_FROM` vs `WAGE_RATE_OF_PAY_FROM_1`, etc.). If a core column disappears from a future DOL file, the run fails loudly naming the missing column instead of returning rows with silently empty fields.
- Annualizes hourly (`×2,080`), weekly (`×52`), bi-weekly (`×26`), and monthly (`×12`) wage rates into `wageAnnualFromUsd` and `prevailingWageAnnualUsd` for consistent salary filtering and comparison.
- **Prevailing wage is an LCA-only field.** DOL OFLC does **not** publish a prevailing-wage amount in the PERM disclosure file (the official PERM record layout contains only the offered wage, `JOB_OPP_WAGE_*`), so `prevailingWage` and `prevailingWageAnnualUsd` are **always `null` on PERM rows** — use `wageFrom` / `wageAnnualFromUsd` for PERM salary benchmarking. PERM runs say this in their final status message so the empty fields are never a surprise.
- Keeps employer point-of-contact and immigration attorney fields (`pocName`, `pocEmail`, `pocPhone`, `attorneyName`, `attorneyEmail`) **off by default** (`includeContacts: false`), outputting them only when explicitly requested.

Found a problem or need a field? Open an issue on the Issues tab; we respond quickly.

### Who it's for

- **Compensation, people-analytics, and recruiting teams** benchmarking verified base salaries — and, for LCA cases, prevailing wage levels — by job title, SOC code, and worksite city/state.
- **HRTech, global mobility, and relocation providers** identifying companies that actively sponsor H-1B and PERM talent.
- **Corporate immigration law firms** analyzing employer sponsorship volumes and outside counsel representation (`lawFirmName`).
- **Job seekers and labor economists** researching visa-sponsoring employers and salary ranges.

### Input

- **`programType`** *(string enum)* — `"LCA_H1B"` (default; H-1B, H-1B1, and E-3 Labor Condition Applications) or `"PERM"` (Permanent Employment Certification).
- **`employerNames`** *(array of strings)* — Optional list of case-insensitive employer name substrings (e.g. `["Intel", "American Express"]`). Default: `[]` (all employers).
- **`jobTitleKeyword`** *(string)* — Case-insensitive keyword matched against `jobTitle`. Default: `"software"`.
- **`socCodePrefix`** *(string)* — Optional Standard Occupational Classification prefix (e.g. `"15-"` for Computer and Mathematical Occupations). Default: `""`.
- **`worksiteStates`** *(array of strings)* — Two-letter US state codes for the worksite. Default: `["AZ"]`. Leave empty (`[]`) for all states.
- **`caseStatuses`** *(array of strings)* — Case statuses to include (`"Certified"`, `"Certified - Withdrawn"`, `"Certified-Expired"`, `"Denied"`, `"Withdrawn"`). Default: `["Certified"]`.
- **`minWageAnnualUsd`** *(integer)* — Minimum annualized offered base wage in USD. Default: `0`.
- **`decisionDateFrom`** *(string, YYYY-MM-DD)* — Optional earliest decision date filter. Default: `""`.
- **`includeContacts`** *(boolean)* — Defaults to `false` to omit employer point-of-contact and attorney contact fields (`pocName`, `pocEmail`, `pocPhone`, `attorneyName`, `attorneyEmail`).
- **`maxItems`** *(integer)* — Maximum rows to return. Default: `200`.

#### Example input JSON

```json
{
  "programType": "LCA_H1B",
  "employerNames": [],
  "jobTitleKeyword": "software",
  "socCodePrefix": "",
  "worksiteStates": ["AZ"],
  "caseStatuses": ["Certified"],
  "minWageAnnualUsd": 0,
  "decisionDateFrom": "",
  "includeContacts": false,
  "maxItems": 200
}
```

#### Example run options (default max charge $50)

Set a default maximum charge of **$50** (`"maxTotalChargeUsd": 50`) in your run options (the Actor's default memory is preconfigured to 4,096 MB in `actor.json`):

```json
{
  "maxTotalChargeUsd": 50,
  "memoryMbytes": 4096
}
```

### Output

Results are deduplicated by `caseNumber` and sorted newest first by `decisionDate` descending.

| Field | Type | Description |
|---|---|---|
| `caseNumber` | `string` | Unique DOL OFLC case number. |
| `programType` | `string` | `"LCA_H1B"` or `"PERM"`. |
| `caseStatus` | `string \| null` | Case adjudication status (`Certified`, `Denied`, `Withdrawn`, etc.). |
| `receivedDate` | `string \| null` | Application filing date (`YYYY-MM-DD`). |
| `decisionDate` | `string \| null` | Adjudication decision date (`YYYY-MM-DD`). |
| `employerName` | `string \| null` | Legal business name of the sponsoring employer. |
| `employerCity` | `string \| null` | Employer headquarters city. |
| `employerState` | `string \| null` | Employer headquarters 2-letter state code. |
| `employerFein` | `string \| null` | Employer FEIN if published in the disclosure file (`null` when redacted by DOL). |
| `jobTitle` | `string \| null` | Offered job title. |
| `socCode` | `string \| null` | SOC occupational code (e.g. `15-1252.00`). |
| `socTitle` | `string \| null` | SOC occupational title (e.g. `Software Developers`). |
| `fullTime` | `boolean \| null` | Whether the position is full-time (`true`/`false`). |
| `wageFrom` | `number \| null` | Offered base wage (lower bound). |
| `wageTo` | `number \| null` | Offered base wage (upper bound, if range). |
| `wageUnit` | `string \| null` | Pay unit (`Year`, `Hour`, `Month`, `Bi-Weekly`, `Week`). |
| `wageAnnualFromUsd` | `number \| null` | Annualized offered base wage in USD. |
| `prevailingWage` | `number \| null` | Prevailing wage rate on the application. **LCA only** — DOL does not publish a prevailing wage for PERM, so PERM rows are always `null`. |
| `prevailingWageAnnualUsd` | `number \| null` | Annualized prevailing wage in USD. **LCA only** — always `null` for PERM. |
| `worksiteCity` | `string \| null` | Primary worksite city. |
| `worksiteState` | `string \| null` | Primary worksite 2-letter state code. |
| `lawFirmName` | `string \| null` | Representing immigration law firm business name. |
| `sourceFile` | `string` | Name of the DOL OFLC disclosure workbook parsed. |
| `pocName` | `string \| null` | Employer point-of-contact name (only when `includeContacts` is `true`). |
| `pocEmail` | `string \| null` | Employer point-of-contact email (only when `includeContacts` is `true`). |
| `pocPhone` | `string \| null` | Employer point-of-contact phone (only when `includeContacts` is `true`). |
| `attorneyName` | `string \| null` | Representing attorney name (only when `includeContacts` is `true`). |
| `attorneyEmail` | `string \| null` | Representing attorney email (only when `includeContacts` is `true`). |

#### Sample output row (synthetic example)

```json
{
  "caseNumber": "I-200-26150-999999",
  "programType": "LCA_H1B",
  "caseStatus": "Certified",
  "receivedDate": "2026-06-01",
  "decisionDate": "2026-06-08",
  "employerName": "EXAMPLE SEMICONDUCTOR SYSTEMS INC",
  "employerCity": "CHANDLER",
  "employerState": "AZ",
  "employerFein": "99-9999999",
  "jobTitle": "Senior Systems Software Engineer",
  "socCode": "15-1252.00",
  "socTitle": "Software Developers",
  "fullTime": true,
  "wageFrom": 168000.0,
  "wageTo": 195000.0,
  "wageUnit": "Year",
  "wageAnnualFromUsd": 168000.0,
  "prevailingWage": 146200.0,
  "prevailingWageAnnualUsd": 146200.0,
  "worksiteCity": "CHANDLER",
  "worksiteState": "AZ",
  "lawFirmName": "Example Global Immigration LLP",
  "sourceFile": "LCA_Disclosure_Data_FY2026_Q3.xlsx"
}
```

### How pricing works

Pay per result. You only pay for rows returned. Set `"maxTotalChargeUsd": 50` in your run options to cap spend automatically.

### Data source and terms

- **Official source:** [U.S. Department of Labor ETA — Office of Foreign Labor Certification (OFLC) Performance Data](https://www.dol.gov/agencies/eta/foreign-labor/performance).
- **Update frequency:** Cumulative fiscal-year disclosure files are published **quarterly** by DOL OFLC (not daily or real-time).
- **Affiliation disclaimer:** This Actor is an independent tool and is **not affiliated with or endorsed by the U.S. Department of Labor (DOL) or the Employment and Training Administration (ETA)**.

### Compliance

When `includeContacts` is enabled, the dataset includes corporate employer point-of-contact and immigration counsel contact fields published in the official DOL OFLC disclosure files. Buyers are solely responsible for complying with the Telephone Consumer Protection Act (TCPA), the National Do-Not-Call Registry, the CAN-SPAM Act, and all applicable federal and state privacy laws before any business outreach. Do not use this Actor for consumer or individual targeting.

### Troubleshooting

- **The run ends PARTIAL with a "stale" note.** DOL OFLC publishes cumulative disclosure files quarterly, so the newest decisions in the file can be weeks or months old. When the newest decision in the file is more than ~4 months old, the run reports PARTIAL and tells you the exact date, rather than calling old data current.
- **`prevailingWage` is empty on PERM rows.** That's the source, not a bug: DOL OFLC does not publish a prevailing-wage amount in the PERM disclosure file. Prevailing-wage fields are populated for LCA runs only; PERM runs note this in their final status message.
- **The run ends with "0 cases matched your filters".** The whole file was scanned and nothing matched. Widen `worksiteStates`, clear `employerNames` / `jobTitleKeyword` / `socCodePrefix`, or lower `minWageAnnualUsd`. An empty result is a completed run, not an error.
- **The run stops early at the max charge limit.** Results are newest-first, so you already have the most recent cases. Raise `maxTotalChargeUsd` in the run options to get more.
- **Canary / smoke-test input:** `{ "maxItems": 5 }` with the other defaults (LCA, "software", AZ, Certified). A healthy canary returns 5 rows and ends Complete, or PARTIAL with the stale note above while the current quarterly file is old.

### Limits and FAQ

- **Why is memory set to 4,096 MB?**
  Quarterly DOL OFLC `.xlsx` workbooks are 150–240+ MB compressed Archives containing 400,000+ rows and a large XML shared-strings table. Running with 4 GB RAM (`"memoryMbytes": 4096`) prevents out-of-memory errors while streaming rows.
- **How are hourly or monthly wages compared against `minWageAnnualUsd`?**
  The Actor converts `Hour` (`× 2,080`), `Week` (`× 52`), `Bi-Weekly` (`× 26`), and `Month` (`× 12`) pay units into an annualized USD figure (`wageAnnualFromUsd`).

### Changelog

- **v0.4 (2026-10-27):** docs: free-sample section + store refresh; no code changes.

- **v0.3 (2026-10-27):** Red Team fix round. PERM prevailing wage is now disclosed instead of silently empty: DOL does not publish it for PERM, the README and PERM run status say so, and the prevailing-wage amount/unit pair is guarded (an LCA file missing either fails loudly; the prevailing wage is never annualized with the offered wage's unit). `worksiteCity` and `socCode` joined the core-column drift check (a rename could silently lose rows / zero a filtered run). Charge-limit PARTIALs carry the Issues CTA only when a source-caused reason coexists. The post-push dataset count check now reads the real stored count (it previously read 0 on every run and warned spuriously). A workbook with a missing shared-strings table fails with a schema-drift diagnosis, and an empty/truncated source file fails instead of reporting "0 cases matched".

- **v0.2 (2026-10-26):** Hardening pass. Core-column schema-drift check (a renamed DOL column now fails the run loudly instead of silently nulling fields); state prefilter can no longer drop every row if the file's string table changes; zero-match runs complete honestly instead of failing; charge-limit stops and stale quarterly files report PARTIAL with the reason; dataset count verified after push.

- **v0.1 (2026-10-23):** Initial release supporting dynamic quarterly LCA & PERM `.xlsx` discovery, cross-year column aliases, wage annualization, and contact privacy controls.

# Actor input Schema

## `programType` (type: `string`):

Select H-1B / H-1B1 / E-3 Labor Condition Applications (LCA_H1B) or Permanent Employment Certification (PERM).

## `employerNames` (type: `array`):

Optional list of employer name substrings (case-insensitive) to match. Leave empty for all employers.

## `jobTitleKeyword` (type: `string`):

Case-insensitive keyword matched against jobTitle (e.g. 'software', 'engineer', 'analyst'). Leave empty for all job titles.

## `socCodePrefix` (type: `string`):

Optional Standard Occupational Classification prefix (e.g. '15-' for Computer and Mathematical Occupations).

## `worksiteStates` (type: `array`):

Two-letter US state codes for the worksite location (e.g. \['AZ', 'CA', 'TX']). Leave empty for all states.

## `caseStatuses` (type: `array`):

OFLC case statuses to include.

## `minWageAnnualUsd` (type: `integer`):

Minimum annualized offered wage in USD (hourly wages are annualized x2080, weekly x52, bi-weekly x26, monthly x12).

## `decisionDateFrom` (type: `string`):

Optional earliest CASE_STATUS decision date in YYYY-MM-DD format.

## `includeContacts` (type: `boolean`):

Defaults to false so employer POC and attorney contact fields (pocName, pocEmail, pocPhone, attorneyName, attorneyEmail) are omitted unless explicitly enabled.

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

Maximum number of LCA or PERM case rows to return.

## Actor input object example

```json
{
  "programType": "LCA_H1B",
  "employerNames": [],
  "jobTitleKeyword": "software",
  "socCodePrefix": "",
  "worksiteStates": [
    "AZ"
  ],
  "caseStatuses": [
    "Certified"
  ],
  "minWageAnnualUsd": 0,
  "includeContacts": false,
  "maxItems": 200
}
```

# Actor output Schema

## `results` (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 = {
    "programType": "LCA_H1B",
    "employerNames": [],
    "jobTitleKeyword": "software",
    "socCodePrefix": "",
    "worksiteStates": [
        "AZ"
    ],
    "caseStatuses": [
        "Certified"
    ],
    "minWageAnnualUsd": 0,
    "includeContacts": false,
    "maxItems": 200
};

// Run the Actor and wait for it to finish
const run = await client.actor("groundplanes/dol-h1b-lca-employer-wages").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 = {
    "programType": "LCA_H1B",
    "employerNames": [],
    "jobTitleKeyword": "software",
    "socCodePrefix": "",
    "worksiteStates": ["AZ"],
    "caseStatuses": ["Certified"],
    "minWageAnnualUsd": 0,
    "includeContacts": False,
    "maxItems": 200,
}

# Run the Actor and wait for it to finish
run = client.actor("groundplanes/dol-h1b-lca-employer-wages").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 '{
  "programType": "LCA_H1B",
  "employerNames": [],
  "jobTitleKeyword": "software",
  "socCodePrefix": "",
  "worksiteStates": [
    "AZ"
  ],
  "caseStatuses": [
    "Certified"
  ],
  "minWageAnnualUsd": 0,
  "includeContacts": false,
  "maxItems": 200
}' |
apify call groundplanes/dol-h1b-lca-employer-wages --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,groundplanes/dol-h1b-lca-employer-wages"
        }
    }
}
```

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/OJAzut5kmXszOjYAk/builds/smkyjCpzBfx68U1Mx/openapi.json
