CSV and Parquet File Downloads Documentation
Weekly CSV and Parquet downloads for complete job data, company context, and analytical workflows.
Table of contents
Introduction
The jobdata API access ultra and access ultra+ packages provide complete weekly data downloads for bulk backfills, warehouse loading, offline analysis, and reporting. Downloads are available in compressed CSV files and in one denormalized Parquet file for customers who need fast analytical scans.
The download snapshot contains all jobs in the database, including historical and expired jobs. It is not limited to the API's default age window. The export is refreshed weekly, normally on Sunday around 13:00 UTC.
The CSV files preserve the existing export fields and relational model. Large files sourced from the Job table are segmented by the job's published year, while reference and dimension tables remain single non-annual files. The Parquet file combines essential job fields with basic company data in one row per job.
Access and download links
File downloads are available only to access ultra and access ultra+ customers. Staff accounts may also have administrative access.
After subscribing, open the dashboard and select Generate new links in the Download access section. The dashboard shows the files currently available, friendly labels, file sizes, annual groupings, and individual copy buttons. Copy all available links copies the complete current link set.
Download URLs use a separate download access key, not an API key:
https://jobdataapi.com/download/<DOWNLOAD_ACCESS_KEY>/<FILE_NAME>
Keep the download key private. Generating a new download key invalidates the previous key and its associated download URLs. Do not commit download URLs to source repositories, embed them in public frontend code, or expose them in public logs.
Files are large and are intended for server-side or command-line downloads. For example:
curl -L -o jobs_2025.csv.gz https://jobdataapi.com/download/<DOWNLOAD_ACCESS_KEY>/jobs_2025.csv.gz
gzip -dc jobs_2025.csv.gz | head
The dashboard file size is the compressed download size. Decompressed CSV files require substantially more disk space.
CSV file organization
Non-annual reference files
The following files remain single files and are not segmented by year:
industries.csv.gzcompany_types.csv.gzcompanies.csv.gzcurrency_rates.csv.gzjob_types.csv.gzjob_regions.csv.gzjob_countries.csv.gzjob_states.csv.gzjob_cities.csv.gztags.csv.gz
These files contain dimensions and reference data used to interpret IDs in job and relation files. Company counts and open-job counts are snapshots taken at export time.
Annual job files
Every CSV generated directly from the Job table is segmented by the UTC publication year:
jobs_<YEAR>.csv.gz: job records with the original HTMLdescriptionfield.jobs_md_<YEAR>.csv.gz: the same job records withdescription_mdinstead of HTMLdescription.jobs_em_<YEAR>.csv.gz: job ID, company ID, and the 768-dimensionalembedding_3shvector.
For example, a 2025 export contains jobs_2025.csv.gz, jobs_md_2025.csv.gz, and jobs_em_2025.csv.gz.
Annual relation files
The many-to-many relation tables are segmented using the publication year of their related job. Each row still has the same two-column structure as before, and each relation row appears in the same year as its job:
job_job_types_<YEAR>.csv.gz:id,typesjob_job_cities_<YEAR>.csv.gz:id,citiesjob_job_states_<YEAR>.csv.gz:id,statesjob_job_countries_<YEAR>.csv.gz:id,countriesjob_job_regions_<YEAR>.csv.gz:id,regionsjob_job_tags_<YEAR>.csv.gz:id,tags
Compatibility filenames
The historical unsuffixed job filenames remain available as compatibility aliases:
jobs.csv.gzjobs_md.csv.gzjobs_em.csv.gzjob_job_types.csv.gzjob_job_cities.csv.gzjob_job_states.csv.gzjob_job_countries.csv.gzjob_job_regions.csv.gzjob_job_tags.csv.gz
These aliases point to the current publication year only. They are not full historical snapshots. Use the year-suffixed files for a complete export. New integrations should use explicit annual filenames.
CSV field reference
The annual files retain the established field names and values. The year suffix changes only the filename, not the row or column format.
jobs_<YEAR>.csv.gz
id: Unique job identifier.ext_id: External job identifier.company_id: ID of the company offering the job.published: Publication date/time.expired: Expiration date/time, when available.title: Job title.location: Original job location value.location_string: Normalized job location value, when available.description: Sanitized HTML job description.application_url: Application link.experience_level: Classified experience level.language: Two-letter language code.has_remote: Whether remote work is indicated.work_mode: Work mode classification.salary_min,salary_max: Original annual salary range.salary_currency: Original salary currency code.salary_min_est,salary_max_est: Estimated annual salary range.salary_min_usd,salary_max_usd: Annual salary converted or estimated in USD.salary_min_eur,salary_max_eur: Annual salary converted or estimated in EUR.
jobs_md_<YEAR>.csv.gz
This file has the same fields as jobs_<YEAR>.csv.gz, except that description_md replaces the HTML description field. Markdown is useful for text processing, search indexing, and systems that do not want to parse HTML.
jobs_em_<YEAR>.csv.gz
id: Job identifier.company_id: Company identifier.embedding_3sh: 768-dimensional half-precision vector embedding.
Some recent jobs may have an empty embedding while downstream embedding processing completes.
Denormalized Parquet export
jobs_companies.parquet is a single compressed Parquet file designed for fast analytical reads with DuckDB, Polars, PyArrow, Spark, and similar tools. It contains one row per job and includes basic company attributes in the same row, avoiding a company join for common analysis.
The file contains all jobs, including historical and expired jobs. Use published_year to filter annual analyses. The file is compressed with Zstandard and written in batches. A replacement is published atomically, so the previous complete file remains downloadable while a new weekly file is being generated.
Embeddings are intentionally not included in Parquet. The Markdown description is included, while the original HTML description is omitted. The following fields are also omitted from the Parquet schema to keep analytical scans lean: job creation and update timestamps, experience_level_predicted, and denormalized job-level company flags.
Parquet job fields
job_id,job_ext_id: Job identifiers.published_at,expired_at: Publication and expiration timestamps in UTC.published_year: UTC publication year.job_title,job_location,job_location_string: Job title and location values.job_description_md: Markdown job description.job_application_url: Application link.job_language: Language code.job_has_remote: Remote-work indicator.job_work_mode: Work mode classification.job_experience_level: Experience-level classification.job_salary_min,job_salary_max: Original annual salary range.job_salary_currency: Original salary currency.job_salary_min_est,job_salary_max_est: Estimated annual salary range.job_salary_min_usd,job_salary_max_usd: USD salary range.job_salary_min_eur,job_salary_max_eur: EUR salary range.
Parquet relation fields
The following fields are native Parquet lists, preserving many-to-many relationships without duplicating the job row:
job_cities: List of structs containingid,name,state_name,country_code,latitude, andlongitude.job_states: List of structs containingid,name,code, andcountry_code.job_countries: List of structs containingid,code,name, andregion_name.job_regions: List of structs containingidandname.job_types: List of structs containingidandname.job_tags: List of structs containingid,tag_type,name, andparent_id.
List values can be unnested in analytical SQL. A job with multiple locations remains one job row, so job counts do not require deduplication unless a query intentionally unnests a relation.
Parquet company fields
company_id,company_meta_id: Company identifiers.company_source_id,company_source_name: Source identifiers and source name.company_name: Company name.company_website_url,company_linkedin_url: Company web and LinkedIn URLs.company_info_hq,company_info_hq_string: Headquarters values.company_info_size,company_info_founded: Company size and founding year.company_info_specialties: Company specialties text.company_industry_id,company_industry_name: Industry classification.company_type_id,company_type_name: Company type classification.company_num_jobs_open,company_num_jobs_total: Company job-count snapshots at export time.
DuckDB examples
import duckdb
path = 'jobs_companies.parquet'
con = duckdb.connect()
rows = con.execute('''
SELECT
published_year,
COUNT(*) AS jobs,
COUNT(*) FILTER (WHERE job_has_remote) AS remote_jobs,
ROUND(AVG(job_salary_min_usd), 0) AS avg_min_salary_usd
FROM read_parquet(?)
WHERE published_year IS NOT NULL
GROUP BY published_year
ORDER BY published_year
''', [path]).fetchall()
for row in rows:
print(row)
To query nested country relations without changing the one-row-per-job grain:
rows = con.execute('''
SELECT
country.code AS country_code,
country.name AS country_name,
COUNT(DISTINCT jobs.job_id) AS jobs
FROM read_parquet(?) AS jobs
CROSS JOIN UNNEST(jobs.job_countries) AS countries(country)
GROUP BY country.code, country.name
ORDER BY jobs DESC
LIMIT 20
''', [path]).fetchall()
for row in rows:
print(row)
Sample files and SQLite demo
The public sample files contain recent information for 100 companies and their latest 5,000 jobs. They are intended to demonstrate the CSV field formats and the relational SQLite schema; they are not the complete weekly customer export and they are not annualized. The sample database excludes the Markdown and embedding files, as before.
Sample files:
- Industries
- Company types
- Companies
- Currency rates
- Job types
- Job regions
- Job countries
- Job states
- Job cities
- Jobs
- Jobs in Markdown
- Job embeddings
- Job type relations
- Job city relations
- Job state relations
- Job country relations
- Job region relations
- Job tag relations
- Tags
The SQLite demo database contains the sample data without Markdown descriptions or embeddings. The SQLite schema shows how to join the relational CSV tables.
Examples
Data downloads are particularly useful for data scientists and analysts who need complete datasets for advanced analytics, reporting, and repeatable offline workflows. They avoid the repeated pagination, joining, and retry logic required for a large API backfill. The examples below use an explicit publication year because the customer job CSVs are annualized. Replace 2025 with each year you need; download companies.csv.gz once because reference files are not annualized.
Use Case 1: Market analysis
Objective: Analyze job market trends to identify high-demand skills and emerging industries.
1. Download and load data
YEAR=2025
curl -L -o "jobs_${YEAR}.csv.gz" "https://jobdataapi.com/download/<DOWNLOAD_ACCESS_KEY>/jobs_${YEAR}.csv.gz"
curl -L -o companies.csv.gz "https://jobdataapi.com/download/<DOWNLOAD_ACCESS_KEY>/companies.csv.gz"
gzip -dc "jobs_${YEAR}.csv.gz" > "jobs_${YEAR}.csv"
gzip -dc companies.csv.gz > companies.csv
2. Analyze job postings
import pandas as pd
year = 2025
jobs = pd.read_csv(f'jobs_{year}.csv')
companies = pd.read_csv('companies.csv')
job_data = jobs.merge(
companies[['id', 'name', 'info_industry_id', 'info_type_id']],
left_on='company_id',
right_on='id',
how='left',
suffixes=('', '_company'),
)
skills = ['Python', 'JavaScript', 'SQL', 'Java', 'AWS']
description = job_data['description'].fillna('')
skill_counts = {
skill: description.str.contains(skill, case=False, regex=False).sum()
for skill in skills
}
print(skill_counts)
print(job_data.groupby('info_industry_id').size().sort_values(ascending=False).head(10))
3. Visualize results
import matplotlib.pyplot as plt
plt.bar(skill_counts.keys(), skill_counts.values())
plt.xlabel('Skill')
plt.ylabel('Job count')
plt.title(f'High-demand skills in {year}')
plt.show()
Use Case 2: Competitive analysis
Objective: Analyze hiring patterns and job distribution for a known set of companies.
1. Download and load data
YEAR=2025
curl -L -o "jobs_${YEAR}.csv.gz" "https://jobdataapi.com/download/<DOWNLOAD_ACCESS_KEY>/jobs_${YEAR}.csv.gz"
curl -L -o companies.csv.gz "https://jobdataapi.com/download/<DOWNLOAD_ACCESS_KEY>/companies.csv.gz"
gzip -dc "jobs_${YEAR}.csv.gz" > "jobs_${YEAR}.csv"
gzip -dc companies.csv.gz > companies.csv
2. Analyze competitor hiring patterns
import pandas as pd
year = 2025
jobs = pd.read_csv(f'jobs_{year}.csv')
companies = pd.read_csv('companies.csv')
competitor_ids = [101, 102, 103] # Example company IDs
competitor_jobs = jobs[jobs['company_id'].isin(competitor_ids)]
competitor_counts = competitor_jobs['company_id'].value_counts()
company_names = companies.set_index('id')['name'].to_dict()
competitor_counts.index = competitor_counts.index.map(company_names)
print(competitor_counts)
3. Visualize results
import matplotlib.pyplot as plt
competitor_counts.sort_values().plot(kind='barh')
plt.xlabel('Job count')
plt.ylabel('Company')
plt.title(f'Competitor hiring in {year}')
plt.show()
Use Case 3: Salary analysis
Objective: Analyze salary distributions across job titles or other job attributes.
1. Download and load data
YEAR=2025
curl -L -o "jobs_${YEAR}.csv.gz" "https://jobdataapi.com/download/<DOWNLOAD_ACCESS_KEY>/jobs_${YEAR}.csv.gz"
gzip -dc "jobs_${YEAR}.csv.gz" > "jobs_${YEAR}.csv"
2. Analyze salary data
import pandas as pd
year = 2025
jobs = pd.read_csv(f'jobs_{year}.csv')
salary_data = jobs.dropna(subset=['salary_min', 'salary_max']).copy()
salary_data['salary_min'] = pd.to_numeric(salary_data['salary_min'])
salary_data['salary_max'] = pd.to_numeric(salary_data['salary_max'])
salary_data['average_salary'] = (
salary_data['salary_min'] + salary_data['salary_max']
) / 2
title_salary = (
salary_data.groupby('title')['average_salary']
.mean()
.sort_values(ascending=False)
)
print(title_salary.head(10))
3. Visualize results
import matplotlib.pyplot as plt
title_salary.head(10).sort_values().plot(kind='barh')
plt.xlabel('Average annual salary')
plt.ylabel('Job title')
plt.title(f'Highest average advertised salaries in {year}')
plt.show()
For cross-currency comparisons, use the salary_min_usd and salary_max_usd fields, or use the estimated salary fields when original salary values are incomplete.
Use Case 4: Job posting trends over time
Objective: Compare job posting volume across months or years.
Download the annual files for the period of interest. The reference companies.csv.gz file is not required for this analysis:
for YEAR in 2023 2024 2025; do
curl -L -o "jobs_${YEAR}.csv.gz" "https://jobdataapi.com/download/<DOWNLOAD_ACCESS_KEY>/jobs_${YEAR}.csv.gz"
gzip -dc "jobs_${YEAR}.csv.gz" > "jobs_${YEAR}.csv"
done
Load and combine the annual files without relying on the current-year compatibility alias:
from pathlib import Path
import pandas as pd
files = sorted(Path('.').glob('jobs_20*.csv'))
jobs = pd.concat(
[pd.read_csv(path) for path in files],
ignore_index=True,
)
jobs['published'] = pd.to_datetime(jobs['published'], errors='coerce', utc=True)
monthly_trends = (
jobs.dropna(subset=['published'])
.groupby(jobs['published'].dt.to_period('M'))
.size()
)
print(monthly_trends)
Visualize the resulting time series:
import matplotlib.pyplot as plt
monthly_trends.plot(kind='line')
plt.xlabel('Month')
plt.ylabel('Job count')
plt.title('Job posting trends over time')
plt.show()
For large analytical scans, use jobs_companies.parquet instead of loading multiple CSV files. It combines essential job fields with basic company data, includes a published_year column, and supports column-pruned reads in DuckDB and other Parquet-compatible tools. Use the CSV files when you need the established relational tables, original HTML descriptions, or the separate embedding export.
These examples are starting points. Adjust the year range, filters, salary fields, and classification dimensions to match your analysis, and process annual files incrementally when local memory is limited.
For API field definitions and endpoint behavior, see the relevant API endpoint documentation.
Explore jobdata API with AI tools
Ask an AI assistant how to search and build with live job data.