jobdata

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.

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.gz
  • company_types.csv.gz
  • companies.csv.gz
  • currency_rates.csv.gz
  • job_types.csv.gz
  • job_regions.csv.gz
  • job_countries.csv.gz
  • job_states.csv.gz
  • job_cities.csv.gz
  • tags.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 HTML description field.
  • jobs_md_<YEAR>.csv.gz: the same job records with description_md instead of HTML description.
  • jobs_em_<YEAR>.csv.gz: job ID, company ID, and the 768-dimensional embedding_3sh vector.

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, types
  • job_job_cities_<YEAR>.csv.gz: id, cities
  • job_job_states_<YEAR>.csv.gz: id, states
  • job_job_countries_<YEAR>.csv.gz: id, countries
  • job_job_regions_<YEAR>.csv.gz: id, regions
  • job_job_tags_<YEAR>.csv.gz: id, tags

Compatibility filenames

The historical unsuffixed job filenames remain available as compatibility aliases:

  • jobs.csv.gz
  • jobs_md.csv.gz
  • jobs_em.csv.gz
  • job_job_types.csv.gz
  • job_job_cities.csv.gz
  • job_job_states.csv.gz
  • job_job_countries.csv.gz
  • job_job_regions.csv.gz
  • job_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 containing id, name, state_name, country_code, latitude, and longitude.
  • job_states: List of structs containing id, name, code, and country_code.
  • job_countries: List of structs containing id, code, name, and region_name.
  • job_regions: List of structs containing id and name.
  • job_types: List of structs containing id and name.
  • job_tags: List of structs containing id, tag_type, name, and parent_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:

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.

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.