Skip to main content

Unofficial Python client for JSearch API by Open Web Ninja

Project description

py-jsearch

Test Code Quality Python 3.8+ License: MIT

An unofficial Python client for the JSearch API by Open Web Ninja. Search for jobs, get job details, and retrieve salary estimates with a clean, type-safe interface.

Features

  • Job Search - Search across millions of jobs from Google for Jobs
  • Job Details - Get detailed information for specific jobs
  • Salary Estimates - Get salary information by job title and location
  • Company Salaries - Get salary data for specific companies
  • Async & Sync Support - Use async or synchronous clients based on your needs
  • Type-Safe - Full type hints with Pydantic models
  • Easy to Use - Intuitive API with comprehensive examples

Installation

pip install py-jsearch

Or with uv:

uv add py-jsearch

Quick Start

Get Your API Key

Sign up at Open Web Ninja to get your JSearch API access key.

Basic Usage (Synchronous)

from py_jsearch import JSearchClient, JobSearchParams

# Initialize the client
client = JSearchClient(access_key="your-api-key-here")

# Search for jobs
params = JobSearchParams(
    query="python developer in San Francisco",
    num_pages=1
)

jobs = client.search_jobs(params)

for job in jobs:
    print(f"Title: {job.job_title}")
    print(f"Company: {job.employer_name}")
    print(f"Location: {job.job_location}")
    print(f"Type: {job.job_employment_type}")
    print(f"Apply: {job.job_apply_link}")
    print("-" * 50)

# Don't forget to close the client
client.close()

Using Context Manager (Recommended)

from py_jsearch import JSearchClient, JobSearchParams

with JSearchClient(access_key="your-api-key-here") as client:
    params = JobSearchParams(query="data scientist jobs")
    jobs = client.search_jobs(params)
    
    for job in jobs:
        print(f"{job.job_title} at {job.employer_name}")

Async Usage

import asyncio
from py_jsearch import JSearchAsyncClient, JobSearchParams

async def main():
    async with JSearchAsyncClient(access_key="your-api-key-here") as client:
        params = JobSearchParams(
            query="software engineer",
            country="us",
            language="en"
        )
        
        jobs = await client.search_jobs(params)
        
        for job in jobs:
            print(f"{job.job_title} - {job.employer_name}")

asyncio.run(main())

Examples

1. Advanced Job Search with Filters

from py_jsearch import JSearchClient, JobSearchParams

with JSearchClient(access_key="your-api-key") as client:
    params = JobSearchParams(
        query="remote full stack developer",
        page=1,
        num_pages=2,
        date_posted="week",  # Jobs posted in the last week
        work_from_home=True,  # Remote jobs only
        employment_types=["FULLTIME", "CONTRACTOR"],
        job_requirements=["under_3_years_experience"],
        country="us",
        language="en"
    )
    
    jobs = client.search_jobs(params)
    
    for job in jobs:
        print(f"• {job.job_title}")
        print(f"  Company: {job.employer_name}")
        print(f"  Location: {job.job_location}")
        print(f"  Type: {job.job_employment_type}")
        print(f"  Posted: {job.job_posted_at}")
        
        if job.job_min_salary and job.job_max_salary:
            print(f"  Salary: ${job.job_min_salary:,.0f} - ${job.job_max_salary:,.0f} {job.job_salary_period}")
        
        print(f"  Apply: {job.job_apply_link}")
        print("-" * 80)

2. Get Job Details

from py_jsearch import JSearchClient, JobDetailsParams

with JSearchClient(access_key="your-api-key") as client:
    # Get details for a specific job
    params = JobDetailsParams(
        job_id="woj2gE2S_6LqvmLAAAAAAA==",
        country="us",
        language="en"
    )
    
    job = client.get_job(params)
    
    if job:
        print(f"Title: {job.job_title}")
        print(f"Company: {job.employer_name}")
        print(f"Description:\n{job.job_description}")
        
        # Check benefits
        if job.job_benefits:
            print(f"\nBenefits: {', '.join(job.job_benefits)}")
        
        # Check highlights
        if job.job_highlights:
            if job.job_highlights.qualifications:
                print("\nQualifications:")
                for qual in job.job_highlights.qualifications:
                    print(f"  • {qual}")
            
            if job.job_highlights.responsibilities:
                print("\nResponsibilities:")
                for resp in job.job_highlights.responsibilities:
                    print(f"  • {resp}")

