Blog/Sales & enrichment
11 min read

H-1B Data by API: 549,133 Records, and the Domain Field Is Invented

One call returned half a million sponsorship records. The top wage was a React developer on 1.9 million. The company domain on every row does not resolve.

H-1B Data by API: 549,133 Records, and the Domain Field Is Invented

Copy this line to your agent to pull H-1B sponsorship records.

set up https://monid.ai/SKILL.md and use levels.fyi /get_h1b_salaries for a year

One call on 2026-09-18 returned a hundred H-1B records out of 549,133 for a single year. The highest wage on the first page was a React developer at level two on 1,939,644 a year. The company domain attached to that row, and to every other row we checked, does not resolve to anything. Both of those are worth knowing before you build on this dataset, and neither is a fault in the API. This guide runs through Monid, the OpenRouter for agent tools.

What is in an H-1B dataset?

Employer filings, not employee surveys. That distinction sets everything else about how the data behaves.

Where the records come from

When a US employer sponsors a worker on an H-1B visa they file a Labor Condition Application stating the job title, the work location, the wage they will pay and the period of employment. Those filings are disclosed. An H-1B dataset is a cleaned copy of them.

What that means in practice

It is the offered wage, not the paid wage. The filing states what the employer commits to pay. Actual compensation, bonuses and equity are elsewhere, which is what the salary data guide covers across three providers that ask employees instead.

It is nearly complete. Every sponsoring employer files, so unlike a self-reported salary site there is no sampling bias in who shows up. Our single year held 549,133 records.

It carries the employer, not the employee. Company, job title, city, state, wage, level, dates, case status. No names.

And it inherits whatever the employer typed. Nobody validates a wage field at filing time, which is the subject of the next section.

📖 See also Salary Data by API: Three Sources That Disagree

Why is the top wage a React developer on 1.9 million?

Because page one is sorted by wage descending, and the top of a wage-sorted government dataset is where the typing errors collect.

The measurement

levels.fyi/get_h1b_salaries with year: 2025 and page: 1, 7.3 seconds:

totalRecords  549,133          totalPages  5,492        rows on a page  100

wage, page 1 (descending)
  1,939,644   React Developer          level II
  1,602,432   Sterling Java Developer  level III
    445,000   Corporate Fund Associate level III
  …
    245,000   75th percentile of this page
    220,504   median of this page
    192,500   lowest on this page

The two figures at the top belong to the same employer with the same begin date. A React developer at level two does not earn 1.9 million dollars. Somebody filed a number in the wrong unit or with extra digits, and the disclosure published it because disclosure is not validation.

What follows for anyone aggregating

Never take a maximum. The maximum of this dataset is a data-entry error with near certainty, and it will be in any chart you build from max().

Trim before you average. A mean over page one is dragged upward by two rows out of a hundred. Use a median, or drop the top and bottom percentile, and say in your output that you did.

Page one is not a sample. Because the default sort is by wage, the first page is the highest-paid hundred filings in the country for that year, with a floor of 192,500. If you pull page one and call it representative you have measured the top 0.02 percent. Page through, or sample pages at random.

Two counters, one truth. totalRecords of 549,133 against totalPages of 5,492 is consistent at a hundred rows a page, which is a small reassurance worth checking on any paginated source, because it often is not.

And the years are not all there

The schema says valid years include 2020 through 2025. On 2026-09-18, year: 2025 returned data, and year: 2020 and year: 2022 both came back as a provider 502. A documented range and an available range are different claims, and only one of them you can check. Test each year you plan to depend on before building a time series across them.

How do you pull H-1B data through one key?

Three steps, and the third one is the one that saves you from the domain field.

For agents

Grab an API key at app.monid.ai, then paste this to your agent and hand it the key:

set up https://monid.ai/SKILL.md

It learns the whole discover, inspect, run workflow itself. More in the agent quickstart.

For humans

npm install -g @monid-ai/cli
monid keys add -k <your-api-key> -l main

Step 1. Pull a page

The endpoints. levels.fyi/get_h1b_salaries, per call, takes year and page.

monid run -p levels.fyi -e /get_h1b_salaries --query '{"year": 2025, "page": 1}'

What comes back. A hundred rows and the two counters. Read totalPages first: at 5,492 pages for one year, a full pull is a batch job, not something you do inside a request.

Step 2. Decode the letters

Every row uses single-letter keys. Ours mapped as follows, read off the values themselves:

KeyFieldExample
cCompanyGunderson Dettmer Stough Villeneuve Franklin and Hachigian
dDomain, derivedsee the next section
jJob titleCorporate Fund Associate
sOccupation categoryLawyers
wWage445000
lLevelIII
ct / stCity and stateRedwood City, CA
yYear2025
csCase statusCertified
bd / adBegin and decision datesDec 31, 2025 and Jul 25, 2025
etEmployment typeC

cs came back as Certified on 98 rows and "Certified - Withdrawn" on 2. et produced six distinct letters across one page, C, P, A, E, N and X, none of which the schema explains. Store the raw letter and decode it in your own layer rather than guessing at load time.

Step 3. Fix the employer identity

Do not trust d. Resolve the company name yourself on a free call, which is the next section and the one thing in this guide that will silently corrupt a dataset if you skip it.

Give this to your agent

$Set up https://monid.ai/SKILL.md, and then use Monid to pull H-1B records for 2025, page through the first twenty pages, group by company and job title, report the median wage per group and flag any row above three times its group median as a suspected filing error.

📖 See also Turn a Domain Into Full Company Firmographics in One Call

Why should you never join on the domain field?

Because it is the company name with the spaces removed and .com attached, and it does not resolve.

The two we checked

c  "Gunderson Dettmer Stough Villeneuve Franklin and Hachigian"
d  "gundersondettmerstoughvilleneuvefranklinandhachigian.com"   does not resolve

