Technical writing

BLS Occupational Employment and Wage Statistics: The Federal Database Behind Median Salary Data for Every US Occupation

· 15 min read· AI Analytics
BLSWagesOccupationsLabor MarketFederal Data

The Bureau of Labor Statistics Occupational Employment and Wage Statistics survey is the most comprehensive federal source of wage data by occupation — covering 830 detailed occupations across every industry and geographic area in the United States, with employment counts and full wage distributions (10th through 90th percentile) for 1.1 million surveyed establishments, making it the authoritative data source behind every published median salary figure in the United States.

What OEWS is

The Occupational Employment and Wage Statistics program, administered by the Bureau of Labor Statistics within the U.S. Department of Labor, is the only federal statistical program that produces comprehensive wage data broken down simultaneously by occupation, industry, and geography at sub-state resolution. Every time a journalist reports that software developers earn a median of $130,000 per year, or a lawyer cites prevailing wages in an H-1B petition, or a hospital benchmarks nursing salaries against market rates, the underlying data almost always comes from OEWS.

OEWS was established in 1988 as the Occupational Employment Statistics (OES) program, initially focused on employment counts by occupation rather than wages. Wage data collection was phased in during the 1990s, and the program was renamed Occupational Employment and Wage Statistics in 2021 to better reflect what it actually produces. The program operates in partnership with state workforce agencies, which conduct the underlying employer surveys on behalf of BLS, allowing for geographic coverage far beyond what any single federal agency could achieve independently.

The practical importance of OEWS extends well beyond salary benchmarking. The Department of Labor's Office of Foreign Labor Certification uses OEWS wage data to determine prevailing wages for H-1B, H-2A, and H-2B visa petitions — if an employer wants to hire a foreign worker, the minimum wage they must pay is set by reference to the OEWS wage distribution for the relevant occupation and geographic area. Every H-1B labor condition application filed with DOL is stamped with an OEWS-derived prevailing wage tier (Level I through Level IV, corresponding to the 17th, 34th, 50th, and 67th percentiles of the OEWS distribution for that occupation and area). OEWS data also drives occupational projections published in the Occupational Outlook Handbook, federal and state workforce planning, military pay grade benchmarking, and the calibration of unemployment insurance wage replacement calculations.

Survey methodology and coverage

OEWS uses a three-year rolling panel design that distinguishes it from most BLS surveys, which have monthly or quarterly collection cycles. The survey collects data during a reference period of the prior November, and each annual publication pools three consecutive years of survey responses — approximately 1.1 million establishment responses per publication cycle, drawn from roughly 1.2 million sampled establishments contacted across those three years. Pooling three years of data is necessary to achieve the statistical reliability required for small occupations in small geographic areas; a single year of data would produce unacceptably wide confidence intervals for, say, nuclear engineers in Wyoming.

The sample frame is the Quarterly Census of Employment and Wages (QCEW), the same administrative universe of employer payrolls that underlies the Current Employment Statistics program. Establishments are stratified by state, ownership type (private, federal government, state government, local government), and industry (NAICS code), then sampled within strata with probability proportional to employment. Large establishments are sampled with certainty; smaller ones are sampled at rates that vary by stratum. The OEWS sample covers approximately 800 industries at the national level and a subset of industries in each state and metropolitan area, depending on the industry's employment presence in that geography.

Responding establishments report the number of workers in each occupation in their workforce during the reference week and the wage rate for each worker in one of twelve wage intervals. BLS defines the intervals to span from under $10.60 per hour to over $115.38 per hour (for the most recent reference year), with finer gradations in the middle of the distribution where most workers are concentrated. Collecting interval-coded wages rather than exact wages reduces respondent burden and allows employers to report from payroll summary data rather than pulling individual records. The interval data are then fit to a Weibull wage distribution using a minimum-distance estimation method to produce the published wage percentiles. This methodological choice means that OEWS wage distributions are modeled fits, not direct tabulations from exact wages — a distinction that matters when using OEWS data for precise legal or contractual purposes.

The three-year pooling and Weibull fitting also mean that OEWS estimates lag economic conditions. Data published in May 2025 reflect November 2022, November 2023, and November 2024 surveys, with each year weighted by the inverse of its probability of selection. Wages in rapidly changing occupations — software engineering during a tech hiring surge, for example — will be understated relative to current market conditions because two of the three constituent years are already stale by the time of publication.

