USAJOBS Historic JOA
Historical federal job openings from USAJOBS.
usajobs_historic_joa — the dataset name to pass to the Obscura API.
What one row means
One archived federal Job Opportunity Announcement (JOA) from the USAJOBS historic API (`https://data.usajobs.gov/api/historicjoa`) — a single job posting that was published on usajobs.gov, carrying its open/close/expire dates, hiring organization, title, and salary range; metadata only, no full announcement text.
One row per (usajobs_control_number).
Point-in-time availability
Every Obscura dataset carries available_date: the calendar day the publisher made the row
available, day-of, with no session rounding. It is the one column a backtest filters on, and it means the
same thing on every dataset in the catalog.
For usajobs_historic_joa: published: available_date = positionOpenDate, the calendar day the specific announcement went live and became visible/applicable on usajobs.gov.
Refresh cadence
Obscura refreshes usajobs_historic_joa daily — the most frequent scheduled job that re-collects or re-exports it. This is Obscura's own pipeline cadence, not the upstream publisher's release schedule; when a row became public is recorded per row in available_date.
Schema — 29 columns
The full public column list for usajobs_historic_joa, with the meaning of every field. The same
schema is served unauthenticated at https://api.obscura.trade/v1/catalog/usajobs_historic_joa.
| Column | Type | Description |
|---|---|---|
| usajobs_control_number | text · not null | USAJOBS' own stable identifier for the announcement (source field `usajobsControlNumber`, arrives as either a JSON string or number and is normalized to text). Primary key, no auto-increment — re-walking the corpus upserts on this column so the same announcement is never duplicated across runs. |
| available_date | date · not null | The day this specific announcement opened and became publicly visible/applicable on usajobs.gov, sourced from `positionOpenDate` (which may arrive as a bare date or a full timestamp — only the leading date component is kept). NOT NULL; records missing a parseable open date are dropped entirely since this is the required conformed event field. |
| position_close_date | date | The day the announcement stopped accepting applications, from source field `positionCloseDate`. Nullable — some archived announcements lack a recorded close date. Distinct from available_date and never used as the conformed event column; a natural date of the posting's own lifecycle. |
| position_expire_date | date | The day the announcement record itself expires/is removed from the vendor's system, from source field `positionExpireDate`. Nullable. Can differ materially from position_close_date (e.g. a rolling/continuous announcement that stays open long past a given close cycle, or an expiry set well after closing for record-keeping). |
| announcement_number | text | The hiring agency's own human-readable announcement/vacancy number (source field `announcementNumber`, e.g. "18-SV-OIA-001", "OARMAV1"), distinct from the USAJOBS-assigned control number. Nullable; empty/whitespace-only values are normalized to NULL. |
| hiring_agency_name | text | The sub-agency/office actually doing the hiring (source field `hiringAgencyName`, e.g. "Offices, Boards and Divisions", "Veterans Benefits Administration") — one level below hiring_department_name in the federal org hierarchy. Nullable. |
| hiring_agency_code | text | Stable short code for the hiring agency (source field `hiringAgencyCode`, e.g. "ARTA"). A join-stable entity key that is more robust than the free-text hiring_agency_name. Nullable. |
| hiring_department_name | text | The parent cabinet-level or independent department (source field `hiringDepartmentName`, e.g. "Department of Justice", "Department of Veterans Affairs"). Nullable; pair with hiring_agency_name for the full org path. |
| hiring_department_code | text | Stable short code for the hiring department (source field `hiringDepartmentCode`, e.g. "AR"). A join-stable entity key that is more robust than the free-text hiring_department_name. Nullable. |
| hiring_subelement_name | text | The third org-hierarchy level below hiring_agency_name (source field `hiringSubelementName`) — completes the department → agency → subelement path. Nullable (often null in the archive). |
| position_title | text | The job title as posted (source field `positionTitle`, e.g. "Attorney and Assistant United States Attorney", "Student Volunteer Intern - Office of International Affairs (Summer 2025)"). Nullable; free text, not normalized to a controlled taxonomy. |
| minimum_salary | double precision | Bottom of the announced pay range in USD (source field `minimumSalary`). Nullable, but frequently present as 0 in the archive (e.g. unpaid student volunteer postings, or announcements where the vendor simply omitted a range) rather than truly missing — treat 0 as "no stated floor", not a real wage. |
| maximum_salary | double precision | Top of the announced pay range in USD (source field `maximumSalary`). Same 0-vs-null caveat as minimum_salary: 0 typically indicates an unpaid or unspecified-pay posting rather than a genuine ceiling of $0. |
| salary_type | text | Pay basis / unit for the salary range (source field `salaryType`, e.g. "Per Year", "Per Hour", "Without Compensation"). Required to interpret minimum_salary/maximum_salary — an hourly 54 vs an annual 54,494 are indistinguishable without it. Nullable. |
| pay_scale | text | Pay-plan code governing the salary scale (source field `payScale`, e.g. "GS", "YA", "ES", "WG"). Categorical dimension core to comparing pay across postings. Nullable. |
| minimum_grade | text | Bottom of the GS/pay-plan grade band (source field `minimumGrade`, e.g. "2", "13"). A seniority/level signal tied to the salary range; kept as text since grades are not strictly numeric. Nullable. |
| maximum_grade | text | Top of the GS/pay-plan grade band (source field `maximumGrade`). Seniority/level signal; text for the same reason as minimum_grade. Nullable. |
| promotion_potential | text | Target grade the role can promote to (source field `promotionPotential`) — a career-ladder signal. Nullable (often null in the archive). |
| appointment_type | text | Appointment tenure category (source field `appointmentType`, e.g. "Permanent", "Temporary", "Term", "Seasonal") — central to federal-hiring composition and trend analysis. Nullable. |
| work_schedule | text | Labor-composition category (source field `workSchedule`, e.g. "Full-time", "Part-time", "Intermittent"). Nullable. |
| service_type | text | Structural service classification (source field `serviceType`, "Competitive" vs "Excepted" service). Nullable (often null in the archive). |
| who_may_apply | text | Applicant-eligibility text (source field `whoMayApply`, e.g. "Status Candidates (Merit Promotion and VEOA Eligibles)") — free-text nuance beyond the hiring_paths array. Nullable. |
| telework_eligible | text | Telework-eligibility flag (source field `teleworkEligible`, "Y"/"N") — a remote-work-trend signal over time. Kept as the raw Y/N text. Nullable. |
| security_clearance_required | text | Whether the position requires a security clearance (source field `securityClearanceRequired`, "Y"/"N") — distinguishes cleared vs non-cleared demand. Raw Y/N text. Nullable. |
| security_clearance | text | The required clearance level when applicable (source field `securityClearance`). Nullable (often null in the archive). |
| travel_requirement | text | Required-travel category (source field `travelRequirement`). Nullable (often null in the archive). |
| total_openings | bigint | Number of positions the announcement is hiring for (source field `totalOpenings`) — turns announcements into positions for demand estimation. Often null in the archive, populated in newer records. |
| position_opening_status | text | Announcement lifecycle status (source field `positionOpeningStatus`, e.g. "Applications under review"). Nullable. |
| hiring_paths | text | The announcement's eligible `hiringPaths` array (e.g. objects like {"hiringPath": "public"}), serialized verbatim to a JSON text blob rather than normalized to rows/columns — parse this column as JSON to inspect individual hiring-path codes (e.g. public, veterans, internal, students). Nullable when the source field is null. |
Access usajobs_historic_joa
Two delivery paths, one identifier. Both require an Obscura account and an active subscription; the catalog entry and the schema above are public.
import obscura
client = obscura.Client("obs_live_…")
df = client.query(
dataset="usajobs_historic_joa",
start="2024-01-01",
)
Create a free account Browse all 95 datasets
Frequently asked questions
What is in the usajobs_historic_joa dataset?
Historical federal job openings from USAJOBS. One archived federal Job Opportunity Announcement (JOA) from the USAJOBS historic API (`https://data.usajobs.gov/api/historicjoa`) — a single job posting that was published on usajobs.gov, carrying its open/close/expire dates, hiring organization, title, and salary range; metadata only, no full announcement text.
How do I avoid look-ahead bias with usajobs_historic_joa?
Filter on usajobs_historic_joa.available_date, the day the publisher made the row public. For this dataset that date is derived as follows — published: available_date = positionOpenDate, the calendar day the specific announcement went live and became visible/applicable on usajobs.gov. A query of the form WHERE available_date <= '<as-of date>' never sees a row before it existed.
In what formats can I get usajobs_historic_joa?
As a Parquet bulk export (POST https://api.obscura.trade/v1/download) or as JSON from the typed query API (POST https://api.obscura.trade/v1/query), both with dataset="usajobs_historic_joa". The column schema is public at https://api.obscura.trade/v1/catalog/usajobs_historic_joa.
How often is usajobs_historic_joa updated?
Obscura refreshes usajobs_historic_joa on a daily schedule — that is the most frequent scheduled job that re-collects or re-exports the table. It is Obscura's own pipeline cadence, not the upstream publisher's release schedule; when the publisher makes a row available is described by the availability rule above, and is recorded per row in available_date.