3. Get Salary Estimates by Location

from py_jsearch import JSearchClient, JobSalarySearchParams

with JSearchClient(access_key="your-api-key") as client:
    params = JobSalarySearchParams(
        job_title="software engineer",
        location="New York",
        location_type="CITY",
        years_of_experience="FOUR_TO_SIX"
    )
    
    salary_info = client.get_job_salary(params)
    
    if salary_info:
        print(f"Location: {salary_info.location}")
        print(f"Job Title: {salary_info.job_title}")
        print(f"\nTotal Compensation:")
        print(f"  Min: ${salary_info.min_salary:,.2f}")
        print(f"  Median: ${salary_info.median_salary:,.2f}")
        print(f"  Max: ${salary_info.max_salary:,.2f}")
        
        print(f"\nBase Salary:")
        print(f"  Min: ${salary_info.min_base_salary:,.2f}")
        print(f"  Median: ${salary_info.median_base_salary:,.2f}")
        print(f"  Max: ${salary_info.max_base_salary:,.2f}")
        
        print(f"\nAdditional Pay:")
        print(f"  Min: ${salary_info.min_additional_pay:,.2f}")
        print(f"  Median: ${salary_info.median_additional_pay:,.2f}")
        print(f"  Max: ${salary_info.max_additional_pay:,.2f}")
        
        print(f"\nData: {salary_info.salary_count} salaries")
        print(f"Confidence: {salary_info.confidence}")
        print(f"Updated: {salary_info.salaries_updated_at}")

4. Get Company-Specific Salaries

from py_jsearch import JSearchClient, CompanySalarySearchParams

with JSearchClient(access_key="your-api-key") as client:
    params = CompanySalarySearchParams(
        company="Google",
        job_title="Software Engineer",
        location="United States",
        location_type="COUNTRY",
        years_of_experience="ONE_TO_THREE"
    )
    
    salary_info = client.get_company_salary(params)
    
    if salary_info:
        print(f"Company: {salary_info.company}")
        print(f"Position: {salary_info.job_title}")
        print(f"Location: {salary_info.location}")
        print(f"\nSalary Range:")
        print(f"  ${salary_info.min_salary:,.0f} - ${salary_info.max_salary:,.0f}")
        print(f"  Median: ${salary_info.median_salary:,.0f} {salary_info.salary_period}")
        print(f"\nBased on {salary_info.salary_count} salaries")

5. Search Jobs with Multiple Pages

from py_jsearch import JSearchClient, JobSearchParams

with JSearchClient(access_key="your-api-key") as client:
    params = JobSearchParams(
        query="machine learning engineer",
        page=1,
        num_pages=3,  # Get 3 pages of results (30 jobs)
        date_posted="month",
        country="us"
    )
    
    jobs = client.search_jobs(params)
    
    job_list = list(jobs)
    print(f"Found {len(job_list)} jobs")
    
    for i, job in enumerate(job_list, 1):
        print(f"{i}. {job.job_title} at {job.employer_name}")

6. Filter Jobs by Publisher

from py_jsearch import JSearchClient, JobSearchParams

with JSearchClient(access_key="your-api-key") as client:
    # Exclude specific job boards
    params = JobSearchParams(
        query="frontend developer",
        exclude_job_publishers=["Indeed", "ZipRecruiter"],
        work_from_home=True
    )
    
    jobs = client.search_jobs(params)
    
    for job in jobs:
        print(f"{job.job_title} - Posted on {job.job_publisher}")

7. Get Specific Job Fields (Field Projection)

from py_jsearch import JSearchClient, JobDetailsParams