The SOC classification system

OEWS organizes all occupations using the Standard Occupational Classification (SOC) system, a hierarchical taxonomy maintained by the Office of Management and Budget and used consistently across federal statistical programs. The SOC structure has four levels, each identified by a portion of the six-digit code in the form XX-XXXX.

The first two digits identify the major group, of which there are 23 in the 2018 SOC revision used in current OEWS publications. Major groups cover broad domains of work: Management Occupations (11), Business and Financial Operations (13), Computer and Mathematical (15), Architecture and Engineering (17), Life, Physical, and Social Science (19), Community and Social Service (21), Legal (23), Educational Instruction and Library (25), Arts, Design, Entertainment, Sports, and Media (27), Healthcare Practitioners and Technical (29), Healthcare Support (31), Protective Service (33), Food Preparation and Serving Related (35), Building and Grounds Cleaning and Maintenance (37), Personal Care and Service (39), Sales and Related (41), Office and Administrative Support (43), Farming, Fishing, and Forestry (45), Construction and Extraction (47), Installation, Maintenance, and Repair (49), Production (51), Transportation and Material Moving (53), and Military Specific (55).

The third and fourth digits identify the minor group within the major group. The fifth and sixth digits identify the detailed occupation. The 2018 SOC defines 867 detailed occupations across all 23 major groups, though OEWS publishes data for a somewhat smaller set because some occupations are too thinly staffed in the employer population to yield statistically reliable estimates. In practice, OEWS regularly publishes data for approximately 830 detailed occupations nationally, with smaller counts for state and metropolitan area tabulations.

Software Developers, for example, carries the code 15-1252: major group 15 (Computer and Mathematical), minor group 15-1 (Computer Occupations), broad occupation 15-12 (Software and Web Developers, Programmers, and Testers), detailed occupation 15-1252 (Software Developers). A registered nurse is 29-1141: major group 29 (Healthcare Practitioners and Technical), minor group 29-1 (Health Diagnosing and Treating Practitioners), broad occupation 29-11 (Physicians and Other Health Diagnosing and Treating Practitioners), detailed occupation 29-1141 (Registered Nurses). The SOC code is the primary key for joining OEWS data to other federal datasets, including the Bureau of Labor Statistics Occupational Outlook Handbook projections, the Census Bureau American Community Survey occupation codes (which map from Census OCC codes to SOC), and OFLC H-1B prevailing wage determinations.

Data fields: employment, wages, and percentile distributions

Each OEWS observation — a combination of occupation, industry, and geography — contains a set of standardized data fields. Employment is expressed as the estimated number of jobs in that occupation-industry-geography cell, in whole numbers. OEWS wage fields include both hourly and annual equivalents for the mean and six percentiles: the 10th, 25th, 50th (median), 75th, and 90th percentile wages. Annual wages are computed by multiplying hourly wages by 2,080 (52 weeks × 40 hours), which represents a standard full-time work year and is applied uniformly regardless of the actual hours patterns for a given occupation.

The distinction between mean and median wages is analytically significant. In right-skewed wage distributions — which describe most occupations, because the top earners can be paid many times the typical wage — the mean will exceed the median. For management consultants and investment bankers, the mean is substantially above the median because a small fraction of very high earners pull the average up. For occupations with more compressed distributions, such as mail carriers or school bus drivers, mean and median are close. OEWS publishes both, and the median is generally considered the more representative central tendency for benchmarking a typical worker's compensation, while the mean is more useful for estimating aggregate wage bills or computing total labor costs at scale.

When wages are above the top interval threshold — $115.38 per hour or $240,000 annually, as of recent publications — OEWS cannot estimate the upper tail of the distribution and publishes a flag rather than a number. This affects the 90th percentile and sometimes the 75th percentile for the highest-paid occupations, including surgeons, anesthesiologists, and chief executives. For these occupations, OEWS data is most useful for characterizing the lower and middle portions of the wage distribution; the top-end data should be supplemented with other sources such as corporate proxy filings or specialty compensation surveys.

OEWS also publishes an employment percent relative standard error (RSE), which indicates the statistical precision of the employment estimate. Small occupation-geography cells may have RSEs exceeding 30%, indicating that the estimate should be used with caution. Wage RSEs are not published separately, but the same precision concerns apply: a cell with high employment RSE will also have unreliable wage estimates.

