NewSearch millions of jobs from your AI agent with MCP
All posts
ReferenceBLS
Reference·Aug 8, 2026·7 min read

BLS API: how to pull US labor statistics, and where the survey data stops

The Bureau of Labor Statistics serves the most cited numbers in the US economy through a free JSON API - once you decode the series ID system. How to register a key, query CPS, CES, JOLTS and OEWS series, the real v1 vs v2 limits, and an honest map of what the API will never contain: job postings, company names, or anything fresher than the survey cycle.

Dvir Atias

Dvir Atias

Founder, JobsPipe

The Bureau of Labor Statistics publishes the most cited numbers in the US economy - the unemployment rate, the monthly jobs report, CPI inflation, occupational wages - and every published series is queryable through a free JSON API. The BLS API (officially the BLS Public Data API) is stable, genuinely free, and public domain, which already puts it ahead of most of the data industry. It is also one of the more awkward APIs you will ever integrate, because it assumes you walk in knowing the exact ID of every time series you want. This guide covers registration, the real limits, working requests, the series ID system - and a precise map of where the survey data stops, because the most common BLS API mistake is asking it a question it was never built to answer.

What the API actually serves

Everything BLS publishes is a time series: one number per period per slice, revised on the agency’s schedule. The API is a thin JSON layer over that archive. The surveys that matter if you are building with labor-market data:

  • CPS (Current Population Survey) - the monthly household survey behind the unemployment rate and participation.
  • CES (Current Employment Statistics) - the payroll survey behind the “US added 187,000 jobs” headline.
  • JOLTS - job openings, hires, quits and layoffs by industry. The closest official measure of hiring demand.
  • OEWS - occupational employment and wages for 800+ SOC occupations, sliced by state and metro, published once a year.
  • CPI and ECI - consumer prices and the employment cost index, if you are deflating salaries or tracking comp inflation.

All of it is aggregate. There is no record-level anything: no postings, no companies, no individual wages. That is not a flaw - it is what a statistical agency is for - but it decides what you can build on top.

Keys and limits: v1 vs v2

The API has two versions and the difference is purely quota. Version 1 needs no key at all: 25 queries a day, up to 25 series per query, 10 years of data per request. Version 2 requires registering at data.bls.gov/registrationEngine - email address, organization, CAPTCHA - and the key arrives by email. That lifts you to 500 queries a day, 50 series per query and 20 years per request, and unlocks the useful extras: series catalog metadata, computed net and percent changes, and annual averages. Registration costs nothing, so the only reason to stay on v1 is a quick one-off script.

Your first request

A single series is a GET:

curl "https://api.bls.gov/publicAPI/v2/timeseries/data/LNS14000000?registrationkey=YOUR_KEY"

Multiple series, a year range, and the v2 extras go in a POST body:

curl -X POST "https://api.bls.gov/publicAPI/v2/timeseries/data/" \
  -H "Content-Type: application/json" \
  -d '{
    "seriesid": ["LNS14000000", "CES0000000001", "JTS000000000000000JOL"],
    "startyear": "2024",
    "endyear": "2026",
    "catalog": true,
    "calculations": true,
    "annualaverage": true,
    "registrationkey": "YOUR_KEY"
  }'

The envelope comes back with a status of REQUEST_SUCCEEDED and a Results.series array; each series carries a data array of observations with year, period (M06 for June), periodName, value and any footnotes. One gotcha that bites every first integration: every value is a string, including the numbers, so cast before you chart. And one operational note: limits reset daily, failures still count, and BLS quietly blocks clients that hammer the endpoint around the 8:30am ET release window - schedule pulls after releases, not in anticipation of them.

The series ID problem

Here is the part the official docs undersell: there is no search endpoint. You cannot ask the API “what series exist for software developer wages in Texas” - you must already hold the series ID, and the ID is a positional encoding that differs by survey. Read the three from the example above:

  • LNS14000000 - CPS, seasonally adjusted, the headline unemployment rate.
  • CES0000000001 - CES, total nonfarm, all employees, seasonally adjusted.
  • JTS000000000000000JOL - JOLTS, total nonfarm, all US, job openings, level, seasonally adjusted. Every zero is a parameter you could vary.

Each survey publishes its own format documentation, and the honest workflow is archaeology: find the number you want in a published BLS table, use the Series Report or Data Finder tools on bls.gov to recover its ID, hardcode the ID, and only then automate. The community wrappers - blsAPI on CRAN, several bls packages on PyPI - save you HTTP boilerplate, but none of them solve discovery, because the API gives them nothing to solve it with.

What the BLS API will never tell you

The wall is not the quota. It is what the surveys measure. JOLTS tells you openings in an industry, two months after the reference month, revised later. OEWS tells you the wage distribution for a SOC occupation, published once a year, roughly a year behind the market it describes. Nothing in the API names an employer, lists a vacancy, states a skill, or updates faster than the survey cycle. If your question is “how many software engineering roles are open at companies like mine, and what are they advertising in pay, today” - the API has no series for that, and never will.

That question is answered by the other layer: live job postings. Measured against our database the day this post published, there are 898,444 active US postings, of which 380,168 - about 42% - carry an advertised pay range stated by the employer. For software developers alone (ISCO-08 unit group 2512, roughly SOC 15-1252 in the US taxonomy), that is 25,379 open, verified-live US roles right now, each with a company name attached. Survey data and postings data answer different questions - the salary API comparison covers that split in depth - and mature products triangulate: BLS for the authoritative level, postings for the current, per-company signal.

Pairing BLS series with live postings

The postings side is one request. Sweep an occupation regardless of title phrasing, restricted to fresh US roles:

curl -X POST "https://api.jobspipe.dev/v1/jobs/search" \
  -H "Authorization: Bearer jp_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "job_country_code_or": ["US"],
    "occupation_code_or": ["2512"],
    "posted_at_max_age_days": 7,
    "limit": 25
  }'

Swap occupation_code_or for skills_or or job_title_or to slice differently, or add isic_division_or to stay inside an industry the way JOLTS does. Each record returns the employer, the posting URL, date_posted, extracted skills, an ISCO-08 occupation code, and salary fields with currency when the employer stated them - the advertised-pay complement to OEWS’s surveyed distribution. BLS publishes the SOC-to-ISCO crosswalk, so joining the two layers is a lookup table, not a modeling project. The free tier is 1,000 jobs a month, no card required. For the wider map of official and commercial sources, the labor market data comparison and the O*NET API guide cover the neighbors.

Which layer is yours

  • Citable macro numbers, dashboards, research. The BLS API, v2 key, and patience with series IDs. Free, rigorous, aggregate, lagged.
  • Per-company, per-role, current-week signal. A postings API. Surveys structurally cannot see this layer.
  • Both. The strongest labor-market products use BLS for the level and postings for the pulse, joined on occupation codes.

Live US postings with occupations, skills and advertised pay - the numbers between BLS releases.

Get a free API key