Start with the free snapshot on Hugging Face: HeadcountLabs/job-postings, 4,871,084 jobs in Parquet under CC BY 4.0, with daily change files. If you need descriptions, a smaller slice, or data from the last hour, use our API's bulk export (up to 100,000 jobs per export) and then the changes feed, which returns only the jobs created, updated or closed since your last call. The first 100,000 API records each month are free.
What "daily" has to mean
A jobs table goes stale fast. Jobs open and close every hour. A copy that is a month old will have many jobs that are already filled and will miss the newest ones. So a useful dataset needs two parts:
- A full starting copy.
- A cheap way to apply what changed since then: new jobs, edited jobs, and jobs that closed.
The second part is the one most free datasets leave out. Without it, you have to download everything again to stay current.
Three routes compared
| Route | What you get | Freshness | Cost | Best for |
|---|---|---|---|---|
| Hugging Face snapshot | Every job our API served on October 14, 2026: jobs, postings, locations, companies. No description text | Snapshot plus daily change folders (October 15, 16 and 17 so far) | Free, CC BY 4.0, no account | Research, analysis, a first load |
| Bulk export (API) | Up to 100,000 jobs per export for any filter, with the fields you pick, including descriptions | As of the export | Counts as records: first 100,000 a month free, then $0.01 per 1,000 | A slice you care about, with full text |
| Changes feed (API) | Created, updated and expired events since a time or cursor | Minutes behind our live data | Each event counts as one record | Keeping any copy current |
Facts from our dataset card and docs, read on October 19, 2026. Prices from /pricing/.
Route 1: the free snapshot
The dataset holds four tables. From its card on October 19, 2026:
- jobs: 4,871,084 rows, one per job.
- postings: 5,716,621 rows, one per listing of a job on one source.
- job_locations: 5,583,156 rows, with country, region, city, zip and metro area code.
- companies: 12,073 rows, a summary of company IDs seen on jobs.
The total is 4.9 GB of zstd Parquet. Files in data/jobs and data/postings are named part-YYYYMM-N.parquet by the month the job was posted, so you can read one month without the rest. Only postings whose source terms allow redistribution are included. Job description text is left out; each posting links to the original listing.
Query it in place with DuckDB. You do not need to download it first. This ran on October 19, 2026 at 2:09 PM ET and took 0.9 seconds:
SELECT employer_name, count(*) AS jobs
FROM 'hf://datasets/HeadcountLabs/job-postings/data/jobs/part-202610-*.parquet'
WHERE employer_name IS NOT NULL
GROUP BY 1 ORDER BY 2 DESC LIMIT 5;
┌──────────────────┬───────┐
│ employer_name │ jobs │
├──────────────────┼───────┤
│ AMN Healthcare │ 11440 │
│ M&S │ 9444 │
│ Allied Universal │ 4322 │
│ MasTec Inc │ 2219 │
│ Best Buy │ 2160 │
└──────────────────┴───────┘
That is the five employers with the most jobs first posted in October 2026, in the snapshot.
Or from Python. The same engine works inside a script. This counts US jobs by state. It ran at 2:23 PM ET and took 13.4 seconds over the network:
import duckdb
rows = duckdb.sql("""
SELECT region, count(DISTINCT job_id) AS jobs
FROM 'hf://datasets/HeadcountLabs/job-postings/data/job_locations/*.parquet'
WHERE country = 'US'
GROUP BY 1 ORDER BY 2 DESC LIMIT 5
""").fetchall()
for region, jobs in rows:
print(region, jobs)
US-CA 298261
US-TX 282222
None 213968
US-FL 174801
US-NY 162952
The third line matters. 213,968 US jobs have no state, because the source gave no state for them. We leave that as published instead of guessing a state. Plan for it in your own counts.
The dataset card also shows loading with the Hugging Face datasets library in streaming mode and with pandas.
Keeping the snapshot current
Daily change files sit under data/changes/date=YYYY-MM-DD/. On October 19, 2026 there were folders for October 15, 16 and 17. Each folder has four parts: jobs, postings and job_locations hold the full new version of every row that changed that day, and removed lists the jobs that left the set.
SELECT count(*) AS changed_jobs
FROM 'hf://datasets/HeadcountLabs/job-postings/data/changes/date=2026-10-17/jobs/*.parquet';
┌──────────────┐
│ changed_jobs │
├──────────────┤
│ 931672 │
└──────────────┘
The removed file for the same day held 31,266 jobs. To apply a day: upsert the changed rows by job_id, then delete the removed ones. Apply the days in order. The card says a full refresh is planned every week, and its MANIFEST.json tells you which version you hold.
A day with 931,672 changed jobs looks large. A job lands in the folder when any of its fields changed that day, so changed does not mean new.
Route 2: a bulk export with descriptions
When you want a slice with the full description text, ask the API for an export. You send one request, wait, then download gzip files with one JSON job per line. Each file holds up to 10,000 jobs, and files are kept for 24 hours.
curl -s -X POST https://api.headcountlabs.com/v1/jobs/exports \
-H "Authorization: Bearer $HEADCOUNT_API_KEY" -H 'Content-Type: application/json' \
-d '{"title":"actuary","country":"US","max_records":200,
"fields":["title","employer_name","locations","postings"]}'
Real answer, October 19, 2026 at 2:07 PM ET (HTTP 202):
{"export_id":"afa31c27-192d-4182-af59-caa3343ffd5e","status":"queued",
"created_at":"2026-10-19T18:07:54.660Z","record_count":0,"files":[]}
Then poll GET /v1/jobs/exports/{export_id}. This one finished in under half a second:
{"export_id":"afa31c27-192d-4182-af59-caa3343ffd5e","status":"completed",
"completed_at":"2026-10-19T18:07:55.077Z","expires_at":"2026-10-20T18:07:55.077Z",
"record_count":200,"snapshot_watermark":"2026-10-19T15:20:54.671Z",
"files":[{"n":1,"records":200,"bytes":52687,
"url":"/v1/jobs/exports/afa31c27-192d-4182-af59-caa3343ffd5e/files/1"}]}
One line from the file, trimmed:
{"job_id":"252ba915-bf6f-4e2f-956a-b5f80057f5c8",
"employer_name":{"state":"known","value":"iShare Inc"},
"title":{"state":"known","value":"Casualty Reserving Actuary - FCAS"},
"locations":[{"state":"known","value":{"city":"Ontario","region":"US-CA","country":"US",
"msa":{"cbsa_code":"40140","cbsa_title":"Riverside-San Bernardino-Ontario, CA"}}}],
"postings":[{"source":"careerplug","posting_status":"listed",
"posting_url":{"state":"known","value":"https://ishare-inc.careerplug.com/jobs/3638651"},
"pay":{"state":"known","value":{"min":50,"max":55,"currency":"USD","interval":"hour"}},
"source_posted_at":"2026-10-11T16:37:31.000Z"}]}
Be ready for gaps. The first line of the same file had "employer_name":{"state":"extraction_failed"}: we could not read the employer's name from that source. 19 of the 200 rows had that state. We mark it rather than fill in a guess, so filter on state if you need a name on every row.
Exports are limited to 100,000 jobs each and one at a time per account. For more, split by country, state or date, after checking the size with the free GET /v1/jobs/count.
Route 3: the changes feed
Once you have a copy, the changes feed keeps it current. Ask for events after a time, then keep the resume_cursor it returns and pass it as after next time. Each page holds up to 1,000 events.
curl -s "https://api.headcountlabs.com/v1/jobs/changes?since=2026-10-19T12:00:00Z&limit=2&types=created,expired" \
-H "Authorization: Bearer $HEADCOUNT_API_KEY"
Real answer, October 19, 2026 at 2:16 PM ET:
{"data":[
{"event_id":"34626340","job_id":"e116ab46-8e2e-4612-b765-4359dbcb7ea8",
"type":"expired","occurred_at":"2026-10-19T12:01:12.420Z"},
{"event_id":"34626348","job_id":"e1173381-5b7a-4f71-8ffc-61beba72b016",
"type":"expired","occurred_at":"2026-10-19T12:01:12.420Z"}],
"resume_cursor":"eyJ2IjoxLCJraW5kIjoiam9icy5jaGFuZ2VzIi...",
"has_more":true,"snapshot_watermark":"2026-10-19T15:20:54.671Z"}
An expired event carries only the job ID: delete or close that row. A created or updated event carries the full job object, so you can upsert it without another call. Keep reading while has_more is true.
Every event counts as one record against your monthly allowance. Use types to ask only for the events you act on, so you do not pay for ones you would throw away.
Which route to pick
- Analysis across the whole market (which employers are hiring, where, at what pay): the Hugging Face snapshot. It is free and has every job, and DuckDB answers most questions in seconds.
- A job board or search product: an export of your slice to start, then the changes feed every few minutes. You need descriptions and fresh closures, which the snapshot does not give.
- A model or a classifier: the snapshot for titles, pay and locations. Add descriptions from an export for the slice you label.
- A one-off answer: neither. Ask for a count.
GET /v1/jobs/countis free, and so iscount_jobson the keyless MCP endpoint.
What to store in your own copy
Keep job_id as the key. Postings of the same opening share one stable job_id. When we find that two jobs are the same opening, one is merged into the other: its membership changes and merged_into_job_id names the job to keep. Follow that link instead of keeping both rows. Keep each posting's posting_url and application_url, so users can reach the employer. Keep the state next to each value, or at least a flag for values that are not known, so you can leave out guessed or missing fields later. And keep the time you last saw each job. A job that stops appearing in changes and exports for weeks deserves a second look, even before an expired event arrives.
How this compares with other options
Kaggle datasets. Kaggle has many job datasets, and some are good for learning. They are one-off snapshots. On October 19, 2026, Kaggle's own listing showed "LinkedIn Job Postings (2023 - 2024)" with 124,000+ postings under CC BY-SA 4.0, last updated August 19, 2024. "Online Job Postings" was last updated in April 2017, and "Data Analyst Job Postings" in April 2025. None of these three has daily change files, so none can tell you which jobs are open today.
Paid datasets. Coresignal sells a job postings dataset of 482M+ records. Techmap sells feeds and datasets from 2020 on, and Bright Data sells per-record job listing datasets with a monthly minimum. These carry history and sources that we do not, such as job boards and years of closed jobs. If you need history for a model or a study, they are better than our snapshot, which only holds jobs open in October 2026. They cost far more, and most are sold on a contract.
Scraping it yourself. Possible for a few hundred employers on documented ATS feeds. Past that, see our guide to public ATS job APIs for the work involved.
Licence and attribution
The Hugging Face dataset is CC BY 4.0. You may share and adapt it, including commercially, if you credit the source. The card asks for this line: "Source: Headcount Labs, licensed under CC BY 4.0."
The licence covers our compilation and the fields we derive: normalised titles, locations, pay, dates, company links and the merging of postings into jobs. Job text, employer names and logos stay with their owners. Some postings carry a source_rights_json field saying the source requires credit. When it does, show that credit next to the job.
API data is covered by our terms, not by CC BY 4.0.
Known limits
- The snapshot has no descriptions. Use an export or the API when you need the text.
- Pay is as published: hourly, yearly or other, in the posted currency, not converted.
currentnessis our evidence that a job is open, not a guarantee. In a check of 200 snapshot jobs against the source, 188 (94%) matched in every column. The rest had been updated at the source after we read them.- Coverage is mostly US. We read employers' own systems, not job boards, so jobs posted only on job boards are missing.
Questions
Is there a free daily dataset of open job postings?
Yes. HeadcountLabs/job-postings on Hugging Face is free under CC BY 4.0. It held 4,871,084 jobs from an October 14, 2026 snapshot, plus daily change folders for the following days.
Does the dataset include job descriptions?
No. Each posting links to the original listing. Our API exports include descriptions, with the first 100,000 records each month free.
How do I keep my copy up to date?
Apply the daily change folders in date order, upserting changed rows by job_id and deleting removed ones. For fresher data, use the API changes feed and pass its resume_cursor back as after.
Can I use it commercially?
Yes, with attribution: "Source: Headcount Labs, licensed under CC BY 4.0." Show any source credit the posting asks for.
How big is it?
4.9 GB of Parquet across four tables. You can query it in place with DuckDB without downloading it.
Does it have historical job postings?
No. It holds jobs open at the snapshot and the changes after it. For years of history, paid datasets such as Coresignal's or Techmap's are the better fit.
Sources
All read or run on October 19, 2026.
- https://huggingface.co/datasets/HeadcountLabs/job-postings (dataset card and file tree)
- https://headcountlabs.com/docs/bulk-download/
- https://headcountlabs.com/pricing/
- https://www.kaggle.com/datasets/arshkon/linkedin-job-postings and Kaggle's dataset listing for "job postings"
- https://coresignal.com/pricing/
- https://jobdatafeeds.com/pricing
- https://brightdata.com/pricing/datasets
More guides
- Free job posting APIs in 2026: what is actually free
- Job search MCP servers compared
- Coresignal alternatives for job postings data
- Public job APIs of applicant tracking systems
- Best job posting data APIs in 2026
- Does Indeed have an API?
- Does LinkedIn have a jobs API?
- Is there a Google Jobs API?
- Does Glassdoor have an API?