c  "Cloud Social Technologies"
d  "cloudsocialtechnologies.com"                                 does not resolve

Both requests failed to connect. The field is a string built from another string. It has the shape of a domain and none of the properties of one.

What the real answer looks like

hunterio/domain-finder on the name "Gunderson Dettmer", free per call:

289 known emails   gunder.com          <- the actual firm
 40                dettmer-rail.com
 37                dettmer-group.com
  1                gundersoncleaners.com

The law firm's real domain is gunder.com, which no amount of string concatenation would ever produce, and the free endpoint found it in two seconds along with a size signal that separates it from three unrelated companies. That resolution step, and why the email count is the field that disambiguates, is the whole subject of the company lookup guide.

Why this matters more than it looks

A join key that is wrong but well-formed is the worst kind. If d were null you would notice on the first join. Because it is a plausible domain string, a pipeline will happily join it against a company table, match nothing, and report zero enrichment coverage as though the enrichment provider were at fault. Treat c as the only employer identifier in this dataset and resolve it yourself.

Which endpoint should I use for which job?

EndpointWhat it doesInputOutputBest forBilling
levels.fyi/get_h1b_salariesH-1B filing recordsyear, page100 rows, 13 fields, two countersSponsorship and offered wagesPer call
levels.fyi/get_salary_dataSelf-reported pay for a companyCompanyLevelled compensationWhat people actually earnPer call
indeed/get_salary_infoSalary statistics for a titleTitle, locationRange statisticsA market cross-checkPer call
hunterio/domain-finderCompany name to real domaincompany, limitCandidate domains with email countsRepairing the employer identityFree
hunterio/companies/findDomain to firmographicsdomainSize, industry codes, tech, locationEnriching the employerPer call

Every row was verified with monid inspect on 2026-09-18. The table gives billing shape rather than figures, because shape drives design and current numbers live on monid.ai/tools.

The free row is in the table because it repairs the paid one.

When is this the wrong dataset?

Four cases.

You want to know what people earn. This is what employers committed to pay on a form, at a level and a location, for a role as the employer titled it. Titles are inconsistent between filers, equity is absent, and bonuses are absent. For compensation research the self-reported sources are closer to the question even though they are a biased sample.

You want current openings. A Labor Condition Application is not a job posting. It tells you an employer sponsored a role starting on a date, which is a hiring signal with a lag, not a vacancy. The vacancy question is the job postings guide.

You want individuals. There are none here. The dataset is employer, role, place and wage. Anyone promising you named visa holders is selling something else, and you should ask where it came from.

You need every year. Two of the six documented years returned a provider error on the day we tried. If your analysis is a trend line across 2020 to 2025, verify each year returns data before you promise the chart.

And the disclosure: this is Monid's blog and we resell the endpoints here. The load-bearing advice in this article is to ignore a field we sell you and replace it with a free call, which costs us the difference.

Conclusion

H-1B data is one of the more complete labour datasets available, and the two things most likely to break a build on it have nothing to do with completeness. The first page of a wage-sorted year opens with a React developer at level two on 1.9 million dollars, which is a filing error faithfully published, so any aggregate built on a maximum is built on a typo. The company domain on every row is the company name with the spaces taken out, and the two we tested resolve to nothing at all.

What matters more than the provider is what you join on. Keep the company name, resolve it on a free call, and throw the domain field away. Trim your aggregates, page past the top, and store the single-letter codes raw until you have a decode table you trust, because six distinct employment-type letters appeared on one page and the schema explains none of them.

Free next step: run monid inspect -p levels.fyi -e /get_h1b_salaries, then pull one page and sort by wage yourself. The top two rows will tell you more about this dataset than any documentation. Start at monid.ai.

FAQ

Is H-1B data public?

The underlying filings are disclosure records, which is why datasets like this one exist at all and why coverage is close to complete rather than sampled. What you get through an API is a cleaned and indexed copy, which saves you parsing bulk files and gives you pagination and a year filter. What cleaning does not do is correct the filings: a wage typed with extra digits arrives in your response exactly as it was submitted. Treat the API as a convenient transport for a public record, not as a validated dataset, and put your own sanity checks on the numbers.

Is the wage in these records what the person actually earns?

No. It is the wage the employer stated it would pay for that role at that level in that location, on a form, at filing time. Real compensation adds bonuses and equity, which never appear here, and it changes after the filing while the record does not. The practical use is comparative rather than absolute: which employers sponsor, in which cities, at what stated levels, and how those stated floors move year over year. For what people report actually receiving, the self-reported sources in the salary guide are the closer answer, with their own sampling problems.

How do you decode the single-letter field names?

By reading the values, not the schema. Ours were unambiguous for most of them: c held company names, j job titles, w four to seven digit wages, ct and st city and state, bd and ad two dates in the right order. The one that resisted was et, employment type, which produced C, P, A, E, N and X across a single page with no explanation anywhere in the endpoint definition. Store the letter verbatim in your own schema, build the decode table from observed distributions and from the underlying filing form, and never map an unknown code to a default, because a silent default turns an unknown into a confident wrong answer.

Can you use sponsorship data for prospecting?

Yes, and it is one of the better uses, as long as you fix the employer identity first. A company that filed sponsorships in a city for a role category is a company that was hiring that role, with a wage floor attached, which is a usable signal for anything that targets growing teams. The trap is the domain field: joining on it matches nothing and looks like an enrichment failure. Take the company name, resolve it on a free name-to-domain call, then enrich the resolved domain. That sequence, and why the free call is also the accurate one, is measured in the company lookup guide.

Last updated September 2026.

h1b datah1b apih1b salary datavisa sponsorship datalabor condition application