---
title: "BLS API: how to pull US labor statistics, and where the survey data stops"
description: "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 ser"
canonical: https://jobspipe.dev/blog/bls-api
---

[NewSearch millions of jobs from your AI agent with MCP→](/blog/jobspipe-mcp-server)

[All posts](/blog)

![](/listly/card-field.png)

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](/authors/dvir-atias.jpg)

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. Its percentiles also define the four H-1B prevailing-wage levels you see in the [H1B salary database](/blog/h1b-salary-database).
-   **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](/blog/soc-codes)), 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](/blog/salary-data-api) 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 to start, no card required. For the wider map of official and commercial sources, [the labor market data comparison](/blog/labor-market-data-sources) and [the O\*NET API guide](/blog/onet-api) 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.

![](/listly/shape-heart.png)

FAQs

## Frequently Asked Questions

### Is the BLS API free, and do I need an API key?

Yes, it is free and public domain. Version 1 needs no key at all but caps you at 25 queries a day, 25 series per query and 10 years of data per request. Version 2 requires registering at data.bls.gov/registrationEngine with an email address, organization and CAPTCHA; the key arrives by email and lifts the limits to 500 queries a day, 50 series per query and 20 years per request. Registration costs nothing.

### How do I find a BLS series ID?

There is no search endpoint, so you must already hold the ID, which is a positional encoding that differs by survey: LNS14000000 is the CPS headline unemployment rate and CES0000000001 is CES total nonfarm employment. The workflow the post recommends is to find the number in a published BLS table, use the Series Report or Data Finder tools on bls.gov to recover its ID, hardcode it, and only then automate. Community wrappers such as blsAPI on CRAN save HTTP boilerplate but do not solve discovery.

### Does the BLS API include job postings or company-level data?

No. Everything BLS publishes is an aggregate time series: JOLTS gives openings by industry about two months after the reference month, and OEWS gives the wage distribution for a SOC occupation once a year. Nothing in the API names an employer, lists a vacancy, states a skill or updates faster than the survey cycle. For per-company, current-week signal the post pairs BLS series with a postings API such as JobsPipe, joined on occupation codes through the BLS SOC-to-ISCO crosswalk.

[

← Previous

Introducing Signals: stop checking whether a company started hiring

](/blog/introducing-signals)[

Next →

Job scraping explained: how it works, what you get by board, and when it is legal

](/blog/what-is-job-scraping)

---
Canonical URL: https://jobspipe.dev/blog/bls-api
Title: BLS API: how to pull US labor statistics, and where the survey data stops
Description: 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.

---
AI agents: the full machine-readable index of this site is https://jobspipe.dev/llms.txt?src=md-twin - API quickstart, no-key sandbox (POST https://api.jobspipe.dev/v1/sandbox/jobs/search), MCP server, pricing. Free key: https://jobspipe.dev/signup