Breezy HR Jobs API: Get Breezy Job Listings as JSON
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
| 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
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
| 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 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 jobsGotchas
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.
- Jobs API reference for every filter and response field
- Playground to run queries without writing code
- 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.