Breezy HR Jobs API: Get Breezy Job Listings as JSON

CleanJobData Engineering

TL;DR

Breezy HR has no public JSON endpoint for job listings. The career site at {company}.breezy.hr lists every open job as /p/{id}-{slug} links in one HTML page, and each job page embeds a schema.org JobPosting JSON-LD block with the title, HTML description, datePosted, employment type and location. Some jobs include a structured baseSalary, and jobLocationType TELECOMMUTE marks remote roles.

On this page

To get a company's jobs from Breezy HR, fetch the career site at https://{company}.breezy.hr/ for the list of job links, then read the schema.org JobPosting JSON-LD on each job page to get the job as JSON. There's no public JSON API and no key involved: both pages are plain public HTML.

That's the DIY route, and it works for a handful of companies you already know. If you'd rather not build and maintain a Breezy HR scraper, CleanJobData already ingests ATS platforms and company career sites and serves the jobs normalized through one REST API — see the Jobs API docs.

At a Glance

EndpointGET https://{company}.breezy.hr/ (HTML list of job links)
DetailGET https://{company}.breezy.hr/p/{id}-{slug} (JSON-LD)
API keyNone
PaginationNone: the career page lists every job
Full descriptionJob page JSON-LD, one request per job
Salary fieldbaseSalary in the JSON-LD, when the employer sets one
RemotejobLocationType: "TELECOMMUTE" on some jobs; otherwise infer from text

Finding the Company Host

Breezy career sites live at https://{company}.breezy.hr/, so for acme.breezy.hr the host is all you need. Some companies point their own domain at the Breezy career site instead. In that case, use whichever host serves the job pages: job links on a Breezy site always look like /p/{id}-{slug}, and that path is the easiest way to recognize one.

Step 1: List the Jobs

curl "https://acme.breezy.hr/"

The response is HTML. Each job card links to its job page:

<a href="/p/493d0eedba0f-sales-executive">...</a>

The ID is the hex string before the first hyphen (493d0eedba0f here), and the rest is a readable slug. The same link often appears twice per card, once on an icon and once on the title, so dedupe by ID.

A per-company sitemap.xml also lists every job URL with a lastmod date. It has no job data, but it's another way to collect the links.

Step 2: Read Each Job's JSON-LD

curl "https://acme.breezy.hr/p/493d0eedba0f-sales-executive"

Inside the page is a <script type="application/ld+json"> block like this (trimmed):

{
  "@context": "https://schema.org/",
  "@type": "JobPosting",
  "url": "https://acme.breezy.hr/p/493d0eedba0f-sales-executive?source=GoogleJobs",
  "title": "Sales Executive",
  "description": "<h1>Sales Executive</h1>\n<p>Are you excited about technology...</p>",
  "employmentType": "FULL_TIME",
  "datePosted": "2026-02-17",
  "hiringOrganization": {
    "@type": "Organization",
    "name": "Acme",
    "logo": "https://...",
    "sameAs": "https://acme.breezy.hr"
  },
  "jobLocation": {
    "@type": "Place",
    "address": { "@type": "PostalAddress", "addressCountry": "US", "addressRegion": "AL" }
  },
  "jobLocationType": "TELECOMMUTE",
  "applicantLocationRequirements": { "@type": "Country", "name": "US" }
}

JSON-LD Fields

FieldWhat it holds
titleJob title
descriptionFull HTML description
datePostedPlain date, YYYY-MM-DD
employmentTypeschema.org value such as FULL_TIME
jobLocation.addressaddressLocality, addressRegion, addressCountry, any of which can be missing
jobLocationTypeTELECOMMUTE for remote jobs, when set
baseSalaryPay range with currency and value.minValue, value.maxValue, value.unitText, on some jobs
hiringOrganization.nameThe employer's name
urlJob page URL, sometimes with a tracking query string

These are the same schema.org fields Google for Jobs reads. Our JobPosting schema guide has the full reference.

Fetching Jobs (Python)

import json
import re
import time
import requests
 
LINK_RE = re.compile(r'href="(/p/([a-f0-9]+)-[^"]*)"', re.I)
JSONLD_RE = re.compile(r'<script[^>]+type="application/ld\+json"[^>]*>(.*?)</script>', re.S)
 
def job_posting_from_html(html: str):
    for block in JSONLD_RE.findall(html):
        try:
            data = json.loads(block)
        except ValueError:
            continue
        if data.get("@type") == "JobPosting":
            return data
    return None
 