OccupationSOC CodeEmploymentMedian Annual90th Pct Annual
Chief Executives11-1011197,640$206,680>$239,200
Anesthesiologists29-121127,280>$239,200>$239,200
Surgeons, Except Ophthalmologic29-124824,840>$239,200>$239,200
Physicians, All Other29-1229119,950$224,260>$239,200
Lawyers23-1011681,010$145,760>$239,200
Software Developers15-12521,847,900$130,160$208,620
Registered Nurses29-11413,205,690$81,220$120,250
Accountants and Auditors13-20111,394,800$79,880$137,280
Secondary School Teachers25-20311,081,200$65,220$103,260
Electricians47-2111747,090$61,590$99,800
Truck Drivers, Heavy53-30322,073,530$52,720$79,850
Customer Service Representatives43-40512,879,830$40,990$62,660
Retail Salespersons41-20313,813,540$33,290$58,120
Cashiers41-20113,371,970$30,110$40,430
Food Preparation Workers35-2021857,640$30,060$41,540

The table above illustrates the extraordinary wage span captured by OEWS: from food preparation workers at a median of $30,060 annually to surgeons and anesthesiologists whose median wages exceed the top interval threshold of $239,200. The 90th percentile for software developers at $208,620 reflects the skewed distribution of technology compensation, where senior engineers at large technology companies earn substantially above the median but the occupation remains accessible at much lower levels at smaller employers across the country.

Geographic detail

OEWS publishes wage and employment data at four geographic levels: national all-industries (the most commonly cited), national by industry, state-level, and metropolitan and nonmetropolitan area level. The geographic resolution available decreases as the occupation becomes less common, because cells with fewer than 10 establishments reporting or employment below 10 are suppressed to avoid disclosure of individual employer data.

At the state level, OEWS publishes data for all 50 states and the District of Columbia, plus Puerto Rico and the U.S. Virgin Islands. California, Texas, New York, Florida, and Illinois account for the majority of total employment and exhibit some of the largest wage premiums relative to the national median. California's software development wages, driven by the San Francisco Bay Area technology cluster, consistently run 30–40% above the national median for the same occupation.

At the metropolitan statistical area level, OEWS publishes data for approximately 580 metropolitan and nonmetropolitan areas. MSAs are defined by the Office of Management and Budget as groupings of counties centered on urban cores; OEWS uses the current OMB MSA definitions for each reference year. Nonmetropolitan areas are the residual geography within each state — all employment in counties not included in an MSA — and are published as a single aggregate for each state's nonmetro portion. This means that rural wage patterns are partially visible in OEWS data but are not resolvable to the county level, which limits the program's utility for highly localized economic development analyses.

Geographic wage variation is one of the most practically important features of OEWS data. The same occupation can pay dramatically different wages depending on where the work is performed, driven by local cost of living, industry concentration, labor market tightness, and unionization rates. The following table illustrates this variation for registered nurses across five major metropolitan areas:

Metro AreaMedian AnnualMean Annual90th Pct Annual
San Francisco-Oakland-Hayward, CA$155,230$157,490$196,220
New York-Newark-Jersey City, NY-NJ-PA$103,760$107,380$141,580
Chicago-Naperville-Elgin, IL-IN-WI$84,760$87,230$116,430
Dallas-Fort Worth-Arlington, TX$77,500$79,640$104,860
Birmingham-Hoover, AL$66,330$68,150$91,200

The San Francisco Bay Area median for registered nurses is more than twice the Birmingham median — a gap driven primarily by California's strong nursing unions, state-mandated nurse-to-patient staffing ratios, and the region's high cost of living. These geographic differentials are why OEWS data is used at the metropolitan area level rather than the national level for most compensation benchmarking purposes; comparing a Dallas nurse's pay to the national median would significantly overstate the local market rate.

The highest- and lowest-paid occupations

