onet2r is a modern, tidyverse-first R client for the O*NET Web Services API v2. It provides a clean, consistent, analysis-ready interface to U. S. occupational data.
π API Base URL: https://api-v2.onetcenter.org
- π Search occupations by keyword or O*NET-SOC code
- π Retrieve occupation details β skills, knowledge, abilities, tasks, and hot technologies
- ποΈ Access database tables with automatic pagination
- π Perform crosswalks between military codes and civilian occupations
- π Map taxonomies between O*NET-SOC versions
- β Consistent tibble outputs with snake_case columns and stable empty schemas
- π Automatic retry logic for rate limits (HTTP 429) and transient server errors
- π Type-safe schemas with validated empty result handling
Install the development version from GitHub:
# install.packages("pak")
pak::pak("farach/onet2r")O*NET API requests require an API key. Register for a free key here.
Recommended: Store your key in .Renviron for persistence across sessions:
usethis::edit_r_environ()Add this line:
ONET_API_KEY=your-api-key-here
Restart R to load the environment variable.
Alternative: Set for the current session only:
Sys.setenv(ONET_API_KEY = "your-api-key-here")Verify your key is configured:
library(onet2r)
onet_api_key()library(onet2r)
# Search for occupations
onet_search("software developer")
# List occupations (paged)
occ <- onet_occupations(start = 1, end = 25)
# Get occupation summary
onet_occupation("15-1252.00")
# Retrieve structured details (returns tibbles)
onet_skills("15-1252.00", end = 5)
onet_knowledge("15-1252.00", end = 5)
onet_abilities("15-1252.00", end = 5)
onet_tasks("15-1252.00", end = 5)
# Technology endpoints
onet_hot_technology("15-1252.00", end = 5)
onet_technology_skills("15-1252.00", end = 2)| Function | Description |
|---|---|
onet_occupations() |
List one page of occupations |
onet_occupations_all() |
List all occupations |
onet_occupation() |
Get occupation summary |
onet_occupation_details() |
Get full occupation report |
onet_search() |
Search occupations by keyword |
| Function | Description |
|---|---|
onet_skills() |
Skills data |
onet_skills_all() |
All skills rows |
onet_knowledge() |
Knowledge areas |
onet_abilities() |
Abilities |
onet_work_styles() |
Work styles |
onet_interests() |
Occupational interests |
onet_work_context() |
Work context |
onet_work_context_all() |
All work context rows |
onet_work_activities() |
Work activities |
onet_work_activities_all() |
All work activities rows |
onet_tasks() |
Tasks |
onet_detailed_work_activities() |
Detailed work activities |
onet_related_occupations() |
Related occupations |
onet_professional_associations() |
Professional associations |
onet_apprenticeship() |
Apprenticeship info |
onet_education() |
Education requirements |
onet_job_zone() |
Job zone (returns list) |
| Function | Description |
|---|---|
onet_hot_technology() |
Hot technology skills |
onet_technology_skills() |
Technology skills |
onet_in_demand_skills() |
In-demand skills |
| Function | Description |
|---|---|
onet_tables() |
List available tables |
onet_table_info() |
Get table column info |
onet_table() |
Retrieve full table (auto-paginated) |
onet_table_page() |
Retrieve single page |
| Function | Description |
|---|---|
onet_crosswalk_military() |
Map military to civilian occupations |
onet_taxonomy_map() |
Map between O*NET-SOC versions |
A typical workflow: search β select β pull details.
library(dplyr)
library(onet2r)
# Find target occupation
target_code <- onet_search("data scientist") |>
filter(title == "Data Scientists") |>
pull(code)
# Pull structured details
skills <- onet_skills(target_code, end = 25)
tasks <- onet_tasks(target_code, end = 25)
tech <- onet_hot_technology(target_code, end = 25)
# Pull complete occupation-level sections for downstream analysis
all_occupations <- onet_occupations_all(show_progress = FALSE)
all_skills <- onet_skills_all(target_code, show_progress = FALSE)
# Combine skills from multiple occupations
codes <- c("15-2051.00", "15-1252.00")
all_skills <- codes |>
purrr::map(\(code) onet_skills_all(code, show_progress = FALSE) |> dplyr::mutate(code = code)) |>
purrr::list_rbind()Most endpoints return tibbles with:
- snake_case column names (e. g.,
percentage_of_respondents) - Stable empty schemas (empty tibble with correct columns and types)
- Predictable pagination via
start/endarguments
onet_education("15-1252.00")
#> # A tibble: 6 Γ 3
#> code title percentage_of_respondents
#> <chr> <chr> <dbl>
#> 1 6 Bachelor's degree 64.7
#> 2 7 Master's degree 20.5
#> ... # Missing API key
#> Error: O*NET API key not found.
#> βΉ Set your API key with `Sys.setenv(ONET_API_KEY = "your-key")`
#> βΉ Get a key at <https://services.onetcenter.org/developer/>
# Invalid occupation code
onet_skills("invalid-code")
#> Error: Invalid O*NET-SOC code format: "invalid-code"
#> βΉ Expected format: XX-XXXX or XX-XXXX. XX (e.g., 15-1252 or 15-1252.00)
# No results (returns empty tibble with correct schema)
onet_search("xyzabc123nonexistent")
#> # A tibble: 0 Γ 2
#> # βΉ 2 variables: code <chr>, title <chr>| Practice | Why |
|---|---|
Store API key in .Renviron |
Keeps credentials out of scripts |
Use start/end for pagination |
Smaller, faster requests |
| Prefer detail endpoints over summaries | Structured data for analysis |
- Package documentation:
?onet2ror?onet_search - Vignettes:
browseVignettes("onet2r") - O*NET API docs: https://services.onetcenter.org/reference/
- Report issues: https://github.com/farach/onet2r/issues
Issues and PRs are welcome!
When adding a new endpoint:
- Return a tibble for record-list endpoints
- Include stable empty schemas
- Add roxygen documentation for all arguments
- Write minimal tests (smoke test + schema validation)
Please note that onet2r is released with a Contributor Code of Conduct. By contributing, you agree to abide by its terms.
MIT Β© 2026 Alex Farach