Skip to content

A user can query the Job Search API endpoint #4

Closed

Description

@paddy74

The Job Search API is designed to support lightweight JOA content consumption by consumers and delivers currently open job opportunity announcements.

Enable usage of the Job Search API endpoint as documented in the API Reference - GET /api/Search.

Activity

  1. changed the title [-]Support the Job Search API endpoint[/-] [+]A user can query the Job Search API endpoint[/+] on Sep 11, 2025
  2. paddy74 commented on Sep 11, 2025

    @paddy74
    OwnerAuthor

    Here is an initial generated solution for usajobsapi/endpoints/search.py

    class SortField(str, Enum):
        opendate = "opendate"
        closedate = "closedate"
        organizationname = "organizationname"
        jobtitle = "jobtitle"
        positiontitle = "positiontitle"
        openingdate = "openingdate"
        closingdate = "closingdate"
        honame = "honame"
        salarymin = "salarymin"
        location = "location"
        department = "department"
        title = "title"
        agency = "agency"
        salary = "salary"
    
    
    class SortDirection(str, Enum):
        Asc = "Asc"
        Desc = "Desc"
    
    
    class WhoMayApply(str, Enum):
        All = "All"
        Public = "Public"
        Status = "Status"
    
    
    class Fields(str, Enum):
        Min = "Min"
        Full = "Full"
    
    
    class HiringPath(str, Enum):
        public = "public"
        vet = "vet"
        nguard = "nguard"
        disability = "disability"
        native = "native"
        mspouse = "mspouse"
        student = "student"
        ses = "ses"
        peace = "peace"
        overseas = "overseas"
        fed_internal_search = "fed-internal-search"
        graduates = "graduates"
        fed_excepted = "fed-excepted"
        fed_competitive = "fed-competitive"
        fed_transition = "fed-transition"
        land = "land"
        special_authorities = "special-authorities"
    
    
    class SearchEndpoint(BaseModel):
        """Declarative endpoint enclosing params + response shapes + metadata."""
        method: str = "GET"
        path: str = "/api/Search"  # per docs. :contentReference[oaicite:1]{index=1}
    
        class Params(BaseModel):
            model_config = ConfigDict(frozen=True, extra="forbid", populate_by_name=True)
    
            keyword: Optional[str] = Field(None, serialization_alias="Keyword")
            position_title: Optional[str] = Field(None, serialization_alias="PositionTitle")
    
            remuneration_min: Optional[int] = Field(None, serialization_alias="RemunerationMinimumAmount")
            remuneration_max: Optional[int] = Field(None, serialization_alias="RemunerationMaximumAmount")
            pay_grade_high: Optional[str] = Field(None, serialization_alias="PayGradeHigh")
            pay_grade_low: Optional[str] = Field(None, serialization_alias="PayGradeLow")
    
            job_category_codes: List[str] = Field(default_factory=list, serialization_alias="JobCategoryCode")
            position_schedule_type_codes: List[str] = Field(default_factory=list, serialization_alias="PositionScheduleTypeCode")
            position_offering_type_codes: List[str] = Field(default_factory=list, serialization_alias="PositionOfferingTypeCode")
    
            organization: List[str] = Field(default_factory=list, serialization_alias="Organization")
            location_names: List[str] = Field(default_factory=list, serialization_alias="LocationName")
            radius: Optional[conint(gt=0)] = Field(None, serialization_alias="Radius")
    
            travel_percentage: List[str] = Field(default_factory=list, serialization_alias="TravelPercentage")
            relocation: Optional[bool] = Field(None, serialization_alias="RelocationIndicator")
            security_clearance_required: List[str] = Field(default_factory=list, serialization_alias="SecurityClearanceRequired")
            position_sensitivity: List[str] = Field(default_factory=list, serialization_alias="PositionSensitivity")
    
            who_may_apply: Optional[WhoMayApply] = Field(None, serialization_alias="WhoMayApply")
            hiring_paths: List[HiringPath] = Field(default_factory=list, serialization_alias="HiringPath")
    
            salary_bucket: List[str] = Field(default_factory=list, serialization_alias="SalaryBucket")
            grade_bucket: List[str] = Field(default_factory=list, serialization_alias="GradeBucket")
    
            supervisory_status: Optional[str] = Field(None, serialization_alias="SupervisoryStatus")
            date_posted_days: Optional[conint(ge=0, le=60)] = Field(None, serialization_alias="DatePosted")
            job_grade_codes: List[str] = Field(default_factory=list, serialization_alias="JobGradeCode")
            mission_critical_tags: List[str] = Field(default_factory=list, serialization_alias="MissionCriticalTags")
    
            sort_field: Optional[SortField] = Field(None, serialization_alias="SortField")
            sort_direction: Optional[SortDirection] = Field(None, serialization_alias="SortDirection")
            page: Optional[conint(ge=1)] = Field(None, serialization_alias="Page")
            results_per_page: Optional[conint(ge=1, le=500)] = Field(None, serialization_alias="ResultsPerPage")
            fields: Optional[Fields] = Field(None, serialization_alias="Fields")
    
            remote_indicator: Optional[bool] = Field(None, serialization_alias="RemoteIndicator")
    
            @model_validator(mode="after")
            def _radius_requires_location(self) -> "SearchEndpoint.Params":
                if self.radius is not None and not self.location_names:
                    raise ValueError("Radius requires at least one LocationName.")
                return self
    
            @field_validator("remuneration_max")
            @classmethod
            def _check_min_le_max(cls, v, info):
                mn = info.data.get("remuneration_min")
                if v is not None and mn is not None and v < mn:
                    raise ValueError("RemunerationMaximumAmount must be >= RemunerationMinimumAmount.")
                return v
    
            def to_params(self) -> Dict[str, str]:
                return _dump_by_alias(self)
    
        # --- minimal response shapes (expand as needed) -------------------------
        class JobSummary(BaseModel):
            id: str = Field(alias="MatchedObjectId")
            position_title: str = Field(alias="PositionTitle")
            organization_name: Optional[str] = Field(default=None, alias="OrganizationName")
            locations_display: Optional[str] = Field(default=None, alias="PositionLocationDisplay")
            min_salary: Optional[float] = Field(default=None, alias="MinimumRange")
            max_salary: Optional[float] = Field(default=None, alias="MaximumRange")
            application_close_date: Optional[str] = Field(default=None, alias="ApplicationCloseDate")
    
        class Response(BaseModel):
            # The Search API wraps results under SearchResultItems; adjust if you request `Fields=Full`.
            total: Optional[int] = Field(default=None, alias="SearchResultCount")
            items: List[Dict[str, Any]] = Field(default_factory=list, alias="SearchResultItems")
    
            def jobs(self) -> List["SearchEndpoint.JobSummary"]:
                # Most SDKs keep raw, but you may want a normalized list:
                out: List[SearchEndpoint.JobSummary] = []
                for item in self.items:
                    # Some responses nest the item under 'MatchedObjectDescriptor'
                    descriptor = item.get("MatchedObjectDescriptor") or item
                    try:
                        out.append(SearchEndpoint.JobSummary.model_validate(descriptor))
                    except Exception:
                        continue
                return out
  3. self-assigned this
    on Sep 11, 2025
  4. linked a pull request that will close this issuefeat(api): support the job search api endpoint #9on Sep 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions