All platforms

Greenhouse Jobs API.

Popular ATS for tech companies and startups. Access jobs from thousands of innovative companies.

Greenhouse
Live
400K+jobs indexed monthly
<3haverage discovery time
1hrefresh interval
Companies using Greenhouse
AirbnbPinterestStripeFigmaNotion
Developer tools

Try the API.

Test Jobs, Feed, and Auto-Apply endpoints against https://connect.jobo.world with live request/response examples, then copy ready-to-use curl commands.

What's in every response.

Data fields, real-world applications, and the companies already running on Greenhouse.

Data fields
  • Tech-focused job listings
  • Startup ecosystem coverage
  • Structured job data
  • Department & team info
  • Location flexibility data
  • Structured pay ranges
Use cases
  1. 01Tech job boards
  2. 02Startup job aggregation
  3. 03Remote work tracking
Trusted by
AirbnbPinterestStripeFigmaNotionAnthropicA247shifts
DIY GUIDE

How to scrape Greenhouse.

Step-by-step guide to extracting jobs from Greenhouse-powered career pages—endpoints, authentication, and working code.

RESTbeginnerNo official limits; 200-500ms between requests recommendedNo auth

Extract the company token

Identify the company token from a Greenhouse job board URL. The token is the path segment after the domain.

Step 1: Extract the company token
from urllib.parse import urlparse

# URL patterns:
# https://job-boards.greenhouse.io/{companyToken}
# https://boards.greenhouse.io/{companyToken}

url = "https://job-boards.greenhouse.io/anthropic"
company_token = urlparse(url).path.strip("/")

print(f"Company token: {company_token}")  # "anthropic"

Fetch job listings with pagination

Use the Remix data loader endpoint to get paginated job listings with the ?_data= query parameter. The API returns full job descriptions in the content field.

Step 2: Fetch job listings with pagination
import requests
import time

def get_greenhouse_jobs(company_token: str) -> list[dict]:
    all_jobs = []
    page = 1
    base_url = f"https://job-boards.greenhouse.io/{company_token}"

    while True:
        url = f"{base_url}?page={page}&_data="
        response = requests.get(url, headers={"Accept": "application/json"})
        response.raise_for_status()

        data = response.json()

        # Jobs are in data["jobPosts"]["data"]
        if data.get("jobPosts", {}).get("data"):
            all_jobs.extend(data["jobPosts"]["data"])

        # Check for more pages
        total_pages = data.get("jobPosts", {}).get("total_pages", 1)
        if page >= total_pages:
            break
        page += 1
        time.sleep(0.3)  # Rate limiting

    return all_jobs

jobs = get_greenhouse_jobs("anthropic")
print(f"Found {len(jobs)} jobs")

Parse job listings data

Extract the key fields from each job listing. The listings API already contains full descriptions, but structured pay ranges require the details endpoint.

Step 3: Parse job listings data
import html

for job in jobs:
    # Decode HTML entities in the content
    description = html.unescape(job.get("content", ""))

    parsed = {
        "id": job.get("id"),
        "title": job.get("title"),
        "location": job.get("location", {}).get("name"),
        "department": job.get("departments", [{}])[0].get("name") if job.get("departments") else None,
        "description_html": description[:500],  # Full description available
        "url": f"https://job-boards.greenhouse.io/anthropic/jobs/{job.get('id')}",
    }
    print(parsed)

Fetch individual job details for richer data

For structured pay ranges and application questions, fetch individual job details using the same ?_data= pattern on the job page URL.

Step 4: Fetch individual job details for richer data
import requests

def get_job_details(company_token: str, job_id: str) -> dict:
    url = f"https://job-boards.greenhouse.io/{company_token}/jobs/{job_id}?_data="
    response = requests.get(url, headers={"Accept": "application/json"})
    response.raise_for_status()

    data = response.json()

    return {
        "id": data.get("jobPostId"),
        "title": data.get("jobPost", {}).get("title"),
        "location": data.get("job_post_location"),
        "description": data.get("jobPost", {}).get("content"),
        "employment_type": data.get("employment", {}).get("name"),
        "pay_ranges": [
            {
                "min": pr.get("min"),
                "max": pr.get("max"),
                "currency": pr.get("currency"),
                "title": pr.get("title"),
            }
            for pr in data.get("pay_ranges", [])
        ],
        "department": data.get("departments", [{}])[0].get("name") if data.get("departments") else None,
        "published_at": data.get("published_at"),
        "apply_url": f"https://job-boards.greenhouse.io/{company_token}/jobs/{job_id}#app",
    }