The highest-paid occupations in OEWS data fall almost entirely within two major groups: healthcare practitioners (SOC 29) and management (SOC 11). Within healthcare, the surgical and anesthesiology specialties dominate the top of the wage distribution. Anesthesiologists (29-1211), surgeons (29-1248), oral and maxillofacial surgeons (29-1022), and obstetricians and gynecologists (29-1216) all report median wages above the $239,200 upper interval threshold — meaning that more than half of practitioners in these specialties earn above the maximum wage that OEWS can precisely measure. Psychiatrists (29-1223) and general internal medicine physicians (29-1221) earn somewhat less but still report medians above $200,000.

Below the physician specialties, airline pilots and flight engineers (53-2011) rank among the highest non-physician occupations, with a median near $202,180. The 2021 SOC revision created a new occupation code for airline pilots separately from commercial pilots, making this figure more precise than in prior years. Lawyers (23-1011), with a median of approximately $145,760 but a 90th percentile above the measurement threshold, have the widest intra-occupational wage spread of any major profession — a starting public defender and a senior partner at a BigLaw firm are both classified as SOC 23-1011.

The lowest-paid occupations in OEWS are concentrated in food preparation and serving (SOC 35), personal care and service (SOC 39), and farming, fishing, and forestry (SOC 45). Combined food preparation and serving workers (35-3099), dishwashers (35-9021), and counter attendants (35-3023) consistently report median wages near the federal minimum wage or slightly above it. The compression of the distribution at the low end reflects both the wage floor imposed by minimum wage laws and the limited wage variation in occupations where nearly all employers compete within a narrow band. By contrast, high-paid occupations exhibit enormous dispersion: the 90th percentile for software developers is more than three times the 10th percentile, and for physicians the entire distribution is truncated at the top by the OEWS measurement ceiling.

Industry-occupation matrices

One of OEWS's most analytically powerful and underutilized features is the industry-occupation matrix: for many occupation-industry combinations, OEWS publishes separate wage estimates that allow comparison of wages for the same occupation across different employing industries. The same SOC code can be associated with dramatically different wages depending on where the work is done — a reflection of industry wage premiums, unionization, the specific job content within the occupation, and the bargaining power of workers in different sectors.

Software developers (15-1252) illustrate this variation clearly. At software publishing companies (NAICS 5112), the median annual wage runs approximately $145,000. At computer systems design and related services (NAICS 5415), the dominant employer of software developers, the median is around $130,000. At insurance carriers (NAICS 5241), software developers earn a median of roughly $116,000. In state government (NAICS 999200), the median drops to approximately $94,000 — a gap of more than $50,000 compared to the software publishing sector. For career planning purposes, the industry matters as much as the occupation.

Accountants and auditors (13-2011) show a different pattern. In accounting and tax preparation services (NAICS 5412) — public accounting firms — the median annual wage is approximately $83,000. In securities and commodity contracts dealing (NAICS 5232), accountants earn a median of approximately $112,000. At management of companies and enterprises (NAICS 5511), the median is around $96,000. Accountants employed directly by financial sector firms command a substantial premium over those in public accounting, which is obscured when the occupation is examined at the all-industries level only.

The industry dimension of OEWS data is also critical for analyzing the connection between OEWS wages and H-1B prevailing wage determinations. DOL's Office of Foreign Labor Certification sets H-1B prevailing wages using the OEWS occupation-MSA combination most directly relevant to the position, without an industry breakdown. This can result in prevailing wages that significantly understate wages for financial sector technology roles (where industry premiums are high) and overstate wages for government technology roles (where they are lower). Critics of the H-1B prevailing wage methodology have cited this limitation as a way in which the system can be used to underpay foreign workers relative to true market rates in their specific industry.

Data access: downloads and BLS API series IDs

BLS provides OEWS data through several access mechanisms. The most comprehensive is the bulk download at bls.gov/oes/data.htm, which provides flat-file downloads organized by reference year, geographic level, and ownership type. Each annual publication includes national, state, and metropolitan area files in tab-delimited text format, with columns for area code, area name, industry code, industry title, SOC occupation code, occupation title, employment, employment RSE, hourly and annual wages at each percentile, and wage RSE. The national all-industries file is the most commonly used starting point and contains approximately 870 rows (one per detailed occupation) plus summary rows for major groups, minor groups, and broad occupations.