with JSearchClient(access_key="your-api-key") as client:
    # Only get specific fields to reduce response size
    params = JobDetailsParams(
        job_id="woj2gE2S_6LqvmLAAAAAAA==",
        fields=["job_title", "employer_name", "job_description", "job_apply_link"]
    )
    
    job = client.get_job(params)
    
    if job:
        print(f"Title: {job.job_title}")
        print(f"Company: {job.employer_name}")
        print(f"Apply: {job.job_apply_link}")

8. Handle Multiple Apply Options

from py_jsearch import JSearchClient, JobSearchParams

with JSearchClient(access_key="your-api-key") as client:
    params = JobSearchParams(query="product manager")
    jobs = client.search_jobs(params)
    
    for job in jobs:
        print(f"Job: {job.job_title}")
        
        if job.apply_options:
            print(f"  Available on {len(job.apply_options)} platforms:")
            for option in job.apply_options:
                direct = "✓" if option.is_direct else "✗"
                print(f"    {direct} {option.publisher}: {option.apply_link}")
        
        print()

9. Async Batch Processing

import asyncio
from py_jsearch import JSearchAsyncClient, JobSearchParams

async def search_multiple_queries(client, queries):
    tasks = []
    for query in queries:
        params = JobSearchParams(query=query, num_pages=1)
        tasks.append(client.search_jobs(params))
    
    results = await asyncio.gather(*tasks)
    return results

async def main():
    queries = [
        "python developer in Seattle",
        "data analyst in Boston",
        "devops engineer in Austin"
    ]
    
    async with JSearchAsyncClient(access_key="your-api-key") as client:
        all_results = await search_multiple_queries(client, queries)
        
        for query, jobs in zip(queries, all_results):
            print(f"\n=== {query} ===")
            for job in jobs:
                print(f"  • {job.job_title} at {job.employer_name}")

asyncio.run(main())

10. Error Handling

from py_jsearch import (
    JSearchClient,
    JobSearchParams,
    JSearchClientError,
    JSearchAuthError,
    JSearchResponseError
)

try:
    with JSearchClient(access_key="your-api-key") as client:
        params = JobSearchParams(query="software engineer")
        jobs = client.search_jobs(params)
        
        for job in jobs:
            print(f"{job.job_title} at {job.employer_name}")

except JSearchAuthError as e:
    print(f"Authentication failed: {e}")
    print("Please check your API key")

except JSearchResponseError as e:
    print(f"Failed to parse response: {e}")

except JSearchClientError as e:
    print(f"API error: {e}")
    if e.code:
        print(f"Status code: {e.code}")
    if e.response:
        print(f"Request ID: {e.response.request_id}")

except Exception as e:
    print(f"Unexpected error: {e}")

API Parameters

JobSearchParams

Parameter Type Default Description
query str required Search query (e.g., "python developer in NYC")
page int 1 Page number (1-100)
num_pages int 1 Number of pages to fetch (1-20)
country str "us" Country code (ISO 3166-1 alpha-2)
language str None Language code (ISO 639)
date_posted str "all" Filter: "all", "today", "3days", "week", "month"
work_from_home bool False Remote jobs only
employment_types list[str] None ["FULLTIME", "CONTRACTOR", "PARTTIME", "INTERN"]
job_requirements list[str] None Experience/education requirements
radius float None Search radius in km
exclude_job_publishers list[str] None Publishers to exclude
fields list[str] None Specific fields to return

JobDetailsParams

Parameter Type Default Description
job_id str required Job ID (supports batch up to 20 IDs)
country str "us" Country code
language str None Language code
fields list[str] None Specific fields to return

JobSalarySearchParams

Parameter Type Default Description
job_title str required Job title for salary estimation
location str required Location for salary data
location_type str "ANY" "ANY", "CITY", "STATE", "COUNTRY"
years_of_experience str "ALL" Experience level filter
fields list[str] None Specific fields to return

CompanySalarySearchParams

Parameter Type Default Description
company str required Company name
job_title str required Job title
location str None Location filter
location_type str "ANY" Location type
years_of_experience str "ALL" Experience level

Response Models

Job

Contains detailed job information including:

  • Basic info: job_id, job_title, employer_name, job_description
  • Location: job_city, job_state, job_country, job_latitude, job_longitude
  • Employment: job_employment_type, job_employment_types, job_is_remote
  • Application: job_apply_link, apply_options, job_apply_is_direct
  • Compensation: job_min_salary, job_max_salary, job_salary_period, job_salary_currency
  • Requirements: job_required_experience, job_required_education, job_required_skills
  • Highlights: job_highlights (qualifications, responsibilities, benefits)
  • Dates: job_posted_at, job_posted_at_timestamp, job_posted_at_datetime_utc

JobSalaryInfo

Salary estimation data:

  • location, job_title
  • Total compensation: min_salary, median_salary, max_salary
  • Base salary: min_base_salary, median_base_salary, max_base_salary
  • Additional pay: min_additional_pay, median_additional_pay, max_additional_pay
  • Metadata: salary_count, confidence, salaries_updated_at, publisher_name

CompanySalaryInfo

Company-specific salary data (same fields as JobSalaryInfo plus company field)

Client Options

Both JSearchClient and JSearchAsyncClient support:

client = JSearchClient(
    access_key="your-api-key",
    base_url="https://api.openwebninja.com/jsearch",  # Optional: custom API URL
    timeout=30.0  # Optional: request timeout in seconds
)

Best Practices

  1. Use Context Managers: Always use with statements to ensure proper cleanup

    with JSearchClient(access_key="key") as client:
        # Your code here
    
  2. Handle Pagination: Use num_pages carefully as requests beyond 10 pages cost 3x

    params = JobSearchParams(query="developer", num_pages=2)  # 2x cost
    
  3. Filter Early: Use filters to reduce results and API costs

    params = JobSearchParams(
        query="engineer",
        date_posted="week",
        work_from_home=True,
        employment_types=["FULLTIME"]
    )
    
  4. Field Projection: Request only needed fields to reduce response size

    params = JobDetailsParams(
        job_id="abc123",
        fields=["job_title", "employer_name", "job_apply_link"]
    )
    
  5. Error Handling: Always wrap API calls in try-except blocks

    try:
        jobs = client.search_jobs(params)
    except JSearchAuthError:
        # Handle auth error
    except JSearchClientError:
        # Handle API error
    

Requirements

  • Python 3.8+
  • httpx
  • pydantic
  • typing-extensions

License

This is an unofficial client and is not affiliated with Open Web Ninja.

Support

For issues and questions:

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

py_jsearch-0.0.1.tar.gz (103.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

py_jsearch-0.0.1-py3-none-any.whl (14.7 kB view details)

Uploaded Python 3

File details

Details for the file py_jsearch-0.0.1.tar.gz.

File metadata

  • Download URL: py_jsearch-0.0.1.tar.gz
  • Upload date:
  • Size: 103.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.16 {"installer":{"name":"uv","version":"0.9.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for py_jsearch-0.0.1.tar.gz
Algorithm Hash digest
SHA256 6082b408be13292de1eec59c3cb83a9ef231d2e3e857977eb409272b697ba5b3
MD5 a1ac6f99194796d6a2cdfbfa3779a621
BLAKE2b-256 70e619152634e358d759b3b91f4997f0b6459948b1568196f0e8543d40336499

See more details on using hashes here.

File details

Details for the file py_jsearch-0.0.1-py3-none-any.whl.

File metadata

  • Download URL: py_jsearch-0.0.1-py3-none-any.whl
  • Upload date:
  • Size: 14.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.16 {"installer":{"name":"uv","version":"0.9.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for py_jsearch-0.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 80ae581e0c9b456b75e92cafcad5055b254dda4c77a1dcaaf60586dfe5438918
MD5 54e84a35b3d41500483f08c02a3d5014
BLAKE2b-256 c27c2d1d39a9f7c59524f078107ca105122b284ad5f6bb38582e28c90f45b71b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page