details = get_job_details("anthropic", "5431234")
print(f"Title: {details['title']}")
print(f"Pay ranges: {details['pay_ranges']}")

Handle multiple locations

Greenhouse location fields may contain semicolon-separated values for jobs available in multiple locations. Parse these into a list for easier processing.

Step 5: Handle multiple locations
def parse_locations(location_str: str) -> list[str]:
    """Parse semicolon-separated locations into a list."""
    if not location_str:
        return []

    # Split by semicolon and clean up whitespace
    locations = [loc.strip() for loc in location_str.split(";")]
    return [loc for loc in locations if loc]

# Example usage
location_raw = "San Francisco, CA; New York, NY; Remote"
locations = parse_locations(location_raw)
print(f"Locations: {locations}")
# Output: ['San Francisco, CA', 'New York, NY', 'Remote']

Use the Boards API as an alternative

Greenhouse also provides a simpler Boards API that returns all jobs in a single request without pagination. This is useful for smaller job boards.

Step 6: Use the Boards API as an alternative
import requests

def get_jobs_via_boards_api(company_token: str) -> list[dict]:
    """Fetch all jobs using the simpler Boards API."""
    url = f"https://boards-api.greenhouse.io/v1/boards/{company_token}/jobs"
    response = requests.get(url)
    response.raise_for_status()

    data = response.json()
    # Returns: {"jobs": [{"id", "title", "location": {"name"}, "absolute_url", ...}]}
    return data.get("jobs", [])

jobs = get_jobs_via_boards_api("anthropic")
print(f"Found {len(jobs)} jobs")

for job in jobs[:5]:
    print(f"- {job.get('title')} ({job.get('location', {}).get('name')})")
Common issues
criticalInvalid company token or board not found

Verify the company token from the actual career page URL. Some companies use custom domains that redirect away from greenhouse.io. Check both job-boards.greenhouse.io and boards.greenhouse.io domains.

highEmpty job board response

Some company tokens redirect to custom domains. Check for HTTP redirects and follow them. The response may also indicate the company uses a different ATS.

mediumRate limiting / 429 errors

Add 200-500ms delays between requests. While Greenhouse has no official rate limits, aggressive scraping may trigger throttling. Use exponential backoff if errors occur.

lowMissing pay range data

Not all companies expose salary information. The pay_ranges array may be empty or missing. Check if the company publishes salaries on their job board and handle null values gracefully.

lowHTML content needs decoding

Job descriptions contain HTML entity encoded text (&lt;, &amp;, etc.). Use Python's html.unescape() function to decode before storage or display.

lowMultiple locations in single field

The location field may contain semicolon-separated values like 'San Francisco; New York; Remote'. Split by semicolon to handle multiple locations properly.

Best practices
  1. 1Use the ?_data= query parameter for clean JSON responses
  2. 2Fetch job details endpoint for structured pay_ranges data
  3. 3Add 200-500ms delay between requests to avoid throttling
  4. 4Decode HTML entities in job descriptions using html.unescape()
  5. 5Handle semicolon-separated locations for multi-location jobs
  6. 6Cache results - job boards typically update daily
Or skip the complexity

One endpoint. All Greenhouse jobs. No scraping, no sessions, no maintenance.

Get API access
cURL
curl "https://enterprise.jobo.world/api/jobs?sources=greenhouse" \
  -H "X-Api-Key: YOUR_KEY"
Ready to integrate

Access Greenhouse
job data today.

One API call. Structured data. No scraping infrastructure to build or maintain — start with the free tier and scale as you grow.

99.9%API uptime
<200msAvg response
50M+Jobs processed