All platforms

Breezy HR Jobs API.

Pull an entire careers board in one public JSON request — full HTML descriptions, salary strings, and remote flags included, with no auth and no pagination to work around.

Get API access
Breezy HR
Live
50K+jobs indexed monthly
<3haverage discovery time
1hrefresh interval
Companies using Breezy HR
New IncentivesDuolingo
Developer tools

Try the API.

Test Jobs and Feed 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 Breezy HR.

Data fields
  • Full HTML Job Descriptions
  • Free-Text Salary Strings
  • Department & Team Labels
  • Multi-Location Arrays
  • Remote Work Flags
  • Employment Type & Published Date
Use cases
  1. 01SMB Job Tracking
  2. 02Startup Recruiting Data
  3. 03Remote Job Aggregation
  4. 04Salary Benchmarking
Trusted by
New IncentivesDuolingo
DIY GUIDE

How to scrape Breezy HR.

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

RESTbeginnerUnpublished; reference scraper runs 1 request at a time with a ~2s delayNo auth

Extract the company slug from the subdomain

Every employer gets its own breezy.hr subdomain backed by a public JSON endpoint — extract structured listings, departments, and locations with no authentication.

Step 1: Extract the company slug from the subdomain
import re

def extract_company_slug(careers_url: str) -> str | None:
    """Extract company slug from a Breezy HR URL."""
    pattern = r"https?://([^.]+).breezy.hr"
    match = re.search(pattern, careers_url)
    return match.group(1) if match else None

# Example usage
url = "https://new-incentives.breezy.hr/"
slug = extract_company_slug(url)
print(f"Company slug: {slug}")  # Output: new-incentives

Fetch every job in one verbose request

Call the /json endpoint with verbose=true to return all active jobs, including full HTML descriptions, in a single response. There is no pagination, so this one request is the whole board.

Step 2: Fetch every job in one verbose request
import requests

def fetch_breezy_jobs(company_slug: str) -> list[dict]:
    """Fetch all jobs from a Breezy HR company."""
    url = f"https://{company_slug}.breezy.hr/json"
    params = {"verbose": "true"}

    response = requests.get(url, params=params, timeout=10)
    response.raise_for_status()

    return response.json()

# Example usage
jobs = fetch_breezy_jobs("new-incentives")
print(f"Found {len(jobs)} active jobs")

Parse the job fields you need

Each job object carries the title, department, location, salary string, employment type, and HTML description. Use defensive .get() access because most fields are optional and vary per company.

Step 3: Parse the job fields you need
def parse_job(job: dict) -> dict:
    """Parse a Breezy job object into a clean format."""
    location = job.get("location", {}) or {}

    return {
        "id": job.get("id"),
        "friendly_id": job.get("friendly_id"),
        "title": job.get("name"),
        "department": job.get("department"),
        "location": location.get("name", "Not specified"),
        "is_remote": location.get("is_remote", False),
        "employment_type": job.get("type", {}).get("name"),
        "salary": job.get("salary"),
        "description_html": job.get("description", ""),
        "url": job.get("url"),
        "published_date": job.get("published_date"),
        "company_name": job.get("company", {}).get("name"),
    }

# Parse all jobs
parsed_jobs = [parse_job(job) for job in jobs]
for job in parsed_jobs[:3]:
    print(f"{job['title']} - {job['location']}")

Validate a board with verbose=false

Hit the same endpoint with verbose=false for a lightweight check that skips descriptions. It confirms a company exists and returns its name before you commit to a full scrape.

Step 4: Validate a board with verbose=false
import requests

def validate_company(company_slug: str) -> dict | None:
    """Validate if a Breezy HR company exists."""
    url = f"https://{company_slug}.breezy.hr/json"
    params = {"verbose": "false"}

    try:
        response = requests.get(url, params=params, timeout=10)
        if response.status_code == 404:
            return None
        response.raise_for_status()
        jobs = response.json()
        # Get company name from first job if available
        if jobs:
            return {"name": jobs[0].get("company", {}).get("name"), "job_count": len(jobs)}
        return {"name": None, "job_count": 0}
    except requests.RequestException:
        return None

# Example usage
result = validate_company("new-incentives")
if result:
    print(f"Valid company with {result['job_count']} jobs")
else:
    print("Company not found")

Add retries and pace your requests

Breezy does not publish rate limits, but aggressive traffic can be blocked with 403 or 429. Retry with backoff and keep a delay between companies to stay well-behaved.

Step 5: Add retries and pace your requests
import time
import requests

def fetch_with_retry(company_slug: str, max_retries: int = 3) -> list[dict]:
    """Fetch jobs with retry logic and rate limiting."""
    url = f"https://{company_slug}.breezy.hr/json"
    params = {"verbose": "true"}

    for attempt in range(max_retries):
        try:
            response = requests.get(url, params=params, timeout=10)

            if response.status_code == 404:
                print(f"Company '{company_slug}' not found")
                return []

            if response.status_code in (403, 429):
                wait_time = (attempt + 1) * 2
                print(f"Throttled ({response.status_code}), waiting {wait_time}s...")
                time.sleep(wait_time)
                continue

            response.raise_for_status()
            return response.json()

        except requests.RequestException as e:
            if attempt == max_retries - 1:
                print(f"Failed after {max_retries} attempts: {e}")
                return []
            time.sleep(attempt + 1)

    return []

# Pace requests between companies (~2s matches the reference scraper)
companies = ["new-incentives", "duolingo"]
for company in companies:
    jobs = fetch_with_retry(company)
    print(f"{company}: {len(jobs)} jobs")
    time.sleep(2)
Common issues
criticalJob descriptions are missing from the response

Add verbose=true to the query string. Without it, /json returns job metadata but omits the HTML description entirely. Use https://{company}.breezy.hr/json?verbose=true for full scrapes.

high404 Not Found when calling the JSON endpoint

The subdomain slug is wrong, or the company has moved off Breezy. Confirm the slug against the live careers URL. Invalid subdomains can also redirect to the breezy.hr main site instead of returning 404.

mediumRequests blocked with 403 or 429

Breezy publishes no rate limit but will throttle bursts. Send one request at a time, add a ~2 second delay between companies, back off exponentially on 403/429, and cache results to cut call volume.

lowOptional fields like department or salary come back empty

These fields depend on what each company fills out, and salary is a free-text string (e.g. '$6.00 - $8.00 / hr') rather than a structured object. Use .get() with fallbacks and never assume a field is present.

mediumLocation shape differs between jobs

A job may expose a single 'location' object, a 'locations' array, or neither, and remote data lives under is_remote and remote_details. Check which key exists before reading nested country/state/remote fields.

mediumNo directory to discover every Breezy company

Breezy exposes no company index. Maintain your own slug list built from job-board crawling, and backfill via Wayback Machine or Common Crawl queries against *.breezy.hr/* to find historical subdomains.

Best practices
  1. 1Always request /json?verbose=true — descriptions are dropped without it
  2. 2Use /json?verbose=false for lightweight company validation
  3. 3Send one request at a time with a ~2s delay, matching the reference scraper config
  4. 4Cache responses to cut redundant calls and overall request volume
  5. 5Null-check optional fields like department, salary, and location before use
  6. 6Derive the company slug from the subdomain to build the JSON URL
Or skip the complexity

One endpoint. All Breezy HR jobs. No scraping, no sessions, no maintenance.

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

Access Breezy HR
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