OEWS data is also accessible through the BLS Public Data API v2 using structured series IDs. The OEWS series ID format is OEU followed by a seven-digit area code, a six-digit industry code, a seven-digit occupation code (the SOC code with the hyphen removed and a leading zero prepended), and a two-digit data type code. Data type codes for OEWS are: 01 for employment, 03for mean hourly wage, 04 for median hourly wage, 05 for mean annual wage, 06 for median annual wage (note: BLS API documentation occasionally uses different numbering; verify against the BLS series ID format reference), 07 for 10th percentile annual wage, 08 for 25th percentile, 09 for 75th percentile, and 10 for 90th percentile. The national all-industries all-occupations code uses area code0000000 and industry code 000000.

For bulk analysis, the flat-file downloads are more practical than the API because the API imposes series-per-request limits and rate limits that make it cumbersome for pulling hundreds of occupation-area combinations simultaneously. The API is well suited for production monitoring of a specific set of occupations and geographies — for example, a human resources analytics system that checks whether a company's salaries for a defined set of roles remain competitive with the latest OEWS benchmarks.

Python workflow: querying OEWS via the BLS API

The following script uses only the Python standard library plus requeststo query the BLS Public Data API v2 for OEWS wage distributions. It pulls the full percentile distribution for software developers (SOC 15-1252) nationally, the 10th, median, and 90th percentile wages for registered nurses (SOC 29-1141), and median wages for software developers across five major metropolitan areas. Register a free API key at data.bls.gov/registrationEngine/ and substitute it for the placeholder in the script.

import requests
import json
import time

# ---------------------------------------------------------------------------
# BLS Occupational Employment and Wage Statistics (OEWS) -- API v2 Data Pull
# ---------------------------------------------------------------------------
# BLS Public Data API v2: https://api.bls.gov/publicAPI/v2/timeseries/data/
#
# OEWS series ID format: OEU + area_code + industry_code + occupation_code + data_type
#
#   area_code    : 0000000 = national; state FIPS (e.g., 0060000 = CA);
#                  MSA FIPS (e.g., 0016980 = Chicago-Naperville)
#   industry_code: 000000 = all industries; NAICS 6-digit otherwise
#   occupation_code: 7-digit SOC with leading zero, e.g. 1512520 = SOC 15-1252
#   data_type    : 01 = employment; 03 = annual mean wage; 04 = annual median wage
#                  07 = 10th pct annual wage; 08 = 25th pct annual wage
#                  09 = 75th pct annual wage; 10 = 90th pct annual wage
#
# Free registration key: https://data.bls.gov/registrationEngine/
# Registered: 500 series/query, 10 years, 50 req/day
# Unregistered: 25 series/query, 3 years, 25 req/day

BLS_API_URL = "https://api.bls.gov/publicAPI/v2/timeseries/data/"
REGISTRATION_KEY = "YOUR_BLS_API_KEY_HERE"

# ---------------------------------------------------------------------------
# Example 1: Full wage distribution for Software Developers (SOC 15-1252)
# nationwide, all industries
# Data types: 03=mean, 04=median, 07=pct10, 08=pct25, 09=pct75, 10=pct90
# ---------------------------------------------------------------------------
SW_DEV_SERIES = {
    "OEU000000000000000151252003": "Software Developers - Mean Annual Wage",
    "OEU000000000000000151252004": "Software Developers - Median Annual Wage",
    "OEU000000000000000151252007": "Software Developers - 10th Percentile",
    "OEU000000000000000151252008": "Software Developers - 25th Percentile",
    "OEU000000000000000151252009": "Software Developers - 75th Percentile",
    "OEU000000000000000151252010": "Software Developers - 90th Percentile",
}

# ---------------------------------------------------------------------------
# Example 2: Wage distribution for Registered Nurses (SOC 29-1141)
# nationwide, all industries
# ---------------------------------------------------------------------------
RN_SERIES = {
    "OEU000000000000000291141003": "Registered Nurses - Mean Annual Wage",
    "OEU000000000000000291141004": "Registered Nurses - Median Annual Wage",
    "OEU000000000000000291141007": "Registered Nurses - 10th Percentile",
    "OEU000000000000000291141009": "Registered Nurses - 75th Percentile",
    "OEU000000000000000291141010": "Registered Nurses - 90th Percentile",
}

# ---------------------------------------------------------------------------
# Example 3: Software Developer median wage across 5 major MSAs
# MSA area codes (7-digit FIPS prefix 00 + 5-digit CBSA):
#   New York-Newark (35620)   -> 0035620
#   San Francisco (41860)     -> 0041860
#   Seattle (42660)           -> 0042660
#   Austin (12420)            -> 0012420
#   Atlanta (12060)           -> 0012060
# Median annual wage data type = 04
# ---------------------------------------------------------------------------
MSA_SERIES = {
    "OEU003562000000000151252004": "Software Developers - New York MSA",
    "OEU004186000000000151252004": "Software Developers - San Francisco MSA",
    "OEU004266000000000151252004": "Software Developers - Seattle MSA",
    "OEU001242000000000151252004": "Software Developers - Austin MSA",
    "OEU001206000000000151252004": "Software Developers - Atlanta MSA",
}

ALL_SERIES = {**SW_DEV_SERIES, **RN_SERIES, **MSA_SERIES}
series_ids = list(ALL_SERIES.keys())

# OEWS is published once per year (May reference, published the following May).
# The BLS API returns it as annual data; use the latest available year.
import datetime
current_year = datetime.date.today().year
start_year = current_year - 3   # pull 3 years to see if current year is available

print(f"Fetching {len(series_ids)} OEWS series from BLS API...")

payload = {
    "seriesid":        series_ids,
    "startyear":       str(start_year),
    "endyear":         str(current_year),
    "registrationkey": REGISTRATION_KEY,
    "catalog":         False,
    "calculations":    False,
    "annualaverage":   True,   # OEWS data comes back as annual averages
}

resp = requests.post(BLS_API_URL, json=payload, timeout=60)
resp.raise_for_status()
data = resp.json()

if data.get("status") != "REQUEST_SUCCEEDED":
    print(f"BLS API error: {data.get('message', data)}")
    raise SystemExit(1)

# ---------------------------------------------------------------------------
# Parse: series_id -> most recent value
# ---------------------------------------------------------------------------
results = {}
for series_obj in data.get("Results", {}).get("series", []):
    sid  = series_obj["seriesID"]
    rows = series_obj.get("data", [])
    if not rows:
        results[sid] = None
        continue
    # Sort descending by year; take the latest non-preliminary value
    rows_sorted = sorted(rows, key=lambda r: int(r["year"]), reverse=True)
    latest = rows_sorted[0]
    year   = latest["year"]
    value  = latest["value"]
    results[sid] = (year, float(value) if value not in ("", "-", "*") else None)

def fmt_wage(sid):
    """Return formatted wage string for a series ID."""
    if sid not in results or results[sid] is None:
        return "N/A"
    year, val = results[sid]
    if val is None:
        return "N/A"
    return f"${val:,.0f}"

def ref_year(sid):
    if sid not in results or results[sid] is None:
        return "?"
    return results[sid][0]

# ---------------------------------------------------------------------------
# Report 1: Software Developer wage distribution (national)
# ---------------------------------------------------------------------------
print()
print("=" * 70)
print("  SOFTWARE DEVELOPERS (SOC 15-1252) -- NATIONAL WAGE DISTRIBUTION")
ref = ref_year(list(SW_DEV_SERIES.keys())[0])
print(f"  Reference year: {ref}  |  All industries, all areas")
print("=" * 70)

for sid, label in SW_DEV_SERIES.items():
    print(f"  {label:<50}  {fmt_wage(sid):>12}")

# ---------------------------------------------------------------------------
# Report 2: Registered Nurse wage distribution (national)
# ---------------------------------------------------------------------------
print()
print("=" * 70)
print("  REGISTERED NURSES (SOC 29-1141) -- NATIONAL WAGE DISTRIBUTION")
ref_rn = ref_year(list(RN_SERIES.keys())[0])
print(f"  Reference year: {ref_rn}  |  All industries, all areas")
print("=" * 70)

for sid, label in RN_SERIES.items():
    print(f"  {label:<50}  {fmt_wage(sid):>12}")

# ---------------------------------------------------------------------------
# Report 3: Software Developer median wage by MSA
# ---------------------------------------------------------------------------
print()
print("=" * 70)
print("  SOFTWARE DEVELOPERS (SOC 15-1252) -- MEDIAN WAGE BY METRO AREA")
print(f"  Reference year: {ref}  |  Median annual wage, all industries")
print("=" * 70)
print(f"  {'Metro Area':<45}  {'Median Annual Wage':>18}")
print("  " + "-" * 66)

