# USCIS H-1B Employer Data: Approvals and Denials by Company (`yadroo/uscis-h1b-employers`) Actor

Official USCIS H-1B employer files as rows: fiscal year, employer, initial and continuing approvals and denials, totals and denial rate, NAICS sector with its name, tax-ID last four, city, state and ZIP. Filter by employer, state, city, industry, ZIP or petition volume. FY2009-FY2023, UTC dates.

- **URL**: https://apify.com/yadroo/uscis-h1b-employers.md
- **Developed by:** [Samat Makatov](https://apify.com/yadroo) (community)
- **Categories:** Business, Jobs, AI
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 96.6% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.40 / 1,000 employer rows

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

Official USCIS H-1B employer files as rows: fiscal year, employer, initial and continuing approvals and denials, totals and denial rate, NAICS sector with its name, tax-ID last four, city, state and ZIP. Filter by employer, state, city, industry, ZIP or petition volume. FY2009-FY2023, UTC dates.

This actor reads the H-1B Employer Data Hub files published by U.S. Citizenship and Immigration Services — one CSV per
fiscal year, no key and no account — and turns them into filtered, sorted dataset rows. One published row is one
employer in one fiscal year with the counts of workers USCIS **first decided** on: initial employment and continuing
employment, approvals and denials. On top of the published columns every row gets the totals, the number of decisions
and the denial rate, the plain name of the NAICS sector and the full name of the state, so a dataset reads without a
code table. Fiscal years 2009 through 2023 are published as files; a fiscal year runs 1 October to 30 September. No
API key, no proxy, no browser.

> **Need it tailored to your workflow?** Missing a field, a filter, another source or a different output format? [Open an issue](https://apify.com/yadroo/uscis-h1b-employers/issues) and describe what you need — we read every request and adapt the actor.

### Use cases

- **One company's H-1B record** — `employers: ["microsoft"]` with a fiscal year: every entity whose filed name
  contains that text, with approvals, denials and the denial rate. Case, punctuation and spaces are ignored, so
  `walmart` finds `WAL MART ASSOCIATES INC`. For journalists and analysts after a news story.
- **The biggest sponsors in a state** — `states: ["CA"]`, `minTotalApprovals: 50`, `sort: "approvalsDesc"`: the long
  alphabetical tail of one-petition employers disappears and the employers that sponsor at scale come first. For
  recruiters, relocation services and sales teams.
- **Where decisions went against the employer** — `naics: ["54"]`, `minTotalDenials: 1`, `sort: "denialsDesc"`, or
  `maxDenialRate: 0.05` for the other direction. For immigration lawyers and in-house mobility teams.
- **A five-year sponsor profile** — `mode: "employerTotals"` with five fiscal years: one row per employer with
  `yearCount`, `firstFiscalYear`, `lastFiscalYear` and the summed counts. For investor and policy research.
- **Sponsors in one city** — `cities: ["Seattle"]`: who files H-1B petitions from a city, largest first. For job
  seekers and local press.
- **A watch on a state or an industry** — `onlyNew: true`: each run writes only rows that were not written before, so
  you never pay twice for the same employer when you re-run a watch with wider filters, take the next `maxItems` rows
  of a long list, or add a fiscal year to it. The published files do not change, so a schedule on the same input
  writes rows until the list is exhausted; a fiscal year USCIS publishes later has to be chosen in `fiscalYears` once
  this actor lists it.

### Input

Every field is optional. A run with nothing but `fiscalYears` returns the top `maxItems` employers of that year by
approvals.

| Field | Type | Default | Allowed values / notes |
|---|---|---|---|
| `mode` | string (enum) | `byYear` | `byYear` = one row per employer per fiscal year, as published. `employerTotals` = one row per employer with the selected fiscal years summed up (adds `yearCount`, `firstFiscalYear`, `lastFiscalYear`, `fiscalYearsCovered`, and has no `fiscalYear`). Strict: the platform refuses any other value before the run starts. |
| `fiscalYears` | string\[] (enum) | prefill `["2023"]`, empty = FY2023 | One file per year: `2023` … `2009` ([all years](#fiscal-years)). Strict enum — FY2024 and later are refused before the run starts, because they are not published as files (see [Limits](#limits--faq)). Max 15 values. |
| `employers` | string\[] (free text) | prefill `["microsoft"]` | Matched **inside** the employer name as filed: `microsoft`, `deloitte`, `mayo clinic`. Case, punctuation and spaces are ignored — `walmart` finds `WAL MART ASSOCIATES INC`; several values are OR. The value that found a row is written to `matchedEmployer`. A value that matched nothing is named in the run's status message; a value written in another script (`日本電気株式会社`) matches nothing, because the files hold the names in ASCII capitals. Max 20 values. |
| `states` | string\[] (enum) | — | Two-letter codes of the employer's mailing address ([all 56 codes](#states-and-territories)), OR-combined. Strict enum. `stateName` is written to every row. |
| `cities` | string\[] (free text) | — | Whole city name: `Seattle`, `NEW YORK`, `san jose`. Case and extra spaces are ignored, several values are OR; `york` does **not** match `NEW YORK`. Unmatched values are named in the status message. Max 20. |
| `zipPrefixes` | string\[] (free text) | — | A full ZIP (`98101`) or its first 2-5 digits (`100` keeps every 100xx ZIP). Several values are OR; combined with `states`/`cities` as AND. Anything that is not 2-5 digits is refused with a message. Max 20. |
| `naics` | string\[] (enum) | — | Two-digit NAICS sectors ([all 25](#naics-sectors)), OR-combined. Strict enum. `99` is the file's own "industry unknown". `naicsLabel` is written to every row. |
| `minTotalApprovals` | integer | — | Keep rows with at least this many approved workers (`totalApprovals`). In `employerTotals` the threshold applies to the summed row. |
| `minTotalDenials` | integer | — | Keep rows with at least this many denied workers (`totalDenials`). `1` = every row where a decision went against the employer. |
| `maxDenialRate` | number | — | Keep rows whose `denialRate` is at most this share (`0.1` = refused in at most a tenth of the decisions). Rows with no decision at all have no rate and are dropped by this filter. |
| `sort` | string (enum) | `approvalsDesc` | `approvalsDesc`, `denialsDesc`, `denialRateDesc`, `employer`, `fiscalYear`. Strict enum. In `byYear` the order is applied inside each fiscal year (years are written newest first); in `employerTotals` to the whole result. |
| `maxItems` | integer | 50 | 1-5000. Hard cap on the rows this run writes, and therefore on what it costs. |
| `onlyNew` | boolean | `false` | Remember the rows written and, on the next run of the same actor or task, write only rows that were not written before — a rerun therefore returns the next rows of the list, not nothing. The published files do not change, so this is a way to avoid paying twice, not a freshness window: the files carry no per-row timestamp and a new fiscal year has to be chosen in `fiscalYears`. |
| `fields` | string\[] (free text) | all columns | Keep only these columns, **in this order**; `fiscalYear`, `employer`, `totalApprovals`, `totalDenials`, `url` and `fetchedAt` are always kept (first, unless you list them yourself, and then where you put them). Column names are matched ignoring case and underscores, and a few short names work (`approvals`, `denials`, `decisions`, `rate`, `industry`, `year`, `taxid`). A name that is none of these is reported in the run's status message with the closest column; if no name is a column at all, the run stops at once and names them. |

### Reference

#### Fiscal years

`2023`, `2022`, `2021`, `2020`, `2019`, `2018`, `2017`, `2016`, `2015`, `2014`, `2013`, `2012`, `2011`, `2010`, `2009`.

A fiscal year runs from 1 October of the previous calendar year to 30 September (FY2023 = 1 October 2022 to
30 September 2023). Each year is a separate file and a separate download. FY2023 is the newest year published as a
file; a value outside this list is refused by the platform before the run starts, with the list of allowed values.

#### NAICS sectors

| Code | Sector |
|---|---|
| `11` | Agriculture, forestry, fishing and hunting |
| `21` | Mining, quarrying, oil and gas extraction |
| `22` | Utilities |
| `23` | Construction |
| `31` | Manufacturing: food, textiles, apparel |
| `32` | Manufacturing: wood, paper, chemicals, plastics |
| `33` | Manufacturing: metals, machinery, electronics, vehicles |
| `42` | Wholesale trade |
| `44` | Retail trade: vehicles, building, food, health stores |
| `45` | Retail trade: clothing, sports, general and online stores |
| `48` | Transportation |
| `49` | Postal service, couriers and warehousing |
| `51` | Information: software publishing, telecoms, media |
| `52` | Finance and insurance |
| `53` | Real estate, rental and leasing |
| `54` | Professional, scientific and technical services (IT consulting, engineering, research) |
| `55` | Management of companies and enterprises |
| `56` | Administrative and support services (staffing agencies) |
| `61` | Educational services (universities, schools) |
| `62` | Health care and social assistance (hospitals, clinics) |
| `71` | Arts, entertainment and recreation |
| `72` | Accommodation and food services |
| `81` | Other services except public administration |
| `92` | Public administration |
| `99` | Industry unknown, as the file records it |

`naicsLabel` in every row carries the sector name from this table. Code `99` covers a large share of the smaller
employers: the file itself does not know their industry.

#### States and territories

`AL` Alabama · `AK` Alaska · `AZ` Arizona · `AR` Arkansas · `CA` California · `CO` Colorado · `CT` Connecticut ·
`DE` Delaware · `DC` District of Columbia · `FL` Florida · `GA` Georgia · `HI` Hawaii · `ID` Idaho · `IL` Illinois ·
`IN` Indiana · `IA` Iowa · `KS` Kansas · `KY` Kentucky · `LA` Louisiana · `ME` Maine · `MD` Maryland ·
`MA` Massachusetts · `MI` Michigan · `MN` Minnesota · `MS` Mississippi · `MO` Missouri · `MT` Montana · `NE` Nebraska ·
`NV` Nevada · `NH` New Hampshire · `NJ` New Jersey · `NM` New Mexico · `NY` New York · `NC` North Carolina ·
`ND` North Dakota · `OH` Ohio · `OK` Oklahoma · `OR` Oregon · `PA` Pennsylvania · `RI` Rhode Island ·
`SC` South Carolina · `SD` South Dakota · `TN` Tennessee · `TX` Texas · `UT` Utah · `VT` Vermont · `VA` Virginia ·
`WA` Washington · `WV` West Virginia · `WI` Wisconsin · `WY` Wyoming · `PR` Puerto Rico · `GU` Guam ·
`VI` U.S. Virgin Islands · `AS` American Samoa · `MP` Northern Mariana Islands.

The state is the employer's mailing address on the petition, not the worksite. Some rows of the oldest files carry no
state at all; those rows can never match a state filter.

#### Sort orders

- `approvalsDesc` — most approved workers first (the default; the published files are alphabetical).
- `denialsDesc` — most denied workers first.
- `denialRateDesc` — highest share of denials first; rows without a single decision go last.
- `employer` — employer name, A to Z.
- `fiscalYear` — newest fiscal year first, then most approvals.

### Examples

**One company in the newest published year**

```json
{ "mode": "byYear", "fiscalYears": ["2023"], "employers": ["microsoft"], "sort": "approvalsDesc", "maxItems": 25 }
```

Every Microsoft entity in FY2023 with its approvals, denials and denial rate.

**The biggest sponsors in California, with chosen columns**

```json
{
  "fiscalYears": ["2023"],
  "states": ["CA"],
  "minTotalApprovals": 50,
  "sort": "approvalsDesc",
  "maxItems": 25,
  "fields": ["employer", "state", "totalApprovals", "denialRate"]
}
```

Californian employers with 50 or more approved workers, largest first, narrowed to four columns (plus the
always-kept ones).

**Denials in technical services**

```json
{ "fiscalYears": ["2023"], "naics": ["54"], "minTotalDenials": 1, "sort": "denialsDesc", "maxItems": 25 }
```

IT consulting, engineering and research employers with at least one denied worker, most denials first.

**Five fiscal years of one employer group, summed**

```json
{ "mode": "employerTotals", "fiscalYears": ["2023", "2022", "2021", "2020", "2019"], "employers": ["amazon"], "maxItems": 20 }
```

One row per Amazon entity with the five years added up, `yearCount` and the first and last year with decisions.

**Hospitals and clinics above a size threshold**

```json
{ "fiscalYears": ["2023"], "naics": ["62"], "minTotalApprovals": 10, "sort": "approvalsDesc", "maxItems": 25 }
```

**Universities in one state, 20 rows at a time**

```json
{ "fiscalYears": ["2023"], "naics": ["61"], "states": ["MA"], "onlyNew": true, "maxItems": 20 }
```

Run it again and the next 20 Massachusetts universities arrive, without paying for the first 20 twice.

### Output

A real row from a cloud run of 2026-10-08 (`byYear` mode, FY2023, `employers: ["microsoft"]`):

```json
{
  "fiscalYear": 2023,
  "sourceRow": 18720,
  "employer": "MICROSOFT CORPORATION",
  "matchedEmployer": "microsoft",
  "taxIdLast4": "4442",
  "naics": "51",
  "naicsLabel": "Information: software publishing, telecoms, media",
  "city": "REDMOND",
  "state": "WA",
  "stateName": "Washington",
  "zip": "98052",
  "initialApprovals": 39,
  "initialDenials": 0,
  "continuingApprovals": 1950,
  "continuingDenials": 45,
  "totalApprovals": 1989,
  "totalDenials": 45,
  "totalDecisions": 2034,
  "denialRate": 0.0221,
  "sourceMode": "byYear",
  "url": "https://www.uscis.gov/sites/default/files/document/data/h1b_datahubexport-2023.csv",
  "fetchedAt": "2026-10-08T20:02:55.704Z"
}
```

| Field | Type | Meaning |
|---|---|---|
| `fiscalYear` | integer | Fiscal year of the file the row comes from. Always present in `byYear`; absent in `employerTotals`. |
| `sourceRow` | integer | The data line of the file this row was read from. A few lines repeat an employer, an address and the same counts exactly; this is what tells them apart. `byYear` only. |
| `employer` | string | Petitioner name as filed, in capitals and often shortened. Empty on the rows where the file carries no name. |
| `matchedEmployer` | string | The `employers` value that found this row; empty when no employer filter was used. |
| `taxIdLast4` | string | Last four digits of the petitioner's tax id, as published. Empty where the file leaves it empty. |
| `naics` | string | Two-digit NAICS sector as the file records it; `99` = unknown, empty = no code in the file. |
| `naicsLabel` | string | Plain name of the sector (computed by this actor). |
| `city`, `state`, `zip` | string | Employer's mailing address as filed. `state` and `zip` are empty on some rows of the oldest files. |
| `stateName` | string | Full name of the state code (computed by this actor). |
| `initialApprovals`, `initialDenials` | integer | Workers first decided for **initial** employment. |
| `continuingApprovals`, `continuingDenials` | integer | Workers first decided for **continuing** employment (extensions, employer changes, amended petitions). |
| `totalApprovals`, `totalDenials`, `totalDecisions` | integer | The four published counts added up — **computed by this actor, not published**. Always present. |
| `denialRate` | number or null | `totalDenials / totalDecisions`, four decimals — computed by this actor; `null` when the row holds no decision at all. |
| `yearCount`, `firstFiscalYear`, `lastFiscalYear`, `fiscalYearsCovered` | integer / integer\[] | `employerTotals` only: how many of the selected fiscal years carry decisions for this employer, and which. |
| `sourceMode` | string | `byYear` or `employerTotals`. |
| `url` | string | The fiscal-year file the row was read from. The published rows have no page of their own, so the file is the row's provenance. Always present. |
| `fetchedAt` | string | When this run read the file, ISO 8601 UTC. Always present. |

Dataset views: **H-1B employers** (employer, place, industry name, the approval split, denials, rate), **Decisions in
detail** (the four published counts plus the computed ones), **Employer totals over the selected years**.

### Use it from code / agents

```bash
curl -X POST "https://api.apify.com/v2/acts/yadroo~uscis-h1b-employers/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"fiscalYears":["2023"],"states":["CA"],"minTotalApprovals":50,"sort":"approvalsDesc","maxItems":25}'
```

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('yadroo/uscis-h1b-employers').call({
    fiscalYears: ['2023'],
    employers: ['microsoft'],
    maxItems: 25,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

```python
import os

from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("yadroo/uscis-h1b-employers").call(run_input={
    "mode": "employerTotals",
    "fiscalYears": ["2023", "2022", "2021"],
    "employers": ["amazon"],
    "maxItems": 20,
})
items = client.dataset(run["defaultDatasetId"]).list_items().items
```

MCP: add `https://mcp.apify.com` to Claude / Cursor / any MCP client and call the `yadroo/uscis-h1b-employers` tool
with the same JSON input. Two things help an agent: `fields` keeps the answer small (`["employer","totalApprovals","denialRate"]`),
and `mode`, `fiscalYears`, `states`, `naics` and `sort` are validated by the platform before the run starts, so a
wrong value comes back as an error with the allowed values instead of a wrong dataset.

### Pricing

Pay per event: **$0.001 per run start + $0.002 per dataset row**. The start event is charged on every run, including a
run that ends with no rows. Tier discounts apply to the row event (Bronze −10 %, Silver −20 %, Gold and above −30 %);
the start event is flat.

Typical runs at the full price:

- a company lookup in one fiscal year, 5 rows: `0.001 + 5 × 0.002` = **$0.011**;
- a state shortlist, 25 rows: `0.001 + 25 × 0.002` = **$0.051**;
- the default 50 rows: `0.001 + 50 × 0.002` = **$0.101**;
- a five-year totals run, 20 rows: `0.001 + 20 × 0.002` = **$0.041** (reading five files costs time, not rows).

Every row written to the dataset is charged, and only rows: an employer filter that matched nothing writes **no**
marker row, so you pay the start event alone and the status message tells you what did not match. Reading a file
always costs the same few seconds whatever `maxItems` is, so filter first, sort second and keep `maxItems` small. The
run stops as soon as your **Maximum cost per run** is reached, keeps every row it already wrote and says so in its
status message ("Stopped at your spending limit: N rows delivered").

### Limits & FAQ

**FY2024 and later are not in here.** Only fiscal years 2009 to 2023 are published as files. Newer years exist inside
the interactive data hub, whose pages and export buttons the site's `robots.txt` closes to automated clients, so this
actor does not read them and offers no workaround.

**Speed and the site's pace.** `robots.txt` of uscis.gov asks for a crawl delay of 10 seconds, and this actor keeps
it: one request per 10 seconds to the site, retries included. Everything else is fast. Measured in the Apify cloud on
2026-10-08: a one-fiscal-year run takes 4-7 seconds end to end (the FY2023 file is 2.1 MB with 33,332 published rows,
FY2022 3.9 MB with 59,983 rows, and each one downloads and parses in well under a second); a run over all 15 published
fiscal years read 15 files and wrote 913 rows in 145 seconds — about 6 files and 380 rows a minute, nearly all of it
waiting politely between requests. Nothing was throttled or blocked in that run. The largest input the schema allows
therefore fits comfortably into the 900 s run timeout, and a run that still would not fit stops by itself, keeps the
rows it has written and says so ("Stopped before the run timeout: N rows saved" — verified in the cloud with a 60 s
run timeout: 40 rows of 4 fiscal years kept).

**What the numbers are and are not.** They are **first decisions** on petitions — appeals, revocations, reopened cases
and pending petitions are not in them, so a case later won on appeal still counts as a denial here. They count
**workers**, not petitions in flight. The address is the employer's **mailing** address, not the worksite, so a
national employer filing from one headquarters appears in one state only. `99` means the file does not know the
industry; some rows of the oldest files have no state and no ZIP, and some rows of the newest files have no employer
name at all — those rows are real decisions and are kept, with empty values. Data-entry spellings in the source are
kept as filed (`SAGINAW VLY ST UNIV`), which is why the employer filter matches a part of the name. The files
themselves are not uniform — the newest ones name the columns "Initial Approval", the older ones "Initial Approvals",
and the quoting differs too; the actor reads both and stops with the header it saw if a column is ever missing.

**Repeated lines and employer identity.** A few lines of the published files repeat an employer, an address and the
same counts exactly; every published line becomes its own row and `sourceRow` tells them apart. In `employerTotals`
mode employers are grouped by their name (ignoring case and punctuation) plus `taxIdLast4`, so `AMAZON.COM SERVICES
LLC` and `AMAZON COM SERVICES LLC` are one employer — but a changed legal form (`… INC` to `… LLC`) counts as two,
because the files give no company id beyond the name and the four digits. A totals run with no employer, state, city,
ZIP or industry filter is cut at 120,000 employers; the status message says so when it happens.

**Not personal data.** The rows carry an employer name plus the last four digits of a tax id — an employer
identifier. No beneficiaries, no salaries, no contact details.

**H-1B is not a green card.** This is the record of H-1B petition decisions. It says nothing about permanent
residence, PERM or the green-card lottery.

**Salaries are elsewhere.** Wages and job titles come from the Department of Labor's own disclosure data, a different
dataset that is not in this actor.

**Empty results.** A filter that matched nothing is a successful run with no rows and a status message naming what
did not match (an employer value, a city) — never a silent empty dataset. With `onlyNew` a run that finds nothing new
says so and how many matching rows earlier runs already delivered. A bad value (a ZIP that is not digits, a
denial rate above 1) stops the run at once with a message saying what to change, and an off-list value in one of the
enum fields is refused by the platform before the run even starts.

Questions or problems: open an issue on the Issues tab of this actor.

***

Made by **Yadroo**. Sibling actors:
[federal-register-documents](https://apify.com/yadroo/federal-register-documents) ·
[sec-edgar-filings](https://apify.com/yadroo/sec-edgar-filings) ·
[data-gov-datasets](https://apify.com/yadroo/data-gov-datasets) ·
[bls-release-calendar](https://apify.com/yadroo/bls-release-calendar) ·
[himalayas-remote-jobs](https://apify.com/yadroo/himalayas-remote-jobs)

# Actor input Schema

## `mode` (type: `string`):

What one dataset row is. `byYear` keeps the published granularity: one row per employer per fiscal year you asked for - for a year-over-year chart or a research table. `employerTotals` sums the four decision counts of an employer over the selected fiscal years into one row and adds `yearCount`, `firstFiscalYear`, `lastFiscalYear` and `fiscalYearsCovered` - for a sponsor profile. The filters below work the same in both modes and are applied to the published rows before anything is summed.

## `fiscalYears` (type: `array`):

Which fiscal-year files to read; each one is a separate download, so each extra year costs time, not extra rows per employer. FY2023 is the newest year published as a file. The platform refuses any other value before the run starts - a later fiscal year exists only inside the interactive data hub, whose own pages and export buttons the site closes to automated clients, so this actor does not read them. Leave empty and the newest year (FY2023) is used.

## `employers` (type: `array`):

Free text matched inside the employer name as filed: `microsoft`, `mayo clinic`. Case, punctuation and spaces are ignored, so `walmart` finds `WAL MART ASSOCIATES INC`; several values are combined with OR. Matching is on a part of the name on purpose: these names are filed by hand and often shortened (`SAGINAW VLY ST UNIV`). The value that found a row is written to `matchedEmployer`, and a value that matched nothing is named in the run's status message, so you can shorten it and run again.

## `states` (type: `array`):

Two-letter codes of the employer's mailing address, combined with OR. The platform refuses any value outside the list before the run starts, so pick the codes from the dropdown. This is the address on the petition, not where the worker sits, so a national employer filing from one headquarters shows up in one state only - say so in any report you build on it. The full state name is written to every row as `stateName`. The oldest files leave the state empty on some rows.

## `cities` (type: `array`):

City names as a whole value: `Seattle`, `NEW YORK`, `san jose`. Case and extra spaces are ignored and several values are combined with OR, but unlike the employer filter this one matches the whole city name, so `york` does not pull in `NEW YORK`. The files write the city as filed and in capitals; a value that matched nothing is named in the run's status message.

## `zipPrefixes` (type: `array`):

A full five-digit ZIP (`98101`) or the first two to five digits of one (`100` keeps every 100xx ZIP in New York). Several values are combined with OR, with the state and city filters combined as AND, so `states: ['NY']` plus `zipPrefixes: ['100']` is Manhattan only. The ZIP is the employer's mailing address and is empty on some rows of the oldest files.

## `naics` (type: `array`):

The sector the employer is classified in, as the two-digit code the files carry; values are combined with OR. Code 99 means the file itself does not know the industry, and it covers a large share of the smaller employers. Every row also gets `naicsLabel`, the plain name of the sector, so a dataset can be read without a code table. The platform refuses any value outside this list before the run starts.

## `minTotalApprovals` (type: `integer`):

Keep only rows whose initial and continuing approvals add up to at least this many workers - `totalApprovals` in the row. Most employers in these files have one or two approvals, so a value like 50 or 100 turns a whole state into a short list of the employers that really sponsor at scale. In employerTotals mode the threshold is applied to the summed row, so it means 'this many over all the selected years'.

## `minTotalDenials` (type: `integer`):

Keep only rows with at least this many denied workers (`totalDenials`). Set it to 1 to study where decisions went against the employer; combine it with an industry or a state to compare denial patterns. Note that a denial here is a first decision, so a case later won on appeal still counts as a denial in the file.

## `maxDenialRate` (type: `number`):

Keep only rows whose `denialRate` (denials divided by all decisions in the row) is at most this much: `0.1` for employers that were refused in at most a tenth of their decisions. Rows with no decisions at all have no rate and are dropped by this filter. The rate is computed by this actor - the files publish the counts only.

## `sort` (type: `string`):

Which rows you get first when `maxItems` cuts the answer - the published files are in alphabetical order, so without a sort the biggest sponsors would be hidden behind thousands of one-petition employers. In `byYear` mode the order is applied inside each fiscal year, and the years are written newest first, so a two-year run with `maxItems: 25` gives the top 25 of the newest year. In `employerTotals` mode every selected year is read first and the order is applied to the whole result.

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

Hard cap on the rows this run writes, and therefore on what it costs. A single fiscal-year file holds tens of thousands of employers, so a run with no filters and a high cap is a lot of rows for little insight - filter first, sort second, and keep the cap small. Reading a file always costs the same few seconds whatever the cap is.

## `onlyNew` (type: `boolean`):

Write only rows no earlier run wrote. Every row written is remembered (fiscal year, employer name and tax-ID last four) in the actor's key-value store, per actor or task, so two tasks watching two states do not interfere. It is a way not to pay twice for the same employer: a rerun returns the next rows of the list, so you can take a long result 50 rows at a time or widen the filters later. The published files do not change, so this is not a 'last N hours' window.

## `fields` (type: `array`):

Keep only the named columns, in the order given - for example `employer, state, totalApprovals, denialRate`. Empty = every column. `fiscalYear`, `employer`, `totalApprovals`, `totalDenials`, `url` and `fetchedAt` are always present; name one of them here and it moves to the position you gave it. Unknown names are reported in the run's status message instead of being dropped in silence.

## Actor input object example

```json
{
  "mode": "byYear",
  "fiscalYears": [
    "2023"
  ],
  "employers": [
    "microsoft"
  ],
  "sort": "approvalsDesc",
  "maxItems": 50,
  "onlyNew": false
}
```

# 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 = {
    "fiscalYears": [
        "2023"
    ],
    "employers": [
        "microsoft"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("yadroo/uscis-h1b-employers").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 = {
    "fiscalYears": ["2023"],
    "employers": ["microsoft"],
}

# Run the Actor and wait for it to finish
run = client.actor("yadroo/uscis-h1b-employers").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 '{
  "fiscalYears": [
    "2023"
  ],
  "employers": [
    "microsoft"
  ]
}' |
apify call yadroo/uscis-h1b-employers --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,yadroo/uscis-h1b-employers"
        }
    }
}
```

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/ajavx4CJMuRAmrevw/builds/16cVrAVAuxAxZ7CfX/openapi.json
