# BLS Data Library — Usage Guide A Python library for querying the BLS Public Data API v2, with pre-built series for employment, prices, wages, and productivity. --- ## Data Flow ```mermaid flowchart TD subgraph YOUR_CODE ["Your Code"] direction TB Q["Query functions\nbls_client/queries/\nemployment · prices · wages · productivity"] S["Series ID builders\nbls_client/series.py\nlaus_state() · ces_national() · cpi() · jolts()"] C["BLSClient\nbls_client/client.py\nfetch() · fetch_named() · to_rows()"] Q -->|"returns series ID string"| C S -->|"returns series ID string"| C end subgraph BLS_API ["BLS Public Data API — api.bls.gov/publicAPI/v2"] direction TB EP1["/timeseries/data/\nPOST · up to 50 series · 20 years"] EP2["/surveys\nGET · all 68 survey codes"] EP3["/timeseries/popular?survey=XX\nGET · top series per survey"] end subgraph OUTPUT ["Output"] R["dict keyed by series ID\nor labeled dict via fetch_named()"] ROW["Flat row list\nclient.to_rows() → CSV / DataFrame"] HTML["HTML Reports\ngenerate_report.py\nbls_dataset_explorer.py"] end C -->|"POST JSON\nwith API key + series IDs"| EP1 C -->|GET| EP2 C -->|GET| EP3 EP1 -->|"JSON: value · period · footnotes\n± catalog · calculations"| R R -->|"client.to_rows()"| ROW R -->|"passed to report generator"| HTML ``` --- ## API Limits | | Unregistered (v1) | Registered (v2) | |---|---|---| | Daily queries | 25 | **500** | | Series per call | 25 | **50** | | Years per call | 10 | **20** | | Catalog metadata | No | Yes | | MoM/YoY calculations | No | Yes | Register free at **https://data.bls.gov/registrationEngine/** --- ## Setup ```bash # 1. Copy and fill in your API key cp config.example.py config.py # Edit config.py and paste your key # 2. Install dependencies (standard library only + requests) pip install requests ``` --- ## Quick Start ```python from config import BLS_API_KEY from bls_client import BLSClient from bls_client.queries import employment, prices client = BLSClient(BLS_API_KEY) # Fetch a single series result = client.fetch_latest(employment.nonfarm_payrolls(), years=2) # Fetch multiple named series at once results = client.fetch_named(prices.cpi_dashboard(), 2023, 2025) for label, s in results.items(): obs = client.latest_obs(s) print(f"{label}: {obs['value']} ({obs['periodName']} {obs['year']})") ``` --- ## BLSClient API ### `fetch(series_ids, start_year, end_year, **kwargs)` Fetch one or more series. Handles batching automatically (50 per API call). ```python result = client.fetch( ["CES0000000001", "LAUST110000000000003"], start_year=2020, end_year=2025, catalog=True, # include series title and metadata calculations=True, # include MoM/YoY net and % changes annual_average=False, # include M13 annual average row ) # Returns: {"CES0000000001": {"data": [...], "catalog": {...}}, ...} ``` **Observation format:** ```python { "year": "2025", "period": "M04", # M01-M12=monthly, Q01-Q04=quarterly, A01=annual "periodName": "April", "value": "6.4", # "-" when not available "footnotes": [{"code": "R", "text": "Revised..."}], "calculations": { # only when calculations=True "net_changes": {"1": "-0.2", "3": "0.1", "12": "1.3"}, "pct_changes": {"1": "-3.0", "3": "1.5", "12": "25.5"}, } } ``` **Footnote codes:** | Code | Meaning | |------|---------| | R | Revised | | P | Preliminary | | X | Not available (government shutdown gap, etc.) | | N | Not available | ### `fetch_latest(series_ids, years=2, **kwargs)` Convenience wrapper — fetches the most recent N years. ```python result = client.fetch_latest(employment.nonfarm_payrolls(), years=3) ``` ### `fetch_named(named_dict, start_year, end_year, **kwargs)` Fetch a `{label: series_id}` dict and return results keyed by label. ```python results = client.fetch_named( {"DC Unemployment": "LAUST110000000000003", "MD Unemployment": "LAUST240000000000003"}, 2023, 2025 ) ``` ### `client.to_rows(results)` Flatten fetch results into a list of dicts for CSV/DataFrame use. ```python rows = client.to_rows(result) # [{"series_id": "CES0000000001", "series_title": "...", # "year": "2025", "period": "M04", "period_name": "April", "value": "158987"}, ...] import csv with open("output.csv", "w", newline="") as f: w = csv.DictWriter(f, fieldnames=rows[0].keys()) w.writeheader(); w.writerows(rows) ``` --- ## Query Library ### Employment (`bls_client/queries/employment.py`) ```mermaid graph LR E["employment module"] E --> NP["nonfarm_payrolls()"] E --> PP["private_payrolls()"] E --> GE["government_employment()"] E --> NUR["national_unemployment_rate()"] E --> LFP["national_labor_force_participation()"] E --> DC["dc_unemployment_rate()"] E --> MD["md_unemployment_rate()"] E --> VA["va_unemployment_rate()"] E --> SU["state_unemployment(state_fips)\nreturns dict of 4 measures"] E --> DRU["dc_region_unemployment()\n6 geos as labeled dict"] E --> JD["jolts_dashboard()\n5 JOLTS measures"] E --> QCEW["qcew_national_private()\nqcew_dc_private()\nqcew_state(fips)"] ``` | Function | Series | Description | |---|---|---| | `nonfarm_payrolls()` | CES0000000001 | Total nonfarm payroll employment | | `private_payrolls()` | CES0500000001 | Total private employment | | `government_employment()` | CES9000000001 | Federal + state + local | | `national_unemployment_rate()` | LNS14000000 | CPS U-3 rate (SA) | | `dc_unemployment_rate()` | LAUST110000000000003 | DC, not SA | | `state_unemployment(fips)` | 4 LAUS series | Rate/employed/unemployed/LF | | `dc_region_unemployment()` | 6 series | DC/MD/VA + 3 MSAs | | `job_openings_level()` | JTS000000000000000JOL | JOLTS openings | | `jolts_dashboard()` | 5 series | Full JOLTS flow | | `qcew_state(fips)` | EN… | QCEW quarterly by state | ### Prices (`bls_client/queries/prices.py`) | Function | Series | Description | |---|---|---| | `cpi_all_items()` | CUUR0000SA0 | CPI-U headline | | `cpi_core()` | CUSR0000SA0L1E | All items less food & energy (SA) | | `cpi_food()` | CUUR0000SAF | Food at home + away | | `cpi_energy()` | CUUR0000SA0E | Energy component | | `cpi_shelter()` | CUUR0000SAH1 | Shelter/housing | | `cpi_gasoline()` | CUSR0000SS47014 | Gasoline (SA) | | `cpi_dashboard()` | 7 series | Full CPI labeled dict | | `cpi_w_all_items()` | CWUR0000SA0 | CPI-W (COLA basis) | | `ppi_all_commodities()` | WPU00000000 | PPI all commodities | | `ppi_dashboard()` | 4 series | PPI components | | `avg_price_electricity()` | APU000072610 | ¢/KWh retail | | `avg_price_gasoline_regular()` | APU00007471A | $/gallon regular | | `import_price_index()` | EIUIR | Import price index | | `export_price_index()` | EIUIQ | Export price index | ### Wages (`bls_client/queries/wages.py`) | Function | Series | Description | |---|---|---| | `eci_total_compensation()` | CIU1010000000000A | ECI civilian all workers | | `eci_wages()` | CIU1010000000000W | ECI wages only | | `eci_benefits()` | CIU1010000000000B | ECI benefits only | | `eci_dashboard()` | 5 series | Civilian + private + state/local | | `ecec_total_compensation()` | CMU1010000000000D | Cost per hour worked | | `ecec_health_insurance()` | CMU1010000000000H | Health ins. cost/hr | | `median_weekly_earnings()` | LEU0252881600 | Median weekly earnings (SA) | | `occupation_annual_median_wage(soc)` | OE… | OEWS wage for SOC code | **SOC codes** available in `wages.SOC_CODES` dict. ### Productivity (`bls_client/queries/productivity.py`) | Function | Series | Description | |---|---|---| | `business_output_per_hour()` | PRS85006092 | Business sector productivity | | `business_unit_labor_cost()` | PRS85006111 | Unit labor costs | | `nonfarm_output_per_hour()` | PRS86006092 | Nonfarm business | | `manufacturing_output_per_hour()` | PRS88006092 | Manufacturing | | `productivity_dashboard()` | 4 series | Labeled dict | --- ## Series ID Builders Use `bls_client/series.py` when you need series not covered by the query library. ```mermaid flowchart LR S["series.py"] S --> LA["laus_state(state_fips, measure, seasonal)\nlaus_county(state, county, measure)\nlaus_msa(state, cbsa_code, measure)"] S --> CE["ces_national(industry, data_type, seasonal)\nces_state(state_fips, industry, data_type)"] S --> CP["cpi(item, area, seasonal, series)"] S --> PP["ppi_commodity(commodity_code)"] S --> OE["oes_national(occupation, industry, data_type)"] S --> JT["jolts(element, rate_level, industry, ownership)"] S --> EC["eci(worker_type, occupation, industry, component)"] S --> PR["productivity(sector, measure, seasonal)"] ``` **Examples:** ```python from bls_client import series # LAUS: any geography series.laus_state(36, "rate") # New York unemployment rate series.laus_county(24, 33, "employed") # Howard County MD employment series.laus_msa(51, 40060, "rate") # Richmond VA MSA rate # CES: specific industry series.ces_national("healthcare", "avg_hourly_earn") # healthcare wages series.ces_state(11, "government") # DC govt employment # CPI: any component series.cpi("shelter", seasonal=True) # SA shelter index series.cpi("all_items", series="W") # CPI-W all items # JOLTS: any flow series.jolts("quits", "R") # quits rate series.jolts("hires", "L", seasonal=False) # hires level, NSA # OES: any occupation series.oes_national("291141", data_type="annual_median") # RN median wage ``` --- ## Examples | File | What it shows | |---|---| | `examples/basic_pull.py` | Fetch single series, named dict, flatten to rows | | `examples/custom_series.py` | Build series IDs manually; JOLTS, OES, CPI, LAUS by state | | `dc_md_va_unemployment.py` | LAUS pull for 6 DC-region geos → CSV | | `generate_report.py` | Full HTML report with Chart.js charts | | `bls_dataset_explorer.py` | All 68 surveys with live data samples | --- ## LAUS State FIPS Reference | FIPS | State | | FIPS | State | |------|-------|-|------|-------| | 01 | Alabama | | 30 | Montana | | 02 | Alaska | | 31 | Nebraska | | 04 | Arizona | | 32 | Nevada | | 05 | Arkansas | | 33 | New Hampshire | | 06 | California | | 34 | New Jersey | | 08 | Colorado | | 35 | New Mexico | | 09 | Connecticut | | 36 | New York | | 10 | Delaware | | 37 | North Carolina | | **11** | **District of Columbia** | | 38 | North Dakota | | 12 | Florida | | 39 | Ohio | | 13 | Georgia | | 40 | Oklahoma | | 15 | Hawaii | | 41 | Oregon | | 16 | Idaho | | 42 | Pennsylvania | | 17 | Illinois | | 44 | Rhode Island | | 18 | Indiana | | 45 | South Carolina | | 19 | Iowa | | 46 | South Dakota | | 20 | Kansas | | 47 | Tennessee | | 21 | Kentucky | | 48 | Texas | | 22 | Louisiana | | 49 | Utah | | 23 | Maine | | 50 | Vermont | | **24** | **Maryland** | | **51** | **Virginia** | | 25 | Massachusetts | | 53 | Washington | | 26 | Michigan | | 54 | West Virginia | | 27 | Minnesota | | 55 | Wisconsin | | 28 | Mississippi | | 56 | Wyoming | | 29 | Missouri | | | | --- ## Files in This Repository ``` bls-data/ ├── USAGE.md ← this file ├── README.md ← API reference (endpoints, limits, formats) ├── config.example.py ← copy to config.py and add your key │ ├── bls_client/ ← Python library │ ├── __init__.py │ ├── client.py ← BLSClient class │ ├── series.py ← series ID builder functions │ └── queries/ │ ├── employment.py ← LAUS, CES, JOLTS, QCEW series │ ├── prices.py ← CPI, PPI, Import/Export series │ ├── wages.py ← OES, ECI, ECEC, CPS earnings │ └── productivity.py ← Major sector productivity │ ├── examples/ │ ├── basic_pull.py ← Getting started │ └── custom_series.py ← Building series IDs manually │ ├── generate_report.py ← DC/MD/VA HTML report generator ├── bls_dataset_explorer.py ← All-surveys HTML reference ├── dc_md_va_unemployment.py ← CLI pull → CSV │ ├── series_id_formats.md ← Series ID decode tables (all surveys) ├── qcew_field_schema.md ← QCEW CSV field layouts ├── surveys.json ← All 68 surveys from API └── api_response_example.json ← Sample API response ```