for sid, label in MSA_SERIES.items():
    metro = label.replace("Software Developers - ", "")
    print(f"  {metro:<45}  {fmt_wage(sid):>18}")

print()
print("Source: U.S. Bureau of Labor Statistics, Occupational Employment")
print("        and Wage Statistics (OEWS)")
print("        https://www.bls.gov/oes/  |  API: https://api.bls.gov/publicAPI/v2/")

The script constructs OEWS series IDs by concatenating the fixed prefix with area, industry, occupation, and data type components. The area code0000000 selects national data; metropolitan area codes follow a two-zero prefix followed by the five-digit CBSA code. Industry code000000 selects all industries. The occupation code for software developers is formed by removing the hyphen from 15-1252 and prepending a zero to reach the required seven digits: 1512520. Because OEWS is an annual survey, the API returns annual data points; the script sorts descending by year to retrieve the most recent available publication.

Limitations: lag, exclusions, and methodological notes

OEWS data has several significant limitations that users should understand before applying it to wage benchmarking or policy analysis. The most consequential is the publication lag. OEWS is published each May, but the reference period is the prior November — already six months old at publication. Moreover, because the data pool three years of surveys, the effective vintage of the published estimates is the weighted midpoint of three November reference periods. Data published in May 2025 reflects surveys from November 2022, 2023, and 2024; the weighted average reference period is approximately November 2023, eighteen months before the publication date. In occupations with rapidly changing wages — software engineering during the 2021–2022 hiring surge or the subsequent 2022–2023 correction — OEWS estimates will substantially understate or overstate current market conditions.

OEWS excludes self-employed workers and independent contractors. The survey is conducted among establishments (employers), and only workers on the employer's payroll are in scope. This is a major limitation for occupations where significant portions of the workforce operate as freelancers or independent contractors: writers and authors (27-3043), artists and related workers (27-1019), software developers who work independently (a growing segment following the 2022–2023 technology layoffs), and most occupations in the gig economy. The self-employed typically earn very different amounts than their employer-payroll counterparts, and OEWS data characterizes only the latter.

OEWS also excludes agricultural workers on farms, workers on unpaid family farms, and private household workers employed by individuals (as opposed to domestic staffing agencies). Military occupations (SOC major group 55) are excluded entirely, as are occupations in the underground economy. These exclusions mean that OEWS covers a large majority of the formal civilian labor force but should not be treated as a census of all work performed in the United States.

The Weibull distribution fitting methodology, while technically well-suited to right-skewed wage distributions, introduces a layer of model uncertainty that is not present in surveys collecting exact wages. The fitted percentiles can diverge from true percentiles when the underlying distribution departs from the Weibull functional form — which can occur in multimodal distributions (occupations where wages cluster at distinct levels, such as teacher pay scales set by collective bargaining agreements) or in occupations where the wage interval data is sparse. BLS does not publish the confidence intervals on the fitted wage percentiles, which makes it difficult for users to assess the precision of the estimates beyond the employment RSE.

Finally, OEWS wages are measured as straight-time wages and salaries; they do not include employer-paid benefits (health insurance, retirement contributions), bonuses, overtime premiums, equity compensation, or other forms of non-wage compensation. For occupations where non-wage compensation is substantial — finance and technology professionals, for example, who may receive annual bonuses equal to 50% or more of base salary plus substantial equity grants — OEWS wages significantly understate total compensation. The BLS National Compensation Survey and the Employment Cost Index provide some data on employer benefit costs as a share of total compensation, but do not produce occupation-by-occupation estimates of total compensation that would allow a straightforward adjustment of OEWS wage data.

Related writing

BLS QCEW: The Federal Database Behind US Payroll Data for Every Industry and County — quarterly payroll data from UI administrative records, 40M+ records by NAICS industry and county.

BLS Current Employment Statistics: The Federal Database Behind the Monthly Jobs Report — monthly payroll survey of 140,000 businesses covering nonfarm employment, average hourly earnings, and the Jobs Friday release.

DOL OFLC H-1B Disclosure Data: The Federal Database Behind Employer Visa Petitions — Labor Condition Application records for every H-1B petition, including prevailing wage tiers derived from OEWS data.