def fetch_breezy_jobs(host: str) -> list:
    list_html = requests.get(f"https://{host}/", timeout=20).text
    links = {}
    for path, job_id in LINK_RE.findall(list_html):
        links.setdefault(job_id, path)
 
    jobs = []
    for job_id, path in links.items():
        r = requests.get(f"https://{host}{path}", timeout=20)
        if r.status_code != 200:
            continue
        ld = job_posting_from_html(r.text)
        if not ld:
            continue
        addr = (ld.get("jobLocation") or {}).get("address") or {}
        salary = ld.get("baseSalary") or {}
        value = salary.get("value") or {}
        jobs.append({
            "id": job_id,
            "title": ld.get("title"),
            "date_posted": ld.get("datePosted"),
            "employment_type": ld.get("employmentType"),
            "city": addr.get("addressLocality"),
            "region": addr.get("addressRegion"),
            "country": addr.get("addressCountry"),
            "remote": ld.get("jobLocationType") == "TELECOMMUTE",
            "salary_min": value.get("minValue"),
            "salary_max": value.get("maxValue"),
            "salary_unit": value.get("unitText"),
            "salary_currency": salary.get("currency"),
            "url": (ld.get("url") or "").split("?")[0],
            "html": ld.get("description"),
        })
        time.sleep(0.2)
    return jobs

Gotchas

One request per job. The list page only gives you links, with no title, date or location. Every new job costs a page fetch. On later runs, skip IDs you've already stored and only fetch new ones. The trade-off is that you won't see edits to jobs you already have.

Salary is optional and comes in different units. Some jobs have a full baseSalary range and others have none. When it's there, check value.unitText (HOUR, YEAR and so on) before comparing figures; an hourly rate isn't an annual salary. If there's no baseSalary, pay may still be in the description text. See the salary extraction guide.

Locations can be partial. A remote job may have only addressRegion and addressCountry, with no city. addressCountry is a country code such as US in the jobs we've seen.

Remote isn't always flagged. jobLocationType: "TELECOMMUTE" is only sometimes present. Also check the title and location text for "remote" or "hybrid".

sameAs isn't the company website. hiringOrganization.sameAs points back to the Breezy career site, not the employer's own domain. Don't store it as the company's website.

Strip tracking parameters. The JSON-LD url can end in ?source=GoogleJobs. Remove the query string before you use it as a key or a link.

No webhooks. Re-read the career page on a schedule and treat a job as closed when its link disappears from a complete list. See syncing job data in depth.

Public Pages vs Breezy's Official API

Breezy HR has an official API for its customers. It needs credentials from the employer's own Breezy account and covers that company's hiring data. For published job ads from companies you don't work with, the public career pages and their JSON-LD are the practical source.

When This Is Enough

For one company, a list page plus a JSON-LD read per job is straightforward, and the JSON-LD is a documented format that doesn't depend on Breezy's page design.

When It Isn't

Across many companies and platforms you end up maintaining a different collector for each. Some have JSON APIs, some XML feeds, and some, like Breezy, only HTML and JSON-LD. The ATS public job postings API list shows how they compare.

CleanJobData reads Breezy HR career sites alongside other platforms and returns one schema, with locations resolved to cities, states and countries, a remote type, and salary converted to a consistent range when the employer publishes one. Try it in the Playground or see the API Documentation.

Skip the scraper: get this data from the CleanJobData API

If you'd rather not maintain a Breezy HR collector, you can query normalized jobs from ATS platforms and company career sites with a single request. The response has structured city/state/country locations, parsed salary when the employer publishes one, a remote type, and company metadata, and you page through results with the cursor in pagination.next_page.

curl "https://api.cleanjobdata.com/jobs?title=sales&domain=acme.breezy.hr&limit=20" \
  -H "Authorization: Bearer $CLEANJOBDATA_API_KEY"

The domain filter is an exact match on the host of each job's application_url. Breezy jobs apply on each company's own breezy.hr subdomain, so this scopes the query to one employer; use company_website_url or employer_id as alternatives. Coverage depends on which employers are in the dataset, so check that the companies you care about appear before you rely on it.

FAQ

Does Breezy HR have a public jobs API?

Not a public JSON one for job listings. The career site at {company}.breezy.hr lists every job link in one HTML page, and each job page carries a schema.org JobPosting JSON-LD block, which gives you the job as JSON without an API key.

How do I get the job description from Breezy HR?

Fetch the job page at https://{company}.breezy.hr/p/{id}-{slug} and parse the <script type="application/ld+json"> block whose @type is JobPosting. Its description field is the full HTML description.

Does Breezy HR include salary data?

Sometimes. When the employer sets pay, the job page's JSON-LD has a baseSalary with a currency, a min and max value and a unit such as HOUR or YEAR. Many jobs have no baseSalary, and pay may only appear in the description text.

How do I tell if a Breezy HR job is remote?

Check for "jobLocationType": "TELECOMMUTE" in the JSON-LD. It isn't set on every remote job, so also look for "remote" or "hybrid" in the title and location.