# Breezy HR Jobs API: Get Breezy Job Listings as JSON

> Breezy HR has no public jobs JSON API, but every job page carries schema.org JSON-LD. How to list a company's jobs and get them as JSON, salary included.

Source: https://cleanjobdata.com/articles/how-to-scrape-breezy-hr-job-listings
Published: 2026-11-01

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.

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](https://cleanjobdata.com/docs/api/jobs).

## At a Glance

| | |
|---|---|
| Endpoint | `GET https://{company}.breezy.hr/` (HTML list of job links) |
| Detail | `GET https://{company}.breezy.hr/p/{id}-{slug}` (JSON-LD) |
| API key | None |
| Pagination | None: the career page lists every job |
| Full description | Job page JSON-LD, one request per job |
| Salary field | `baseSalary` in the JSON-LD, when the employer sets one |
| Remote | `jobLocationType: "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

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

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

```html
<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

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

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

```json
{
  "@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

| Field | What it holds |
|---|---|
| `title` | Job title |
| `description` | Full HTML description |
| `datePosted` | Plain date, `YYYY-MM-DD` |
| `employmentType` | schema.org value such as `FULL_TIME` |
| `jobLocation.address` | `addressLocality`, `addressRegion`, `addressCountry`, any of which can be missing |
| `jobLocationType` | `TELECOMMUTE` for remote jobs, when set |
| `baseSalary` | Pay range with `currency` and `value.minValue`, `value.maxValue`, `value.unitText`, on some jobs |
| `hiringOrganization.name` | The employer's name |
| `url` | Job page URL, sometimes with a tracking query string |

These are the same schema.org fields Google for Jobs reads. Our [JobPosting schema guide](https://cleanjobdata.com/articles/jobposting-schema-guide) has the full reference.

## Fetching Jobs (Python)

```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](https://cleanjobdata.com/guides/salary-data-extraction).

**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](https://cleanjobdata.com/guides/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](https://cleanjobdata.com/articles/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](https://cleanjobdata.com/playground) or see the [API Documentation](https://cleanjobdata.com/docs).

## 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`.

```bash
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.

- [Jobs API reference](https://cleanjobdata.com/docs/api/jobs) for every filter and response field
- [Playground](https://cleanjobdata.com/playground) to run queries without writing code
- [Pricing](https://cleanjobdata.com/pricing) — the Trial is free with no card required

## 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.
