PayCrunch Salary API — free, public, static JSON
Annual pay for 1,008 U.S. job titles and the best-paying state for 824 occupations, served as flat JSON files. There is no server, no account, no key and no rate limit: paycrunch.co is a static site, and every endpoint below is a file.
762 of the 1,008 job records carry Bureau of Labor Statistics OEWS figures published under a
named SOC code. The remaining 246 cover job titles BLS does not track separately; those records are
labelled a PayCrunch estimate and carry a disclaimer field. Nothing in this API mixes the
two — see telling the two apart.
Quick start
curl
fetch()
Slugs come from /api/v1/jobs.json and are the same slugs the site uses, so
registered-nurse-salary is both /api/v1/jobs/registered-nurse-salary.json
and /jobs/registered-nurse-salary.html.
Endpoints
Base URL https://paycrunch.co/api/v1. Everything is GET, everything returns application/json; charset=utf-8. 1,461 files in total.
| Endpoint | Returns | Records |
|---|---|---|
/api/v1/index.json | API metadata, counts, licence, citation string, links to every endpoint | 1 |
/api/v1/jobs.json | Compact list of every job. The discovery endpoint | 1,008 |
/api/v1/jobs/{slug}.json | One job in full: every percentile, the ceiling and its basis, the step-up role | 1,008 |
/api/v1/soc/{code}.json | Lookup by SOC code, for anyone arriving from BLS data | 449 |
/api/v1/state-wages.json | Median and 90th-percentile wage by state for every occupation BLS reports at the 500-employee floor. Also embedded in each /soc/ record | 11,548 |
/api/v1/states.json | Best-paying state per occupation, with the unfiltered comparison | 824 |
There is a machine-readable description of all of this at
/openapi.json (OpenAPI 3.1), if you would rather
generate a client than read a table.
index.json
Start here if you are exploring. It states the record counts, the licence, the citation string and the URL of every other endpoint, so a client can discover the rest of the API from one request.
Trimmed: the live file also carries
sources, tiers, attribution and the full
endpoints array.
jobs.json
Every job in one file, one row each. Kept to the fields you need to search, sort and link: slug, name, SOC code, median, ceiling, ceiling state, and the provenance block. Fetch the per-job file when you need percentiles.
Trimmed to two rows of 1,008 — one BLS-sourced,
one estimate — and the file’s field_notes block is omitted.
| Field | Meaning |
|---|---|
slug | Stable identifier. Use for /api/v1/jobs/<slug>.json and /jobs/<slug>.html. |
soc | Standard Occupational Classification code. null on estimates, so an estimate cannot be joined into BLS data by SOC. |
tier | "A" BLS figures under a named SOC code. "B" PayCrunch estimate. |
estimated | Boolean. The one field to branch on. |
median | Annual USD, 50th percentile of the wage distribution. |
ceiling | Annual USD, top of the published range. |
ceiling_state | The state that ceiling comes from, or null where the figure is national. |
source_release | "BLS OEWS May 2025" or "PayCrunch estimate". |
url | The human page for this record. |
jobs/{slug}.json
The full record: identity, all five wage percentiles, the ceiling with the basis it was computed from, and the step-up role — the job people in this one most often move up into, with that occupation’s own figures.
Trimmed: the live file also carries
tier_note, core_tasks, hot_tech, license and
attribution.
What the ceiling is
ceiling.amount is the highest state-level 90th-percentile annual wage BLS publishes for
the occupation, restricted to states with at least 500 people employed in it. That floor exists because
unfiltered state figures produce artefacts. Where no state clears the floor, the national 90th
percentile is used and ceiling.scope is "national". ceiling.basis
always spells out which of the two you are looking at.
Telling a BLS figure from an estimate
This is the part worth reading before you display anything. 762 job records carry real BLS OEWS figures. 246 cover job titles BLS does not publish separate wage data for, and their figures are a PayCrunch estimate derived from the closest occupation BLS does track.
estimated.
Every record in every endpoint carries estimated (boolean), tier,
source and source_release. Estimates additionally set soc to
null, expose the nearest occupation as nearest_soc and
nearest_bls_title instead, and carry a disclaimer string. Please do not
present an estimate as a Bureau of Labor Statistics figure.Trimmed: the live file also carries
tier_note, job_zone, step_up, core_tasks,
hot_tech, license and attribution.
soc/{code}.json
Keyed by Standard Occupational Classification code, so if you already hold BLS data you can join on
it directly. Each file gives the occupation’s figures once, then lists the PayCrunch pages that
sit under it. Estimate pages that merely name this SOC as their nearest occupation are kept in a
separate estimated_pages array and never mixed into jobs.
Trimmed to one entry in each array — the live file
lists 18 job pages and 2 estimate pages, and also carries
best_paying_state, license and attribution.
Where PayCrunch has no BLS-sourced page under a SOC code, bls_figures_available is
false, wage and ceiling are null, and a
note says so.
states.json
The best-paying state for each of 824 occupations. BLS publishes a state figure regardless of how
few people were surveyed there, so this dataset requires a state to employ at least 500 people in an
occupation before its figure can be quoted. Each record also carries the unfiltered answer, so you can
see where the floor changes it: filter_changed_answer.
Trimmed to one record of 824; the file’s
method, attribution and source_url fields are omitted here.
The same data as CSV, and the write-up of what the floor changes, are on .
Licence and attribution
The API is published under Creative Commons Attribution 4.0 International (CC BY 4.0). You may use it commercially, redistribute it and build on it, including in closed-source products. The one condition is credit.
Attribution
A link back to paycrunch.co in your UI, docs or about page is
enough. Every response also carries an attribution string you can render verbatim:
Citation
Underlying wage data is from the U.S. Bureau of Labor Statistics Occupational Employment and Wage Statistics, May 2025 release, which is a public-domain U.S. Government work. PayCrunch is not affiliated with, and this API is not endorsed by, the Bureau of Labor Statistics.
Practical notes
CORS
Browser fetch() works from any origin. paycrunch.co is served by GitHub Pages, which
returns Access-Control-Allow-Origin: * on static files; this was checked against a live
JSON file on this domain on 2026-08-31, and you can check it yourself:
No key, no limit, no quota
There is nothing to sign up for and nothing to throttle, because there is no application here —
just files on a CDN. Be reasonable: if you need every job record, fetch jobs.json once
rather than 1,008 files, and cache what you pull.
Caching and freshness
Responses carry an ETag and a Last-Modified header, so conditional requests
work. Every record carries last_updated, and index.json carries the record
counts — poll that one small file to detect a rebuild rather than diffing the whole set.
Stability
The API is versioned in the path. Endpoints under /api/v1/ keep their shape: fields may
be added, but existing fields will not be renamed or repurposed. A breaking change becomes
/api/v2/.
Wage figures are distributions, not people
wage.p90 is the 90th percentile of the wage distribution for an occupation — the
annual figure that nine tenths of published wages for that job fall below. It is a property of the pay
range, not a rank for any individual.
Frequently asked questions
jobs.json once instead of walking 1,008 individual files, and cache what you take.last_updated.estimated. Where it is true, source_release reads "PayCrunch estimate", soc is null, and the record carries a disclaimer string written for display.jobs.json (1,008 rows) and filter client-side, or index it into whatever you already run.